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