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