saml2 metadata creation for sp

This commit is contained in:
Bernaldo Mihasi 2023-06-12 15:05:51 +03:00
parent ce50a72ac1
commit 4c9652abc3
5 changed files with 311 additions and 99 deletions

View File

@ -0,0 +1,59 @@
package eu.eudat.controllers;
import eu.eudat.logic.proxy.config.configloaders.ConfigLoader;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
import eu.eudat.logic.security.validators.configurableProvider.Saml2SSOUtils;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.models.data.helpers.responses.ResponseItem;
import eu.eudat.models.data.security.Principal;
import eu.eudat.types.ApiMessageCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.nio.charset.StandardCharsets;
@RestController
@CrossOrigin
@RequestMapping(value = {"/api/saml2/metadata"})
public class Saml2MetadataController extends BaseController {
private final ConfigLoader configLoader;
@Autowired
public Saml2MetadataController(ApiContext apiContext, ConfigLoader configLoader) {
super(apiContext);
this.configLoader = configLoader;
}
@RequestMapping(method = RequestMethod.GET, value = {"/{configurableProviderId}"})
public @ResponseBody
ResponseEntity getMetadata(@PathVariable String configurableProviderId) {
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider) this.configLoader.getConfigurableProviders().getProviders().stream()
.filter(prov -> prov.getConfigurableLoginId().equals(configurableProviderId))
.findFirst().orElse(null);
if (saml2ConfigurableProvider != null) {
try {
String metadataXml = Saml2SSOUtils.getMetadata(saml2ConfigurableProvider);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(metadataXml.length());
responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
responseHeaders.set("Content-Disposition", "attachment;filename=" + configurableProviderId + ".xml");
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
return new ResponseEntity<>(metadataXml.getBytes(StandardCharsets.UTF_8),
responseHeaders,
HttpStatus.OK);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to fetch metadata."));
}
}
else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to fetch metadata."));
}
}
}

View File

@ -0,0 +1,68 @@
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2;
import com.fasterxml.jackson.annotation.JsonValue;
public class CertificateInfo {
public enum KeyFormat {
JKS("JKS"), PKCS12("PKCS12");
private String type;
KeyFormat(String type) {
this.type = type;
}
@JsonValue
public String getType() { return type; }
public static KeyFormat fromType(String type) {
for (KeyFormat t: KeyFormat.values()) {
if (type.equals(t.getType())) {
return t;
}
}
throw new IllegalArgumentException("Unsupported Keystore format " + type);
}
}
private String alias;
private String password;
private String keystorePath;
private String keystorePassword;
private KeyFormat keyFormat;
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getKeystorePath() {
return keystorePath;
}
public void setKeystorePath(String keystorePath) {
this.keystorePath = keystorePath;
}
public String getKeystorePassword() {
return keystorePassword;
}
public void setKeystorePassword(String keystorePassword) {
this.keystorePassword = keystorePassword;
}
public KeyFormat getKeyFormat() {
return keyFormat;
}
public void setKeyFormat(KeyFormat keyFormat) {
this.keyFormat = keyFormat;
}
}

View File

@ -47,49 +47,24 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
}
}
public enum KeyFormat {
JKS("JKS"), PKCS12("PKCS12");
private String type;
KeyFormat(String type) {
this.type = type;
}
@JsonValue
public String getType() { return type; }
public static KeyFormat fromType(String type) {
for (KeyFormat t: KeyFormat.values()) {
if (type.equals(t.getType())) {
return t;
}
}
throw new IllegalArgumentException("Unsupported Keystore format " + type);
}
}
private String spEntityId;
private String idpEntityId;
private String idpUrl;
private String idpArtifactUrl;
private String idpMetadataUrl;
private boolean assertionEncrypted;
private KeyFormat keyFormat;
private String keyAlias;
private String credentialPath;
private String archivePassword;
private String keyPassword;
private CertificateInfo encryptionCert;
private CertificateInfo signingCert;
private boolean responseSigned;
private boolean assertionSigned;
private boolean signatureRequired;
private String signatureKeyAlias;
private String signaturePath;
private String signatureKeyStorePassword;
private String signatureKeyPassword;
private SAML2UsingFormat usingFormat;
private Map<String, SAML2AttributeType> attributeTypes;
private Map<String, String> configurableUserFromAttributes;
private String binding;
private String assertionConsumerServiceUrl;
private boolean wantAssertionsSigned;
private boolean authnRequestsSigned;
public String getSpEntityId() {
return spEntityId;
@ -133,39 +108,18 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.assertionEncrypted = assertionEncrypted;
}
public KeyFormat getKeyFormat() {
return keyFormat;
public CertificateInfo getEncryptionCert() {
return encryptionCert;
}
public void setKeyFormat(KeyFormat keyFormat) {
this.keyFormat = keyFormat;
public void setEncryptionCert(CertificateInfo encryptionCert) {
this.encryptionCert = encryptionCert;
}
public String getKeyAlias() {
return keyAlias;
public CertificateInfo getSigningCert() {
return signingCert;
}
public void setKeyAlias(String keyAlias) {
this.keyAlias = keyAlias;
}
public String getCredentialPath() {
return credentialPath;
}
public void setCredentialPath(String credentialPath) {
this.credentialPath = credentialPath;
}
public String getArchivePassword() {
return archivePassword;
}
public void setArchivePassword(String archivePassword) {
this.archivePassword = archivePassword;
}
public String getKeyPassword() {
return keyPassword;
}
public void setKeyPassword(String keyPassword) {
this.keyPassword = keyPassword;
public void setSigningCert(CertificateInfo signingCert) {
this.signingCert = signingCert;
}
public boolean isResponseSigned() {
@ -189,34 +143,6 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.signatureRequired = signatureRequired;
}
public String getSignatureKeyAlias() {
return signatureKeyAlias;
}
public void setSignatureKeyAlias(String signatureKeyAlias) {
this.signatureKeyAlias = signatureKeyAlias;
}
public String getSignaturePath() {
return signaturePath;
}
public void setSignaturePath(String signaturePath) {
this.signaturePath = signaturePath;
}
public String getSignatureKeyStorePassword() {
return signatureKeyStorePassword;
}
public void setSignatureKeyStorePassword(String signatureKeyStorePassword) {
this.signatureKeyStorePassword = signatureKeyStorePassword;
}
public String getSignatureKeyPassword() {
return signatureKeyPassword;
}
public void setSignatureKeyPassword(String signatureKeyPassword) {
this.signatureKeyPassword = signatureKeyPassword;
}
public SAML2UsingFormat getUsingFormat() {
return usingFormat;
}
@ -252,4 +178,17 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
}
public boolean isWantAssertionsSigned() {
return wantAssertionsSigned;
}
public void setWantAssertionsSigned(boolean wantAssertionsSigned) {
this.wantAssertionsSigned = wantAssertionsSigned;
}
public boolean isAuthnRequestsSigned() {
return authnRequestsSigned;
}
public void setAuthnRequestsSigned(boolean authnRequestsSigned) {
this.authnRequestsSigned = authnRequestsSigned;
}
}

View File

@ -1,6 +1,8 @@
package eu.eudat.logic.security.validators.configurableProvider;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.CertificateInfo;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
import eu.eudat.logic.utilities.builders.XmlBuilder;
import jakarta.xml.soap.*;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
@ -32,6 +34,7 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.opensaml.core.xml.io.*;
import org.opensaml.core.xml.schema.*;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.common.SAMLObjectContentReference;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.criterion.EntityRoleCriterion;
@ -40,7 +43,7 @@ import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver;
import org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver;
import org.opensaml.saml.saml2.core.*;
import org.opensaml.saml.saml2.encryption.Decrypter;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml.saml2.metadata.*;
import org.opensaml.saml.security.impl.MetadataCredentialResolver;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialSupport;
@ -55,10 +58,14 @@ import org.opensaml.xml.util.Base64;
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
import org.opensaml.xmlsec.encryption.EncryptedKey;
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator;
import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
import org.opensaml.xmlsec.signature.KeyInfo;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.X509Data;
import org.opensaml.xmlsec.signature.impl.SignatureBuilder;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
import org.opensaml.xmlsec.signature.support.Signer;
import org.slf4j.Logger;
@ -85,6 +92,7 @@ import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.*;
@ -264,7 +272,7 @@ public class Saml2SSOUtils {
ArtifactResolve artifactResolve = createArtifactResolveObject(samlArtReceived, saml2Provider.getSpEntityId());
if (saml2Provider.isSignatureRequired()) {
signArtifactResolveReq(artifactResolve, saml2Provider);
signArtifactResolveReq(artifactResolve, saml2Provider.getSigningCert());
}
return artifactResolve;
@ -290,14 +298,14 @@ public class Saml2SSOUtils {
}
private static void signArtifactResolveReq(ArtifactResolve artifactResolve, Saml2ConfigurableProvider saml2Provider) throws Exception {
private static void signArtifactResolveReq(ArtifactResolve artifactResolve, CertificateInfo singingCertificateInfo) throws Exception {
try {
KeyStore ks = KeyStore.getInstance("JKS");
String archivePassword = saml2Provider.getSignatureKeyStorePassword();
String archivePassword = singingCertificateInfo.getKeystorePassword();
char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray();
ks.load(new FileInputStream(saml2Provider.getSignaturePath()), pwdArray);
X509Credential cred = new KeyStoreX509CredentialAdapter(ks, saml2Provider.getSignatureKeyAlias(), saml2Provider.getSignatureKeyPassword().toCharArray());
ks.load(new FileInputStream(singingCertificateInfo.getKeystorePath()), pwdArray);
X509Credential cred = new KeyStoreX509CredentialAdapter(ks, singingCertificateInfo.getAlias(), singingCertificateInfo.getPassword().toCharArray());
Signature signature = setSignatureRaw(XMLSignature.ALGO_ID_SIGNATURE_RSA, cred);
artifactResolve.setSignature(signature);
@ -541,7 +549,7 @@ public class Saml2SSOUtils {
if (!CollectionUtils.isEmpty(encryptedAssertions)) {
encryptedAssertion = encryptedAssertions.get(0);
try {
assertion = getDecryptedAssertion(encryptedAssertion, saml2Provider);
assertion = getDecryptedAssertion(encryptedAssertion, saml2Provider.getEncryptionCert());
} catch (Exception e) {
throw new Exception("Unable to decrypt the SAML2 Assertion");
}
@ -601,15 +609,15 @@ public class Saml2SSOUtils {
}
private static Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion, Saml2ConfigurableProvider saml2Provider) throws Exception {
private static Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion, CertificateInfo encryptionCertificateInfo) throws Exception {
try {
KeyStore ks = (saml2Provider.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12");
String archivePassword = saml2Provider.getArchivePassword();
KeyStore ks = (encryptionCertificateInfo.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12");
String archivePassword = encryptionCertificateInfo.getKeystorePassword();
char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray();
ks.load(new FileInputStream(saml2Provider.getCredentialPath()), pwdArray);
X509Certificate cert = (X509Certificate)ks.getCertificate(saml2Provider.getKeyAlias());
PrivateKey pk = (PrivateKey) ks.getKey(saml2Provider.getKeyAlias(), saml2Provider.getKeyPassword().toCharArray());
ks.load(new FileInputStream(encryptionCertificateInfo.getKeystorePath()), pwdArray);
X509Certificate cert = (X509Certificate)ks.getCertificate(encryptionCertificateInfo.getAlias());
PrivateKey pk = (PrivateKey) ks.getKey(encryptionCertificateInfo.getAlias(), encryptionCertificateInfo.getPassword().toCharArray());
KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(
new BasicX509Credential(cert, pk));
EncryptedKey key = encryptedAssertion.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0);
@ -685,4 +693,105 @@ public class Saml2SSOUtils {
}
}
private static Credential getCredential(CertificateInfo certificateInfo) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
KeyStore ks = (certificateInfo.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12");
String archivePassword = certificateInfo.getKeystorePassword();
char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray();
ks.load(new FileInputStream(certificateInfo.getKeystorePath()), pwdArray);
X509Certificate cert = (X509Certificate)ks.getCertificate(certificateInfo.getAlias());
PrivateKey pk = (PrivateKey) ks.getKey(certificateInfo.getAlias(), certificateInfo.getPassword().toCharArray());
return new BasicX509Credential(cert, pk);
}
public static String getMetadata(Saml2ConfigurableProvider provider) throws Exception {
EntityDescriptor spEntityDescriptor = (EntityDescriptor) buildXMLObject(EntityDescriptor.DEFAULT_ELEMENT_NAME);
spEntityDescriptor.setEntityID(provider.getSpEntityId());
SPSSODescriptor spSSODescriptor = (SPSSODescriptor) buildXMLObject(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
spSSODescriptor.setWantAssertionsSigned(provider.isWantAssertionsSigned()); spSSODescriptor.setAuthnRequestsSigned(provider.isAuthnRequestsSigned());
X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory();
keyInfoGeneratorFactory.setEmitEntityCertificate(true);
KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance();
if (provider.isAssertionEncrypted()) {
KeyDescriptor encKeyDescriptor = (KeyDescriptor) buildXMLObject(KeyDescriptor.DEFAULT_ELEMENT_NAME);
encKeyDescriptor.setUse(UsageType.ENCRYPTION); //Set usage
// Generating key info. The element will contain the public key. The key is used to by the IDP to encrypt data
try {
encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(getCredential(provider.getEncryptionCert())));
} catch (SecurityException e) {
logger.error(e.getMessage(), e);
}
spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor);
}
if (provider.isWantAssertionsSigned()) {
KeyDescriptor signKeyDescriptor = (KeyDescriptor) buildXMLObject(KeyDescriptor.DEFAULT_ELEMENT_NAME);
signKeyDescriptor.setUse(UsageType.SIGNING); //Set usage
// Generating key info. The element will contain the public key. The key is used to by the IDP to verify signatures
try {
signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(getCredential(provider.getSigningCert())));
} catch (SecurityException e) {
logger.error(e.getMessage(), e);
}
spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor);
}
NameIDFormat nameIDFormat = (NameIDFormat) buildXMLObject(NameIDFormat.DEFAULT_ELEMENT_NAME);
nameIDFormat.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");
spSSODescriptor.getNameIDFormats().add(nameIDFormat);
AssertionConsumerService assertionConsumerService = (AssertionConsumerService) buildXMLObject(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
assertionConsumerService.setIndex(0);
switch (provider.getBinding()) {
case "Redirect":
assertionConsumerService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
break;
case "Artifact":
assertionConsumerService.setBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
break;
case "Post":
assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
break;
}
assertionConsumerService.setLocation(provider.getAssertionConsumerServiceUrl());
spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService);
spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);
spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor);
String metadataXML = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.newDocument();
Marshaller out = registry.getMarshallerFactory().getMarshaller(spEntityDescriptor);
out.marshall(spEntityDescriptor, document);
metadataXML = XmlBuilder.generateXml(document);
}
catch (MarshallingException | ParserConfigurationException e) {
logger.error(e.getMessage(), e);
}
return metadataXML;
}
}

View File

@ -30,6 +30,43 @@
"type": "saml2",
"configurableLoginId": "",
"name": ""
"name": "",
"spEntityId": "",
"idpEntityId": "",
"idpUrl": "",
"idpMetadataUrl": "",
"idpArtifactUrl": "",
"binding": "Redirect",
"logoUrl": "",
"responseSigned": true,
"assertionSigned": true,
"assertionEncrypted": true,
"encryptionCert" : {
"alias": "",
"password": "",
"keystorePath": "encryptionkeystore.jks",
"keystorePassword": "",
"keyFormat": "JKS"
},
"signingCert" : {
"alias": "",
"password": "",
"keystorePath": "signingkeystore.jks",
"keystorePassword": "",
"keyFormat": "JKS"
},
"assertionConsumerServiceUrl": "",
"wantAssertionsSigned": true,
"authnRequestsSigned": true,
"usingFormat": "friendly_name",
"configurableUserFromAttributes": {
"email": "email",
"name": "givenName"
},
"attributeTypes": {
"email": "XSString",
"givenName": "XSString"
}
}
]
}