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.auth; 019 020 021import java.net.URI; 022import java.security.PrivateKey; 023import java.security.Provider; 024import java.security.interfaces.ECPrivateKey; 025import java.security.interfaces.RSAPrivateKey; 026import java.util.*; 027 028import net.jcip.annotations.Immutable; 029 030import com.nimbusds.common.contenttype.ContentType; 031import com.nimbusds.jose.JOSEException; 032import com.nimbusds.jose.JWSAlgorithm; 033import com.nimbusds.jose.util.Base64; 034import com.nimbusds.jose.util.Base64URL; 035import com.nimbusds.jwt.SignedJWT; 036import com.nimbusds.oauth2.sdk.ParseException; 037import com.nimbusds.oauth2.sdk.assertions.jwt.JWTAssertionFactory; 038import com.nimbusds.oauth2.sdk.http.HTTPRequest; 039import com.nimbusds.oauth2.sdk.id.Audience; 040import com.nimbusds.oauth2.sdk.id.ClientID; 041import com.nimbusds.oauth2.sdk.util.URLUtils; 042 043 044/** 045 * Private key JWT authentication at the Token endpoint. Implements 046 * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT}. 047 * 048 * <p>Supported signature JSON Web Algorithms (JWAs) by this implementation: 049 * 050 * <ul> 051 * <li>RS256 052 * <li>RS384 053 * <li>RS512 054 * <li>PS256 055 * <li>PS384 056 * <li>PS512 057 * <li>ES256 058 * <li>ES256K 059 * <li>ES384 060 * <li>ES512 061 * </ul> 062 * 063 * <p>Example {@link com.nimbusds.oauth2.sdk.TokenRequest} with private key JWT 064 * authentication: 065 * 066 * <pre> 067 * POST /token HTTP/1.1 068 * Host: server.example.com 069 * Content-Type: application/x-www-form-urlencoded 070 * 071 * grant_type=authorization_code& 072 * code=i1WsRn1uB1& 073 * client_id=s6BhdRkqt3& 074 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer& 075 * client_assertion=PHNhbWxwOl...[omitted for brevity]...ZT 076 * </pre> 077 * 078 * <p>Related specifications: 079 * 080 * <ul> 081 * <li>Assertion Framework for OAuth 2.0 Client Authentication and 082 * Authorization Grants (RFC 7521). 083 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 084 * Authorization Grants (RFC 7523) 085 * </ul> 086 */ 087@Immutable 088public final class PrivateKeyJWT extends JWTAuthentication { 089 090 091 /** 092 * Returns the supported signature JSON Web Algorithms (JWAs). 093 * 094 * @return The supported JSON Web Algorithms (JWAs). 095 */ 096 public static Set<JWSAlgorithm> supportedJWAs() { 097 098 Set<JWSAlgorithm> supported = new HashSet<>(); 099 supported.addAll(JWSAlgorithm.Family.RSA); 100 supported.addAll(JWSAlgorithm.Family.EC); 101 return Collections.unmodifiableSet(supported); 102 } 103 104 105 /** 106 * Creates a new private key JWT authentication. The expiration 107 * time (exp) is set to five minutes from the current system time. 108 * Generates a default identifier (jti) for the JWT. The issued-at 109 * (iat) and not-before (nbf) claims are not set. 110 * 111 * @param clientID The client identifier. Must not be {@code null}. 112 * @param endpoint The endpoint URI where the client will submit 113 * the JWT authentication, for example the token 114 * endpoint. Must not be {@code null}. 115 * @param jwsAlgorithm The expected RSA (RS256, RS384, RS512, PS256, 116 * PS384 or PS512) or EC (ES256, ES384, ES512) 117 * signature algorithm for the JWT assertion. Must 118 * be supported and not {@code null}. 119 * @param privateKey The signing private RSA or EC key. Must not be 120 * {@code null}. 121 * @param keyID Optional identifier for the key, to aid key 122 * selection on the recipient side. Recommended. 123 * {@code null} if not specified. 124 * @param jcaProvider Optional specific JCA provider, {@code null} to 125 * use the default one. 126 * 127 * @throws JOSEException If RSA signing failed. 128 */ 129 public PrivateKeyJWT(final ClientID clientID, 130 final URI endpoint, 131 final JWSAlgorithm jwsAlgorithm, 132 final PrivateKey privateKey, 133 final String keyID, 134 final Provider jcaProvider) 135 throws JOSEException { 136 137 this(new JWTAuthenticationClaimsSet(clientID, new Audience(endpoint.toString())), 138 jwsAlgorithm, 139 privateKey, 140 keyID, 141 null, 142 null, 143 jcaProvider); 144 } 145 146 147 /** 148 * Creates a new private key JWT authentication. The expiration 149 * time (exp) is set to five minutes from the current system time. 150 * Generates a default identifier (jti) for the JWT. The issued-at 151 * (iat) and not-before (nbf) claims are not set. 152 * 153 * @param clientID The client identifier. Must not be {@code null}. 154 * @param endpoint The endpoint URI where the client will submit 155 * the JWT authentication, for example the token 156 * endpoint. Must not be {@code null}. 157 * @param jwsAlgorithm The expected RSA (RS256, RS384, RS512, PS256, 158 * PS384 or PS512) or EC (ES256, ES384, ES512) 159 * signature algorithm for the JWT assertion. Must 160 * be supported and not {@code null}. 161 * @param privateKey The signing private RSA or EC key. Must not be 162 * {@code null}. 163 * @param keyID Optional identifier for the key, to aid key 164 * selection on the recipient side. Recommended. 165 * {@code null} if not specified. 166 * @param x5c Optional X.509 certificate chain for the public 167 * key, {@code null} if not specified. 168 * @param x5t256 Optional X.509 certificate SHA-256 thumbprint, 169 * {@code null} if not specified. 170 * @param jcaProvider Optional specific JCA provider, {@code null} to 171 * use the default one. 172 * 173 * @throws JOSEException If RSA signing failed. 174 */ 175 public PrivateKeyJWT(final ClientID clientID, 176 final URI endpoint, 177 final JWSAlgorithm jwsAlgorithm, 178 final PrivateKey privateKey, 179 final String keyID, 180 final List<Base64> x5c, 181 final Base64URL x5t256, 182 final Provider jcaProvider) 183 throws JOSEException { 184 185 this(new JWTAuthenticationClaimsSet(clientID, new Audience(endpoint.toString())), 186 jwsAlgorithm, 187 privateKey, 188 keyID, 189 x5c, 190 x5t256, 191 jcaProvider); 192 } 193 194 195 /** 196 * Creates a new private key JWT authentication. 197 * 198 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 199 * be {@code null}. 200 * @param jwsAlgorithm The expected RSA (RS256, RS384, RS512, 201 * PS256, PS384 or PS512) or EC (ES256, ES384, 202 * ES512) signature algorithm for the JWT 203 * assertion. Must be supported and not 204 * {@code null}. 205 * @param privateKey The signing private RSA or EC key. Must not 206 * be {@code null}. 207 * @param keyID Optional identifier for the key, to aid key 208 * selection on the recipient side. 209 * Recommended. {@code null} if not specified. 210 * @param jcaProvider Optional specific JCA provider, {@code null} 211 * to use the default one. 212 * 213 * @throws JOSEException If RSA signing failed. 214 */ 215 public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 216 final JWSAlgorithm jwsAlgorithm, 217 final PrivateKey privateKey, 218 final String keyID, 219 final Provider jcaProvider) 220 throws JOSEException { 221 222 this(jwtAuthClaimsSet, jwsAlgorithm, privateKey, keyID, null, null, jcaProvider); 223 } 224 225 226 /** 227 * Creates a new private key JWT authentication. 228 * 229 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 230 * be {@code null}. 231 * @param jwsAlgorithm The expected RSA (RS256, RS384, RS512, 232 * PS256, PS384 or PS512) or EC (ES256, ES384, 233 * ES512) signature algorithm for the JWT 234 * assertion. Must be supported and not 235 * {@code null}. 236 * @param privateKey The signing private RSA or EC key. Must not 237 * be {@code null}. 238 * @param keyID Optional identifier for the key, to aid key 239 * selection on the recipient side. 240 * Recommended. {@code null} if not specified. 241 * @param x5c Optional X.509 certificate chain for the 242 * public key, {@code null} if not specified. 243 * @param x5t256 Optional X.509 certificate SHA-256 244 * thumbprint, {@code null} if not specified. 245 * @param jcaProvider Optional specific JCA provider, {@code null} 246 * to use the default one. 247 * 248 * @throws JOSEException If RSA signing failed. 249 */ 250 public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 251 final JWSAlgorithm jwsAlgorithm, 252 final PrivateKey privateKey, 253 final String keyID, 254 final List<Base64> x5c, 255 final Base64URL x5t256, 256 final Provider jcaProvider) 257 throws JOSEException { 258 259 this(JWTAssertionFactory.create(jwtAuthClaimsSet, jwsAlgorithm, privateKey, keyID, x5c, x5t256, jcaProvider)); 260 } 261 262 263 /** 264 * Creates a new RSA private key JWT authentication. The expiration 265 * time (exp) is set to five minutes from the current system time. 266 * Generates a default identifier (jti) for the JWT. The issued-at 267 * (iat) and not-before (nbf) claims are not set. 268 * 269 * @param clientID The client identifier. Must not be 270 * {@code null}. 271 * @param endpoint The endpoint URI where the client will submit 272 * the JWT authentication, for example the token 273 * endpoint. Must not be {@code null}. 274 * @param jwsAlgorithm The expected RSA signature algorithm (RS256, 275 * RS384 or RS512) for the private key JWT 276 * assertion. Must be supported and not 277 * {@code null}. 278 * @param rsaPrivateKey The RSA private key. Must not be {@code null}. 279 * @param keyID Optional identifier for the RSA key, to aid 280 * key selection at the authorisation server. 281 * Recommended. {@code null} if not specified. 282 * @param jcaProvider Optional specific JCA provider, {@code null} to 283 * use the default one. 284 * 285 * @throws JOSEException If RSA signing failed. 286 */ 287 @Deprecated 288 public PrivateKeyJWT(final ClientID clientID, 289 final URI endpoint, 290 final JWSAlgorithm jwsAlgorithm, 291 final RSAPrivateKey rsaPrivateKey, 292 final String keyID, 293 final Provider jcaProvider) 294 throws JOSEException { 295 296 this(new JWTAuthenticationClaimsSet(clientID, new Audience(endpoint.toString())), 297 jwsAlgorithm, 298 rsaPrivateKey, 299 keyID, 300 jcaProvider); 301 } 302 303 304 /** 305 * Creates a new RSA private key JWT authentication. 306 * 307 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 308 * be {@code null}. 309 * @param jwsAlgorithm The expected RSA signature algorithm (RS256, 310 * RS384 or RS512) for the private key JWT 311 * assertion. Must be supported and not 312 * {@code null}. 313 * @param rsaPrivateKey The RSA private key. Must not be 314 * {@code null}. 315 * @param keyID Optional identifier for the RSA key, to aid 316 * key selection at the authorisation server. 317 * Recommended. {@code null} if not specified. 318 * @param jcaProvider Optional specific JCA provider, {@code null} 319 * to use the default one. 320 * 321 * @throws JOSEException If RSA signing failed. 322 */ 323 @Deprecated 324 public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 325 final JWSAlgorithm jwsAlgorithm, 326 final RSAPrivateKey rsaPrivateKey, 327 final String keyID, 328 final Provider jcaProvider) 329 throws JOSEException { 330 331 this(JWTAssertionFactory.create(jwtAuthClaimsSet, jwsAlgorithm, rsaPrivateKey, keyID, null, null, jcaProvider)); 332 } 333 334 335 /** 336 * Creates a new EC private key JWT authentication. The expiration 337 * time (exp) is set to five minutes from the current system time. 338 * Generates a default identifier (jti) for the JWT. The issued-at 339 * (iat) and not-before (nbf) claims are not set. 340 * 341 * @param clientID The client identifier. Must not be 342 * {@code null}. 343 * @param endpoint The endpoint URI where the client will submit 344 * the JWT authentication, for example the token 345 * endpoint. Must not be {@code null}. 346 * @param jwsAlgorithm The expected EC signature algorithm (ES256, 347 * ES384 or ES512) for the private key JWT 348 * assertion. Must be supported and not 349 * {@code null}. 350 * @param ecPrivateKey The EC private key. Must not be {@code null}. 351 * @param keyID Optional identifier for the EC key, to aid key 352 * selection at the authorisation server. 353 * Recommended. {@code null} if not specified. 354 * @param jcaProvider Optional specific JCA provider, {@code null} to 355 * use the default one. 356 * 357 * @throws JOSEException If RSA signing failed. 358 */ 359 @Deprecated 360 public PrivateKeyJWT(final ClientID clientID, 361 final URI endpoint, 362 final JWSAlgorithm jwsAlgorithm, 363 final ECPrivateKey ecPrivateKey, 364 final String keyID, 365 final Provider jcaProvider) 366 throws JOSEException { 367 368 this(new JWTAuthenticationClaimsSet(clientID, new Audience(endpoint.toString())), 369 jwsAlgorithm, 370 ecPrivateKey, 371 keyID, 372 jcaProvider); 373 } 374 375 376 /** 377 * Creates a new EC private key JWT authentication. 378 * 379 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 380 * be {@code null}. 381 * @param jwsAlgorithm The expected ES signature algorithm (ES256, 382 * ES384 or ES512) for the private key JWT 383 * assertion. Must be supported and not 384 * {@code null}. 385 * @param ecPrivateKey The EC private key. Must not be 386 * {@code null}. 387 * @param keyID Optional identifier for the EC key, to aid 388 * key selection at the authorisation server. 389 * Recommended. {@code null} if not specified. 390 * @param jcaProvider Optional specific JCA provider, {@code null} 391 * to use the default one. 392 * 393 * @throws JOSEException If RSA signing failed. 394 */ 395 @Deprecated 396 public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 397 final JWSAlgorithm jwsAlgorithm, 398 final ECPrivateKey ecPrivateKey, 399 final String keyID, 400 final Provider jcaProvider) 401 throws JOSEException { 402 403 this(JWTAssertionFactory.create(jwtAuthClaimsSet, jwsAlgorithm, ecPrivateKey, keyID, null, null, jcaProvider)); 404 } 405 406 407 /** 408 * Creates a new private key JWT authentication. 409 * 410 * @param clientAssertion The client assertion, corresponding to the 411 * {@code client_assertion} parameter, as a 412 * supported RSA or ECDSA-signed JWT. Must be 413 * signed and not {@code null}. 414 */ 415 public PrivateKeyJWT(final SignedJWT clientAssertion) { 416 417 super(ClientAuthenticationMethod.PRIVATE_KEY_JWT, clientAssertion); 418 419 JWSAlgorithm alg = clientAssertion.getHeader().getAlgorithm(); 420 421 if (! JWSAlgorithm.Family.RSA.contains(alg) && ! JWSAlgorithm.Family.EC.contains(alg)) 422 throw new IllegalArgumentException("The client assertion JWT must be RSA or ECDSA-signed (RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or ES512)"); 423 } 424 425 426 /** 427 * Parses the specified parameters map for a private key JSON Web Token 428 * (JWT) authentication. Note that the parameters must not be 429 * {@code application/x-www-form-urlencoded} encoded. 430 * 431 * @param params The parameters map to parse. The private key JSON 432 * Web Token (JWT) parameters must be keyed under 433 * "client_assertion" and "client_assertion_type". The 434 * map must not be {@code null}. 435 * 436 * @return The private key JSON Web Token (JWT) authentication. 437 * 438 * @throws ParseException If the parameters map couldn't be parsed to a 439 * private key JSON Web Token (JWT) 440 * authentication. 441 */ 442 public static PrivateKeyJWT parse(final Map<String,List<String>> params) 443 throws ParseException { 444 445 JWTAuthentication.ensureClientAssertionType(params); 446 447 SignedJWT clientAssertion = JWTAuthentication.parseClientAssertion(params); 448 449 PrivateKeyJWT privateKeyJWT; 450 451 try { 452 privateKeyJWT = new PrivateKeyJWT(clientAssertion); 453 454 }catch (IllegalArgumentException e) { 455 456 throw new ParseException(e.getMessage(), e); 457 } 458 459 // Check that the top level client_id matches the assertion subject + issuer 460 461 ClientID clientID = JWTAuthentication.parseClientID(params); 462 463 if (clientID != null) { 464 465 if (! clientID.equals(privateKeyJWT.getClientID())) 466 throw new ParseException("Invalid private key JWT authentication: The client identifier doesn't match the client assertion subject / issuer"); 467 } 468 469 return privateKeyJWT; 470 } 471 472 473 /** 474 * Parses a private key JSON Web Token (JWT) authentication from the 475 * specified {@code application/x-www-form-urlencoded} encoded 476 * parameters string. 477 * 478 * @param paramsString The parameters string to parse. The private key 479 * JSON Web Token (JWT) parameters must be keyed 480 * under "client_assertion" and 481 * "client_assertion_type". The string must not be 482 * {@code null}. 483 * 484 * @return The private key JSON Web Token (JWT) authentication. 485 * 486 * @throws ParseException If the parameters string couldn't be parsed 487 * to a private key JSON Web Token (JWT) 488 * authentication. 489 */ 490 public static PrivateKeyJWT parse(final String paramsString) 491 throws ParseException { 492 493 Map<String,List<String>> params = URLUtils.parseParameters(paramsString); 494 495 return parse(params); 496 } 497 498 499 /** 500 * Parses the specified HTTP POST request for a private key JSON Web 501 * Token (JWT) authentication. 502 * 503 * @param httpRequest The HTTP POST request to parse. Must not be 504 * {@code null} and must contain a valid 505 * {@code application/x-www-form-urlencoded} encoded 506 * parameters string in the entity body. The private 507 * key JSON Web Token (JWT) parameters must be 508 * keyed under "client_assertion" and 509 * "client_assertion_type". 510 * 511 * @return The private key JSON Web Token (JWT) authentication. 512 * 513 * @throws ParseException If the HTTP request header couldn't be parsed 514 * to a private key JSON Web Token (JWT) 515 * authentication. 516 */ 517 public static PrivateKeyJWT parse(final HTTPRequest httpRequest) 518 throws ParseException { 519 520 httpRequest.ensureMethod(HTTPRequest.Method.POST); 521 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 522 523 return parse(httpRequest.getQueryParameters()); 524 } 525}