Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/maven/commons-io-commons-io-2.14.0
Browse files Browse the repository at this point in the history
  • Loading branch information
justinbleach authored Dec 21, 2024
2 parents d7c9876 + 762ab37 commit 80931ce
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 0 deletions.
178 changes: 178 additions & 0 deletions src/main/java/com/coveo/saml/MetadataUtils.java
Original file line number Diff line number Diff line change
@@ -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> T createSAMLObject(final Class<T> 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;
}
}
52 changes: 52 additions & 0 deletions src/test/java/com/coveo/saml/MetadataUtilsTest.java
Original file line number Diff line number Diff line change
@@ -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(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"> <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"> <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"/> <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat> <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" index=\"1\"/> </md:SPSSODescriptor></md:EntityDescriptor>",
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(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"testSp\"> <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"> <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8080/logout\"/> <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat> <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8080/consume\" index=\"1\"/> </md:SPSSODescriptor></md:EntityDescriptor>",
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(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"testSp\"> <md:SPSSODescriptor AuthnRequestsSigned=\"true\" WantAssertionsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"> <md:KeyDescriptor use=\"encryption\"> <ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"> <ds:X509Data> <ds:X509Certificate>MIIDCTCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJERTEMMAoGA1UECBMDTlJXMQ0wCwYDVQQKEwR0ZXN0MQ0wCwYDVQQLEwR0ZXN0MQ0wCwYDVQQDEwR0ZXN0MB4XDTIwMDQyNzEzNDcwMFoXDTIxMDQyNzEzNDcwMFowSDELMAkGA1UEBhMCREUxDDAKBgNVBAgTA05SVzENMAsGA1UEChMEdGVzdDENMAsGA1UECxMEdGVzdDENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALhZvjhIbJYV+ufRs/wF2W3710XzK8Cdg19pXZw9rOP5OE54ixAh6Hbbe0AHLTAt+T1Ljepqshwyo3an85q1JyuSLkPJBGks7WQKT6x0389V8c2nwbxOU2FkuIrOAG1y2rCmh+zjbndSBRLVMLPRwm7He+zeLH2yDl8tlPT3rVOzPX6/SEvhnG2yz2qsz1tNeski+9gK8+Anzzu+Ze2uf/q2y7tEFgrNOdkxEHtta4kqjglbacWougyNKFbRzILsDLJP7S0csssunXdIuYNmdsQ857Emjh1Yth4ZHaks8Np4TBRfjX+91PSQ5CTlw4zDijk/vNPgQ39cnY6SiucMEnkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAIGnBuviT6kDVK/b2mhCKKROp3bEqIaO3Ccl55H1ZKQNaY/xw4FUxaMGTdUuVo3Kbh5AT5iyEd+U+hd0skG4HbQ0nPkeEg15w07fh04mgTccC/IPAyrT++w9yiHOrXB0R6sXlwLOebXK6/6GQdt6pNDPc1GJaDhYhmI0IoXGO2iVFRlefqCSmGSRRbW4hU5SIdPrmCX/oOfnGBVN3Vo3wQtq9MAUTYnzpdVKBWaAbwzJdWXkF5GbHue5lxOnKmZB7ctd7VZk+L+dtmCozABk+NjdF0nGnjc3zIHD3EE+NCIas9jYPr0Ib8SReNsVL46zF3w1BvxQfkpMLIQThXyoZ/w==</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </md:KeyDescriptor> <md:KeyDescriptor use=\"signing\"> <ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"> <ds:X509Data> <ds:X509Certificate>MIIDCTCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJERTEMMAoGA1UECBMDTlJXMQ0wCwYDVQQKEwR0ZXN0MQ0wCwYDVQQLEwR0ZXN0MQ0wCwYDVQQDEwR0ZXN0MB4XDTIwMDQyNzEzNDcwMFoXDTIxMDQyNzEzNDcwMFowSDELMAkGA1UEBhMCREUxDDAKBgNVBAgTA05SVzENMAsGA1UEChMEdGVzdDENMAsGA1UECxMEdGVzdDENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALhZvjhIbJYV+ufRs/wF2W3710XzK8Cdg19pXZw9rOP5OE54ixAh6Hbbe0AHLTAt+T1Ljepqshwyo3an85q1JyuSLkPJBGks7WQKT6x0389V8c2nwbxOU2FkuIrOAG1y2rCmh+zjbndSBRLVMLPRwm7He+zeLH2yDl8tlPT3rVOzPX6/SEvhnG2yz2qsz1tNeski+9gK8+Anzzu+Ze2uf/q2y7tEFgrNOdkxEHtta4kqjglbacWougyNKFbRzILsDLJP7S0csssunXdIuYNmdsQ857Emjh1Yth4ZHaks8Np4TBRfjX+91PSQ5CTlw4zDijk/vNPgQ39cnY6SiucMEnkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAIGnBuviT6kDVK/b2mhCKKROp3bEqIaO3Ccl55H1ZKQNaY/xw4FUxaMGTdUuVo3Kbh5AT5iyEd+U+hd0skG4HbQ0nPkeEg15w07fh04mgTccC/IPAyrT++w9yiHOrXB0R6sXlwLOebXK6/6GQdt6pNDPc1GJaDhYhmI0IoXGO2iVFRlefqCSmGSRRbW4hU5SIdPrmCX/oOfnGBVN3Vo3wQtq9MAUTYnzpdVKBWaAbwzJdWXkF5GbHue5lxOnKmZB7ctd7VZk+L+dtmCozABk+NjdF0nGnjc3zIHD3EE+NCIas9jYPr0Ib8SReNsVL46zF3w1BvxQfkpMLIQThXyoZ/w==</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </md:KeyDescriptor> <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8080/logout\"/> <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat> <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8080/consume\" index=\"1\"/> </md:SPSSODescriptor></md:EntityDescriptor>",
metadata.replace("\r\n", "").replace("\n", ""));
}

}
Binary file added src/test/resources/com/coveo/saml/test.p12
Binary file not shown.

0 comments on commit 80931ce

Please sign in to comment.