[CXF-5697] Some minor updates for WHICH_JAR
[cxf.git] / services / sts / sts-core / src / main / java / org / apache / cxf / sts / token / provider / jwt / JWTTokenProvider.java
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.cxf.sts.token.provider.jwt;
21
22 import java.security.KeyStore;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.Properties;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import javax.security.auth.callback.CallbackHandler;
32
33 import org.apache.cxf.common.logging.LogUtils;
34 import org.apache.cxf.common.util.StringUtils;
35 import org.apache.cxf.rs.security.jose.common.JoseConstants;
36 import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
37 import org.apache.cxf.rs.security.jose.jwa.KeyAlgorithm;
38 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
39 import org.apache.cxf.rs.security.jose.jwe.JweEncryptionProvider;
40 import org.apache.cxf.rs.security.jose.jwe.JweHeaders;
41 import org.apache.cxf.rs.security.jose.jwe.JweUtils;
42 import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
43 import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer;
44 import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
45 import org.apache.cxf.rs.security.jose.jws.JwsUtils;
46 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
47 import org.apache.cxf.sts.STSPropertiesMBean;
48 import org.apache.cxf.sts.SignatureProperties;
49 import org.apache.cxf.sts.cache.CacheUtils;
50 import org.apache.cxf.sts.request.KeyRequirements;
51 import org.apache.cxf.sts.request.TokenRequirements;
52 import org.apache.cxf.sts.service.EncryptionProperties;
53 import org.apache.cxf.sts.token.provider.TokenProvider;
54 import org.apache.cxf.sts.token.provider.TokenProviderParameters;
55 import org.apache.cxf.sts.token.provider.TokenProviderResponse;
56 import org.apache.cxf.sts.token.realm.RealmProperties;
57 import org.apache.cxf.ws.security.sts.provider.STSException;
58 import org.apache.cxf.ws.security.tokenstore.SecurityToken;
59 import org.apache.wss4j.common.crypto.Crypto;
60 import org.apache.wss4j.common.crypto.Merlin;
61 import org.apache.wss4j.common.ext.WSPasswordCallback;
62
63 /**
64 * A TokenProvider implementation that provides a JWT Token.
65 */
66 public class JWTTokenProvider implements TokenProvider {
67
68 public static final String JWT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt";
69 private static final Logger LOG = LogUtils.getL7dLogger(JWTTokenProvider.class);
70
71 private boolean signToken = true;
72 private Map<String, RealmProperties> realmMap = new HashMap<>();
73 private JWTClaimsProvider jwtClaimsProvider = new DefaultJWTClaimsProvider();
74
75 /**
76 * Return true if this TokenProvider implementation is capable of providing a token
77 * that corresponds to the given TokenType.
78 */
79 public boolean canHandleToken(String tokenType) {
80 return canHandleToken(tokenType, null);
81 }
82
83 /**
84 * Return true if this TokenProvider implementation is capable of providing a token
85 * that corresponds to the given TokenType in a given realm.
86 */
87 public boolean canHandleToken(String tokenType, String realm) {
88 if (realm != null && !realmMap.containsKey(realm)) {
89 return false;
90 }
91 return JWT_TOKEN_TYPE.equals(tokenType);
92 }
93
94 /**
95 * Create a token given a TokenProviderParameters
96 */
97 public TokenProviderResponse createToken(TokenProviderParameters tokenParameters) {
98 //KeyRequirements keyRequirements = tokenParameters.getKeyRequirements();
99 TokenRequirements tokenRequirements = tokenParameters.getTokenRequirements();
100 if (LOG.isLoggable(Level.FINE)) {
101 LOG.fine("Handling token of type: " + tokenRequirements.getTokenType());
102 }
103
104 String realm = tokenParameters.getRealm();
105 RealmProperties jwtRealm = null;
106 if (realm != null && realmMap.containsKey(realm)) {
107 jwtRealm = realmMap.get(realm);
108 }
109
110 // Get the claims
111 JWTClaimsProviderParameters jwtClaimsProviderParameters = new JWTClaimsProviderParameters();
112 jwtClaimsProviderParameters.setProviderParameters(tokenParameters);
113 if (jwtRealm != null) {
114 jwtClaimsProviderParameters.setIssuer(jwtRealm.getIssuer());
115 }
116
117 JwtClaims claims = jwtClaimsProvider.getJwtClaims(jwtClaimsProviderParameters);
118
119 try {
120 String tokenData = signToken(claims, jwtRealm, tokenParameters.getStsProperties());
121 if (tokenParameters.isEncryptToken()) {
122 tokenData = encryptToken(tokenData, new JweHeaders(),
123 tokenParameters.getStsProperties(),
124 tokenParameters.getEncryptionProperties(),
125 tokenParameters.getKeyRequirements());
126 }
127
128 TokenProviderResponse response = new TokenProviderResponse();
129 response.setToken(tokenData);
130
131 response.setTokenId(claims.getTokenId());
132
133 if (claims.getIssuedAt() > 0) {
134 response.setCreated(new Date(claims.getIssuedAt() * 1000L));
135 }
136 Date expires = null;
137 if (claims.getExpiryTime() > 0) {
138 expires = new Date(claims.getExpiryTime() * 1000L);
139 response.setExpires(expires);
140 }
141
142 // set the token in cache (only if the token is signed)
143 if (signToken && tokenParameters.getTokenStore() != null) {
144 SecurityToken securityToken =
145 CacheUtils.createSecurityTokenForStorage(null, claims.getTokenId(),
146 expires, tokenParameters.getPrincipal(), tokenParameters.getRealm(),
147 tokenParameters.getTokenRequirements().getRenewing());
148 securityToken.setData(tokenData.getBytes());
149
150 String signature = tokenData.substring(tokenData.lastIndexOf(".") + 1);
151 CacheUtils.storeTokenInCache(
152 securityToken, tokenParameters.getTokenStore(), signature.getBytes());
153 }
154
155 LOG.fine("JWT Token successfully created");
156 return response;
157 } catch (Exception e) {
158 e.printStackTrace();
159 LOG.log(Level.WARNING, "", e);
160 throw new STSException("Can't serialize JWT token", e, STSException.REQUEST_FAILED);
161 }
162 }
163
164 /**
165 * Return whether the provided token will be signed or not. Default is true.
166 */
167 public boolean isSignToken() {
168 return signToken;
169 }
170
171 /**
172 * Set whether the provided token will be signed or not. Default is true.
173 */
174 public void setSignToken(boolean signToken) {
175 this.signToken = signToken;
176 }
177
178 /**
179 * Set the map of realm->RealmProperties for this token provider
180 * @param realms the map of realm->RealmProperties for this token provider
181 */
182 public void setRealmMap(Map<String, ? extends RealmProperties> realms) {
183 this.realmMap.clear();
184 this.realmMap.putAll(realms);
185 }
186
187 /**
188 * Get the map of realm->RealmProperties for this token provider
189 * @return the map of realm->RealmProperties for this token provider
190 */
191 public Map<String, RealmProperties> getRealmMap() {
192 return Collections.unmodifiableMap(realmMap);
193 }
194
195 public JWTClaimsProvider getJwtClaimsProvider() {
196 return jwtClaimsProvider;
197 }
198
199 public void setJwtClaimsProvider(JWTClaimsProvider jwtClaimsProvider) {
200 this.jwtClaimsProvider = jwtClaimsProvider;
201 }
202
203 private String signToken(
204 JwtClaims claims,
205 RealmProperties jwtRealm,
206 STSPropertiesMBean stsProperties
207 ) throws Exception {
208
209 if (signToken) {
210 // Initialise signature objects with defaults of STSPropertiesMBean
211 Crypto signatureCrypto = stsProperties.getSignatureCrypto();
212 CallbackHandler callbackHandler = stsProperties.getCallbackHandler();
213 SignatureProperties signatureProperties = stsProperties.getSignatureProperties();
214 String alias = stsProperties.getSignatureUsername();
215
216 if (jwtRealm != null) {
217 // If SignatureCrypto configured in realm then
218 // callbackhandler and alias of STSPropertiesMBean is ignored
219 if (jwtRealm.getSignatureCrypto() != null) {
220 LOG.fine("SAMLRealm signature keystore used");
221 signatureCrypto = jwtRealm.getSignatureCrypto();
222 callbackHandler = jwtRealm.getCallbackHandler();
223 alias = jwtRealm.getSignatureAlias();
224 }
225 // SignatureProperties can be defined independently of SignatureCrypto
226 if (jwtRealm.getSignatureProperties() != null) {
227 signatureProperties = jwtRealm.getSignatureProperties();
228 }
229 }
230
231 // Get the signature algorithm to use - for now we don't allow the client to ask
232 // for a particular signature algorithm, as with SAML
233 String signatureAlgorithm = signatureProperties.getSignatureAlgorithm();
234 try {
235 SignatureAlgorithm.getAlgorithm(signatureAlgorithm);
236 } catch (IllegalArgumentException ex) {
237 signatureAlgorithm = SignatureAlgorithm.RS256.name();
238 }
239
240 // If alias not defined, get the default of the SignatureCrypto
241 if ((alias == null || "".equals(alias)) && (signatureCrypto != null)) {
242 alias = signatureCrypto.getDefaultX509Identifier();
243 if (LOG.isLoggable(Level.FINE)) {
244 LOG.fine("Signature alias is null so using default alias: " + alias);
245 }
246 }
247 // Get the password
248 WSPasswordCallback[] cb = {new WSPasswordCallback(alias, WSPasswordCallback.SIGNATURE)};
249 callbackHandler.handle(cb);
250 String password = cb[0].getPassword();
251
252 Properties signingProperties = new Properties();
253
254 signingProperties.put(JoseConstants.RSSEC_SIGNATURE_ALGORITHM, signatureAlgorithm);
255 if (alias != null) {
256 signingProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, alias);
257 }
258 if (password != null) {
259 signingProperties.put(JoseConstants.RSSEC_KEY_PSWD, password);
260 } else {
261 throw new STSException("Can't get the password", STSException.REQUEST_FAILED);
262 }
263
264 if (!(signatureCrypto instanceof Merlin)) {
265 throw new STSException("Can't get the keystore", STSException.REQUEST_FAILED);
266 }
267 KeyStore keystore = ((Merlin)signatureCrypto).getKeyStore();
268 signingProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore);
269
270 JwsHeaders jwsHeaders = new JwsHeaders(signingProperties);
271 JwsJwtCompactProducer jws = new JwsJwtCompactProducer(jwsHeaders, claims);
272
273 JwsSignatureProvider sigProvider =
274 JwsUtils.loadSignatureProvider(signingProperties, jwsHeaders);
275
276 return jws.signWith(sigProvider);
277 } else {
278 JwsHeaders jwsHeaders = new JwsHeaders(SignatureAlgorithm.NONE);
279 JwsJwtCompactProducer jws = new JwsJwtCompactProducer(jwsHeaders, claims);
280 return jws.getSignedEncodedJws();
281 }
282
283 }
284
285 private String encryptToken(
286 String token,
287 JweHeaders jweHeaders,
288 STSPropertiesMBean stsProperties,
289 EncryptionProperties encryptionProperties,
290 KeyRequirements keyRequirements
291 ) throws Exception {
292
293 Properties encProperties = new Properties();
294
295 String name = encryptionProperties.getEncryptionName();
296 if (name == null) {
297 name = stsProperties.getEncryptionUsername();
298 }
299 if (name == null) {
300 LOG.fine("No encryption alias is configured");
301 return token;
302 }
303 encProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, name);
304
305 // Get the encryption algorithm to use - for now we don't allow the client to ask
306 // for a particular encryption algorithm, as with SAML
307 String encryptionAlgorithm = encryptionProperties.getEncryptionAlgorithm();
308 try {
309 ContentAlgorithm.getAlgorithm(encryptionAlgorithm);
310 } catch (IllegalArgumentException ex) {
311 encryptionAlgorithm = ContentAlgorithm.A128GCM.name();
312 }
313 encProperties.put(JoseConstants.RSSEC_ENCRYPTION_CONTENT_ALGORITHM, encryptionAlgorithm);
314
315 // Get the key-wrap algorithm to use - for now we don't allow the client to ask
316 // for a particular encryption algorithm, as with SAML
317 String keyWrapAlgorithm = encryptionProperties.getKeyWrapAlgorithm();
318 try {
319 KeyAlgorithm.getAlgorithm(keyWrapAlgorithm);
320 } catch (IllegalArgumentException ex) {
321 keyWrapAlgorithm = KeyAlgorithm.RSA_OAEP.name();
322 }
323 encProperties.put(JoseConstants.RSSEC_ENCRYPTION_KEY_ALGORITHM, keyWrapAlgorithm);
324
325 // Initialise encryption objects with defaults of STSPropertiesMBean
326 Crypto encryptionCrypto = stsProperties.getEncryptionCrypto();
327
328 if (!(encryptionCrypto instanceof Merlin)) {
329 throw new STSException("Can't get the keystore", STSException.REQUEST_FAILED);
330 }
331 KeyStore keystore = ((Merlin)encryptionCrypto).getKeyStore();
332 encProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore);
333
334 JweEncryptionProvider encProvider =
335 JweUtils.loadEncryptionProvider(encProperties, jweHeaders, false);
336 // token.getJwsHeaders().setSignatureAlgorithm(sigProvider.getAlgorithm());
337
338 return encProvider.encrypt(StringUtils.toBytesUTF8(token), null);
339
340 }
341
342 }