001package com.nimbusds.oauth2.sdk.auth; 002 003 004import java.net.URI; 005import java.security.Provider; 006import java.security.interfaces.ECPrivateKey; 007import java.security.interfaces.RSAPrivateKey; 008import java.util.Collections; 009import java.util.HashSet; 010import java.util.Map; 011import java.util.Set; 012 013import net.jcip.annotations.Immutable; 014 015import com.nimbusds.jose.JOSEException; 016import com.nimbusds.jose.JWSAlgorithm; 017import com.nimbusds.jose.JWSHeader; 018import com.nimbusds.jose.crypto.ECDSASigner; 019import com.nimbusds.jose.crypto.RSASSASigner; 020import com.nimbusds.jwt.SignedJWT; 021 022import com.nimbusds.oauth2.sdk.ParseException; 023import com.nimbusds.oauth2.sdk.id.Audience; 024import com.nimbusds.oauth2.sdk.id.ClientID; 025import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 026import com.nimbusds.oauth2.sdk.http.HTTPRequest; 027import com.nimbusds.oauth2.sdk.util.URLUtils; 028 029 030/** 031 * Private key JWT authentication at the Token endpoint. Implements 032 * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT}. 033 * 034 * <p>Supported signature JSON Web Algorithms (JWAs) by this implementation: 035 * 036 * <ul> 037 * <li>RS256 038 * <li>RS384 039 * <li>RS512 040 * <li>PS256 041 * <li>PS384 042 * <li>PS512 043 * <li>ES256 044 * <li>ES384 045 * <li>ES512 046 * </ul> 047 * 048 * <p>Example {@link com.nimbusds.oauth2.sdk.TokenRequest} with private key JWT 049 * authentication: 050 * 051 * <pre> 052 * POST /token HTTP/1.1 053 * Host: server.example.com 054 * Content-Type: application/x-www-form-urlencoded 055 * 056 * grant_type=authorization_code& 057 * code=i1WsRn1uB1& 058 * client_id=s6BhdRkqt3& 059 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer& 060 * client_assertion=PHNhbWxwOl...[omitted for brevity]...ZT 061 * </pre> 062 * 063 * <p>Related specifications: 064 * 065 * <ul> 066 * <li>Assertion Framework for OAuth 2.0 (RFC 7521). 067 * <li>JSON Web Token (JWT) Bearer Token Profiles for OAuth 2.0 (RFC 7523). 068 * </ul> 069 */ 070@Immutable 071public final class PrivateKeyJWT extends JWTAuthentication { 072 073 074 /** 075 * Gets the set of supported signature JSON Web Algorithms (JWAs) by 076 * this implementation of private key JSON Web Token (JWT) 077 * authentication. 078 * 079 * @return The set of supported JSON Web Algorithms (JWAs). 080 */ 081 public static Set<JWSAlgorithm> getSupportedJWAs() { 082 083 Set<JWSAlgorithm> supported = new HashSet<>(); 084 085 supported.add(JWSAlgorithm.RS256); 086 supported.add(JWSAlgorithm.RS384); 087 supported.add(JWSAlgorithm.RS512); 088 089 supported.add(JWSAlgorithm.PS256); 090 supported.add(JWSAlgorithm.PS384); 091 supported.add(JWSAlgorithm.PS512); 092 093 supported.add(JWSAlgorithm.ES256); 094 supported.add(JWSAlgorithm.ES384); 095 supported.add(JWSAlgorithm.ES512); 096 097 return Collections.unmodifiableSet(supported); 098 } 099 100 101 /** 102 * Creates a new RSA private key JWT assertion. 103 * 104 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 105 * be {@code null}. 106 * @param jwsAlgorithm The expected RSA signature algorithm 107 * (RS256, RS384, RS512, PS256, PS384 or PS512) 108 * for the private key JWT assertion. Must be 109 * supported and not {@code null}. 110 * @param rsaPrivateKey The RSA private key. Must not be 111 * {@code null}. 112 * @param jcaProvider Optional specific JCA provider, {@code null} 113 * to use the default one. 114 * 115 * @return The private key JWT assertion. 116 * 117 * @throws JOSEException If RSA signing failed. 118 */ 119 public static SignedJWT createClientAssertion(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 120 final JWSAlgorithm jwsAlgorithm, 121 final RSAPrivateKey rsaPrivateKey, 122 final Provider jcaProvider) 123 throws JOSEException { 124 125 SignedJWT signedJWT = new SignedJWT(new JWSHeader(jwsAlgorithm), jwtAuthClaimsSet.toJWTClaimsSet()); 126 RSASSASigner signer = new RSASSASigner(rsaPrivateKey); 127 if (jcaProvider != null) { 128 signer.getJCAContext().setProvider(jcaProvider); 129 } 130 signedJWT.sign(signer); 131 return signedJWT; 132 } 133 134 135 /** 136 * Creates a new EC private key JWT assertion. 137 * 138 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 139 * be {@code null}. 140 * @param jwsAlgorithm The expected RSA signature algorithm 141 * (RS256, RS384, RS512, PS256, PS384 or PS512) 142 * for the private key JWT assertion. Must be 143 * supported and not {@code null}. 144 * @param ecPrivateKey The EC private key. Must not be 145 * {@code null}. 146 * @param jcaProvider Optional specific JCA provider, {@code null} 147 * to use the default one. 148 * 149 * @return The private key JWT assertion. 150 * 151 * @throws JOSEException If RSA signing failed. 152 */ 153 public static SignedJWT createClientAssertion(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 154 final JWSAlgorithm jwsAlgorithm, 155 final ECPrivateKey ecPrivateKey, 156 final Provider jcaProvider) 157 throws JOSEException { 158 159 SignedJWT signedJWT = new SignedJWT(new JWSHeader(jwsAlgorithm), jwtAuthClaimsSet.toJWTClaimsSet()); 160 ECDSASigner signer = new ECDSASigner(ecPrivateKey); 161 if (jcaProvider != null) { 162 signer.getJCAContext().setProvider(jcaProvider); 163 } 164 signedJWT.sign(signer); 165 return signedJWT; 166 } 167 168 169 /** 170 * Creates a new RSA private key JWT authentication. The expiration 171 * time (exp) is set to five minutes from the current system time. 172 * Generates a default identifier (jti) for the JWT. The issued-at 173 * (iat) and not-before (nbf) claims are not set. 174 * 175 * @param clientID The client identifier. Must not be 176 * {@code null}. 177 * @param tokenEndpoint The token endpoint URI of the authorisation 178 * server. Must not be {@code null}. 179 * @param jwsAlgorithm The expected RSA signature algorithm (ES256, 180 * ES384 or ES512) for the private key JWT 181 * assertion. Must be supported and not 182 * {@code null}. 183 * @param rsaPrivateKey The RSA private key. Must not be {@code null}. 184 * @param jcaProvider Optional specific JCA provider, {@code null} to 185 * use the default one. 186 * 187 * @throws JOSEException If RSA signing failed. 188 */ 189 public PrivateKeyJWT(final ClientID clientID, 190 final URI tokenEndpoint, 191 final JWSAlgorithm jwsAlgorithm, 192 final RSAPrivateKey rsaPrivateKey, 193 final Provider jcaProvider) 194 throws JOSEException { 195 196 this(new JWTAuthenticationClaimsSet(clientID, new Audience(tokenEndpoint.toString())), 197 jwsAlgorithm, 198 rsaPrivateKey, 199 jcaProvider); 200 } 201 202 203 /** 204 * Creates a new RSA private key JWT authentication. 205 * 206 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 207 * be {@code null}. 208 * @param jwsAlgorithm The expected RSA signature algorithm (ES256, 209 * ES384 or ES512) for the private key JWT 210 * assertion. Must be supported and not 211 * {@code null}. 212 * @param rsaPrivateKey The RSA private key. Must not be 213 * {@code null}. 214 * @param jcaProvider Optional specific JCA provider, {@code null} 215 * to use the default one. 216 * 217 * @throws JOSEException If RSA signing failed. 218 */ 219 public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 220 final JWSAlgorithm jwsAlgorithm, 221 final RSAPrivateKey rsaPrivateKey, 222 final Provider jcaProvider) 223 throws JOSEException { 224 225 this(createClientAssertion(jwtAuthClaimsSet, jwsAlgorithm, rsaPrivateKey, jcaProvider)); 226 } 227 228 229 /** 230 * Creates a new EC private key JWT authentication. The expiration 231 * time (exp) is set to five minutes from the current system time. 232 * Generates a default identifier (jti) for the JWT. The issued-at 233 * (iat) and not-before (nbf) claims are not set. 234 * 235 * @param clientID The client identifier. Must not be 236 * {@code null}. 237 * @param tokenEndpoint The token endpoint URI of the authorisation 238 * server. Must not be {@code null}. 239 * @param jwsAlgorithm The expected RSA signature algorithm (ES256, 240 * ES384 or ES512) for the private key JWT 241 * assertion. Must be supported and not 242 * {@code null}. 243 * @param ecPrivateKey The EC private key. Must not be {@code null}. 244 * @param jcaProvider Optional specific JCA provider, {@code null} to 245 * use the default one. 246 * 247 * @throws JOSEException If RSA signing failed. 248 */ 249 public PrivateKeyJWT(final ClientID clientID, 250 final URI tokenEndpoint, 251 final JWSAlgorithm jwsAlgorithm, 252 final ECPrivateKey ecPrivateKey, 253 final Provider jcaProvider) 254 throws JOSEException { 255 256 this(new JWTAuthenticationClaimsSet(clientID, new Audience(tokenEndpoint.toString())), 257 jwsAlgorithm, 258 ecPrivateKey, 259 jcaProvider); 260 } 261 262 263 /** 264 * Creates a new EC private key JWT authentication. 265 * 266 * @param jwtAuthClaimsSet The JWT authentication claims set. Must not 267 * be {@code null}. 268 * @param jwsAlgorithm The expected RSA signature algorithm (ES256, 269 * ES384 or ES512) for the private key JWT 270 * assertion. Must be supported and not 271 * {@code null}. 272 * @param ecPrivateKey The EC private key. Must not be 273 * {@code null}. 274 * @param jcaProvider Optional specific JCA provider, {@code null} 275 * to use the default one. 276 * 277 * @throws JOSEException If RSA signing failed. 278 */ 279 public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet, 280 final JWSAlgorithm jwsAlgorithm, 281 final ECPrivateKey ecPrivateKey, 282 final Provider jcaProvider) 283 throws JOSEException { 284 285 this(createClientAssertion(jwtAuthClaimsSet, jwsAlgorithm, ecPrivateKey, jcaProvider)); 286 } 287 288 289 /** 290 * Creates a new private key JWT authentication. 291 * 292 * @param clientAssertion The client assertion, corresponding to the 293 * {@code client_assertion} parameter, as a 294 * supported RSA or ECDSA-signed JWT. Must be 295 * signed and not {@code null}. 296 */ 297 public PrivateKeyJWT(final SignedJWT clientAssertion) { 298 299 super(ClientAuthenticationMethod.PRIVATE_KEY_JWT, clientAssertion); 300 301 if (! getSupportedJWAs().contains(clientAssertion.getHeader().getAlgorithm())) 302 throw new IllegalArgumentException("The client assertion JWT must be RSA or ECDSA-signed (RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or ES512)"); 303 } 304 305 306 /** 307 * Parses the specified parameters map for a private key JSON Web Token 308 * (JWT) authentication. Note that the parameters must not be 309 * {@code application/x-www-form-urlencoded} encoded. 310 * 311 * @param params The parameters map to parse. The private key JSON 312 * Web Token (JWT) parameters must be keyed under 313 * "client_assertion" and "client_assertion_type". The 314 * map must not be {@code null}. 315 * 316 * @return The private key JSON Web Token (JWT) authentication. 317 * 318 * @throws ParseException If the parameters map couldn't be parsed to a 319 * private key JSON Web Token (JWT) 320 * authentication. 321 */ 322 public static PrivateKeyJWT parse(final Map<String,String> params) 323 throws ParseException { 324 325 JWTAuthentication.ensureClientAssertionType(params); 326 327 SignedJWT clientAssertion = JWTAuthentication.parseClientAssertion(params); 328 329 PrivateKeyJWT privateKeyJWT; 330 331 try { 332 privateKeyJWT = new PrivateKeyJWT(clientAssertion); 333 334 }catch (IllegalArgumentException e) { 335 336 throw new ParseException(e.getMessage(), e); 337 } 338 339 // Check that the top level client_id matches the assertion subject + issuer 340 341 ClientID clientID = JWTAuthentication.parseClientID(params); 342 343 if (clientID != null) { 344 345 if (! clientID.equals(privateKeyJWT.getClientID())) 346 throw new ParseException("The client identifier doesn't match the client assertion subject / issuer"); 347 } 348 349 return privateKeyJWT; 350 } 351 352 353 /** 354 * Parses a private key JSON Web Token (JWT) authentication from the 355 * specified {@code application/x-www-form-urlencoded} encoded 356 * parameters string. 357 * 358 * @param paramsString The parameters string to parse. The private key 359 * JSON Web Token (JWT) parameters must be keyed 360 * under "client_assertion" and 361 * "client_assertion_type". The string must not be 362 * {@code null}. 363 * 364 * @return The private key JSON Web Token (JWT) authentication. 365 * 366 * @throws ParseException If the parameters string couldn't be parsed 367 * to a private key JSON Web Token (JWT) 368 * authentication. 369 */ 370 public static PrivateKeyJWT parse(final String paramsString) 371 throws ParseException { 372 373 Map<String,String> params = URLUtils.parseParameters(paramsString); 374 375 return parse(params); 376 } 377 378 379 /** 380 * Parses the specified HTTP POST request for a private key JSON Web 381 * Token (JWT) authentication. 382 * 383 * @param httpRequest The HTTP POST request to parse. Must not be 384 * {@code null} and must contain a valid 385 * {@code application/x-www-form-urlencoded} encoded 386 * parameters string in the entity body. The private 387 * key JSON Web Token (JWT) parameters must be 388 * keyed under "client_assertion" and 389 * "client_assertion_type". 390 * 391 * @return The private key JSON Web Token (JWT) authentication. 392 * 393 * @throws ParseException If the HTTP request header couldn't be parsed 394 * to a private key JSON Web Token (JWT) 395 * authentication. 396 */ 397 public static PrivateKeyJWT parse(final HTTPRequest httpRequest) 398 throws ParseException { 399 400 httpRequest.ensureMethod(HTTPRequest.Method.POST); 401 httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED); 402 403 return parse(httpRequest.getQueryParameters()); 404 } 405}