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