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 2018/05/02 10:53:40 UTC

[cxf-fediz] 02/02: Upport different signature algorithms for the SAML SSO Redirect Binding

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf-fediz.git

commit 32f104c188c6edd1ebaf4074bfb3eeb286782157
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Wed May 2 11:08:53 2018 +0100

    Upport different signature algorithms for the SAML SSO Redirect Binding
---
 .../idp/beans/samlsso/AuthnRequestParser.java      | 116 +++++++++++++--------
 .../webapp/WEB-INF/flows/saml-validate-request.xml |   5 +-
 .../apache/cxf/fediz/systests/samlsso/IdpTest.java |  83 +++++++++++++++
 3 files changed, 158 insertions(+), 46 deletions(-)

diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
index 264fb0f..e86c787 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
@@ -24,8 +24,10 @@ import java.io.InputStreamReader;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.security.cert.X509Certificate;
+import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
+import java.util.List;
 
 import org.w3c.dom.Document;
 
@@ -55,6 +57,7 @@ import org.apache.wss4j.dom.saml.WSSSAMLKeyInfoProcessor;
 import org.apache.wss4j.dom.validate.Credential;
 import org.apache.wss4j.dom.validate.SignatureTrustValidator;
 import org.apache.wss4j.dom.validate.Validator;
+import org.apache.xml.security.algorithms.JCEMapper;
 import org.opensaml.saml.saml2.core.AuthnRequest;
 import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
 import org.opensaml.security.credential.BasicCredential;
@@ -75,11 +78,25 @@ import org.springframework.webflow.execution.RequestContext;
 public class AuthnRequestParser {
 
     private static final Logger LOG = LoggerFactory.getLogger(AuthnRequestParser.class);
+    private static final String RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
+    private static final String RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384";
+    private static final String RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
+    private static final String RSA_SHA1_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1";
+    private static final String RSA_SHA256_MGF1 = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1";
+    private static final String DSA_SHA256 = "http://www.w3.org/2009/xmldsig11#dsa-sha256";
+    private static final List<String> SIG_ALGS;
+
+    static {
+        List<String> sigAlgs = Arrays.asList(SSOConstants.RSA_SHA1, SSOConstants.DSA_SHA1, RSA_SHA256, RSA_SHA384,
+                                             RSA_SHA512, RSA_SHA1_MGF1, RSA_SHA256_MGF1, DSA_SHA256);
+        SIG_ALGS = Collections.unmodifiableList(sigAlgs);
+    }
+
     private boolean supportDeflateEncoding;
     private boolean requireSignature = true;
 
     public void parseSAMLRequest(RequestContext context, Idp idp, String samlRequest,
-                                 String signature, String relayState) throws ProcessingException {
+                                 String sigAlg, String signature, String relayState) throws ProcessingException {
         LOG.debug("Received SAML Request: {}", samlRequest);
 
         if (samlRequest == null) {
@@ -98,8 +115,34 @@ public class AuthnRequestParser {
             SAMLAuthnRequest authnRequest = new SAMLAuthnRequest(parsedRequest);
             WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST, authnRequest);
 
-            validateSignature(context, parsedRequest, idp, signature, relayState,
+            // Check the signature
+            try {
+                if (parsedRequest.isSigned()) {
+                    // Check destination
+                    checkDestination(context, parsedRequest);
+
+                    // Check signature
+                    X509Certificate validatingCert = getValidatingCertificate(idp, authnRequest.getIssuer());
+                    Crypto issuerCrypto = new CertificateStore(new X509Certificate[] {validatingCert});
+                    validateAuthnRequestSignature(parsedRequest.getSignature(), issuerCrypto);
+                } else if (signature != null) {
+                    // Check destination
+                    checkDestination(context, parsedRequest);
+
+                    // Check signature
+                    validateSeparateSignature(idp, sigAlg, signature, relayState,
                               samlRequest, authnRequest.getIssuer());
+                } else if (requireSignature) {
+                    LOG.debug("No signature is present, therefore the request is rejected");
+                    throw new ProcessingException(TYPE.BAD_REQUEST);
+                } else {
+                    LOG.debug("No signature is present, but this is allowed by configuration");
+                }
+            } catch (Exception ex) {
+                LOG.debug("Error validating SAML Signature", ex);
+                throw new ProcessingException(TYPE.BAD_REQUEST);
+            }
+
             validateRequest(parsedRequest);
 
             LOG.debug("SAML Request with id '{}' successfully parsed", parsedRequest.getID());
@@ -230,48 +273,33 @@ public class AuthnRequestParser {
         }
     }
 
-    private void validateSignature(RequestContext context, AuthnRequest authnRequest, Idp idp,
-                                   String signature, String relayState, String samlRequest,
-                                   String realm) throws ProcessingException {
-        try {
-            if (authnRequest.isSigned()) {
-                // Check destination
-                checkDestination(context, authnRequest);
-
-                // Check signature
-                X509Certificate validatingCert = getValidatingCertificate(idp, realm);
-                Crypto issuerCrypto =
-                    new CertificateStore(Collections.singletonList(validatingCert).toArray(new X509Certificate[0]));
-                validateAuthnRequestSignature(authnRequest.getSignature(), issuerCrypto);
-            } else if (signature != null) {
-                // Check destination
-                checkDestination(context, authnRequest);
-
-                // Check signature
-                X509Certificate validatingCert = getValidatingCertificate(idp, realm);
-
-                java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA");
-                sig.initVerify(validatingCert);
-
-                // Recreate request to sign
-                String requestToSign = SSOConstants.SAML_REQUEST + "=" + URLEncoder.encode(samlRequest, "UTF-8")
-                     + "&" + SSOConstants.RELAY_STATE + "=" + relayState + "&" + SSOConstants.SIG_ALG
-                     + "=" + URLEncoder.encode(SSOConstants.RSA_SHA1, StandardCharsets.UTF_8.name());
-
-                sig.update(requestToSign.getBytes(StandardCharsets.UTF_8));
-
-                if (!sig.verify(Base64.getDecoder().decode(signature))) {
-                    LOG.debug("Signature validation failed");
-                    throw new ProcessingException(TYPE.BAD_REQUEST);
-                }
-            } else if (requireSignature) {
-                LOG.debug("No signature is present, therefore the request is rejected");
-                throw new ProcessingException(TYPE.BAD_REQUEST);
-            } else {
-                LOG.debug("No signature is present, but this is allowed by configuration");
-            }
-        } catch (Exception ex) {
-            LOG.debug("Error validating SAML Signature", ex);
+    private void validateSeparateSignature(Idp idp, String sigAlg, String signature, String relayState,
+                                           String samlRequest, String realm) throws Exception {
+        // Check signature
+        X509Certificate validatingCert = getValidatingCertificate(idp, realm);
+
+        // Process the received SigAlg parameter - fall back to RSA SHA1
+        String processedSigAlg = null;
+        if (sigAlg != null && SIG_ALGS.contains(sigAlg)) {
+            processedSigAlg = sigAlg;
+        } else {
+            LOG.debug("Supplied SigAlg parameter is either null or not known, so falling back to use RSA-SHA1");
+            processedSigAlg = SSOConstants.RSA_SHA1;
+        }
+
+        java.security.Signature sig =
+            java.security.Signature.getInstance(JCEMapper.translateURItoJCEID(processedSigAlg));
+        sig.initVerify(validatingCert);
+
+        // Recreate request to sign
+        String requestToSign = SSOConstants.SAML_REQUEST + "=" + URLEncoder.encode(samlRequest, "UTF-8")
+        + "&" + SSOConstants.RELAY_STATE + "=" + relayState + "&" + SSOConstants.SIG_ALG
+        + "=" + URLEncoder.encode(processedSigAlg, StandardCharsets.UTF_8.name());
+
+        sig.update(requestToSign.getBytes(StandardCharsets.UTF_8));
+
+        if (!sig.verify(Base64.getDecoder().decode(signature))) {
+            LOG.debug("Signature validation failed");
             throw new ProcessingException(TYPE.BAD_REQUEST);
         }
     }
diff --git a/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml b/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
index 00ef583..0f76a57 100644
--- a/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
+++ b/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
@@ -63,6 +63,7 @@
             <set name="flowScope.SAMLResponse" value="requestParameters.SAMLResponse" />
             <set name="flowScope.SAMLRequest" value="requestParameters.SAMLRequest" />
             <set name="flowScope.Signature" value="requestParameters.Signature" />
+            <set name="flowScope.SigAlg" value="requestParameters.SigAlg" />
         </on-entry>
         <if test="requestParameters.RelayState == null or requestParameters.RelayState.isEmpty()"
             then="viewBadRequest" />
@@ -86,8 +87,8 @@
     
     <action-state id="parseSAMLAuthnRequest">
         <evaluate expression="authnRequestParser.parseSAMLRequest(flowRequestContext, flowScope.idpConfig,
-                                                              flowScope.SAMLRequest, flowScope.Signature,
-                                                              flowScope.RelayState)" />
+                                                              flowScope.SAMLRequest, flowScope.SigAlg,
+                                                              flowScope.Signature, flowScope.RelayState)" />
         <transition to="retrieveConsumerURL"/>
         <transition on-exception="org.apache.cxf.fediz.core.exception.ProcessingException" to="viewBadRequest" />
     </action-state>
diff --git a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java
index 2ed603c..fcf824f 100644
--- a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java
+++ b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java
@@ -460,6 +460,89 @@ public class IdpTest {
     }
 
     @org.junit.Test
+    public void testSeparateSignatureRSASHA256() throws Exception {
+        OpenSAMLUtil.initSamlEngine();
+
+        // Create SAML AuthnRequest
+        Document doc = DOMUtils.createDocument();
+        doc.appendChild(doc.createElement("root"));
+        // Create the AuthnRequest
+        String consumerURL = "https://localhost:" + getRpHttpsPort() + "/"
+            + getServletContextName() + "/secure/fedservlet";
+        AuthnRequest authnRequest =
+            new DefaultAuthnRequestBuilder().createAuthnRequest(
+                null, "urn:org:apache:cxf:fediz:fedizhelloworld", consumerURL
+            );
+        authnRequest.setDestination("https://localhost:" + getIdpHttpsPort() + "/fediz-idp/saml");
+
+        Element authnRequestElement = OpenSAMLUtil.toDom(authnRequest, doc);
+        String authnRequestEncoded = encodeAuthnRequest(authnRequestElement);
+
+        String urlEncodedRequest = URLEncoder.encode(authnRequestEncoded, "UTF-8");
+
+        String relayState = UUID.randomUUID().toString();
+
+        // Sign request
+        Crypto crypto = CryptoFactory.getInstance("stsKeystoreA.properties");
+
+        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
+        cryptoType.setAlias("realma");
+
+        // Get the private key
+        PrivateKey privateKey = crypto.getPrivateKey("realma", "realma");
+
+        java.security.Signature signature = java.security.Signature.getInstance("SHA256withRSA");
+        signature.initSign(privateKey);
+
+        String requestToSign = SSOConstants.SAML_REQUEST + "=" + urlEncodedRequest;
+        requestToSign += "&" + SSOConstants.RELAY_STATE + "=" + relayState;
+        String encodedSignatureAlgorithm =
+            URLEncoder.encode("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", StandardCharsets.UTF_8.name());
+        requestToSign += "&" + SSOConstants.SIG_ALG + "=" + encodedSignatureAlgorithm;
+
+        signature.update(requestToSign.getBytes(StandardCharsets.UTF_8));
+        byte[] signBytes = signature.sign();
+
+        String encodedSignature = Base64.getEncoder().encodeToString(signBytes);
+
+        String url = "https://localhost:" + getIdpHttpsPort() + "/fediz-idp/saml/up?";
+        url += SSOConstants.RELAY_STATE + "=" + relayState;
+        url += "&" + SSOConstants.SAML_REQUEST + "=" + urlEncodedRequest;
+        url += "&" + SSOConstants.SIG_ALG + "=" + encodedSignatureAlgorithm;
+        url += "&" + SSOConstants.SIGNATURE + "=" + URLEncoder.encode(encodedSignature, StandardCharsets.UTF_8.name());
+
+        String user = "alice";
+        String password = "ecila";
+
+        final WebClient webClient = new WebClient();
+        webClient.getOptions().setUseInsecureSSL(true);
+        webClient.getCredentialsProvider().setCredentials(
+            new AuthScope("localhost", Integer.parseInt(getIdpHttpsPort())),
+            new UsernamePasswordCredentials(user, password));
+
+        webClient.getOptions().setJavaScriptEnabled(false);
+        final HtmlPage idpPage = webClient.getPage(url);
+        webClient.getOptions().setJavaScriptEnabled(true);
+        Assert.assertEquals("IDP SignIn Response Form", idpPage.getTitleText());
+
+        org.opensaml.saml.saml2.core.Response samlResponse =
+            parseSAMLResponse(idpPage, relayState, consumerURL, authnRequest.getID());
+        String expected = "urn:oasis:names:tc:SAML:2.0:status:Success";
+        Assert.assertEquals(expected, samlResponse.getStatus().getStatusCode().getValue());
+
+        // Check claims
+        String parsedResponse = DOM2Writer.nodeToString(samlResponse.getDOM().getOwnerDocument());
+        String claim = ClaimTypes.FIRSTNAME.toString();
+        Assert.assertTrue(parsedResponse.contains(claim));
+        claim = ClaimTypes.LASTNAME.toString();
+        Assert.assertTrue(parsedResponse.contains(claim));
+        claim = ClaimTypes.EMAILADDRESS.toString();
+        Assert.assertTrue(parsedResponse.contains(claim));
+
+        webClient.close();
+    }
+
+    @org.junit.Test
     public void testSuccessfulSSOInvokeOnIdP() throws Exception {
         OpenSAMLUtil.initSamlEngine();
 

-- 
To stop receiving notification emails like this one, please contact
coheigea@apache.org.