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&amp;
072 * code=i1WsRn1uB1&amp;
073 * client_id=s6BhdRkqt3&amp;
074 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&amp;
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("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}