001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.openid.connect.sdk.validators; 019 020 021import java.net.URL; 022 023import com.nimbusds.jose.JOSEException; 024import com.nimbusds.jose.JWSAlgorithm; 025import com.nimbusds.jose.jwk.JWKSet; 026import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 027import com.nimbusds.jose.jwk.source.ImmutableSecret; 028import com.nimbusds.jose.jwk.source.JWKSource; 029import com.nimbusds.jose.jwk.source.RemoteJWKSet; 030import com.nimbusds.jose.proc.BadJOSEException; 031import com.nimbusds.jose.proc.JWEKeySelector; 032import com.nimbusds.jose.proc.JWSKeySelector; 033import com.nimbusds.jose.proc.JWSVerificationKeySelector; 034import com.nimbusds.jose.util.ResourceRetriever; 035import com.nimbusds.jwt.*; 036import com.nimbusds.jwt.proc.BadJWTException; 037import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; 038import com.nimbusds.jwt.proc.DefaultJWTProcessor; 039import com.nimbusds.oauth2.sdk.GeneralException; 040import com.nimbusds.oauth2.sdk.ParseException; 041import com.nimbusds.oauth2.sdk.auth.Secret; 042import com.nimbusds.oauth2.sdk.id.ClientID; 043import com.nimbusds.oauth2.sdk.id.Issuer; 044import com.nimbusds.openid.connect.sdk.claims.LogoutTokenClaimsSet; 045import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; 046import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation; 047import net.jcip.annotations.ThreadSafe; 048 049 050/** 051 * Validator of logout tokens issued by an OpenID Provider (OP). 052 * 053 * <p>Supports processing of logout tokens with the following protection: 054 * 055 * <ul> 056 * <li>Logout tokens signed (JWS) with the OP's RSA or EC key, require the 057 * OP public JWK set (provided by value or URL) to verify them. 058 * <li>Logout tokens authenticated with a JWS HMAC, require the client's 059 * secret to verify them. 060 * </ul> 061 * 062 * <p>Related specifications: 063 * 064 * <ul> 065 * <li>OpenID Connect Back-Channel Logout 1.0, section 2.4 (draft 04). 066 * </ul> 067 */ 068@ThreadSafe 069public class LogoutTokenValidator extends AbstractJWTValidator { 070 071 072 /** 073 * Creates a new validator for RSA or EC signed logout tokens where the 074 * OpenID Provider's JWK set is specified by value. 075 * 076 * @param expectedIssuer The expected logout token issuer (OpenID 077 * Provider). Must not be {@code null}. 078 * @param clientID The client ID. Must not be {@code null}. 079 * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not 080 * be {@code null}. 081 * @param jwkSet The OpenID Provider JWK set. Must not be 082 * {@code null}. 083 */ 084 public LogoutTokenValidator(final Issuer expectedIssuer, 085 final ClientID clientID, 086 final JWSAlgorithm expectedJWSAlg, 087 final JWKSet jwkSet) { 088 089 this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableJWKSet(jwkSet)), null); 090 } 091 092 093 /** 094 * Creates a new validator for RSA or EC signed logout tokens where the 095 * OpenID Provider's JWK set is specified by URL. 096 * 097 * @param expectedIssuer The expected logout token issuer (OpenID 098 * Provider). Must not be {@code null}. 099 * @param clientID The client ID. Must not be {@code null}. 100 * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not 101 * be {@code null}. 102 * @param jwkSetURI The OpenID Provider JWK set URL. Must not be 103 * {@code null}. 104 */ 105 public LogoutTokenValidator(final Issuer expectedIssuer, 106 final ClientID clientID, 107 final JWSAlgorithm expectedJWSAlg, 108 final URL jwkSetURI) { 109 110 this(expectedIssuer, clientID, expectedJWSAlg, jwkSetURI, null); 111 } 112 113 114 /** 115 * Creates a new validator for RSA or EC signed logout tokens where the 116 * OpenID Provider's JWK set is specified by URL. Permits setting of a 117 * specific resource retriever (HTTP client) for the JWK set. 118 * 119 * @param expectedIssuer The expected logout token issuer (OpenID 120 * Provider). Must not be {@code null}. 121 * @param clientID The client ID. Must not be {@code null}. 122 * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must 123 * not be {@code null}. 124 * @param jwkSetURI The OpenID Provider JWK set URL. Must not 125 * be {@code null}. 126 * @param resourceRetriever For retrieving the OpenID Connect Provider 127 * JWK set from the specified URL. If 128 * {@code null} the 129 * {@link com.nimbusds.jose.util.DefaultResourceRetriever 130 * default retriever} will be used, with 131 * preset HTTP connect timeout, HTTP read 132 * timeout and entity size limit. 133 */ 134 public LogoutTokenValidator(final Issuer expectedIssuer, 135 final ClientID clientID, 136 final JWSAlgorithm expectedJWSAlg, 137 final URL jwkSetURI, 138 final ResourceRetriever resourceRetriever) { 139 140 this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new RemoteJWKSet(jwkSetURI, resourceRetriever)), null); 141 } 142 143 144 /** 145 * Creates a new validator for HMAC protected logout tokens. 146 * 147 * @param expectedIssuer The expected logout token issuer (OpenID 148 * Provider). Must not be {@code null}. 149 * @param clientID The client ID. Must not be {@code null}. 150 * @param expectedJWSAlg The expected HMAC JWS algorithm. Must not be 151 * {@code null}. 152 * @param clientSecret The client secret. Must not be {@code null}. 153 */ 154 public LogoutTokenValidator(final Issuer expectedIssuer, 155 final ClientID clientID, 156 final JWSAlgorithm expectedJWSAlg, 157 final Secret clientSecret) { 158 159 this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableSecret(clientSecret.getValueBytes())), null); 160 } 161 162 163 /** 164 * Creates a new logout token validator. 165 * 166 * @param expectedIssuer The expected logout token issuer (OpenID 167 * Provider). Must not be {@code null}. 168 * @param clientID The client ID. Must not be {@code null}. 169 * @param jwsKeySelector The key selector for JWS verification, 170 * {@code null} if unsecured (plain) logout tokens 171 * are expected. 172 * @param jweKeySelector The key selector for JWE decryption, 173 * {@code null} if encrypted logout tokens are 174 * not expected. 175 */ 176 public LogoutTokenValidator(final Issuer expectedIssuer, 177 final ClientID clientID, 178 final JWSKeySelector jwsKeySelector, 179 final JWEKeySelector jweKeySelector) { 180 181 super(expectedIssuer, clientID, jwsKeySelector, jweKeySelector); 182 } 183 184 185 /** 186 * Validates the specified logout token. 187 * 188 * @param logoutToken The logout token. Must not be {@code null}. 189 * 190 * @return The claims set of the verified logout token. 191 * 192 * @throws BadJOSEException If the logout token is invalid or expired. 193 * @throws JOSEException If an internal JOSE exception was 194 * encountered. 195 */ 196 public LogoutTokenClaimsSet validate(final JWT logoutToken) 197 throws BadJOSEException, JOSEException { 198 199 if (logoutToken instanceof PlainJWT) { 200 throw new BadJWTException("Unsecured (plain) logout tokens are illegal"); 201 } else if (logoutToken instanceof SignedJWT) { 202 return validate((SignedJWT) logoutToken); 203 } else if (logoutToken instanceof EncryptedJWT) { 204 return validate((EncryptedJWT) logoutToken); 205 } else { 206 throw new JOSEException("Unexpected JWT type: " + logoutToken.getClass()); 207 } 208 } 209 210 211 /** 212 * Verifies the specified signed logout token. 213 * 214 * @param logoutToken The logout token. Must not be {@code null}. 215 * 216 * @return The claims set of the verified logout token. 217 * 218 * @throws BadJOSEException If the logout token is invalid or expired. 219 * @throws JOSEException If an internal JOSE exception was 220 * encountered. 221 */ 222 private LogoutTokenClaimsSet validate(final SignedJWT logoutToken) 223 throws BadJOSEException, JOSEException { 224 225 if (getJWSKeySelector() == null) { 226 throw new BadJWTException("Verification of signed JWTs not configured"); 227 } 228 229 ConfigurableJWTProcessor<?> jwtProcessor = new DefaultJWTProcessor(); 230 jwtProcessor.setJWSKeySelector(getJWSKeySelector()); 231 jwtProcessor.setJWTClaimsSetVerifier(new LogoutTokenClaimsVerifier(getExpectedIssuer(), getClientID())); 232 JWTClaimsSet jwtClaimsSet = jwtProcessor.process(logoutToken, null); 233 return toLogoutTokenClaimsSet(jwtClaimsSet); 234 } 235 236 237 /** 238 * Verifies the specified signed and encrypted logout token. 239 * 240 * @param logoutToken The logout token. Must not be {@code null}. 241 * 242 * @return The claims set of the verified logout token. 243 * 244 * @throws BadJOSEException If the logout token is invalid or expired. 245 * @throws JOSEException If an internal JOSE exception was 246 * encountered. 247 */ 248 private LogoutTokenClaimsSet validate(final EncryptedJWT logoutToken) 249 throws BadJOSEException, JOSEException { 250 251 if (getJWEKeySelector() == null) { 252 throw new BadJWTException("Decryption of JWTs not configured"); 253 } 254 if (getJWSKeySelector() == null) { 255 throw new BadJWTException("Verification of signed JWTs not configured"); 256 } 257 258 ConfigurableJWTProcessor<?> jwtProcessor = new DefaultJWTProcessor(); 259 jwtProcessor.setJWSKeySelector(getJWSKeySelector()); 260 jwtProcessor.setJWEKeySelector(getJWEKeySelector()); 261 jwtProcessor.setJWTClaimsSetVerifier(new LogoutTokenClaimsVerifier(getExpectedIssuer(), getClientID())); 262 263 JWTClaimsSet jwtClaimsSet = jwtProcessor.process(logoutToken, null); 264 265 return toLogoutTokenClaimsSet(jwtClaimsSet); 266 } 267 268 269 /** 270 * Converts a JWT claims set to a logout token claims set. 271 * 272 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 273 * 274 * @return The logout token claims set. 275 * 276 * @throws JOSEException If conversion failed. 277 */ 278 private static LogoutTokenClaimsSet toLogoutTokenClaimsSet(final JWTClaimsSet jwtClaimsSet) 279 throws JOSEException { 280 281 try { 282 return new LogoutTokenClaimsSet(jwtClaimsSet); 283 } catch (ParseException e) { 284 // Claims set must be verified at this point 285 throw new JOSEException(e.getMessage(), e); 286 } 287 } 288 289 290 /** 291 * Creates a new logout token validator for the specified OpenID 292 * Provider metadata and OpenID Relying Party registration. 293 * 294 * @param opMetadata The OpenID Provider metadata. Must not be 295 * {@code null}. 296 * @param clientInfo The OpenID Relying Party registration. Must 297 * not be {@code null}. 298 * @param clientJWKSource The client private JWK source, {@code null} 299 * if encrypted logout tokens are not expected. 300 * 301 * @return The logout token validator. 302 * 303 * @throws GeneralException If the supplied OpenID Provider metadata or 304 * Relying Party metadata are missing a 305 * required parameter or inconsistent. 306 */ 307 public static LogoutTokenValidator create(final OIDCProviderMetadata opMetadata, 308 final OIDCClientInformation clientInfo, 309 final JWKSource clientJWKSource) 310 throws GeneralException { 311 312 // Logout tokens verified according to registered ID token algorithms! 313 // http://openid.net/specs/openid-connect-backchannel-1_0-ID1.html#Validation 314 315 // Create JWS key selector, unless id_token alg = none 316 final JWSKeySelector jwsKeySelector = IDTokenValidator.createJWSKeySelector(opMetadata, clientInfo); 317 318 // Create JWE key selector if encrypted logout tokens are expected 319 final JWEKeySelector jweKeySelector = IDTokenValidator.createJWEKeySelector(opMetadata, clientInfo, clientJWKSource); 320 321 return new LogoutTokenValidator(opMetadata.getIssuer(), clientInfo.getID(), jwsKeySelector, jweKeySelector); 322 } 323}