001package com.nimbusds.oauth2.sdk.auth;
002
003
004import java.net.URI;
005import java.util.Collections;
006import java.util.HashSet;
007import java.util.Map;
008import java.util.Set;
009
010import net.jcip.annotations.Immutable;
011
012import com.nimbusds.jose.JOSEException;
013import com.nimbusds.jose.JWSAlgorithm;
014import com.nimbusds.jose.JWSHeader;
015import com.nimbusds.jose.crypto.MACSigner;
016import com.nimbusds.jwt.SignedJWT;
017
018import com.nimbusds.oauth2.sdk.ParseException;
019import com.nimbusds.oauth2.sdk.id.Audience;
020import com.nimbusds.oauth2.sdk.id.ClientID;
021import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
022import com.nimbusds.oauth2.sdk.http.HTTPRequest;
023import com.nimbusds.oauth2.sdk.util.URLUtils;
024
025
026/**
027 * Client secret JWT authentication at the Token endpoint. Implements
028 * {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT}.
029 *
030 * <p>Supported signature JSON Web Algorithms (JWAs) by this implementation:
031 *
032 * <ul>
033 *     <li>HS256
034 *     <li>HS384
035 *     <li>HS512
036 * </ul>
037 *
038 * <p>Related specifications:
039 *
040 * <ul>
041 *     <li>Assertion Framework for OAuth 2.0 Client Authentication and
042 *         Authorization Grants (RFC 7521).
043 *     <li>JSON Web Token (JWT) Bearer Token Profiles for OAuth 2.0 (RFC 7523).
044 * </ul>
045 */
046@Immutable
047public final class ClientSecretJWT extends JWTAuthentication {
048
049
050        /**
051         * Gets the set of supported signature JSON Web Algorithms (JWAs) by 
052         * this implementation of client secret JSON Web Token (JWT) 
053         * authentication.
054         *
055         * @return The set of supported JSON Web Algorithms (JWAs).
056         */
057        public static Set<JWSAlgorithm> getSupportedJWAs() {
058        
059                Set<JWSAlgorithm> supported = new HashSet<>();
060                
061                supported.add(JWSAlgorithm.HS256);
062                supported.add(JWSAlgorithm.HS384);
063                supported.add(JWSAlgorithm.HS512);
064                
065                return Collections.unmodifiableSet(supported);
066        }
067
068
069        /**
070         * Creates a new client secret JWT assertion.
071         *
072         * @param jwtAuthClaimsSet The JWT authentication claims set. Must not
073         *                         be {@code null}.
074         * @param jwsAlgorithm     The expected HMAC algorithm (HS256, HS384 or
075         *                         HS512) for the client secret JWT assertion.
076         *                         Must be supported and not {@code null}.
077         * @param clientSecret     The client secret. Must be at least 256-bits
078         *                         long.
079         *
080         * @return The client secret JWT assertion.
081         *
082         * @throws JOSEException If the client secret is too short, or HMAC
083         *                       computation failed.
084         */
085        public static SignedJWT createClientAssertion(final JWTAuthenticationClaimsSet jwtAuthClaimsSet,
086                                                      final JWSAlgorithm jwsAlgorithm,
087                                                      final Secret clientSecret)
088                throws JOSEException {
089
090                SignedJWT signedJWT = new SignedJWT(new JWSHeader(jwsAlgorithm), jwtAuthClaimsSet.toJWTClaimsSet());
091                signedJWT.sign(new MACSigner(clientSecret.getValueBytes()));
092                return signedJWT;
093        }
094
095
096        /**
097         * Creates a new client secret JWT authentication. The expiration
098         * time (exp) is set to five minutes from the current system time.
099         * Generates a default identifier (jti) for the JWT. The issued-at
100         * (iat) and not-before (nbf) claims are not set.
101         *
102         * @param clientID      The client identifier. Must not be
103         *                      {@code null}.
104         * @param tokenEndpoint The token endpoint URI of the authorisation
105         *                      server. Must not be {@code null}.
106         * @param jwsAlgorithm  The expected HMAC algorithm (HS256, HS384 or
107         *                      HS512) for the client secret JWT assertion.
108         *                      Must be supported and not {@code null}.
109         * @param clientSecret  The client secret. Must be at least 256-bits
110         *                      long.
111         *
112         * @throws JOSEException If the client secret is too short, or HMAC
113         *                       computation failed.
114         */
115        public ClientSecretJWT(final ClientID clientID,
116                               final URI tokenEndpoint,
117                               final JWSAlgorithm jwsAlgorithm,
118                               final Secret clientSecret)
119                throws JOSEException {
120
121                this(createClientAssertion(
122                        new JWTAuthenticationClaimsSet(clientID, new Audience(tokenEndpoint.toString())),
123                        jwsAlgorithm,
124                        clientSecret));
125        }
126
127
128        /**
129         * Creates a new client secret JWT authentication.
130         *
131         * @param clientAssertion The client assertion, corresponding to the
132         *                        {@code client_assertion_parameter}, as a
133         *                        supported HMAC-protected JWT. Must be signed
134         *                        and not {@code null}.
135         */
136        public ClientSecretJWT(final SignedJWT clientAssertion) {
137
138                super(ClientAuthenticationMethod.CLIENT_SECRET_JWT, clientAssertion);
139
140                if (! getSupportedJWAs().contains(clientAssertion.getHeader().getAlgorithm()))
141                        throw new IllegalArgumentException("The client assertion JWT must be HMAC-signed (HS256, HS384 or HS512)");
142        }
143        
144        
145        /**
146         * Parses the specified parameters map for a client secret JSON Web 
147         * Token (JWT) authentication. Note that the parameters must not be
148         * {@code application/x-www-form-urlencoded} encoded.
149         *
150         * @param params The parameters map to parse. The client secret JSON
151         *               Web Token (JWT) parameters must be keyed under 
152         *               "client_assertion" and "client_assertion_type". The 
153         *               map must not be {@code null}.
154         *
155         * @return The client secret JSON Web Token (JWT) authentication.
156         *
157         * @throws ParseException If the parameters map couldn't be parsed to a 
158         *                        client secret JSON Web Token (JWT) 
159         *                        authentication.
160         */
161        public static ClientSecretJWT parse(final Map<String,String> params)
162                throws ParseException {
163        
164                JWTAuthentication.ensureClientAssertionType(params);
165                
166                SignedJWT clientAssertion = JWTAuthentication.parseClientAssertion(params);
167
168                ClientSecretJWT clientSecretJWT;
169
170                try {
171                        clientSecretJWT = new ClientSecretJWT(clientAssertion);
172
173                } catch (IllegalArgumentException e) {
174
175                        throw new ParseException(e.getMessage(), e);
176                }
177
178                // Check that the top level client_id matches the assertion subject + issuer
179                
180                ClientID clientID = JWTAuthentication.parseClientID(params);
181
182                if (clientID != null) {
183
184                        if (! clientID.equals(clientSecretJWT.getClientID()))
185                                throw new ParseException("The client identifier doesn't match the client assertion subject / issuer");
186                }
187
188                return clientSecretJWT;
189        }
190        
191        
192        /**
193         * Parses a client secret JSON Web Token (JWT) authentication from the 
194         * specified {@code application/x-www-form-urlencoded} encoded 
195         * parameters string.
196         *
197         * @param paramsString The parameters string to parse. The client secret
198         *                     JSON Web Token (JWT) parameters must be keyed 
199         *                     under "client_assertion" and 
200         *                     "client_assertion_type". The string must not be 
201         *                     {@code null}.
202         *
203         * @return The client secret JSON Web Token (JWT) authentication.
204         *
205         * @throws ParseException If the parameters string couldn't be parsed 
206         *                        to a client secret JSON Web Token (JWT) 
207         *                        authentication.
208         */
209        public static ClientSecretJWT parse(final String paramsString)
210                throws ParseException {
211                
212                Map<String,String> params = URLUtils.parseParameters(paramsString);
213                
214                return parse(params);
215        }
216        
217        
218        /**
219         * Parses the specified HTTP POST request for a client secret JSON Web 
220         * Token (JWT) authentication.
221         *
222         * @param httpRequest The HTTP POST request to parse. Must not be 
223         *                    {@code null} and must contain a valid 
224         *                    {@code application/x-www-form-urlencoded} encoded 
225         *                    parameters string in the entity body. The client 
226         *                    secret JSON Web Token (JWT) parameters must be 
227         *                    keyed under "client_assertion" and 
228         *                    "client_assertion_type".
229         *
230         * @return The client secret JSON Web Token (JWT) authentication.
231         *
232         * @throws ParseException If the HTTP request header couldn't be parsed
233         *                        to a client secret JSON Web Token (JWT) 
234         *                        authentication.
235         */
236        public static ClientSecretJWT parse(final HTTPRequest httpRequest)
237                throws ParseException {
238                
239                httpRequest.ensureMethod(HTTPRequest.Method.POST);
240                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
241                
242                return parse(httpRequest.getQueryParameters());
243        }
244}