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.MalformedURLException; 022import java.net.URL; 023 024import com.nimbusds.jose.*; 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.*; 031import com.nimbusds.jose.util.ResourceRetriever; 032import com.nimbusds.jwt.*; 033import com.nimbusds.jwt.proc.*; 034import com.nimbusds.oauth2.sdk.GeneralException; 035import com.nimbusds.oauth2.sdk.ParseException; 036import com.nimbusds.oauth2.sdk.auth.Secret; 037import com.nimbusds.oauth2.sdk.id.ClientID; 038import com.nimbusds.oauth2.sdk.id.Issuer; 039import com.nimbusds.openid.connect.sdk.Nonce; 040import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; 041import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; 042import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation; 043import net.jcip.annotations.ThreadSafe; 044 045 046/** 047 * Validator of ID tokens issued by an OpenID Provider (OP). 048 * 049 * <p>Supports processing of ID tokens with the following protection: 050 * 051 * <ul> 052 * <li>ID tokens signed (JWS) with the OP's RSA or EC key, require the 053 * OP public JWK set (provided by value or URL) to verify them. 054 * <li>ID tokens authenticated with a JWS HMAC, require the client's secret 055 * to verify them. 056 * <li>Unsecured (plain) ID tokens received at the token endpoint. 057 * </ul> 058 * 059 * <p>Related specifications: 060 * 061 * <ul> 062 * <li>OpenID Connect Core 1.0, sections 3.1.3.7, 3.2.2.11 and 3.3.2.12. 063 * </ul> 064 */ 065@ThreadSafe 066public class IDTokenValidator extends AbstractJWTValidator implements ClockSkewAware { 067 068 069 /** 070 * Creates a new validator for unsecured (plain) ID tokens. 071 * 072 * @param expectedIssuer The expected ID token issuer (OpenID 073 * Provider). Must not be {@code null}. 074 * @param clientID The client ID. Must not be {@code null}. 075 */ 076 public IDTokenValidator(final Issuer expectedIssuer, 077 final ClientID clientID) { 078 079 this(expectedIssuer, clientID, (JWSKeySelector) null, null); 080 } 081 082 083 /** 084 * Creates a new validator for RSA or EC signed ID tokens where the 085 * OpenID Provider's JWK set is specified by value. 086 * 087 * @param expectedIssuer The expected ID token issuer (OpenID 088 * Provider). Must not be {@code null}. 089 * @param clientID The client ID. Must not be {@code null}. 090 * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not 091 * be {@code null}. 092 * @param jwkSet The OpenID Provider JWK set. Must not be 093 * {@code null}. 094 */ 095 public IDTokenValidator(final Issuer expectedIssuer, 096 final ClientID clientID, 097 final JWSAlgorithm expectedJWSAlg, 098 final JWKSet jwkSet) { 099 100 this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableJWKSet(jwkSet)), null); 101 } 102 103 104 /** 105 * Creates a new validator for RSA or EC signed ID tokens where the 106 * OpenID Provider's JWK set is specified by URL. 107 * 108 * @param expectedIssuer The expected ID token issuer (OpenID 109 * Provider). Must not be {@code null}. 110 * @param clientID The client ID. Must not be {@code null}. 111 * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not 112 * be {@code null}. 113 * @param jwkSetURI The OpenID Provider JWK set URL. Must not be 114 * {@code null}. 115 */ 116 public IDTokenValidator(final Issuer expectedIssuer, 117 final ClientID clientID, 118 final JWSAlgorithm expectedJWSAlg, 119 final URL jwkSetURI) { 120 121 this(expectedIssuer, clientID, expectedJWSAlg, jwkSetURI, null); 122 } 123 124 125 /** 126 * Creates a new validator for RSA or EC signed ID tokens where the 127 * OpenID Provider's JWK set is specified by URL. Permits setting of a 128 * specific resource retriever (HTTP client) for the JWK set. 129 * 130 * @param expectedIssuer The expected ID token issuer (OpenID 131 * Provider). Must not be {@code null}. 132 * @param clientID The client ID. Must not be {@code null}. 133 * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must 134 * not be {@code null}. 135 * @param jwkSetURI The OpenID Provider JWK set URL. Must not 136 * be {@code null}. 137 * @param resourceRetriever For retrieving the OpenID Connect Provider 138 * JWK set from the specified URL. If 139 * {@code null} the 140 * {@link com.nimbusds.jose.util.DefaultResourceRetriever 141 * default retriever} will be used, with 142 * preset HTTP connect timeout, HTTP read 143 * timeout and entity size limit. 144 */ 145 public IDTokenValidator(final Issuer expectedIssuer, 146 final ClientID clientID, 147 final JWSAlgorithm expectedJWSAlg, 148 final URL jwkSetURI, 149 final ResourceRetriever resourceRetriever) { 150 151 this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new RemoteJWKSet(jwkSetURI, resourceRetriever)), null); 152 } 153 154 155 /** 156 * Creates a new validator for HMAC protected ID tokens. 157 * 158 * @param expectedIssuer The expected ID token issuer (OpenID 159 * Provider). Must not be {@code null}. 160 * @param clientID The client ID. Must not be {@code null}. 161 * @param expectedJWSAlg The expected HMAC JWS algorithm. Must not be 162 * {@code null}. 163 * @param clientSecret The client secret. Must not be {@code null}. 164 */ 165 public IDTokenValidator(final Issuer expectedIssuer, 166 final ClientID clientID, 167 final JWSAlgorithm expectedJWSAlg, 168 final Secret clientSecret) { 169 170 this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableSecret(clientSecret.getValueBytes())), null); 171 } 172 173 174 /** 175 * Creates a new ID token validator. 176 * 177 * @param expectedIssuer The expected ID token issuer (OpenID 178 * Provider). Must not be {@code null}. 179 * @param clientID The client ID. Must not be {@code null}. 180 * @param jwsKeySelector The key selector for JWS verification, 181 * {@code null} if unsecured (plain) ID tokens 182 * are expected. 183 * @param jweKeySelector The key selector for JWE decryption, 184 * {@code null} if encrypted ID tokens are not 185 * expected. 186 */ 187 public IDTokenValidator(final Issuer expectedIssuer, 188 final ClientID clientID, 189 final JWSKeySelector jwsKeySelector, 190 final JWEKeySelector jweKeySelector) { 191 192 super(expectedIssuer, clientID, jwsKeySelector, jweKeySelector); 193 } 194 195 196 /** 197 * Validates the specified ID token. 198 * 199 * @param idToken The ID token. Must not be {@code null}. 200 * @param expectedNonce The expected nonce, {@code null} if none. 201 * 202 * @return The claims set of the verified ID token. 203 * 204 * @throws BadJOSEException If the ID token is invalid or expired. 205 * @throws JOSEException If an internal JOSE exception was 206 * encountered. 207 */ 208 public IDTokenClaimsSet validate(final JWT idToken, final Nonce expectedNonce) 209 throws BadJOSEException, JOSEException { 210 211 if (idToken instanceof PlainJWT) { 212 return validate((PlainJWT)idToken, expectedNonce); 213 } else if (idToken instanceof SignedJWT) { 214 return validate((SignedJWT) idToken, expectedNonce); 215 } else if (idToken instanceof EncryptedJWT) { 216 return validate((EncryptedJWT) idToken, expectedNonce); 217 } else { 218 throw new JOSEException("Unexpected JWT type: " + idToken.getClass()); 219 } 220 } 221 222 223 /** 224 * Verifies the specified unsecured (plain) ID token. 225 * 226 * @param idToken The ID token. Must not be {@code null}. 227 * @param expectedNonce The expected nonce, {@code null} if none. 228 * 229 * @return The claims set of the verified ID token. 230 * 231 * @throws BadJOSEException If the ID token is invalid or expired. 232 * @throws JOSEException If an internal JOSE exception was 233 * encountered. 234 */ 235 private IDTokenClaimsSet validate(final PlainJWT idToken, final Nonce expectedNonce) 236 throws BadJOSEException, JOSEException { 237 238 if (getJWSKeySelector() != null) { 239 throw new BadJWTException("Signed ID token expected"); 240 } 241 242 JWTClaimsSet jwtClaimsSet; 243 244 try { 245 jwtClaimsSet = idToken.getJWTClaimsSet(); 246 } catch (java.text.ParseException e) { 247 throw new BadJWTException(e.getMessage(), e); 248 } 249 250 JWTClaimsSetVerifier<?> claimsVerifier = new IDTokenClaimsVerifier(getExpectedIssuer(), getClientID(), expectedNonce, getMaxClockSkew()); 251 claimsVerifier.verify(jwtClaimsSet, null); 252 return toIDTokenClaimsSet(jwtClaimsSet); 253 } 254 255 256 /** 257 * Verifies the specified signed ID token. 258 * 259 * @param idToken The ID token. Must not be {@code null}. 260 * @param expectedNonce The expected nonce, {@code null} if none. 261 * 262 * @return The claims set of the verified ID token. 263 * 264 * @throws BadJOSEException If the ID token is invalid or expired. 265 * @throws JOSEException If an internal JOSE exception was 266 * encountered. 267 */ 268 private IDTokenClaimsSet validate(final SignedJWT idToken, final Nonce expectedNonce) 269 throws BadJOSEException, JOSEException { 270 271 if (getJWSKeySelector() == null) { 272 throw new BadJWTException("Verification of signed JWTs not configured"); 273 } 274 275 ConfigurableJWTProcessor<?> jwtProcessor = new DefaultJWTProcessor(); 276 jwtProcessor.setJWSKeySelector(getJWSKeySelector()); 277 jwtProcessor.setJWTClaimsSetVerifier(new IDTokenClaimsVerifier(getExpectedIssuer(), getClientID(), expectedNonce, getMaxClockSkew())); 278 JWTClaimsSet jwtClaimsSet = jwtProcessor.process(idToken, null); 279 return toIDTokenClaimsSet(jwtClaimsSet); 280 } 281 282 283 /** 284 * Verifies the specified signed and encrypted ID token. 285 * 286 * @param idToken The ID token. Must not be {@code null}. 287 * @param expectedNonce The expected nonce, {@code null} if none. 288 * 289 * @return The claims set of the verified ID token. 290 * 291 * @throws BadJOSEException If the ID token is invalid or expired. 292 * @throws JOSEException If an internal JOSE exception was 293 * encountered. 294 */ 295 private IDTokenClaimsSet validate(final EncryptedJWT idToken, final Nonce expectedNonce) 296 throws BadJOSEException, JOSEException { 297 298 if (getJWEKeySelector() == null) { 299 throw new BadJWTException("Decryption of JWTs not configured"); 300 } 301 if (getJWSKeySelector() == null) { 302 throw new BadJWTException("Verification of signed JWTs not configured"); 303 } 304 305 ConfigurableJWTProcessor<?> jwtProcessor = new DefaultJWTProcessor(); 306 jwtProcessor.setJWSKeySelector(getJWSKeySelector()); 307 jwtProcessor.setJWEKeySelector(getJWEKeySelector()); 308 jwtProcessor.setJWTClaimsSetVerifier(new IDTokenClaimsVerifier(getExpectedIssuer(), getClientID(), expectedNonce, getMaxClockSkew())); 309 310 JWTClaimsSet jwtClaimsSet = jwtProcessor.process(idToken, null); 311 312 return toIDTokenClaimsSet(jwtClaimsSet); 313 } 314 315 316 /** 317 * Converts a JWT claims set to an ID token claims set. 318 * 319 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 320 * 321 * @return The ID token claims set. 322 * 323 * @throws JOSEException If conversion failed. 324 */ 325 private static IDTokenClaimsSet toIDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet) 326 throws JOSEException { 327 328 try { 329 return new IDTokenClaimsSet(jwtClaimsSet); 330 } catch (ParseException e) { 331 // Claims set must be verified at this point 332 throw new JOSEException(e.getMessage(), e); 333 } 334 } 335 336 337 /** 338 * Creates a key selector for JWS verification. 339 * 340 * @param opMetadata The OpenID Provider metadata. Must not be 341 * {@code null}. 342 * @param clientInfo The Relying Party metadata. Must not be 343 * {@code null}. 344 * 345 * @return The JWS key selector. 346 * 347 * @throws GeneralException If the supplied OpenID Provider metadata or 348 * Relying Party metadata are missing a 349 * required parameter or inconsistent. 350 */ 351 protected static JWSKeySelector createJWSKeySelector(final OIDCProviderMetadata opMetadata, 352 final OIDCClientInformation clientInfo) 353 throws GeneralException { 354 355 final JWSAlgorithm expectedJWSAlg = clientInfo.getOIDCMetadata().getIDTokenJWSAlg(); 356 357 if (opMetadata.getIDTokenJWSAlgs() == null) { 358 throw new GeneralException("Missing OpenID Provider id_token_signing_alg_values_supported parameter"); 359 } 360 361 if (! opMetadata.getIDTokenJWSAlgs().contains(expectedJWSAlg)) { 362 throw new GeneralException("The OpenID Provider doesn't support " + expectedJWSAlg + " ID tokens"); 363 } 364 365 if (Algorithm.NONE.equals(expectedJWSAlg)) { 366 // Skip creation of JWS key selector, plain ID tokens expected 367 return null; 368 369 } else if (JWSAlgorithm.Family.RSA.contains(expectedJWSAlg) || JWSAlgorithm.Family.EC.contains(expectedJWSAlg)) { 370 371 URL jwkSetURL; 372 try { 373 jwkSetURL = opMetadata.getJWKSetURI().toURL(); 374 } catch (MalformedURLException e) { 375 throw new GeneralException("Invalid jwk set URI: " + e.getMessage(), e); 376 } 377 JWKSource jwkSource = new RemoteJWKSet(jwkSetURL); // TODO specify HTTP response limits 378 379 return new JWSVerificationKeySelector(expectedJWSAlg, jwkSource); 380 381 } else if (JWSAlgorithm.Family.HMAC_SHA.contains(expectedJWSAlg)) { 382 383 Secret clientSecret = clientInfo.getSecret(); 384 if (clientSecret == null) { 385 throw new GeneralException("Missing client secret"); 386 } 387 return new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableSecret(clientSecret.getValueBytes())); 388 389 } else { 390 throw new GeneralException("Unsupported JWS algorithm: " + expectedJWSAlg); 391 } 392 } 393 394 395 /** 396 * Creates a key selector for JWE decryption. 397 * 398 * @param opMetadata The OpenID Provider metadata. Must not be 399 * {@code null}. 400 * @param clientInfo The Relying Party metadata. Must not be 401 * {@code null}. 402 * @param clientJWKSource The client private JWK source, {@code null} 403 * if encrypted ID tokens are not expected. 404 * 405 * @return The JWE key selector. 406 * 407 * @throws GeneralException If the supplied OpenID Provider metadata or 408 * Relying Party metadata are missing a 409 * required parameter or inconsistent. 410 */ 411 protected static JWEKeySelector createJWEKeySelector(final OIDCProviderMetadata opMetadata, 412 final OIDCClientInformation clientInfo, 413 final JWKSource clientJWKSource) 414 throws GeneralException { 415 416 final JWEAlgorithm expectedJWEAlg = clientInfo.getOIDCMetadata().getIDTokenJWEAlg(); 417 final EncryptionMethod expectedJWEEnc = clientInfo.getOIDCMetadata().getIDTokenJWEEnc(); 418 419 if (expectedJWEAlg == null) { 420 // Encrypted ID tokens not expected 421 return null; 422 } 423 424 if (expectedJWEEnc == null) { 425 throw new GeneralException("Missing required ID token JWE encryption method for " + expectedJWEAlg); 426 } 427 428 if (opMetadata.getIDTokenJWEAlgs() == null || ! opMetadata.getIDTokenJWEAlgs().contains(expectedJWEAlg)) { 429 throw new GeneralException("The OpenID Provider doesn't support " + expectedJWEAlg + " ID tokens"); 430 } 431 432 if (opMetadata.getIDTokenJWEEncs() == null || ! opMetadata.getIDTokenJWEEncs().contains(expectedJWEEnc)) { 433 throw new GeneralException("The OpenID Provider doesn't support " + expectedJWEAlg + " / " + expectedJWEEnc + " ID tokens"); 434 } 435 436 return new JWEDecryptionKeySelector(expectedJWEAlg, expectedJWEEnc, clientJWKSource); 437 } 438 439 440 /** 441 * Creates a new ID token validator for the specified OpenID Provider 442 * metadata and OpenID Relying Party registration. 443 * 444 * @param opMetadata The OpenID Provider metadata. Must not be 445 * {@code null}. 446 * @param clientInfo The OpenID Relying Party registration. Must 447 * not be {@code null}. 448 * @param clientJWKSource The client private JWK source, {@code null} 449 * if encrypted ID tokens are not expected. 450 * 451 * @return The ID token validator. 452 * 453 * @throws GeneralException If the supplied OpenID Provider metadata or 454 * Relying Party metadata are missing a 455 * required parameter or inconsistent. 456 */ 457 public static IDTokenValidator create(final OIDCProviderMetadata opMetadata, 458 final OIDCClientInformation clientInfo, 459 final JWKSource clientJWKSource) 460 throws GeneralException { 461 462 // Create JWS key selector, unless id_token alg = none 463 final JWSKeySelector jwsKeySelector = createJWSKeySelector(opMetadata, clientInfo); 464 465 // Create JWE key selector if encrypted ID tokens are expected 466 final JWEKeySelector jweKeySelector = createJWEKeySelector(opMetadata, clientInfo, clientJWKSource); 467 468 return new IDTokenValidator(opMetadata.getIssuer(), clientInfo.getID(), jwsKeySelector, jweKeySelector); 469 } 470}