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