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 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}