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}