FEDIZ-205 - Support creating IdP Metadata for SAML SSO
Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/4808a7b4 Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/4808a7b4 Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/4808a7b4 Branch: refs/heads/master Commit: 4808a7b49a7948e459c57d7ba1d228ea873cdcd7 Parents: 110cac0 Author: Colm O hEigeartaigh <cohei...@apache.org> Authored: Wed Aug 9 12:41:34 2017 +0100 Committer: Colm O hEigeartaigh <cohei...@apache.org> Committed: Wed Aug 9 12:41:34 2017 +0100 ---------------------------------------------------------------------- .../cxf/fediz/service/idp/MetadataServlet.java | 9 +- .../service/idp/metadata/IdpMetadataWriter.java | 89 +++++++++++++++++--- .../cxf/fediz/systests/samlsso/IdpTest.java | 38 +++++++++ 3 files changed, 121 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/4808a7b4/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/MetadataServlet.java ---------------------------------------------------------------------- diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/MetadataServlet.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/MetadataServlet.java index 1077f8b..f09bd08 100644 --- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/MetadataServlet.java +++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/MetadataServlet.java @@ -52,7 +52,6 @@ public class MetadataServlet extends HttpServlet { private ApplicationContext applicationContext; private String realm; - @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -62,6 +61,8 @@ public class MetadataServlet extends HttpServlet { ConfigService cs = (ConfigService)getApplicationContext().getBean("config"); Idp idpConfig = cs.getIDP(realm); try { + boolean isSamlRequest = request.getQueryString() != null + && request.getQueryString().contains("protocol=saml"); if (request.getServletPath() != null && request.getServletPath().startsWith("/metadata")) { String parsedRealm = request.getRequestURI().substring(request.getRequestURI().indexOf("/metadata") @@ -73,7 +74,7 @@ public class MetadataServlet extends HttpServlet { // Default to writing out the metadata for the IdP if (idpConfig.getRealm().equals(parsedRealm) || parsedRealm == null || parsedRealm.isEmpty()) { IdpMetadataWriter mw = new IdpMetadataWriter(); - Document metadata = mw.getMetaData(idpConfig); + Document metadata = mw.getMetaData(idpConfig, isSamlRequest); out.write(DOM2Writer.nodeToString(metadata)); return; } @@ -92,7 +93,7 @@ public class MetadataServlet extends HttpServlet { // Otherwise return the Metadata for the Idp LOG.debug(idpConfig.toString()); IdpMetadataWriter mw = new IdpMetadataWriter(); - Document metadata = mw.getMetaData(idpConfig); + Document metadata = mw.getMetaData(idpConfig, isSamlRequest); out.write(DOM2Writer.nodeToString(metadata)); } } catch (Exception ex) { @@ -118,4 +119,6 @@ public class MetadataServlet extends HttpServlet { return applicationContext; } + + } http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/4808a7b4/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/metadata/IdpMetadataWriter.java ---------------------------------------------------------------------- diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/metadata/IdpMetadataWriter.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/metadata/IdpMetadataWriter.java index 97bcfcb..44eb6cb 100644 --- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/metadata/IdpMetadataWriter.java +++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/metadata/IdpMetadataWriter.java @@ -46,8 +46,11 @@ public class IdpMetadataWriter { private static final Logger LOG = LoggerFactory.getLogger(IdpMetadataWriter.class); - //CHECKSTYLE:OFF - public Document getMetaData(Idp config) throws RuntimeException { + public Document getMetaData(Idp config) { + return getMetaData(config, false); + } + + public Document getMetaData(Idp config, boolean saml) { try { //Return as text/xml Crypto crypto = CertsUtils.getCryptoFromFile(config.getCertificate()); @@ -63,12 +66,13 @@ public class IdpMetadataWriter { writer.writeAttribute("entityID", config.getIdpUrl().toString()); writer.writeNamespace("md", SAML2_METADATA_NS); - writer.writeNamespace("fed", WS_FEDERATION_NS); - writer.writeNamespace("wsa", WS_ADDRESSING_NS); - writer.writeNamespace("auth", WS_FEDERATION_NS); writer.writeNamespace("xsi", SCHEMA_INSTANCE_NS); - writeFederationMetadata(writer, config, crypto); + if (saml) { + writeSAMLSSOMetadata(writer, config, crypto); + } else { + writeFederationMetadata(writer, config, crypto); + } writer.writeEndElement(); // EntityDescriptor @@ -101,13 +105,17 @@ public class IdpMetadataWriter { XMLStreamWriter writer, Idp config, Crypto crypto ) throws XMLStreamException { + writer.writeNamespace("fed", WS_FEDERATION_NS); + writer.writeNamespace("wsa", WS_ADDRESSING_NS); + writer.writeNamespace("auth", WS_FEDERATION_NS); + writer.writeStartElement("md", "RoleDescriptor", WS_FEDERATION_NS); writer.writeAttribute(SCHEMA_INSTANCE_NS, "type", "fed:SecurityTokenServiceType"); writer.writeAttribute("protocolSupportEnumeration", WS_FEDERATION_NS); - if (config.getServiceDescription() != null && config.getServiceDescription().length() > 0 ) { + if (config.getServiceDescription() != null && config.getServiceDescription().length() > 0) { writer.writeAttribute("ServiceDescription", config.getServiceDescription()); } - if (config.getServiceDisplayName() != null && config.getServiceDisplayName().length() > 0 ) { + if (config.getServiceDisplayName() != null && config.getServiceDisplayName().length() > 0) { writer.writeAttribute("ServiceDisplayName", config.getServiceDisplayName()); } @@ -115,11 +123,12 @@ public class IdpMetadataWriter { //missing organization, contactperson //KeyDescriptor - writer.writeStartElement("", "KeyDescriptor", SAML2_METADATA_NS); + writer.writeStartElement("md", "KeyDescriptor", SAML2_METADATA_NS); writer.writeAttribute("use", "signing"); - writer.writeStartElement("", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); - writer.writeStartElement("", "X509Data", "http://www.w3.org/2000/09/xmldsig#"); - writer.writeStartElement("", "X509Certificate", "http://www.w3.org/2000/09/xmldsig#"); + writer.writeStartElement("ds", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); + writer.writeNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + writer.writeStartElement("ds", "X509Data", "http://www.w3.org/2000/09/xmldsig#"); + writer.writeStartElement("ds", "X509Certificate", "http://www.w3.org/2000/09/xmldsig#"); try { String keyAlias = crypto.getDefaultX509Identifier(); @@ -176,5 +185,61 @@ public class IdpMetadataWriter { writer.writeEndElement(); // RoleDescriptor } + private void writeSAMLSSOMetadata( + XMLStreamWriter writer, Idp config, Crypto crypto + ) throws XMLStreamException { + + writer.writeStartElement("md", "IDPSSODescriptor", SAML2_METADATA_NS); + writer.writeAttribute("WantAuthnRequestsSigned", "true"); + writer.writeAttribute("protocolSupportEnumeration", "urn:oasis:names:tc:SAML:2.0:protocol"); + + //KeyDescriptor + writer.writeStartElement("md", "KeyDescriptor", SAML2_METADATA_NS); + writer.writeAttribute("use", "signing"); + writer.writeStartElement("ds", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); + writer.writeNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + writer.writeStartElement("ds", "X509Data", "http://www.w3.org/2000/09/xmldsig#"); + writer.writeStartElement("ds", "X509Certificate", "http://www.w3.org/2000/09/xmldsig#"); + + try { + String keyAlias = crypto.getDefaultX509Identifier(); + X509Certificate cert = CertsUtils.getX509CertificateFromCrypto(crypto, keyAlias); + writer.writeCharacters(Base64.encode(cert.getEncoded())); + } catch (Exception ex) { + LOG.error("Failed to add certificate information to metadata. Metadata incomplete", ex); + } + + writer.writeEndElement(); // X509Certificate + writer.writeEndElement(); // X509Data + writer.writeEndElement(); // KeyInfo + writer.writeEndElement(); // KeyDescriptor + + + writer.writeStartElement("md", "NameIDFormat", SAML2_METADATA_NS); + writer.writeCharacters("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"); + writer.writeEndElement(); // NameIDFormat + + writer.writeStartElement("md", "NameIDFormat", SAML2_METADATA_NS); + writer.writeCharacters("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"); + writer.writeEndElement(); // NameIDFormat + + writer.writeStartElement("md", "NameIDFormat", SAML2_METADATA_NS); + writer.writeCharacters("urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress"); + writer.writeEndElement(); // NameIDFormat + + // SingleSignOnService + writer.writeStartElement("md", "SingleSignOnService", SAML2_METADATA_NS); + writer.writeAttribute("Binding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"); + writer.writeAttribute("Location", config.getIdpUrl().toString()); + writer.writeEndElement(); // SingleSignOnService + + // SingleSignOnService + writer.writeStartElement("md", "SingleSignOnService", SAML2_METADATA_NS); + writer.writeAttribute("Binding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + writer.writeAttribute("Location", config.getIdpUrl().toString()); + writer.writeEndElement(); // SingleSignOnService + + writer.writeEndElement(); // IDPSSODescriptor + } } http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/4808a7b4/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java ---------------------------------------------------------------------- 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 6542eed..d0fa7b9 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,6 +37,7 @@ 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; @@ -47,6 +48,7 @@ import com.gargoylesoftware.htmlunit.html.DomElement; import com.gargoylesoftware.htmlunit.html.DomNodeList; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.util.NameValuePair; +import com.gargoylesoftware.htmlunit.xml.XmlPage; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; @@ -68,10 +70,12 @@ 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.signature.XMLSignature; import org.apache.xml.security.utils.Base64; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; +import org.junit.Test; import org.opensaml.core.xml.XMLObject; import org.opensaml.saml.common.SAMLVersion; import org.opensaml.saml.common.SignableSAMLObject; @@ -216,6 +220,40 @@ public class IdpTest { } */ + + @Test + public void testIdPMetadata() throws Exception { + String url = "https://localhost:" + getIdpHttpsPort() + + "/fediz-idp/metadata?protocol=saml"; + + final WebClient webClient = new WebClient(); + webClient.getOptions().setUseInsecureSSL(true); + webClient.getOptions().setSSLClientCertificate( + this.getClass().getClassLoader().getResource("client.jks"), "storepass", "jks"); + + final XmlPage rpPage = webClient.getPage(url); + final String xmlContent = rpPage.asXml(); + Assert.assertTrue(xmlContent.startsWith("<md:EntityDescriptor")); + + // Now validate the Signature + Document doc = rpPage.getXmlDocument(); + + doc.getDocumentElement().setIdAttributeNS(null, "ID", true); + + Node signatureNode = + DOMUtils.getChild(doc.getDocumentElement(), "Signature"); + Assert.assertNotNull(signatureNode); + + XMLSignature signature = new XMLSignature((Element)signatureNode, ""); + org.apache.xml.security.keys.KeyInfo ki = signature.getKeyInfo(); + Assert.assertNotNull(ki); + Assert.assertNotNull(ki.getX509Certificate()); + + Assert.assertTrue(signature.checkSignatureValue(ki.getX509Certificate())); + + webClient.close(); + } + @org.junit.Test public void testSuccessfulInvokeOnIdP() throws Exception { OpenSAMLUtil.initSamlEngine();