Add the ability to encrypt JWT tokens from the STS
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/b297eed6 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/b297eed6 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/b297eed6 Branch: refs/heads/3.1.x-fixes Commit: b297eed6d676eb0e5ee3fa4f597d04e1fb9652ed Parents: ab05845 Author: Colm O hEigeartaigh <[email protected]> Authored: Wed Nov 11 17:17:46 2015 +0000 Committer: Colm O hEigeartaigh <[email protected]> Committed: Wed Nov 11 17:19:45 2015 +0000 ---------------------------------------------------------------------- .../token/provider/jwt/JWTTokenProvider.java | 77 +++++++++++- .../token/provider/JWTTokenProviderTest.java | 125 ++++++++++++++++++- 2 files changed, 195 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/b297eed6/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 5afffda..54a4c4e 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 @@ -31,10 +31,16 @@ import java.util.logging.Logger; import javax.security.auth.callback.CallbackHandler; import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.util.StringUtils; 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.ContentAlgorithm; +import org.apache.cxf.rs.security.jose.jwa.KeyAlgorithm; import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rs.security.jose.jwe.JweEncryptionProvider; +import org.apache.cxf.rs.security.jose.jwe.JweHeaders; +import org.apache.cxf.rs.security.jose.jwe.JweUtils; 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; @@ -43,7 +49,9 @@ 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.cache.CacheUtils; +import org.apache.cxf.sts.request.KeyRequirements; import org.apache.cxf.sts.request.TokenRequirements; +import org.apache.cxf.sts.service.EncryptionProperties; import org.apache.cxf.sts.token.provider.TokenProvider; import org.apache.cxf.sts.token.provider.TokenProviderParameters; import org.apache.cxf.sts.token.provider.TokenProviderResponse; @@ -113,8 +121,13 @@ public class JWTTokenProvider implements TokenProvider { try { JwtToken token = new JwtToken(claims); - String tokenData = signToken(token, jwtRealm, tokenParameters.getStsProperties(), - tokenParameters.getTokenRequirements()); + String tokenData = signToken(token, jwtRealm, tokenParameters.getStsProperties()); + if (tokenParameters.isEncryptToken()) { + tokenData = encryptToken(tokenData, token.getJweHeaders(), + tokenParameters.getStsProperties(), + tokenParameters.getEncryptionProperties(), + tokenParameters.getKeyRequirements()); + } TokenProviderResponse response = new TokenProviderResponse(); response.setToken(tokenData); @@ -194,8 +207,7 @@ public class JWTTokenProvider implements TokenProvider { private String signToken( JwtToken token, RealmProperties jwtRealm, - STSPropertiesMBean stsProperties, - TokenRequirements tokenRequirements + STSPropertiesMBean stsProperties ) throws Exception { Properties signingProperties = new Properties(); @@ -277,5 +289,62 @@ public class JWTTokenProvider implements TokenProvider { } } + + private String encryptToken( + String token, + JweHeaders jweHeaders, + STSPropertiesMBean stsProperties, + EncryptionProperties encryptionProperties, + KeyRequirements keyRequirements + ) throws Exception { + + Properties encProperties = new Properties(); + + String name = encryptionProperties.getEncryptionName(); + if (name == null) { + name = stsProperties.getEncryptionUsername(); + } + if (name == null) { + LOG.fine("No encryption alias is configured"); + return token; + } + encProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, name); + + // Get the encryption algorithm to use - for now we don't allow the client to ask + // for a particular encryption algorithm, as with SAML + String encryptionAlgorithm = encryptionProperties.getEncryptionAlgorithm(); + try { + ContentAlgorithm.getAlgorithm(encryptionAlgorithm); + } catch (IllegalArgumentException ex) { + encryptionAlgorithm = ContentAlgorithm.A128GCM.name(); + } + encProperties.put(JoseConstants.RSSEC_ENCRYPTION_CONTENT_ALGORITHM, encryptionAlgorithm); + + // Get the key-wrap algorithm to use - for now we don't allow the client to ask + // for a particular encryption algorithm, as with SAML + String keyWrapAlgorithm = encryptionProperties.getKeyWrapAlgorithm(); + try { + KeyAlgorithm.getAlgorithm(keyWrapAlgorithm); + } catch (IllegalArgumentException ex) { + keyWrapAlgorithm = KeyAlgorithm.RSA_OAEP.name(); + } + encProperties.put(JoseConstants.RSSEC_ENCRYPTION_KEY_ALGORITHM, keyWrapAlgorithm); + + // Initialise encryption objects with defaults of STSPropertiesMBean + Crypto encryptionCrypto = stsProperties.getEncryptionCrypto(); + + if (!(encryptionCrypto instanceof Merlin)) { + throw new STSException("Can't get the keystore", STSException.REQUEST_FAILED); + } + KeyStore keystore = ((Merlin)encryptionCrypto).getKeyStore(); + encProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore); + + JweEncryptionProvider encProvider = + JweUtils.loadEncryptionProvider(encProperties, jweHeaders, false); + // token.getJwsHeaders().setSignatureAlgorithm(sigProvider.getAlgorithm()); + + return encProvider.encrypt(StringUtils.toBytesUTF8(token), null); + + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/b297eed6/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 6273e0e..2af75c2 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,6 +18,7 @@ */ package org.apache.cxf.sts.token.provider; +import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Properties; @@ -25,13 +26,19 @@ 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.common.JoseConstants; import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rs.security.jose.jwe.JweDecryptionOutput; +import org.apache.cxf.rs.security.jose.jwe.JweDecryptionProvider; +import org.apache.cxf.rs.security.jose.jwe.JweJwtCompactConsumer; +import org.apache.cxf.rs.security.jose.jwe.JweUtils; 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; import org.apache.cxf.sts.StaticSTSProperties; import org.apache.cxf.sts.cache.DefaultInMemoryTokenStore; import org.apache.cxf.sts.common.PasswordCallbackHandler; +import org.apache.cxf.sts.common.TestUtils; import org.apache.cxf.sts.request.KeyRequirements; import org.apache.cxf.sts.request.TokenRequirements; import org.apache.cxf.sts.service.EncryptionProperties; @@ -41,6 +48,7 @@ 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.crypto.Merlin; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.principal.CustomTokenPrincipal; import org.junit.Assert; @@ -49,9 +57,13 @@ import org.junit.Assert; * Some unit tests for creating JWTTokens. */ public class JWTTokenProviderTest extends org.junit.Assert { - + private static boolean unrestrictedPoliciesInstalled; private static TokenStore tokenStore = new DefaultInMemoryTokenStore(); + static { + unrestrictedPoliciesInstalled = TestUtils.checkUnrestrictedPoliciesInstalled(); + }; + @org.junit.Test public void testCreateUnsignedJWT() throws Exception { TokenProvider jwtTokenProvider = new JWTTokenProvider(); @@ -147,6 +159,97 @@ public class JWTTokenProviderTest extends org.junit.Assert { Assert.assertNotNull(secToken); } + @org.junit.Test + public void testCreateUnsignedEncryptedJWT() throws Exception { + TokenProvider jwtTokenProvider = new JWTTokenProvider(); + ((JWTTokenProvider)jwtTokenProvider).setSignToken(false); + + TokenProviderParameters providerParameters = createProviderParameters(); + providerParameters.setEncryptToken(true); + + 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 == 5); + + if (unrestrictedPoliciesInstalled) { + // Validate the token + JweJwtCompactConsumer jwtConsumer = new JweJwtCompactConsumer(token); + Properties decProperties = new Properties(); + Crypto decryptionCrypto = CryptoFactory.getInstance(getDecryptionProperties()); + KeyStore keystore = ((Merlin)decryptionCrypto).getKeyStore(); + decProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore); + decProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, "myservicekey"); + decProperties.put(JoseConstants.RSSEC_KEY_PSWD, "skpass"); + + JweDecryptionProvider decProvider = + JweUtils.loadDecryptionProvider(decProperties, jwtConsumer.getHeaders(), false); + + JweDecryptionOutput decOutput = decProvider.decrypt(token); + String decToken = decOutput.getContentText(); + + JwsJwtCompactConsumer jwtJwsConsumer = new JwsJwtCompactConsumer(decToken); + JwtToken jwt = jwtJwsConsumer.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)); + } + + } + + @org.junit.Test + public void testCreateSignedEncryptedJWT() throws Exception { + TokenProvider jwtTokenProvider = new JWTTokenProvider(); + + TokenProviderParameters providerParameters = createProviderParameters(); + providerParameters.setEncryptToken(true); + + 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 == 5); + + if (unrestrictedPoliciesInstalled) { + // Validate the token + JweJwtCompactConsumer jwtConsumer = new JweJwtCompactConsumer(token); + Properties decProperties = new Properties(); + Crypto decryptionCrypto = CryptoFactory.getInstance(getDecryptionProperties()); + KeyStore keystore = ((Merlin)decryptionCrypto).getKeyStore(); + decProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore); + decProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, "myservicekey"); + decProperties.put(JoseConstants.RSSEC_KEY_PSWD, "skpass"); + + JweDecryptionProvider decProvider = + JweUtils.loadDecryptionProvider(decProperties, jwtConsumer.getHeaders(), false); + + JweDecryptionOutput decOutput = decProvider.decrypt(token); + String decToken = decOutput.getContentText(); + + JwsJwtCompactConsumer jwtJwsConsumer = new JwsJwtCompactConsumer(decToken); + JwtToken jwt = jwtJwsConsumer.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)); + } + + } + private TokenProviderParameters createProviderParameters() throws WSSecurityException { TokenProviderParameters parameters = new TokenProviderParameters(); @@ -178,6 +281,9 @@ public class JWTTokenProviderTest extends org.junit.Assert { parameters.setStsProperties(stsProperties); parameters.setEncryptionProperties(new EncryptionProperties()); + stsProperties.setEncryptionCrypto(crypto); + stsProperties.setEncryptionUsername("myservicekey"); + stsProperties.setCallbackHandler(new PasswordCallbackHandler()); return parameters; } @@ -188,11 +294,24 @@ public class JWTTokenProviderTest extends org.junit.Assert { "org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin" ); properties.put("org.apache.wss4j.crypto.merlin.keystore.password", "stsspass"); - properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "stsstore.jks"); + if (unrestrictedPoliciesInstalled) { + properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "stsstore.jks"); + } else { + properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "restricted/stsstore.jks"); + } return properties; } - + private Properties getDecryptionProperties() { + Properties properties = new Properties(); + properties.put( + "org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin" + ); + properties.put("org.apache.wss4j.crypto.merlin.keystore.password", "sspass"); + properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "servicestore.jks"); + + return properties; + } }
