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