diff --git a/src/main/java/com/coveo/saml/MetadataUtils.java b/src/main/java/com/coveo/saml/MetadataUtils.java new file mode 100644 index 0000000..ac7ac4a --- /dev/null +++ b/src/main/java/com/coveo/saml/MetadataUtils.java @@ -0,0 +1,178 @@ +package com.coveo.saml; + +import java.io.StringWriter; +import java.security.cert.X509Certificate; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.opensaml.core.config.InitializationService; +import org.opensaml.core.xml.XMLObjectBuilderFactory; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.KeyDescriptor; +import org.opensaml.saml.saml2.metadata.NameIDFormat; +import org.opensaml.saml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml.saml2.metadata.SingleLogoutService; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.UsageType; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator; +import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; + + +public class MetadataUtils { + + private static final Logger logger = LoggerFactory.getLogger(SamlClient.class); + + public static String generateSpMetadata(String entityId, String assertionConsumerServiceURL, String logoutServiceURL) { + return generateSpMetadata(entityId, assertionConsumerServiceURL, logoutServiceURL, null); + } + + public static String generateSpMetadata(String entityId, String assertionConsumerServiceURL, String singleLogoutServiceURL, X509Certificate certificate) { + try { + InitializationService.initialize(); + + EntityDescriptor spEntityDescriptor = createSAMLObject(EntityDescriptor.class); + if (spEntityDescriptor == null) { + return null; + } + spEntityDescriptor.setEntityID(entityId); + SPSSODescriptor spSSODescriptor = createSAMLObject(SPSSODescriptor.class); + if (spSSODescriptor == null) { + return null; + } + + spSSODescriptor.setWantAssertionsSigned(false); + spSSODescriptor.setAuthnRequestsSigned(false); + + if (certificate != null) { + + spSSODescriptor.setWantAssertionsSigned(true); + spSSODescriptor.setAuthnRequestsSigned(true); + + X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); + keyInfoGeneratorFactory.setEmitEntityCertificate(true); + KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance(); + + KeyDescriptor encKeyDescriptor = createSAMLObject(KeyDescriptor.class); + if (encKeyDescriptor == null) { + return null; + } + + encKeyDescriptor.setUse(UsageType.ENCRYPTION); + + Credential credential = new BasicX509Credential(certificate); + + try { + encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); + } + catch (Exception e) { + logger.error("Error while creating credentials", e); + } + spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor); + + KeyDescriptor signKeyDescriptor = createSAMLObject(KeyDescriptor.class); + if (signKeyDescriptor == null) { + return null; + } + + signKeyDescriptor.setUse(UsageType.SIGNING); // Set usage + + try { + signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); + } + catch (SecurityException e) { + logger.error("Error while creating credentials", e); + } + spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); + } + + SingleLogoutService singleLogoutService = createSAMLObject(SingleLogoutService.class); + if (singleLogoutService == null) { + return null; + } + singleLogoutService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + singleLogoutService.setLocation(singleLogoutServiceURL); + spSSODescriptor.getSingleLogoutServices().add(singleLogoutService); + + NameIDFormat nameIDFormat = createSAMLObject(NameIDFormat.class); + if (nameIDFormat == null) { + return null; + } + + nameIDFormat.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); + spSSODescriptor.getNameIDFormats().add(nameIDFormat); + + AssertionConsumerService assertionConsumerService = createSAMLObject(AssertionConsumerService.class); + if (assertionConsumerService == null) { + return null; + } + assertionConsumerService.setIndex(1); + assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + + assertionConsumerService.setLocation(assertionConsumerServiceURL); + spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); + + spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + + spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); + + DocumentBuilder builder; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + + builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Marshaller out = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(spEntityDescriptor); + out.marshall(spEntityDescriptor, document); + + TransformerFactory transformerfactory = TransformerFactory.newInstance(); + transformerfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + Transformer transformer = transformerfactory.newTransformer(); + StringWriter stringWriter = new StringWriter(); + StreamResult streamResult = new StreamResult(stringWriter); + DOMSource source = new DOMSource(document); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + transformer.transform(source, streamResult); + stringWriter.close(); + + return stringWriter.toString(); + } + catch (Exception e) { + logger.error("Error while generation SP metadata", e); + return null; + } + + } + + public static T createSAMLObject(final Class clazz) { + XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); + + QName defaultElementName = null; + try { + defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null); + } + catch (Exception e) { + logger.error("Error while creating SAML object", e); + return null; + } + T object = (T) builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName); + + return object; + } +} diff --git a/src/test/java/com/coveo/saml/MetadataUtilsTest.java b/src/test/java/com/coveo/saml/MetadataUtilsTest.java new file mode 100644 index 0000000..fe20d3f --- /dev/null +++ b/src/test/java/com/coveo/saml/MetadataUtilsTest.java @@ -0,0 +1,52 @@ +package com.coveo.saml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import org.junit.Test; + + +public class MetadataUtilsTest { + + @Test + public void generateSpMetadata_AllNull() { + String metadata = MetadataUtils.generateSpMetadata(null, null, null); + assertEquals( + " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", + metadata.replace("\r\n", "").replace("\n", "")); + } + + @Test + public void generateSpMetadata_AllFields() { + String metadata = MetadataUtils.generateSpMetadata("testSp", "http://localhost:8080/consume", "http://localhost:8080/logout"); + assertEquals( + " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", + metadata.replace("\r\n", "").replace("\n", "")); + } + + @Test + public void generateSpMetadata_AllFieldsAndCertificat() { + Certificate cert = null; + try { + InputStream keyStoreInputStream = this.getClass().getResourceAsStream("/com/coveo/saml/test.p12"); + + KeyStore keystore = KeyStore.getInstance("PKCS12"); + keystore.load(keyStoreInputStream, "test".toCharArray()); + cert = keystore.getCertificate("tester"); + } + catch (Exception e) { + fail(); + } + + String metadata = MetadataUtils.generateSpMetadata("testSp", "http://localhost:8080/consume", "http://localhost:8080/logout", (X509Certificate) cert); + assertEquals( + " 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 urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", + metadata.replace("\r\n", "").replace("\n", "")); + } + +} diff --git a/src/test/resources/com/coveo/saml/test.p12 b/src/test/resources/com/coveo/saml/test.p12 new file mode 100644 index 0000000..d8c7459 Binary files /dev/null and b/src/test/resources/com/coveo/saml/test.p12 differ