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/07/16 16:52:13 UTC
[cxf-fediz] 02/02: FEDIZ-221 - Sign the LogoutResponses from the IdP
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 f934237c80eb2d7b24a63da7d8ce8cfa7cbc14a9
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Mon Jul 16 17:51:46 2018 +0100
FEDIZ-221 - Sign the LogoutResponses from the IdP
---
.../beans/samlsso/AbstractSamlResponseCreator.java | 176 +++++++++++++++++++++
.../idp/beans/samlsso/SamlResponseCreator.java | 62 +-------
.../beans/samlsso/SamlResponseErrorCreator.java | 56 +------
.../apache/cxf/fediz/systests/samlsso/IdpTest.java | 39 +++--
4 files changed, 204 insertions(+), 129 deletions(-)
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AbstractSamlResponseCreator.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AbstractSamlResponseCreator.java
new file mode 100644
index 0000000..0e9c802
--- /dev/null
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AbstractSamlResponseCreator.java
@@ -0,0 +1,176 @@
+/**
+ * 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.fediz.service.idp.beans.samlsso;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.cxf.common.util.Base64Utility;
+import org.apache.cxf.fediz.core.util.CertsUtils;
+import org.apache.cxf.fediz.service.idp.domain.Idp;
+import org.apache.cxf.fediz.service.idp.samlsso.SAML2PResponseComponentBuilder;
+import org.apache.cxf.helpers.DOMUtils;
+import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.CryptoType;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.OpenSAMLUtil;
+import org.apache.wss4j.common.util.DOM2Writer;
+import org.opensaml.saml.common.SAMLObjectContentReference;
+import org.opensaml.saml.common.SignableSAMLObject;
+import org.opensaml.saml.saml2.core.LogoutResponse;
+import org.opensaml.saml.saml2.core.Status;
+import org.opensaml.security.x509.BasicX509Credential;
+import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
+import org.opensaml.xmlsec.signature.KeyInfo;
+import org.opensaml.xmlsec.signature.Signature;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractSamlResponseCreator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractSamlResponseCreator.class);
+ private boolean signLogoutResponse = true;
+ private boolean supportDeflateEncoding;
+ private boolean useRealmForIssuer;
+
+ protected Element createLogoutResponse(Idp idp, String statusValue,
+ String destination, String requestID) throws Exception {
+ Document doc = DOMUtils.newDocument();
+
+ Status status =
+ SAML2PResponseComponentBuilder.createStatus(statusValue, null);
+ String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
+ LogoutResponse response =
+ SAML2PResponseComponentBuilder.createSAMLLogoutResponse(requestID, issuer, status, destination);
+
+ // Sign the LogoutResponse
+ signResponse(response, idp);
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+
+ return policyElement;
+ }
+
+ protected void signResponse(SignableSAMLObject signableObject, Idp idp) throws Exception {
+ if (!signLogoutResponse) {
+ return;
+ }
+ Crypto issuerCrypto = CertsUtils.getCryptoFromCertificate(idp.getCertificate());
+ String issuerKeyName = issuerCrypto.getDefaultX509Identifier();
+ String issuerKeyPassword = idp.getCertificatePassword();
+
+ Signature signature = OpenSAMLUtil.buildSignature();
+ signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
+ CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
+ cryptoType.setAlias(issuerKeyName);
+ X509Certificate[] issuerCerts = null;
+ if (issuerCrypto != null) {
+ issuerCerts = issuerCrypto.getX509Certificates(cryptoType);
+ }
+ if (issuerCerts == null || issuerCerts.length == 0) {
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
+ new Object[] {"No issuer certs were found to sign the SAML Assertion using issuer name: "
+ + issuerKeyName});
+ }
+
+ String sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1;
+ String pubKeyAlgo = issuerCerts[0].getPublicKey().getAlgorithm();
+ LOG.debug("automatic sig algo detection: {}", pubKeyAlgo);
+ if (pubKeyAlgo.equalsIgnoreCase("DSA")) {
+ sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_DSA;
+ } else if (pubKeyAlgo.equalsIgnoreCase("EC")) {
+ sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA1;
+ }
+ LOG.debug("Using Signature algorithm {}", sigAlgo);
+ PrivateKey privateKey;
+ try {
+ privateKey = issuerCrypto.getPrivateKey(issuerKeyName, issuerKeyPassword);
+ } catch (Exception ex) {
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex);
+ }
+ if (privateKey == null) {
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
+ new Object[] {"No private key was found using issuer name: " + issuerKeyName});
+ }
+
+ signature.setSignatureAlgorithm(sigAlgo);
+
+ BasicX509Credential signingCredential =
+ new BasicX509Credential(issuerCerts[0], privateKey);
+
+ signature.setSigningCredential(signingCredential);
+
+ X509KeyInfoGeneratorFactory kiFactory = new X509KeyInfoGeneratorFactory();
+ kiFactory.setEmitEntityCertificate(true);
+
+ try {
+ KeyInfo keyInfo = kiFactory.newInstance().generate(signingCredential);
+ signature.setKeyInfo(keyInfo);
+ } catch (org.opensaml.security.SecurityException ex) {
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
+ new Object[] {"Error generating KeyInfo from signing credential"});
+ }
+
+ signableObject.setSignature(signature);
+ String digestAlg = SignatureConstants.ALGO_ID_DIGEST_SHA1;
+ SAMLObjectContentReference contentRef =
+ (SAMLObjectContentReference)signature.getContentReferences().get(0);
+ contentRef.setDigestAlgorithm(digestAlg);
+ signableObject.releaseDOM();
+ signableObject.releaseChildrenDOM(true);
+ }
+
+ protected String encodeResponse(Element response) throws IOException {
+ String responseMessage = DOM2Writer.nodeToString(response);
+ LOG.debug("Created Response: {}", responseMessage);
+
+ if (supportDeflateEncoding) {
+ DeflateEncoderDecoder encoder = new DeflateEncoderDecoder();
+ byte[] deflatedBytes = encoder.deflateToken(responseMessage.getBytes(StandardCharsets.UTF_8));
+
+ return Base64Utility.encode(deflatedBytes);
+ }
+
+ return Base64Utility.encode(responseMessage.getBytes());
+ }
+
+ public boolean isSupportDeflateEncoding() {
+ return supportDeflateEncoding;
+ }
+
+ public void setSupportDeflateEncoding(boolean supportDeflateEncoding) {
+ this.supportDeflateEncoding = supportDeflateEncoding;
+ }
+
+ public boolean isUseRealmForIssuer() {
+ return useRealmForIssuer;
+ }
+
+ public void setUseRealmForIssuer(boolean useRealmForIssuer) {
+ this.useRealmForIssuer = useRealmForIssuer;
+ }
+}
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java
index 426bbf7..b91544d 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java
@@ -18,15 +18,12 @@
*/
package org.apache.cxf.fediz.service.idp.beans.samlsso;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.apache.cxf.common.util.Base64Utility;
import org.apache.cxf.fediz.core.exception.ProcessingException;
import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE;
import org.apache.cxf.fediz.core.util.CertsUtils;
@@ -37,7 +34,6 @@ import org.apache.cxf.fediz.service.idp.samlsso.SAML2PResponseComponentBuilder;
import org.apache.cxf.fediz.service.idp.samlsso.SAMLAuthnRequest;
import org.apache.cxf.fediz.service.idp.util.WebUtils;
import org.apache.cxf.helpers.DOMUtils;
-import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.saml.OpenSAMLUtil;
import org.apache.wss4j.common.saml.SAMLCallback;
@@ -46,11 +42,9 @@ import org.apache.wss4j.common.saml.SamlAssertionWrapper;
import org.apache.wss4j.common.saml.bean.AudienceRestrictionBean;
import org.apache.wss4j.common.saml.bean.ConditionsBean;
import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean;
-import org.apache.wss4j.common.util.DOM2Writer;
import org.apache.wss4j.dom.WSConstants;
import org.joda.time.DateTime;
import org.opensaml.saml.saml2.core.Assertion;
-import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.Status;
@@ -63,11 +57,9 @@ import org.springframework.webflow.execution.RequestContext;
* Insert the SAML Token received from the STS into a SAML Response
*/
@Component
-public class SamlResponseCreator {
+public class SamlResponseCreator extends AbstractSamlResponseCreator {
private static final Logger LOG = LoggerFactory.getLogger(SamlResponseCreator.class);
- private boolean supportDeflateEncoding;
- private boolean useRealmForIssuer;
public String createSAMLResponse(RequestContext context, Idp idp, Element rpToken,
String consumerURL, String requestId, String requestIssuer)
@@ -100,7 +92,8 @@ public class SamlResponseCreator {
public String createSAMLLogoutResponse(RequestContext context, Idp idp, String destination, String requestId)
throws ProcessingException {
try {
- Element response = createLogoutResponse(idp, destination, requestId);
+ Element response = createLogoutResponse(idp, "urn:oasis:names:tc:SAML:2.0:status:Success",
+ destination, requestId);
return encodeResponse(response);
} catch (Exception ex) {
LOG.warn("Error marshalling SAML Token: {}", ex.getMessage());
@@ -113,7 +106,7 @@ public class SamlResponseCreator {
String remoteAddr, String racs) throws Exception {
// Create an AuthenticationAssertion
SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
- String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
+ String issuer = isUseRealmForIssuer() ? idp.getRealm() : idp.getIdpUrl().toString();
callbackHandler.setIssuer(issuer);
callbackHandler.setSubject(receivedToken.getSaml2().getSubject());
@@ -167,7 +160,7 @@ public class SamlResponseCreator {
SAML2PResponseComponentBuilder.createStatus(
"urn:oasis:names:tc:SAML:2.0:status:Success", null
);
- String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
+ String issuer = isUseRealmForIssuer() ? idp.getRealm() : idp.getIdpUrl().toString();
Response response =
SAML2PResponseComponentBuilder.createSAMLResponse(requestID, issuer, status);
@@ -179,50 +172,5 @@ public class SamlResponseCreator {
return policyElement;
}
- protected Element createLogoutResponse(Idp idp, String destination, String requestID) throws Exception {
- Document doc = DOMUtils.newDocument();
-
- Status status =
- SAML2PResponseComponentBuilder.createStatus(
- "urn:oasis:names:tc:SAML:2.0:status:Success", null
- );
- String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
- LogoutResponse response =
- SAML2PResponseComponentBuilder.createSAMLLogoutResponse(requestID, issuer, status, destination);
-
- Element policyElement = OpenSAMLUtil.toDom(response, doc);
- doc.appendChild(policyElement);
-
- return policyElement;
- }
-
- protected String encodeResponse(Element response) throws IOException {
- String responseMessage = DOM2Writer.nodeToString(response);
- LOG.debug("Created Response: {}", responseMessage);
- if (supportDeflateEncoding) {
- DeflateEncoderDecoder encoder = new DeflateEncoderDecoder();
- byte[] deflatedBytes = encoder.deflateToken(responseMessage.getBytes(StandardCharsets.UTF_8));
-
- return Base64Utility.encode(deflatedBytes);
- }
-
- return Base64Utility.encode(responseMessage.getBytes());
- }
-
- public boolean isSupportDeflateEncoding() {
- return supportDeflateEncoding;
- }
-
- public void setSupportDeflateEncoding(boolean supportDeflateEncoding) {
- this.supportDeflateEncoding = supportDeflateEncoding;
- }
-
- public boolean isUseRealmForIssuer() {
- return useRealmForIssuer;
- }
-
- public void setUseRealmForIssuer(boolean useRealmForIssuer) {
- this.useRealmForIssuer = useRealmForIssuer;
- }
}
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java
index d201d0a..f6996c2 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java
@@ -18,22 +18,15 @@
*/
package org.apache.cxf.fediz.service.idp.beans.samlsso;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.apache.cxf.common.util.Base64Utility;
import org.apache.cxf.fediz.core.exception.ProcessingException;
import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE;
import org.apache.cxf.fediz.service.idp.domain.Idp;
import org.apache.cxf.fediz.service.idp.samlsso.SAML2PResponseComponentBuilder;
import org.apache.cxf.helpers.DOMUtils;
-import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
import org.apache.wss4j.common.saml.OpenSAMLUtil;
-import org.apache.wss4j.common.util.DOM2Writer;
-import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.Status;
import org.slf4j.Logger;
@@ -45,11 +38,9 @@ import org.springframework.webflow.execution.RequestContext;
* Create a SAML Error Response
*/
@Component
-public class SamlResponseErrorCreator {
+public class SamlResponseErrorCreator extends AbstractSamlResponseCreator {
private static final Logger LOG = LoggerFactory.getLogger(SamlResponseErrorCreator.class);
- private boolean supportDeflateEncoding;
- private boolean useRealmForIssuer;
public String createSAMLResponse(RequestContext context, boolean logout, boolean requestor,
Idp idp, String requestID, String destination) throws ProcessingException {
@@ -82,49 +73,4 @@ public class SamlResponseErrorCreator {
}
}
- protected Element createLogoutResponse(Idp idp, String statusValue,
- String destination, String requestID) throws Exception {
- Document doc = DOMUtils.newDocument();
-
- Status status =
- SAML2PResponseComponentBuilder.createStatus(statusValue, null);
- String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
- LogoutResponse response =
- SAML2PResponseComponentBuilder.createSAMLLogoutResponse(requestID, issuer, status, destination);
-
- Element policyElement = OpenSAMLUtil.toDom(response, doc);
- doc.appendChild(policyElement);
-
- return policyElement;
- }
-
- protected String encodeResponse(Element response) throws IOException {
- String responseMessage = DOM2Writer.nodeToString(response);
- LOG.debug("Created Response: {}", responseMessage);
-
- if (supportDeflateEncoding) {
- DeflateEncoderDecoder encoder = new DeflateEncoderDecoder();
- byte[] deflatedBytes = encoder.deflateToken(responseMessage.getBytes(StandardCharsets.UTF_8));
-
- return Base64Utility.encode(deflatedBytes);
- }
-
- return Base64Utility.encode(responseMessage.getBytes());
- }
-
- public boolean isSupportDeflateEncoding() {
- return supportDeflateEncoding;
- }
-
- public void setSupportDeflateEncoding(boolean supportDeflateEncoding) {
- this.supportDeflateEncoding = supportDeflateEncoding;
- }
-
- public boolean isUseRealmForIssuer() {
- return useRealmForIssuer;
- }
-
- public void setUseRealmForIssuer(boolean useRealmForIssuer) {
- this.useRealmForIssuer = useRealmForIssuer;
- }
}
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 6aa8985..57b4df9 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
@@ -37,23 +37,6 @@ import java.util.UUID;
import javax.servlet.ServletException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import com.gargoylesoftware.htmlunit.CookieManager;
-import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
-import com.gargoylesoftware.htmlunit.HttpMethod;
-import com.gargoylesoftware.htmlunit.WebClient;
-import com.gargoylesoftware.htmlunit.WebRequest;
-import com.gargoylesoftware.htmlunit.html.DomElement;
-import com.gargoylesoftware.htmlunit.html.DomNodeList;
-import com.gargoylesoftware.htmlunit.html.HtmlForm;
-import com.gargoylesoftware.htmlunit.html.HtmlPage;
-import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
-import com.gargoylesoftware.htmlunit.util.NameValuePair;
-import com.gargoylesoftware.htmlunit.xml.XmlPage;
-
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.Connector;
@@ -96,6 +79,22 @@ import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
import org.opensaml.xmlsec.signature.KeyInfo;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.gargoylesoftware.htmlunit.CookieManager;
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.HttpMethod;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.WebRequest;
+import com.gargoylesoftware.htmlunit.html.DomElement;
+import com.gargoylesoftware.htmlunit.html.DomNodeList;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
+import com.gargoylesoftware.htmlunit.util.NameValuePair;
+import com.gargoylesoftware.htmlunit.xml.XmlPage;
/**
* Some tests invoking directly on the IdP for SAML SSO
@@ -1737,6 +1736,8 @@ public class IdpTest {
String success = "urn:oasis:names:tc:SAML:2.0:status:Success";
Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
+ Assert.assertNotNull(logoutResponse.getSignature());
+
webClient.close();
// 3. now we try to access the idp without authentication but with the existing cookies
@@ -1864,6 +1865,8 @@ public class IdpTest {
Assert.assertEquals(expectedIssuer, logoutResponse.getIssuer().getValue());
String success = "urn:oasis:names:tc:SAML:2.0:status:Requester";
Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
+
+ Assert.assertNotNull(logoutResponse.getSignature());
webClient.close();
// 3. now we try to access the idp without authentication but with the existing cookies
@@ -1996,6 +1999,7 @@ public class IdpTest {
String success = "urn:oasis:names:tc:SAML:2.0:status:Requester";
Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
+ Assert.assertNotNull(logoutResponse.getSignature());
webClient.close();
// 3. now we try to access the idp without authentication but with the existing cookies
@@ -2105,4 +2109,5 @@ public class IdpTest {
return samlResponseObject;
}
+
}