001package com.nimbusds.oauth2.sdk.auth;
002
003
004import java.net.URI;
005import java.security.Provider;
006import java.security.interfaces.ECPrivateKey;
007import java.security.interfaces.RSAPrivateKey;
008import java.util.Collections;
009import java.util.HashSet;
010import java.util.Map;
011import java.util.Set;
012
013import net.jcip.annotations.Immutable;
014
015import com.nimbusds.jose.JOSEException;
016import com.nimbusds.jose.JWSAlgorithm;
017import com.nimbusds.jose.JWSHeader;
018import com.nimbusds.jose.crypto.ECDSASigner;
019import com.nimbusds.jose.crypto.RSASSASigner;
020import com.nimbusds.jwt.SignedJWT;
021
022import com.nimbusds.oauth2.sdk.ParseException;
023import com.nimbusds.oauth2.sdk.id.Audience;
024import com.nimbusds.oauth2.sdk.id.ClientID;
025import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
026import com.nimbusds.oauth2.sdk.http.HTTPRequest;
027import com.nimbusds.oauth2.sdk.util.URLUtils;
028
029
030/**
031 * Private key JWT authentication at the Token endpoint. Implements
032 * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT}.
033 *
034 * <p>Supported signature JSON Web Algorithms (JWAs) by this implementation:
035 *
036 * <ul>
037 *     <li>RS256
038 *     <li>RS384
039 *     <li>RS512
040 *     <li>PS256
041 *     <li>PS384
042 *     <li>PS512
043 *     <li>ES256
044 *     <li>ES384
045 *     <li>ES512
046 * </ul>
047 *
048 * <p>Example {@link com.nimbusds.oauth2.sdk.TokenRequest} with private key JWT
049 * authentication:
050 *
051 * <pre>
052 * POST /token HTTP/1.1
053 * Host: server.example.com
054 * Content-Type: application/x-www-form-urlencoded
055 *
056 * grant_type=authorization_code&amp;
057 * code=i1WsRn1uB1&amp;
058 * client_id=s6BhdRkqt3&amp;
059 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&amp;
060 * client_assertion=PHNhbWxwOl...[omitted for brevity]...ZT
061 * </pre>
062 *
063 * <p>Related specifications:
064 *
065 * <ul>
066 *     <li>Assertion Framework for OAuth 2.0 (RFC 7521).
067 *     <li>JSON Web Token (JWT) Bearer Token Profiles for OAuth 2.0 (RFC 7523).
068 * </ul>
069 */
070@Immutable
071public final class PrivateKeyJWT extends JWTAuthentication {
072
073
074        /**
075         * Gets the set of supported signature JSON Web Algorithms (JWAs) by 
076         * this implementation of private key JSON Web Token (JWT) 
077         * authentication.
078         *
079         * @return The set of supported JSON Web Algorithms (JWAs).
080         */
081        public static Set<JWSAlgorithm> getSupportedJWAs() {
082        
083                Set<JWSAlgorithm> supported = new HashSet<>();
084                
085                supported.add(JWSAlgorithm.RS256);
086                supported.add(JWSAlgorithm.RS384);
087                supported.add(JWSAlgorithm.RS512);
088
089                supported.add(JWSAlgorithm.PS256);
090                supported.add(JWSAlgorithm.PS384);
091                supported.add(JWSAlgorithm.PS512);
092                
093                supported.add(JWSAlgorithm.ES256);
094                supported.add(JWSAlgorithm.ES384);
095                supported.add(JWSAlgorithm.ES512);
096                
097                return Collections.unmodifiableSet(supported);
098        }
099
100
101        /**
102         * Creates a new RSA private key JWT assertion.
103         *
104         * @param jwtAuthClaimsSet The JWT authentication claims set. Must not
105         *                         be {@code null}.
106         * @param jwsAlgorithm     The expected RSA signature algorithm
107         *                         (RS256, RS384, RS512, PS256, PS384 or PS512)
108         *                         for the private key JWT assertion. Must be
109         *                         supported and not {@code null}.
110         * @param rsaPrivateKey    The RSA private key. Must not be
111         *                         {@code null}.
112         * @param jcaProvider      Optional specific JCA provider, {@code null}
113         *                         to use the default one.
114         *
115         * @return The private key JWT assertion.
116         *
117         * @throws JOSEException If RSA signing failed.
118         */
119        public static SignedJWT createClientAssertion(final JWTAuthenticationClaimsSet jwtAuthClaimsSet,
120                                                      final JWSAlgorithm jwsAlgorithm,
121                                                      final RSAPrivateKey rsaPrivateKey,
122                                                      final Provider jcaProvider)
123                throws JOSEException {
124
125                SignedJWT signedJWT = new SignedJWT(new JWSHeader(jwsAlgorithm), jwtAuthClaimsSet.toJWTClaimsSet());
126                RSASSASigner signer = new RSASSASigner(rsaPrivateKey);
127                if (jcaProvider != null) {
128                        signer.getJCAContext().setProvider(jcaProvider);
129                }
130                signedJWT.sign(signer);
131                return signedJWT;
132        }
133
134
135        /**
136         * Creates a new EC private key JWT assertion.
137         *
138         * @param jwtAuthClaimsSet The JWT authentication claims set. Must not
139         *                         be {@code null}.
140         * @param jwsAlgorithm     The expected RSA signature algorithm
141         *                         (RS256, RS384, RS512, PS256, PS384 or PS512)
142         *                         for the private key JWT assertion. Must be
143         *                         supported and not {@code null}.
144         * @param ecPrivateKey     The EC private key. Must not be
145         *                         {@code null}.
146         * @param jcaProvider      Optional specific JCA provider, {@code null}
147         *                         to use the default one.
148         *
149         * @return The private key JWT assertion.
150         *
151         * @throws JOSEException If RSA signing failed.
152         */
153        public static SignedJWT createClientAssertion(final JWTAuthenticationClaimsSet jwtAuthClaimsSet,
154                                                      final JWSAlgorithm jwsAlgorithm,
155                                                      final ECPrivateKey ecPrivateKey,
156                                                      final Provider jcaProvider)
157                throws JOSEException {
158
159                SignedJWT signedJWT = new SignedJWT(new JWSHeader(jwsAlgorithm), jwtAuthClaimsSet.toJWTClaimsSet());
160                ECDSASigner signer = new ECDSASigner(ecPrivateKey);
161                if (jcaProvider != null) {
162                        signer.getJCAContext().setProvider(jcaProvider);
163                }
164                signedJWT.sign(signer);
165                return signedJWT;
166        }
167
168
169        /**
170         * Creates a new RSA 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 RSA signature algorithm (ES256,
180         *                      ES384 or ES512) for the private key JWT
181         *                      assertion. Must be supported and not
182         *                      {@code null}.
183         * @param rsaPrivateKey The RSA private key. Must not be {@code null}.
184         * @param jcaProvider   Optional specific JCA provider, {@code null} to
185         *                      use the default one.
186         *
187         * @throws JOSEException If RSA signing failed.
188         */
189        public PrivateKeyJWT(final ClientID clientID,
190                             final URI tokenEndpoint,
191                             final JWSAlgorithm jwsAlgorithm,
192                             final RSAPrivateKey rsaPrivateKey,
193                             final Provider jcaProvider)
194                throws JOSEException {
195
196                this(new JWTAuthenticationClaimsSet(clientID, new Audience(tokenEndpoint.toString())),
197                        jwsAlgorithm,
198                        rsaPrivateKey,
199                        jcaProvider);
200        }
201
202
203        /**
204         * Creates a new RSA private key JWT authentication.
205         *
206         * @param jwtAuthClaimsSet The JWT authentication claims set. Must not
207         *                         be {@code null}.
208         * @param jwsAlgorithm     The expected RSA signature algorithm (ES256,
209         *                         ES384 or ES512) for the private key JWT
210         *                         assertion. Must be supported and not
211         *                         {@code null}.
212         * @param rsaPrivateKey    The RSA private key. Must not be
213         *                         {@code null}.
214         * @param jcaProvider      Optional specific JCA provider, {@code null}
215         *                         to use the default one.
216         *
217         * @throws JOSEException If RSA signing failed.
218         */
219        public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet,
220                             final JWSAlgorithm jwsAlgorithm,
221                             final RSAPrivateKey rsaPrivateKey,
222                             final Provider jcaProvider)
223                throws JOSEException {
224
225                this(createClientAssertion(jwtAuthClaimsSet, jwsAlgorithm, rsaPrivateKey, jcaProvider));
226        }
227
228
229        /**
230         * Creates a new EC private key JWT authentication. The expiration
231         * time (exp) is set to five minutes from the current system time.
232         * Generates a default identifier (jti) for the JWT. The issued-at
233         * (iat) and not-before (nbf) claims are not set.
234         *
235         * @param clientID      The client identifier. Must not be
236         *                      {@code null}.
237         * @param tokenEndpoint The token endpoint URI of the authorisation
238         *                      server. Must not be {@code null}.
239         * @param jwsAlgorithm  The expected RSA signature algorithm (ES256,
240         *                      ES384 or ES512) for the private key JWT
241         *                      assertion. Must be supported and not
242         *                      {@code null}.
243         * @param ecPrivateKey  The EC private key. Must not be {@code null}.
244         * @param jcaProvider   Optional specific JCA provider, {@code null} to
245         *                      use the default one.
246         *
247         * @throws JOSEException If RSA signing failed.
248         */
249        public PrivateKeyJWT(final ClientID clientID,
250                             final URI tokenEndpoint,
251                             final JWSAlgorithm jwsAlgorithm,
252                             final ECPrivateKey ecPrivateKey,
253                             final Provider jcaProvider)
254                throws JOSEException {
255
256                this(new JWTAuthenticationClaimsSet(clientID, new Audience(tokenEndpoint.toString())),
257                        jwsAlgorithm,
258                        ecPrivateKey,
259                        jcaProvider);
260        }
261        
262        
263        /**
264         * Creates a new EC private key JWT authentication.
265         *
266         * @param jwtAuthClaimsSet The JWT authentication claims set. Must not
267         *                         be {@code null}.
268         * @param jwsAlgorithm     The expected RSA signature algorithm (ES256,
269         *                         ES384 or ES512) for the private key JWT
270         *                         assertion. Must be supported and not
271         *                         {@code null}.
272         * @param ecPrivateKey     The EC private key. Must not be
273         *                         {@code null}.
274         * @param jcaProvider      Optional specific JCA provider, {@code null}
275         *                         to use the default one.
276         *
277         * @throws JOSEException If RSA signing failed.
278         */
279        public PrivateKeyJWT(final JWTAuthenticationClaimsSet jwtAuthClaimsSet,
280                             final JWSAlgorithm jwsAlgorithm,
281                             final ECPrivateKey ecPrivateKey,
282                             final Provider jcaProvider)
283                throws JOSEException {
284
285                this(createClientAssertion(jwtAuthClaimsSet, jwsAlgorithm, ecPrivateKey, jcaProvider));
286        }
287
288
289        /**
290         * Creates a new private key JWT authentication.
291         *
292         * @param clientAssertion The client assertion, corresponding to the
293         *                        {@code client_assertion} parameter, as a
294         *                        supported RSA or ECDSA-signed JWT. Must be
295         *                        signed and not {@code null}.
296         */
297        public PrivateKeyJWT(final SignedJWT clientAssertion) {
298        
299                super(ClientAuthenticationMethod.PRIVATE_KEY_JWT, clientAssertion);
300
301                if (! getSupportedJWAs().contains(clientAssertion.getHeader().getAlgorithm()))
302                        throw new IllegalArgumentException("The client assertion JWT must be RSA or ECDSA-signed (RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or ES512)");
303        }
304        
305        
306        /**
307         * Parses the specified parameters map for a private key JSON Web Token
308         * (JWT) authentication. Note that the parameters must not be
309         * {@code application/x-www-form-urlencoded} encoded.
310         *
311         * @param params The parameters map to parse. The private key JSON
312         *               Web Token (JWT) parameters must be keyed under 
313         *               "client_assertion" and "client_assertion_type". The 
314         *               map must not be {@code null}.
315         *
316         * @return The private key JSON Web Token (JWT) authentication.
317         *
318         * @throws ParseException If the parameters map couldn't be parsed to a 
319         *                        private key JSON Web Token (JWT) 
320         *                        authentication.
321         */
322        public static PrivateKeyJWT parse(final Map<String,String> params)
323                throws ParseException {
324        
325                JWTAuthentication.ensureClientAssertionType(params);
326                
327                SignedJWT clientAssertion = JWTAuthentication.parseClientAssertion(params);
328
329                PrivateKeyJWT privateKeyJWT;
330
331                try {
332                        privateKeyJWT = new PrivateKeyJWT(clientAssertion);
333
334                }catch (IllegalArgumentException e) {
335
336                        throw new ParseException(e.getMessage(), e);
337                }
338
339                // Check that the top level client_id matches the assertion subject + issuer
340
341                ClientID clientID = JWTAuthentication.parseClientID(params);
342
343                if (clientID != null) {
344
345                        if (! clientID.equals(privateKeyJWT.getClientID()))
346                                throw new ParseException("The client identifier doesn't match the client assertion subject / issuer");
347                }
348
349                return privateKeyJWT;
350        }
351        
352        
353        /**
354         * Parses a private key JSON Web Token (JWT) authentication from the 
355         * specified {@code application/x-www-form-urlencoded} encoded 
356         * parameters string.
357         *
358         * @param paramsString The parameters string to parse. The private key
359         *                     JSON Web Token (JWT) parameters must be keyed 
360         *                     under "client_assertion" and 
361         *                     "client_assertion_type". The string must not be 
362         *                     {@code null}.
363         *
364         * @return The private key JSON Web Token (JWT) authentication.
365         *
366         * @throws ParseException If the parameters string couldn't be parsed 
367         *                        to a private key JSON Web Token (JWT) 
368         *                        authentication.
369         */
370        public static PrivateKeyJWT parse(final String paramsString)
371                throws ParseException {
372                
373                Map<String,String> params = URLUtils.parseParameters(paramsString);
374                
375                return parse(params);
376        }
377        
378        
379        /**
380         * Parses the specified HTTP POST request for a private key JSON Web 
381         * Token (JWT) authentication.
382         *
383         * @param httpRequest The HTTP POST request to parse. Must not be 
384         *                    {@code null} and must contain a valid 
385         *                    {@code application/x-www-form-urlencoded} encoded 
386         *                    parameters string in the entity body. The private 
387         *                    key JSON Web Token (JWT) parameters must be 
388         *                    keyed under "client_assertion" and 
389         *                    "client_assertion_type".
390         *
391         * @return The private key JSON Web Token (JWT) authentication.
392         *
393         * @throws ParseException If the HTTP request header couldn't be parsed
394         *                        to a private key JSON Web Token (JWT) 
395         *                        authentication.
396         */
397        public static PrivateKeyJWT parse(final HTTPRequest httpRequest)
398                throws ParseException {
399                
400                httpRequest.ensureMethod(HTTPRequest.Method.POST);
401                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
402                
403                return parse(httpRequest.getQueryParameters());
404        }
405}