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