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