You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2015/11/13 17:29:16 UTC

cxf git commit: Adding JWTValidator

Repository: cxf
Updated Branches:
  refs/heads/master 67ac0ab27 -> c8905fd54


Adding JWTValidator


Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/c8905fd5
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/c8905fd5
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/c8905fd5

Branch: refs/heads/master
Commit: c8905fd544457546be73f8887e154bb72dee7c7e
Parents: 67ac0ab
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Fri Nov 13 16:28:07 2015 +0000
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Fri Nov 13 16:28:07 2015 +0000

----------------------------------------------------------------------
 .../apache/cxf/sts/request/ReceivedToken.java   |   7 +-
 .../token/validator/jwt/JWTTokenValidator.java  | 207 ++++++++++++++++
 .../token/validator/JWTTokenValidatorTest.java  | 246 +++++++++++++++++++
 3 files changed, 459 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf/blob/c8905fd5/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
----------------------------------------------------------------------
diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
index c2e1aee..252ec60 100644
--- a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
+++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
@@ -33,7 +33,7 @@ import org.apache.cxf.ws.security.sts.provider.STSException;
 
 /**
  * This class contains values that have been extracted from a received Token. The Token can be a
- * JAXB UsernameTokenType/BinarySecurityTokenType or a DOM Element.
+ * JAXB UsernameTokenType/BinarySecurityTokenType, a DOM Element or a String.
  */
 public class ReceivedToken {
     
@@ -74,6 +74,11 @@ public class ReceivedToken {
             }
             this.token = receivedToken;
             isDOMElement = true;
+        } else if (receivedToken instanceof String) {
+            if (LOG.isLoggable(Level.FINE)) {
+                LOG.fine("Found ValidateTarget String");
+            }
+            this.token = receivedToken;
         } else {
             LOG.fine("Found ValidateTarget object of unknown type");
             throw new STSException(

http://git-wip-us.apache.org/repos/asf/cxf/blob/c8905fd5/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
----------------------------------------------------------------------
diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
new file mode 100644
index 0000000..837c3c1
--- /dev/null
+++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
@@ -0,0 +1,207 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.sts.token.validator.jwt;
+
+import java.security.KeyStore;
+import java.security.Principal;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.security.SimplePrincipal;
+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.JwsJwtCompactConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsUtils;
+import org.apache.cxf.rs.security.jose.jwt.JwtToken;
+import org.apache.cxf.rs.security.jose.jwt.JwtUtils;
+import org.apache.cxf.sts.STSPropertiesMBean;
+import org.apache.cxf.sts.request.ReceivedToken;
+import org.apache.cxf.sts.request.ReceivedToken.STATE;
+import org.apache.cxf.sts.token.validator.TokenValidator;
+import org.apache.cxf.sts.token.validator.TokenValidatorParameters;
+import org.apache.cxf.sts.token.validator.TokenValidatorResponse;
+import org.apache.cxf.ws.security.sts.provider.STSException;
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.Merlin;
+
+/**
+ * Validate a SAML Assertion. It is valid if it was issued and signed by this STS.
+ */
+public class JWTTokenValidator implements TokenValidator {
+    
+    private static final Logger LOG = LogUtils.getL7dLogger(JWTTokenValidator.class);
+    private int clockOffset;
+    private int ttl;
+    
+    /**
+     * Return true if this TokenValidator implementation is capable of validating the
+     * ReceivedToken argument.
+     */
+    public boolean canHandleToken(ReceivedToken validateTarget) {
+        return canHandleToken(validateTarget, null);
+    }
+    
+    /**
+     * Return true if this TokenValidator implementation is capable of validating the
+     * ReceivedToken argument. The realm is ignored in this Validator.
+     */
+    public boolean canHandleToken(ReceivedToken validateTarget, String realm) {
+        Object token = validateTarget.getToken();
+        if (token instanceof String) {
+            try {
+                JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer((String)token);
+                if (jwtConsumer.getJwtToken() != null) {
+                    return true;
+                }
+            } catch (RuntimeException ex) {
+                return false;
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * Validate a Token using the given TokenValidatorParameters.
+     */
+    public TokenValidatorResponse validateToken(TokenValidatorParameters tokenParameters) {
+        LOG.fine("Validating JWT Token");
+        STSPropertiesMBean stsProperties = tokenParameters.getStsProperties();
+        
+        TokenValidatorResponse response = new TokenValidatorResponse();
+        ReceivedToken validateTarget = tokenParameters.getToken();
+        validateTarget.setState(STATE.INVALID);
+        response.setToken(validateTarget);
+        
+        String token = (String)validateTarget.getToken();
+        if (token == null) {
+            return response;
+        }
+        
+        if (token.split("\\.").length != 3) {
+            LOG.log(Level.WARNING, "JWT Token appears not to be signed. Validation has failed");
+            return response;
+        }
+        
+        JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(token);
+        JwtToken jwt = jwtConsumer.getJwtToken();
+        
+        // Verify the signature
+        Properties verificationProperties = new Properties();
+        
+        Crypto signatureCrypto = stsProperties.getSignatureCrypto();
+        String alias = stsProperties.getSignatureUsername();
+        
+        if (alias != null) {
+            verificationProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, alias);
+        }
+        
+        if (!(signatureCrypto instanceof Merlin)) {
+            throw new STSException("Can't get the keystore", STSException.REQUEST_FAILED);
+        }
+        KeyStore keystore = ((Merlin)signatureCrypto).getKeyStore();
+        verificationProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore);
+        
+        JwsSignatureVerifier signatureVerifier = 
+            JwsUtils.loadSignatureVerifier(verificationProperties, jwt.getJwsHeaders());
+        
+        if (!jwtConsumer.verifySignatureWith(signatureVerifier)) {
+            return response;
+        }
+        
+        try {
+            validateToken(jwt);
+        } catch (RuntimeException ex) {
+            LOG.log(Level.WARNING, "JWT token validation failed", ex);
+            return response;
+        }
+        
+        /*
+        // Parse roles from the validated token
+        if (samlRoleParser != null) {
+            Set<Principal> roles = 
+                samlRoleParser.parseRolesFromAssertion(principal, null, assertion);
+            response.setRoles(roles);
+        }
+
+        // Get the realm of the SAML token
+        String tokenRealm = null;
+        if (samlRealmCodec != null) {
+            tokenRealm = samlRealmCodec.getRealmFromToken(assertion);
+            // verify the realm against the cached token
+            if (secToken != null) {
+                Map<String, Object> props = secToken.getProperties();
+                if (props != null) {
+                    String cachedRealm = (String)props.get(STSConstants.TOKEN_REALM);
+                    if (cachedRealm != null && !tokenRealm.equals(cachedRealm)) {
+                        return response;
+                    }
+                }
+            }
+        }
+        response.setTokenRealm(tokenRealm);
+        */
+
+        if (isVerifiedWithAPublicKey(jwt)) {
+            Principal principal = new SimplePrincipal(jwt.getClaims().getSubject());
+            response.setPrincipal(principal);
+        }
+
+        validateTarget.setState(STATE.VALID);
+        LOG.fine("JWT Token successfully validated");
+
+        return response;
+    }
+    
+    private boolean isVerifiedWithAPublicKey(JwtToken jwt) {
+        String alg = (String)jwt.getJwsHeader(JoseConstants.HEADER_ALGORITHM);
+        SignatureAlgorithm sigAlg = SignatureAlgorithm.getAlgorithm(alg);
+        return SignatureAlgorithm.isPublicKeyAlgorithm(sigAlg);
+    }
+    
+    protected void validateToken(JwtToken jwt) {
+        // If we have no issued time then we need to have an expiry
+        boolean expiredRequired = jwt.getClaims().getIssuedAt() == null;
+        JwtUtils.validateJwtExpiry(jwt.getClaims(), clockOffset, expiredRequired);
+        
+        JwtUtils.validateJwtNotBefore(jwt.getClaims(), clockOffset, false);
+        
+        // If we have no expiry then we must have an issued at
+        boolean issuedAtRequired = jwt.getClaims().getExpiryTime() == null;
+        JwtUtils.validateJwtIssuedAt(jwt.getClaims(), ttl, clockOffset, issuedAtRequired);
+    }
+
+    public int getClockOffset() {
+        return clockOffset;
+    }
+
+    public void setClockOffset(int clockOffset) {
+        this.clockOffset = clockOffset;
+    }
+    
+    public int getTtl() {
+        return ttl;
+    }
+
+    public void setTtl(int ttl) {
+        this.ttl = ttl;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/c8905fd5/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
----------------------------------------------------------------------
diff --git a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
new file mode 100644
index 0000000..3e2bc05
--- /dev/null
+++ b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
@@ -0,0 +1,246 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.sts.token.validator;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Properties;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.apache.cxf.jaxws.context.WebServiceContextImpl;
+import org.apache.cxf.jaxws.context.WrappedMessageContext;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.sts.STSConstants;
+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.request.KeyRequirements;
+import org.apache.cxf.sts.request.ReceivedToken;
+import org.apache.cxf.sts.request.ReceivedToken.STATE;
+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;
+import org.apache.cxf.sts.token.provider.jwt.JWTTokenProvider;
+import org.apache.cxf.sts.token.validator.jwt.JWTTokenValidator;
+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.ext.WSPasswordCallback;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.principal.CustomTokenPrincipal;
+
+/**
+ * Some unit tests for validating JWTTokens.
+ */
+public class JWTTokenValidatorTest extends org.junit.Assert {
+    private static TokenStore tokenStore = new DefaultInMemoryTokenStore();
+    
+    @org.junit.Test
+    public void testCreateAndValidateSignedJWT() throws Exception {
+        // Create
+        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
+        TokenValidator jwtTokenValidator = new JWTTokenValidator();
+        TokenValidatorParameters validatorParameters = createValidatorParameters();
+        TokenRequirements tokenRequirements = validatorParameters.getTokenRequirements();
+        
+        // Create a ValidateTarget consisting of a JWT Token
+        ReceivedToken validateTarget = new ReceivedToken(token);
+        tokenRequirements.setValidateTarget(validateTarget);
+        validatorParameters.setToken(validateTarget);
+        
+        assertTrue(jwtTokenValidator.canHandleToken(validateTarget));
+        
+        TokenValidatorResponse validatorResponse = 
+            jwtTokenValidator.validateToken(validatorParameters);
+        assertTrue(validatorResponse != null);
+        assertTrue(validatorResponse.getToken() != null);
+        assertTrue(validatorResponse.getToken().getState() == STATE.VALID);
+        
+        Principal principal = validatorResponse.getPrincipal();
+        assertTrue(principal != null && principal.getName() != null);
+    }
+    
+    @org.junit.Test
+    public void testInvalidSignature() throws Exception {
+        // Create
+        TokenProvider jwtTokenProvider = new JWTTokenProvider();
+        ((JWTTokenProvider)jwtTokenProvider).setSignToken(true);
+        
+        TokenProviderParameters providerParameters = createProviderParameters();
+        Crypto crypto = CryptoFactory.getInstance(getEveCryptoProperties());
+        CallbackHandler callbackHandler = new EveCallbackHandler();
+        providerParameters.getStsProperties().setSignatureCrypto(crypto);
+        providerParameters.getStsProperties().setCallbackHandler(callbackHandler);
+        providerParameters.getStsProperties().setSignatureUsername("eve");
+        
+        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
+        TokenValidator jwtTokenValidator = new JWTTokenValidator();
+        TokenValidatorParameters validatorParameters = createValidatorParameters();
+        TokenRequirements tokenRequirements = validatorParameters.getTokenRequirements();
+        
+        // Create a ValidateTarget consisting of a JWT Token
+        ReceivedToken validateTarget = new ReceivedToken(token);
+        tokenRequirements.setValidateTarget(validateTarget);
+        validatorParameters.setToken(validateTarget);
+        
+        assertTrue(jwtTokenValidator.canHandleToken(validateTarget));
+        
+        TokenValidatorResponse validatorResponse = 
+            jwtTokenValidator.validateToken(validatorParameters);
+        assertTrue(validatorResponse != null);
+        assertTrue(validatorResponse.getToken() != null);
+        assertTrue(validatorResponse.getToken().getState() == STATE.INVALID);
+    }
+    
+    private TokenProviderParameters createProviderParameters() throws WSSecurityException {
+        TokenProviderParameters parameters = new TokenProviderParameters();
+        
+        TokenRequirements tokenRequirements = new TokenRequirements();
+        tokenRequirements.setTokenType(JWTTokenProvider.JWT_TOKEN_TYPE);
+        parameters.setTokenRequirements(tokenRequirements);
+        
+        KeyRequirements keyRequirements = new KeyRequirements();
+        parameters.setKeyRequirements(keyRequirements);
+
+        parameters.setTokenStore(tokenStore);
+        
+        parameters.setPrincipal(new CustomTokenPrincipal("alice"));
+        // Mock up message context
+        MessageImpl msg = new MessageImpl();
+        WrappedMessageContext msgCtx = new WrappedMessageContext(msg);
+        WebServiceContextImpl webServiceContext = new WebServiceContextImpl(msgCtx);
+        parameters.setWebServiceContext(webServiceContext);
+        
+        parameters.setAppliesToAddress("http://dummy-service.com/dummy");
+        
+        // Add STSProperties object
+        StaticSTSProperties stsProperties = new StaticSTSProperties();
+        Crypto crypto = CryptoFactory.getInstance(getEncryptionProperties());
+        stsProperties.setSignatureCrypto(crypto);
+        stsProperties.setSignatureUsername("mystskey");
+        stsProperties.setCallbackHandler(new PasswordCallbackHandler());
+        stsProperties.setIssuer("STS");
+        parameters.setStsProperties(stsProperties);
+        
+        parameters.setEncryptionProperties(new EncryptionProperties());
+        stsProperties.setEncryptionCrypto(crypto);
+        stsProperties.setEncryptionUsername("myservicekey");
+        stsProperties.setCallbackHandler(new PasswordCallbackHandler());
+        
+        return parameters;
+    }
+    
+    private TokenValidatorParameters createValidatorParameters() throws WSSecurityException {
+        TokenValidatorParameters parameters = new TokenValidatorParameters();
+        
+        TokenRequirements tokenRequirements = new TokenRequirements();
+        tokenRequirements.setTokenType(STSConstants.STATUS);
+        parameters.setTokenRequirements(tokenRequirements);
+        
+        KeyRequirements keyRequirements = new KeyRequirements();
+        parameters.setKeyRequirements(keyRequirements);
+        
+        parameters.setPrincipal(new CustomTokenPrincipal("alice"));
+        // Mock up message context
+        MessageImpl msg = new MessageImpl();
+        WrappedMessageContext msgCtx = new WrappedMessageContext(msg);
+        WebServiceContextImpl webServiceContext = new WebServiceContextImpl(msgCtx);
+        parameters.setWebServiceContext(webServiceContext);
+        
+        // Add STSProperties object
+        StaticSTSProperties stsProperties = new StaticSTSProperties();
+        Crypto crypto = CryptoFactory.getInstance(getEncryptionProperties());
+        stsProperties.setEncryptionCrypto(crypto);
+        stsProperties.setSignatureCrypto(crypto);
+        stsProperties.setEncryptionUsername("myservicekey");
+        stsProperties.setSignatureUsername("mystskey");
+        stsProperties.setCallbackHandler(new PasswordCallbackHandler());
+        stsProperties.setIssuer("STS");
+        parameters.setStsProperties(stsProperties);
+        parameters.setTokenStore(tokenStore);
+        
+        return parameters;
+    }
+    
+    private Properties getEncryptionProperties() {
+        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", "stsspass");
+        properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "stsstore.jks");
+        
+        return properties;
+    }
+    
+    private Properties getEveCryptoProperties() {
+        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", "evespass");
+        properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "eve.jks");
+        
+        return properties;
+    }
+    
+    public class EveCallbackHandler implements CallbackHandler {
+
+        public void handle(Callback[] callbacks) throws IOException,
+                UnsupportedCallbackException {
+            for (int i = 0; i < callbacks.length; i++) {
+                if (callbacks[i] instanceof WSPasswordCallback) { // CXF
+                    WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
+                    if ("eve".equals(pc.getIdentifier())) {
+                        pc.setPassword("evekpass");
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}