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