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.