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