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