Repository: cxf-fediz
Updated Branches:
  refs/heads/master 82ab06413 -> e68df8357


Added initial support for validating SAML redirect signatures


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

Branch: refs/heads/master
Commit: e68df8357ff18991a22e577cf480903961c08ba5
Parents: 82ab064
Author: Colm O hEigeartaigh <cohei...@apache.org>
Authored: Tue Mar 22 16:45:05 2016 +0000
Committer: Colm O hEigeartaigh <cohei...@apache.org>
Committed: Tue Mar 22 16:45:05 2016 +0000

----------------------------------------------------------------------
 .../idp/beans/samlsso/AuthnRequestParser.java   |   7 +-
 .../idp/samlsso/AuthnRequestValidator.java      |  54 +++++++--
 .../WEB-INF/flows/saml-validate-request.xml     |   5 +-
 .../apache/cxf/fediz/systests/idp/IdpTest.java  | 117 +++++++++++++++++++
 4 files changed, 168 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e68df835/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
----------------------------------------------------------------------
diff --git 
a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
 
b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
index 737425f..410e8c1 100644
--- 
a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
+++ 
b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
@@ -31,7 +31,6 @@ import org.apache.cxf.fediz.service.idp.domain.Idp;
 import org.apache.cxf.fediz.service.idp.samlsso.AuthnRequestValidator;
 import org.apache.cxf.fediz.service.idp.util.WebUtils;
 import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
-import org.apache.cxf.rs.security.saml.sso.SSOConstants;
 import org.apache.cxf.staxutils.StaxUtils;
 import org.apache.wss4j.common.saml.OpenSAMLUtil;
 import org.apache.wss4j.common.util.DOM2Writer;
@@ -49,8 +48,8 @@ public class AuthnRequestParser {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(AuthnRequestParser.class);
 
-    public void parseSAMLRequest(RequestContext context, Idp idp) throws 
ProcessingException {
-        String samlRequest = 
context.getFlowScope().getString(SSOConstants.SAML_REQUEST);
+    public void parseSAMLRequest(RequestContext context, Idp idp, String 
signature, 
+                                 String relayState, String samlRequest) throws 
ProcessingException {
         LOG.debug("Received SAML Request: {}", samlRequest);
 
         AuthnRequest parsedRequest = null;
@@ -69,7 +68,7 @@ public class AuthnRequestParser {
         if (parsedRequest != null) {
             try {
                 AuthnRequestValidator validator = new AuthnRequestValidator();
-                validator.validateAuthnRequest(context, parsedRequest, idp);
+                validator.validateAuthnRequest(context, parsedRequest, idp, 
signature, relayState, samlRequest);
             } catch (Exception ex) {
                 LOG.warn("Error validating request {}", ex.getMessage(), ex);
                 throw new ProcessingException(TYPE.BAD_REQUEST);

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e68df835/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/AuthnRequestValidator.java
----------------------------------------------------------------------
diff --git 
a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/AuthnRequestValidator.java
 
b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/AuthnRequestValidator.java
index ebb48dc..b20b1f1 100644
--- 
a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/AuthnRequestValidator.java
+++ 
b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/AuthnRequestValidator.java
@@ -18,6 +18,10 @@
  */
 package org.apache.cxf.fediz.service.idp.samlsso;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
+
 import org.w3c.dom.Document;
 
 import org.apache.cxf.fediz.core.exception.ProcessingException;
@@ -25,6 +29,7 @@ import 
org.apache.cxf.fediz.core.exception.ProcessingException.TYPE;
 import org.apache.cxf.fediz.core.util.CertsUtils;
 import org.apache.cxf.fediz.service.idp.domain.Idp;
 import org.apache.cxf.fediz.service.idp.util.WebUtils;
+import org.apache.cxf.rs.security.saml.sso.SSOConstants;
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.ext.WSSecurityException;
 import org.apache.wss4j.common.saml.SAMLKeyInfo;
@@ -55,28 +60,57 @@ public class AuthnRequestValidator {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(AuthnRequestValidator.class);
 
-    public void validateAuthnRequest(RequestContext context, AuthnRequest 
authnRequest, Idp idp) 
-        throws ProcessingException, WSSecurityException {
+    public void validateAuthnRequest(RequestContext context, AuthnRequest 
authnRequest, Idp idp, String signature,
+                                     String relayState, String samlRequest) 
+        throws Exception {
         if (authnRequest.isSigned()) {
             // Check destination
-            String destination = authnRequest.getDestination();
-            LOG.debug("Validating destination: {}", destination);
-            
-            String localAddr = 
WebUtils.getHttpServletRequest(context).getRequestURL().toString();
-            if (!localAddr.startsWith(destination)) {
-                LOG.debug("The destination {} does not match the local address 
{}", destination, localAddr);
-                throw new ProcessingException(TYPE.BAD_REQUEST);
-            }
+            checkDestination(context, authnRequest);
             
             // Check signature
             Crypto issuerCrypto = 
CertsUtils.getCryptoFromCertificate(idp.getCertificate());
             validateAuthnRequestSignature(authnRequest.getSignature(), 
issuerCrypto);
+        } else if (signature != null) {
+            // Check destination
+            checkDestination(context, authnRequest);
+            
+            // Check signature
+            X509Certificate validatingCert = 
CertsUtils.parseX509Certificate(idp.getCertificate());
+            
+            java.security.Signature sig = 
java.security.Signature.getInstance("SHA1withRSA");
+            sig.initVerify(validatingCert);
+            
+            // Recreate request to sign
+            String requestToSign = 
WebUtils.getHttpServletRequest(context).getRequestURL().toString() + "?";
+            requestToSign += SSOConstants.RELAY_STATE + "=" + relayState;
+            requestToSign += "&" + SSOConstants.SAML_REQUEST + "=" + 
URLEncoder.encode(samlRequest, "UTF-8");
+            requestToSign += "&" + SSOConstants.SIG_ALG + "=" 
+                + URLEncoder.encode(SSOConstants.RSA_SHA1, 
StandardCharsets.UTF_8.name());
+            
+            sig.update(requestToSign.getBytes(StandardCharsets.UTF_8));
+            
+            if (!sig.verify(signature.getBytes())) {
+                LOG.debug("Signature validation failed");
+                throw new ProcessingException(TYPE.BAD_REQUEST);
+            }
         } else {
             LOG.debug("No signature is present, therefore the request is 
rejected");
             throw new ProcessingException(TYPE.BAD_REQUEST);
         }
     }
     
+    private void checkDestination(RequestContext context, AuthnRequest 
authnRequest) throws ProcessingException {
+        // Check destination
+        String destination = authnRequest.getDestination();
+        LOG.debug("Validating destination: {}", destination);
+        
+        String localAddr = 
WebUtils.getHttpServletRequest(context).getRequestURL().toString();
+        if (!localAddr.startsWith(destination)) {
+            LOG.debug("The destination {} does not match the local address 
{}", destination, localAddr);
+            throw new ProcessingException(TYPE.BAD_REQUEST);
+        }
+    }
+    
     /**
      * Validate the AuthnRequest signature
      */

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e68df835/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
----------------------------------------------------------------------
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 b97d020..52f7960 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
@@ -27,6 +27,7 @@
         <on-entry>
             <set name="flowScope.RelayState" 
value="requestParameters.RelayState" />
             <set name="flowScope.SAMLRequest" 
value="requestParameters.SAMLRequest" />
+            <set name="flowScope.Signature" 
value="requestParameters.Signature" />
             <set name="flowScope.idpConfig" value="config.getIDP(null)" />
         </on-entry>
         <if test="requestParameters.RelayState == null or 
requestParameters.RelayState.length() == 0"
@@ -36,7 +37,9 @@
     </decision-state>
     
     <action-state id="parseAndValidateSAMLRequest">
-        <evaluate 
expression="authnRequestParser.parseSAMLRequest(flowRequestContext, 
flowScope.idpConfig)" />
+        <evaluate 
expression="authnRequestParser.parseSAMLRequest(flowRequestContext, 
flowScope.idpConfig,
+                                                                               
                                          flowScope.Signature, 
flowScope.RelayState,
+                                                                               
                                          flowScope.SAMLRequest)" />
         <transition to="signinSAMLRequest"/>
         <transition 
on-exception="org.apache.cxf.fediz.core.exception.ProcessingException" 
to="viewBadRequest" />
     </action-state>

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e68df835/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
----------------------------------------------------------------------
diff --git 
a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java 
b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
index 441ca2c..e43e62f 100644
--- 
a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
+++ 
b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 import java.util.UUID;
@@ -56,6 +57,7 @@ import org.apache.wss4j.common.crypto.CryptoType;
 import org.apache.wss4j.common.saml.OpenSAMLUtil;
 import org.apache.wss4j.common.util.DOM2Writer;
 import org.apache.wss4j.dom.engine.WSSConfig;
+import org.apache.xml.security.utils.Base64;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -385,6 +387,121 @@ public class IdpTest {
         webClient.close();
     }
     
+    @org.junit.Test
+    @org.junit.Ignore
+    public void testSeparateSignature() throws Exception {
+        OpenSAMLUtil.initSamlEngine();
+        
+        // Create SAML AuthnRequest
+        Document doc = DOMUtils.createDocument();
+        doc.appendChild(doc.createElement("root"));
+        // Create the AuthnRequest
+        String consumerURL = "https://localhost/acsa";;
+        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("SHA1withRSA");
+        signature.initSign(privateKey);
+       
+        String requestToSign = "https://localhost:"; + getIdpHttpsPort() + 
"/fediz-idp/saml?";
+        requestToSign += SSOConstants.RELAY_STATE + "=" + relayState;
+        requestToSign += "&" + SSOConstants.SAML_REQUEST + "=" + 
urlEncodedRequest;
+        requestToSign += "&" + SSOConstants.SIG_ALG + "=" 
+            + URLEncoder.encode(SSOConstants.RSA_SHA1, 
StandardCharsets.UTF_8.name());
+        
+        signature.update(requestToSign.getBytes(StandardCharsets.UTF_8));
+        byte[] signBytes = signature.sign();
+        
+        String encodedSignature = Base64.encode(signBytes);
+        
+        String url = "https://localhost:"; + getIdpHttpsPort() + 
"/fediz-idp/saml?";
+        url += SSOConstants.RELAY_STATE + "=" + relayState;
+        url += "&" + SSOConstants.SAML_REQUEST + "=" + urlEncodedRequest;
+        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());
+        
+        // Parse the form to get the token (SAMLResponse)
+        DomNodeList<DomElement> results = 
idpPage.getElementsByTagName("input");
+
+        String samlResponse = null;
+        boolean foundRelayState = false;
+        for (DomElement result : results) {
+            if ("SAMLResponse".equals(result.getAttributeNS(null, "name"))) {
+                samlResponse = result.getAttributeNS(null, "value");
+            } else if ("RelayState".equals(result.getAttributeNS(null, 
"name"))) {
+                foundRelayState = true;
+                Assert.assertEquals(result.getAttributeNS(null, "value"), 
relayState);
+            }
+        }
+
+        Assert.assertNotNull(samlResponse);
+        Assert.assertTrue(foundRelayState);
+        
+        // Check the "action"
+        DomNodeList<DomElement> formResults = 
idpPage.getElementsByTagName("form");
+        Assert.assertFalse(formResults.isEmpty());
+        
+        DomElement formResult = formResults.get(0);
+        String action = formResult.getAttributeNS(null, "action");
+        Assert.assertTrue(action.equals(consumerURL));
+        
+        // Decode + verify response
+        byte[] deflatedToken = Base64Utility.decode(samlResponse);
+        InputStream inputStream = new ByteArrayInputStream(deflatedToken);
+        
+        Document responseDoc = StaxUtils.read(new 
InputStreamReader(inputStream, "UTF-8"));
+        
+        XMLObject responseObject = 
OpenSAMLUtil.fromDom(responseDoc.getDocumentElement());
+        Assert.assertTrue(responseObject instanceof 
org.opensaml.saml.saml2.core.Response);
+        
+        org.opensaml.saml.saml2.core.Response samlResponseObject = 
+            (org.opensaml.saml.saml2.core.Response)responseObject;
+        
Assert.assertTrue(authnRequest.getID().equals(samlResponseObject.getInResponseTo()));
+        
+        // Check claims
+        String parsedResponse = DOM2Writer.nodeToString(responseDoc);
+        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();
+    }
+    
     private String encodeAuthnRequest(Element authnRequest) throws IOException 
{
         String requestMessage = DOM2Writer.nodeToString(authnRequest);
         

Reply via email to