You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2012/06/22 17:14:00 UTC

svn commit: r1352919 - in /webservices/wss4j/trunk/src: main/java/org/apache/ws/security/handler/ main/java/org/apache/ws/security/validate/ test/java/org/apache/ws/security/message/

Author: coheigea
Date: Fri Jun 22 15:13:59 2012
New Revision: 1352919

URL: http://svn.apache.org/viewvc?rev=1352919&view=rev
Log:
[WSS-395] - Support Certificate Constraints on the Subject DN of the certificate used for signature validation

Added:
    webservices/wss4j/trunk/src/test/java/org/apache/ws/security/message/SignatureCertConstraintsTest.java
Modified:
    webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/RequestData.java
    webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandler.java
    webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandlerConstants.java
    webservices/wss4j/trunk/src/main/java/org/apache/ws/security/validate/SignatureTrustValidator.java

Modified: webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/RequestData.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/RequestData.java?rev=1352919&r1=1352918&r2=1352919&view=diff
==============================================================================
--- webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/RequestData.java (original)
+++ webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/RequestData.java Fri Jun 22 15:13:59 2012
@@ -19,6 +19,15 @@
 
 package org.apache.ws.security.handler;
 
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.xml.namespace.QName;
+
 import org.apache.ws.security.SOAPConstants;
 import org.apache.ws.security.WSConstants;
 import org.apache.ws.security.WSEncryptionPart;
@@ -30,19 +39,13 @@ import org.apache.ws.security.message.WS
 import org.apache.ws.security.message.token.UsernameToken;
 import org.apache.ws.security.validate.Validator;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.security.cert.X509Certificate;
-
-import javax.security.auth.callback.CallbackHandler;
-import javax.xml.namespace.QName;
-
 /**
  * This class holds per request data.
  *
  * @author Werner Dittmann (Werner.Dittmann@t-online.de)
  */
 public class RequestData {
+    
     private Object msgContext = null;
     private boolean noSerialization = false;
     private SOAPConstants soapConstants = null;
@@ -79,6 +82,7 @@ public class RequestData {
     protected boolean requireSignedEncryptedDataElements = false;
     private ReplayCache timestampReplayCache;
     private ReplayCache nonceReplayCache;
+    private Collection<Pattern> subjectDNPatterns = new ArrayList<Pattern>();
 
     public void clear() {
         soapConstants = null;
@@ -102,6 +106,7 @@ public class RequestData {
         enableRevocation = false;
         timestampReplayCache = null;
         nonceReplayCache = null;
+        subjectDNPatterns.clear();
     }
 
     public Object getMsgContext() {
@@ -480,5 +485,21 @@ public class RequestData {
     public ReplayCache getNonceReplayCache() {
         return nonceReplayCache;
     }
+    
+    /**
+     * Set the Signature Subject Cert Constraints
+     */
+    public void setSubjectCertConstraints(Collection<Pattern> subjectCertConstraints) {
+        if (subjectCertConstraints != null) {
+            subjectDNPatterns.addAll(subjectCertConstraints);
+        }
+    }
+    
+    /**
+     * Get the Signature Subject Cert Constraints
+     */
+    public Collection<Pattern> getSubjectCertConstraints() {
+        return subjectDNPatterns;
+    }
 
 }

Modified: webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandler.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandler.java?rev=1352919&r1=1352918&r2=1352919&view=diff
==============================================================================
--- webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandler.java (original)
+++ webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandler.java Fri Jun 22 15:13:59 2012
@@ -42,10 +42,13 @@ import javax.security.auth.callback.Call
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 
 
 /**
@@ -1105,6 +1108,25 @@ public abstract class WSHandler {
                 reqData, WSHandlerConstants.ENABLE_REVOCATION, false
             );
         reqData.setEnableRevocation(enableRevocation);
+        
+        String certConstraints = 
+            getString(WSHandlerConstants.SIG_SUBJECT_CERT_CONSTRAINTS, reqData.getMsgContext());
+        if (certConstraints != null) {
+            String[] certConstraintsList = certConstraints.split(",");
+            if (certConstraintsList != null) {
+                Collection<Pattern> subjectCertConstraints = 
+                    new ArrayList<Pattern>(certConstraintsList.length);
+                for (String certConstraint : certConstraintsList) {
+                    try {
+                        subjectCertConstraints.add(Pattern.compile(certConstraint.trim()));
+                    } catch (PatternSyntaxException ex) {
+                        log.debug(ex.getMessage(), ex);
+                        throw new WSSecurityException(ex.getMessage(), ex);
+                    }
+                }
+                reqData.setSubjectCertConstraints(subjectCertConstraints);
+            }
+        }
     }
 
     /*

Modified: webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandlerConstants.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandlerConstants.java?rev=1352919&r1=1352918&r2=1352919&view=diff
==============================================================================
--- webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandlerConstants.java (original)
+++ webservices/wss4j/trunk/src/main/java/org/apache/ws/security/handler/WSHandlerConstants.java Fri Jun 22 15:13:59 2012
@@ -675,6 +675,15 @@ public final class WSHandlerConstants {
      */
     public static final String TTL_FUTURE_TIMESTAMP = "futureTimeToLive";
     
+    /**
+     * This configuration tag is a comma separated String of regular expressions which
+     * will be applied to the subject DN of the certificate used for signature
+     * validation, after trust verification of the certificate chain associated with the 
+     * certificate. These constraints are not used when the certificate is contained in
+     * the keystore (direct trust).
+     */
+    public static final String SIG_SUBJECT_CERT_CONSTRAINTS = "sigSubjectCertConstraints";
+    
     
     //
     // Internal storage constants

Modified: webservices/wss4j/trunk/src/main/java/org/apache/ws/security/validate/SignatureTrustValidator.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/src/main/java/org/apache/ws/security/validate/SignatureTrustValidator.java?rev=1352919&r1=1352918&r2=1352919&view=diff
==============================================================================
--- webservices/wss4j/trunk/src/main/java/org/apache/ws/security/validate/SignatureTrustValidator.java (original)
+++ webservices/wss4j/trunk/src/main/java/org/apache/ws/security/validate/SignatureTrustValidator.java Fri Jun 22 15:13:59 2012
@@ -24,6 +24,9 @@ import java.security.PublicKey;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.ws.security.WSSecurityException;
 import org.apache.ws.security.components.crypto.Crypto;
@@ -66,9 +69,9 @@ public class SignatureTrustValidator imp
             boolean trust = false;
             boolean enableRevocation = data.isRevocationEnabled();
             if (certs.length == 1) {
-                trust = verifyTrustInCert(certs[0], crypto, enableRevocation);
+                trust = verifyTrustInCert(certs[0], crypto, data, enableRevocation);
             } else {
-                trust = verifyTrustInCerts(certs, crypto, enableRevocation);
+                trust = verifyTrustInCerts(certs, crypto, data, enableRevocation);
             }
             if (trust) {
                 return credential;
@@ -110,25 +113,16 @@ public class SignatureTrustValidator imp
         }
     }
     
-    /**
-     * Evaluate whether a given certificate should be trusted.
-     * 
-     * Policy used in this implementation:
-     * 1. Search the keystore for the transmitted certificate
-     * 2. Search the keystore for a connection to the transmitted certificate
-     * (that is, search for certificate(s) of the issuer of the transmitted certificate
-     * 3. Verify the trust path for those certificates found because the search for the issuer 
-     * might be fooled by a phony DN (String!)
-     *
-     * @param cert the certificate that should be validated against the keystore
-     * @param crypto A crypto instance to use for trust validation
-     * @return true if the certificate is trusted, false if not
-     * @throws WSSecurityException
-     */
     @Deprecated
     protected boolean verifyTrustInCert(X509Certificate cert, Crypto crypto) 
         throws WSSecurityException {
-        return verifyTrustInCert(cert, crypto, false);
+        return verifyTrustInCert(cert, crypto, new RequestData(), false);
+    }
+    
+    @Deprecated
+    protected boolean verifyTrustInCert(X509Certificate cert, Crypto crypto, boolean enableRevocation) 
+        throws WSSecurityException {
+        return verifyTrustInCert(cert, crypto, new RequestData(), enableRevocation);
     }
     
     /**
@@ -143,6 +137,7 @@ public class SignatureTrustValidator imp
      *
      * @param cert the certificate that should be validated against the keystore
      * @param crypto A crypto instance to use for trust validation
+     * @param data A RequestData instance
      * @param enableRevocation Whether revocation is enabled or not
      * @return true if the certificate is trusted, false if not
      * @throws WSSecurityException
@@ -150,6 +145,7 @@ public class SignatureTrustValidator imp
     protected boolean verifyTrustInCert(
         X509Certificate cert, 
         Crypto crypto,
+        RequestData data,
         boolean enableRevocation
     ) throws WSSecurityException {
         String subjectString = cert.getSubjectX500Principal().getName();
@@ -221,7 +217,10 @@ public class SignatureTrustValidator imp
                      + subjectString
                 );
             }
-            return true;
+            Collection<Pattern> subjectCertConstraints = data.getSubjectCertConstraints();
+            if (matches(cert, subjectCertConstraints)) {
+                return true;
+            }
         }
 
         if (LOG.isDebugEnabled()) {
@@ -272,20 +271,21 @@ public class SignatureTrustValidator imp
         return false;
     }
     
-    /**
-     * Evaluate whether the given certificate chain should be trusted.
-     * 
-     * @param certificates the certificate chain that should be validated against the keystore
-     * @param crypto  A Crypto instance to use for trust validation
-     * @return true if the certificate chain is trusted, false if not
-     * @throws WSSecurityException
-     */
     @Deprecated
     protected boolean verifyTrustInCerts(
         X509Certificate[] certificates, 
         Crypto crypto
     ) throws WSSecurityException {
-        return verifyTrustInCerts(certificates, crypto, false);
+        return verifyTrustInCerts(certificates, crypto, new RequestData(), false);
+    }
+    
+    @Deprecated
+    protected boolean verifyTrustInCerts(
+        X509Certificate[] certificates, 
+        Crypto crypto,
+        boolean enableRevocation
+    ) throws WSSecurityException {
+        return verifyTrustInCerts(certificates, crypto, new RequestData(), enableRevocation);
     }
     
     /**
@@ -293,6 +293,7 @@ public class SignatureTrustValidator imp
      * 
      * @param certificates the certificate chain that should be validated against the keystore
      * @param crypto A Crypto instance
+     * @param data A RequestData instance
      * @param enableRevocation Whether revocation is enabled or not
      * @return true if the certificate chain is trusted, false if not
      * @throws WSSecurityException
@@ -300,6 +301,7 @@ public class SignatureTrustValidator imp
     protected boolean verifyTrustInCerts(
         X509Certificate[] certificates, 
         Crypto crypto,
+        RequestData data,
         boolean enableRevocation
     ) throws WSSecurityException {
         if (certificates == null || certificates.length < 2) {
@@ -318,7 +320,10 @@ public class SignatureTrustValidator imp
                     + subjectString
                 );
             }
-            return true;
+            Collection<Pattern> subjectCertConstraints = data.getSubjectCertConstraints();
+            if (matches(certificates[0], subjectCertConstraints)) {
+                return true;
+            }
         }
         
         if (LOG.isDebugEnabled()) {
@@ -340,4 +345,40 @@ public class SignatureTrustValidator imp
         return crypto.verifyTrust(publicKey);
     }
     
+    /**
+     * @return      true if the certificate's SubjectDN matches the constraints defined in the
+     *              subject DNConstraints; false, otherwise. The certificate subject DN only
+     *              has to match ONE of the subject cert constraints (not all).
+     */
+    protected boolean
+    matches(
+        final java.security.cert.X509Certificate cert,
+        final Collection<Pattern> subjectDNPatterns
+    ) {
+        if (subjectDNPatterns.isEmpty()) {
+            LOG.warn("No Subject DN Certificate Constraints were defined. This could be a security issue");
+        }
+        if (!subjectDNPatterns.isEmpty()) {
+            if (cert == null) {
+                LOG.debug("The certificate is null so no constraints matching was possible");
+                return false;
+            }
+            String subjectName = cert.getSubjectX500Principal().getName();
+            boolean subjectMatch = false;
+            for (Pattern subjectDNPattern : subjectDNPatterns) {
+                final Matcher matcher = subjectDNPattern.matcher(subjectName);
+                if (matcher.matches()) {
+                    LOG.debug("Subject DN " + subjectName + " matches with pattern " + subjectDNPattern);
+                    subjectMatch = true;
+                    break;
+                }
+            }
+            if (!subjectMatch) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
 }

Added: webservices/wss4j/trunk/src/test/java/org/apache/ws/security/message/SignatureCertConstraintsTest.java
URL: http://svn.apache.org/viewvc/webservices/wss4j/trunk/src/test/java/org/apache/ws/security/message/SignatureCertConstraintsTest.java?rev=1352919&view=auto
==============================================================================
--- webservices/wss4j/trunk/src/test/java/org/apache/ws/security/message/SignatureCertConstraintsTest.java (added)
+++ webservices/wss4j/trunk/src/test/java/org/apache/ws/security/message/SignatureCertConstraintsTest.java Fri Jun 22 15:13:59 2012
@@ -0,0 +1,140 @@
+/**
+ * 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.ws.security.message;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.ws.security.WSConstants;
+import org.apache.ws.security.WSSConfig;
+import org.apache.ws.security.WSSecurityEngine;
+import org.apache.ws.security.WSSecurityEngineResult;
+import org.apache.ws.security.WSSecurityException;
+import org.apache.ws.security.common.SOAPUtil;
+import org.apache.ws.security.components.crypto.Crypto;
+import org.apache.ws.security.components.crypto.CryptoFactory;
+import org.apache.ws.security.handler.RequestData;
+import org.apache.ws.security.util.WSSecurityUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * A set of test-cases for signing and verifying SOAP requests, where the certificate used to
+ * verify the signature is validated against a set of cert constraints.
+ */
+public class SignatureCertConstraintsTest extends org.junit.Assert {
+    private static final org.apache.commons.logging.Log LOG = 
+        org.apache.commons.logging.LogFactory.getLog(SignatureCertConstraintsTest.class);
+    private Crypto crypto = null;
+    private Crypto cryptoCA = null;
+    
+    public SignatureCertConstraintsTest() throws Exception {
+        WSSConfig.init();
+        crypto = CryptoFactory.getInstance("wss40.properties");
+        cryptoCA = CryptoFactory.getInstance("wss40CA.properties");
+    }
+
+    /**
+     * The test uses the BinarySecurityToken key identifier type.
+     */
+    @org.junit.Test
+    public void testBSTSignature() throws Exception {
+        WSSecSignature builder = new WSSecSignature();
+        builder.setUserInfo("wss40", "security");
+        builder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader();
+        secHeader.insertSecurityHeader(doc);
+        Document signedDoc = builder.build(doc, crypto, secHeader);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Signed message with BST key identifier:");
+            String outputString = 
+                org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);
+            LOG.debug(outputString);
+        }
+        
+        Element securityHeader = WSSecurityUtil.getSecurityHeader(signedDoc, null);
+        String certConstraint = ".*CN=Colm.*O=Apache.*";
+        verify(securityHeader, cryptoCA, certConstraint);
+        
+        certConstraint = ".*CN=Colm2.*O=Apache.*";
+        try {
+            verify(securityHeader, cryptoCA, certConstraint);
+            fail("Failure expected on a bad cert constraint");
+        } catch (WSSecurityException ex) {
+            // expected
+        }
+    }
+    
+    /**
+     * The test uses the BinarySecurityToken key identifier type.
+     */
+    @org.junit.Test
+    public void testBSTSignaturePKIPath() throws Exception {
+        WSSecSignature builder = new WSSecSignature();
+        builder.setUserInfo("wss40", "security");
+        builder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
+        builder.setUseSingleCertificate(false);
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader();
+        secHeader.insertSecurityHeader(doc);
+        Document signedDoc = builder.build(doc, crypto, secHeader);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Signed message with BST key identifier:");
+            String outputString = 
+                org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);
+            LOG.debug(outputString);
+        }
+        
+        Element securityHeader = WSSecurityUtil.getSecurityHeader(signedDoc, null);
+        String certConstraint = ".*CN=Colm.*O=Apache.*";
+        verify(securityHeader, cryptoCA, certConstraint);
+        
+        certConstraint = ".*CN=Colm2.*O=Apache.*";
+        try {
+            verify(securityHeader, cryptoCA, certConstraint);
+            fail("Failure expected on a bad cert constraint");
+        } catch (WSSecurityException ex) {
+            // expected
+        }
+    }
+    
+    private List<WSSecurityEngineResult> verify(
+        Element securityHeader, Crypto sigCrypto, String certConstraint
+    ) throws Exception {
+        WSSecurityEngine secEngine = new WSSecurityEngine();
+        RequestData data = new RequestData();
+        data.setSigCrypto(sigCrypto);
+        
+        if (certConstraint != null) {
+            Pattern subjectDNPattern = Pattern.compile(certConstraint.trim());
+            data.setSubjectCertConstraints(Collections.singletonList(subjectDNPattern));
+        }
+        
+        return secEngine.processSecurityHeader(securityHeader, data);
+    }
+
+}