Adding the ability to sign JWT tokens in the STS plus some NPE fixes
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/6113febb Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/6113febb Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/6113febb Branch: refs/heads/master Commit: 6113febb17a728296d08667ce207aa5ee40d7c4a Parents: bb4ddb5 Author: Colm O hEigeartaigh <[email protected]> Authored: Fri Nov 6 17:21:29 2015 +0000 Committer: Colm O hEigeartaigh <[email protected]> Committed: Fri Nov 6 18:02:32 2015 +0000 ---------------------------------------------------------------------- .../org/apache/cxf/message/MessageUtils.java | 8 +- .../rs/security/jose/common/JoseConstants.java | 5 + .../jose/common/KeyManagementUtils.java | 37 +++--- .../cxf/rs/security/jose/jws/JwsUtils.java | 2 +- .../token/provider/jwt/JWTTokenProvider.java | 115 ++++++++++++++++--- .../token/provider/JWTTokenProviderTest.java | 40 +++++++ 6 files changed, 168 insertions(+), 39 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/6113febb/core/src/main/java/org/apache/cxf/message/MessageUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/cxf/message/MessageUtils.java b/core/src/main/java/org/apache/cxf/message/MessageUtils.java index d8bbf0c..d964070 100644 --- a/core/src/main/java/org/apache/cxf/message/MessageUtils.java +++ b/core/src/main/java/org/apache/cxf/message/MessageUtils.java @@ -124,9 +124,11 @@ public final class MessageUtils { } public static boolean getContextualBoolean(Message m, String key, boolean defaultValue) { - Object o = m.getContextualProperty(key); - if (o != null) { - return PropertyUtils.isTrue(o); + if (m != null) { + Object o = m.getContextualProperty(key); + if (o != null) { + return PropertyUtils.isTrue(o); + } } return defaultValue; } http://git-wip-us.apache.org/repos/asf/cxf/blob/6113febb/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java index cc990b5..cf9f90a 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java @@ -95,6 +95,11 @@ public final class JoseConstants { public static final String RSSEC_KEY_STORE_FILE = "rs.security.keystore.file"; /** + * The KeyStore Object. + */ + public static final String RSSEC_KEY_STORE= "rs.security.keystore"; + + /** * A reference to a PrivateKeyPasswordProvider instance used to retrieve passwords to access keys. */ public static final String RSSEC_KEY_PSWD_PROVIDER = "rs.security.key.password.provider"; http://git-wip-us.apache.org/repos/asf/cxf/blob/6113febb/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java index a53e7a8..2ca6e80 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java @@ -139,8 +139,6 @@ public final class KeyManagementUtils { private static PrivateKey loadPrivateKey(KeyStore keyStore, Message m, Properties props, - Bus bus, - PrivateKeyPasswordProvider provider, KeyOperation keyOper, String alias) { @@ -149,8 +147,11 @@ public final class KeyManagementUtils { if (theAlias != null) { props.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, theAlias); } - char[] keyPswdChars = provider != null ? provider.getPassword(props) - : keyPswd != null ? keyPswd.toCharArray() : null; + char[] keyPswdChars = keyPswd != null ? keyPswd.toCharArray() : null; + if (keyPswdChars == null) { + PrivateKeyPasswordProvider provider = loadPasswordProvider(m, props, keyOper); + keyPswdChars = provider != null ? provider.getPassword(props) : null; + } return CryptoUtils.loadPrivateKey(keyStore, keyPswdChars, theAlias); } @@ -174,7 +175,7 @@ public final class KeyManagementUtils { KeyOperation keyOper) { String kid = null; String altPropertyName = null; - if (keyOper != null) { + if (keyOper != null && m != null) { if (keyOper == KeyOperation.ENCRYPT || keyOper == KeyOperation.DECRYPT) { altPropertyName = preferredPropertyName + ".jwe"; } else if (keyOper == KeyOperation.SIGN || keyOper == KeyOperation.VERIFY) { @@ -216,21 +217,25 @@ public final class KeyManagementUtils { KeyStore keyStore = loadPersistKeyStore(m, props); return loadPrivateKey(keyStore, m, props, keyOper, null); } - private static PrivateKey loadPrivateKey(KeyStore keyStore, Message m, Properties props, KeyOperation keyOper, - String alias) { - Bus bus = m.getExchange().getBus(); - PrivateKeyPasswordProvider cb = loadPasswordProvider(m, props, keyOper); - return loadPrivateKey(keyStore, m, props, bus, cb, keyOper, alias); - } public static KeyStore loadPersistKeyStore(Message m, Properties props) { - if (!props.containsKey(JoseConstants.RSSEC_KEY_STORE_FILE)) { - LOG.warning("No keystore file has been configured"); - throw new JoseException("No keystore file has been configured"); + KeyStore keyStore = null; + if (props.containsKey(JoseConstants.RSSEC_KEY_STORE)) { + keyStore = (KeyStore)props.get(JoseConstants.RSSEC_KEY_STORE); + } + + if (keyStore == null) { + if (!props.containsKey(JoseConstants.RSSEC_KEY_STORE_FILE)) { + LOG.warning("No keystore file has been configured"); + throw new JoseException("No keystore file has been configured"); + } + keyStore = (KeyStore)m.getExchange().get(props.get(JoseConstants.RSSEC_KEY_STORE_FILE)); } - KeyStore keyStore = (KeyStore)m.getExchange().get(props.get(JoseConstants.RSSEC_KEY_STORE_FILE)); + if (keyStore == null) { keyStore = loadKeyStore(props, m.getExchange().getBus()); - m.getExchange().put((String)props.get(JoseConstants.RSSEC_KEY_STORE_FILE), keyStore); + if (m != null) { + m.getExchange().put((String)props.get(JoseConstants.RSSEC_KEY_STORE_FILE), keyStore); + } } return keyStore; } http://git-wip-us.apache.org/repos/asf/cxf/blob/6113febb/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java index b9f0001..0bce50e 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java @@ -261,7 +261,7 @@ public final class JwsUtils { //TODO: validate JWS specific constraints return JoseUtils.validateCriticalHeaders(headers); } - private static JwsSignatureProvider loadSignatureProvider(Message m, + public static JwsSignatureProvider loadSignatureProvider(Message m, Properties props, JoseHeaders headers, boolean ignoreNullProvider) { http://git-wip-us.apache.org/repos/asf/cxf/blob/6113febb/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/jwt/JWTTokenProvider.java ---------------------------------------------------------------------- diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/jwt/JWTTokenProvider.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/jwt/JWTTokenProvider.java index b458281..573788b 100644 --- a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/jwt/JWTTokenProvider.java +++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/jwt/JWTTokenProvider.java @@ -19,6 +19,7 @@ package org.apache.cxf.sts.token.provider.jwt; +import java.security.KeyStore; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -26,17 +27,29 @@ import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; +import javax.security.auth.callback.CallbackHandler; + import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.PhaseInterceptorChain; import org.apache.cxf.rs.security.jose.common.JoseConstants; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer; +import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider; +import org.apache.cxf.rs.security.jose.jws.JwsUtils; import org.apache.cxf.rs.security.jose.jwt.JwtClaims; import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.cxf.sts.STSPropertiesMBean; +import org.apache.cxf.sts.SignatureProperties; import org.apache.cxf.sts.request.TokenRequirements; import org.apache.cxf.sts.token.provider.TokenProvider; import org.apache.cxf.sts.token.provider.TokenProviderParameters; import org.apache.cxf.sts.token.provider.TokenProviderResponse; import org.apache.cxf.sts.token.realm.SAMLRealm; import org.apache.cxf.ws.security.sts.provider.STSException; +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.Merlin; +import org.apache.wss4j.common.ext.WSPasswordCallback; /** * A TokenProvider implementation that provides a JWT Token. @@ -85,13 +98,6 @@ public class JWTTokenProvider implements TokenProvider { JwtClaims claims = jwtClaimsProvider.getJwtClaims(jwtClaimsProviderParameters); - /* - if (signToken) { - STSPropertiesMBean stsProperties = tokenParameters.getStsProperties(); - signToken(assertion, samlRealm, stsProperties, tokenParameters.getKeyRequirements()); - } - */ - try { /* Document doc = DOMUtils.createDocument(); @@ -120,12 +126,8 @@ public class JWTTokenProvider implements TokenProvider { JwtToken token = new JwtToken(claims); - Properties signingProperties = new Properties(); - signingProperties.put(JoseConstants.RSSEC_SIGNATURE_ALGORITHM, "none"); - - JwsJwtCompactProducer jws = new JwsJwtCompactProducer(token); - jws.setSignatureProperties(signingProperties); - String tokenData = jws.getSignedEncodedJws(); + String tokenData = signToken(token, null, tokenParameters.getStsProperties(), + tokenParameters.getTokenRequirements()); TokenProviderResponse response = new TokenProviderResponse(); response.setToken(tokenData); @@ -139,12 +141,6 @@ public class JWTTokenProvider implements TokenProvider { response.setExpires(new Date(claims.getExpiryTime() * 1000L)); } - /*response.setEntropy(entropyBytes); - if (keySize > 0) { - response.setKeySize(keySize); - } - response.setComputedKey(computedKey); - */ LOG.fine("JWT Token successfully created"); return response; } catch (Exception e) { @@ -192,4 +188,85 @@ public class JWTTokenProvider implements TokenProvider { this.jwtClaimsProvider = jwtClaimsProvider; } + private String signToken( + JwtToken token, + SAMLRealm samlRealm, + STSPropertiesMBean stsProperties, + TokenRequirements tokenRequirements + ) throws Exception { + + Properties signingProperties = new Properties(); + + if (signToken) { + // Initialise signature objects with defaults of STSPropertiesMBean + Crypto signatureCrypto = stsProperties.getSignatureCrypto(); + CallbackHandler callbackHandler = stsProperties.getCallbackHandler(); + SignatureProperties signatureProperties = stsProperties.getSignatureProperties(); + String alias = stsProperties.getSignatureUsername(); + + if (samlRealm != null) { + // If SignatureCrypto configured in realm then + // callbackhandler and alias of STSPropertiesMBean is ignored + if (samlRealm.getSignatureCrypto() != null) { + LOG.fine("SAMLRealm signature keystore used"); + signatureCrypto = samlRealm.getSignatureCrypto(); + callbackHandler = samlRealm.getCallbackHandler(); + alias = samlRealm.getSignatureAlias(); + } + // SignatureProperties can be defined independently of SignatureCrypto + if (samlRealm.getSignatureProperties() != null) { + signatureProperties = samlRealm.getSignatureProperties(); + } + } + + // Get the signature algorithm to use - for now we don't allow the client to ask + // for a particular signature algorithm, as with SAML + String signatureAlgorithm = signatureProperties.getSignatureAlgorithm(); + try { + SignatureAlgorithm.getAlgorithm(signatureAlgorithm); + } catch (IllegalArgumentException ex) { + signatureAlgorithm = SignatureAlgorithm.RS256.name(); + } + + // If alias not defined, get the default of the SignatureCrypto + if ((alias == null || "".equals(alias)) && (signatureCrypto != null)) { + alias = signatureCrypto.getDefaultX509Identifier(); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine("Signature alias is null so using default alias: " + alias); + } + } + // Get the password + WSPasswordCallback[] cb = {new WSPasswordCallback(alias, WSPasswordCallback.SIGNATURE)}; + callbackHandler.handle(cb); + String password = cb[0].getPassword(); + + signingProperties.put(JoseConstants.RSSEC_SIGNATURE_ALGORITHM, signatureAlgorithm); + signingProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, alias); + signingProperties.put(JoseConstants.RSSEC_KEY_PSWD, password); + + if (!(signatureCrypto instanceof Merlin)) { + throw new STSException("Can't get the keystore", STSException.REQUEST_FAILED); + } + KeyStore keystore = ((Merlin)signatureCrypto).getKeyStore(); + signingProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore); + + JwsJwtCompactProducer jws = new JwsJwtCompactProducer(token); + jws.setSignatureProperties(signingProperties); + + Message m = PhaseInterceptorChain.getCurrentMessage(); + JwsSignatureProvider sigProvider = + JwsUtils.loadSignatureProvider(m, signingProperties, token.getJwsHeaders(), false); + token.getJwsHeaders().setSignatureAlgorithm(sigProvider.getAlgorithm()); + + return jws.signWith(sigProvider); + } else { + signingProperties.put(JoseConstants.RSSEC_SIGNATURE_ALGORITHM, "none"); + + JwsJwtCompactProducer jws = new JwsJwtCompactProducer(token); + jws.setSignatureProperties(signingProperties); + return jws.getSignedEncodedJws(); + } + + } + } http://git-wip-us.apache.org/repos/asf/cxf/blob/6113febb/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/JWTTokenProviderTest.java ---------------------------------------------------------------------- diff --git a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/JWTTokenProviderTest.java b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/JWTTokenProviderTest.java index 19d41f2..aed28ef 100644 --- a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/JWTTokenProviderTest.java +++ b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/JWTTokenProviderTest.java @@ -18,11 +18,13 @@ */ package org.apache.cxf.sts.token.provider; +import java.security.cert.X509Certificate; import java.util.Properties; import org.apache.cxf.jaxws.context.WebServiceContextImpl; import org.apache.cxf.jaxws.context.WrappedMessageContext; import org.apache.cxf.message.MessageImpl; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer; import org.apache.cxf.rs.security.jose.jwt.JwtConstants; import org.apache.cxf.rs.security.jose.jwt.JwtToken; @@ -36,6 +38,7 @@ import org.apache.cxf.sts.token.provider.jwt.JWTTokenProvider; import org.apache.cxf.ws.security.tokenstore.TokenStore; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.CryptoFactory; +import org.apache.wss4j.common.crypto.CryptoType; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.principal.CustomTokenPrincipal; import org.junit.Assert; @@ -50,6 +53,7 @@ public class JWTTokenProviderTest extends org.junit.Assert { @org.junit.Test public void testCreateUnsignedJWT() throws Exception { TokenProvider jwtTokenProvider = new JWTTokenProvider(); + ((JWTTokenProvider)jwtTokenProvider).setSignToken(false); TokenProviderParameters providerParameters = createProviderParameters(); @@ -73,6 +77,42 @@ public class JWTTokenProviderTest extends org.junit.Assert { jwt.getClaim(JwtConstants.CLAIM_EXPIRY)); } + @org.junit.Test + public void testCreateSignedJWT() throws Exception { + TokenProvider jwtTokenProvider = new JWTTokenProvider(); + ((JWTTokenProvider)jwtTokenProvider).setSignToken(true); + + TokenProviderParameters providerParameters = createProviderParameters(); + + assertTrue(jwtTokenProvider.canHandleToken(JWTTokenProvider.JWT_TOKEN_TYPE)); + TokenProviderResponse providerResponse = jwtTokenProvider.createToken(providerParameters); + assertTrue(providerResponse != null); + assertTrue(providerResponse.getToken() != null && providerResponse.getTokenId() != null); + + String token = (String)providerResponse.getToken(); + assertNotNull(token); + assertTrue(token.split("\\.").length == 3); + + // Validate the token + JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(token); + JwtToken jwt = jwtConsumer.getJwtToken(); + Assert.assertEquals("alice", jwt.getClaim(JwtConstants.CLAIM_SUBJECT)); + Assert.assertEquals(providerResponse.getTokenId(), jwt.getClaim(JwtConstants.CLAIM_JWT_ID)); + Assert.assertEquals(providerResponse.getCreated().getTime() / 1000L, + jwt.getClaim(JwtConstants.CLAIM_ISSUED_AT)); + Assert.assertEquals(providerResponse.getExpires().getTime() / 1000L, + jwt.getClaim(JwtConstants.CLAIM_EXPIRY)); + + // Verify Signature + Crypto crypto = providerParameters.getStsProperties().getSignatureCrypto(); + CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS); + cryptoType.setAlias(providerParameters.getStsProperties().getSignatureUsername()); + X509Certificate[] certs = crypto.getX509Certificates(cryptoType); + assertNotNull(certs); + + assertTrue(jwtConsumer.verifySignatureWith(certs[0], SignatureAlgorithm.RS256)); + } + private TokenProviderParameters createProviderParameters() throws WSSecurityException { TokenProviderParameters parameters = new TokenProviderParameters();
