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