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&amp;
067 * code=i1WsRn1uB1&amp;
068 * client_id=s6BhdRkqt3&amp;
069 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&amp;
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}