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