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