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