diff --git a/dmp-backend/web/pom.xml b/dmp-backend/web/pom.xml
index 37c2258fa..906d08f1a 100644
--- a/dmp-backend/web/pom.xml
+++ b/dmp-backend/web/pom.xml
@@ -170,6 +170,16 @@
xmltooling
1.4.4
+
+ jakarta.xml.soap
+ jakarta.xml.soap-api
+ 3.0.0
+
+
+ com.sun.xml.messaging.saaj
+ saaj-impl
+ 3.0.0-M2
+
diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java
index d1e930075..3a616b1c0 100644
--- a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java
+++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/ConfigurableProvider/entities/saml2/Saml2ConfigurableProvider.java
@@ -47,12 +47,38 @@ 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 boolean responseSigned;
- private String signedResponseCredential;
private boolean assertionSigned;
private SAML2UsingFormat usingFormat;
private Map attributeTypes;
@@ -81,6 +107,13 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.idpUrl = idpUrl;
}
+ public String getIdpArtifactUrl() {
+ return idpArtifactUrl;
+ }
+ public void setIdpArtifactUrl(String idpArtifactUrl) {
+ this.idpArtifactUrl = idpArtifactUrl;
+ }
+
public String getIdpMetadataUrl() {
return idpMetadataUrl;
}
@@ -88,6 +121,48 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.idpMetadataUrl = idpMetadataUrl;
}
+ public boolean isAssertionEncrypted() {
+ return assertionEncrypted;
+ }
+ public void setAssertionEncrypted(boolean assertionEncrypted) {
+ this.assertionEncrypted = assertionEncrypted;
+ }
+
+ public KeyFormat getKeyFormat() {
+ return keyFormat;
+ }
+ public void setKeyFormat(KeyFormat keyFormat) {
+ this.keyFormat = keyFormat;
+ }
+
+ public String getKeyAlias() {
+ return keyAlias;
+ }
+ 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 boolean isResponseSigned() {
return responseSigned;
}
@@ -95,13 +170,6 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.responseSigned = responseSigned;
}
- public String getSignedResponseCredential() {
- return signedResponseCredential;
- }
- public void setSignedResponseCredential(String signedResponseCredential) {
- this.signedResponseCredential = signedResponseCredential;
- }
-
public boolean isAssertionSigned() {
return assertionSigned;
}
diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/ConfigurableProviderTokenValidator.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/ConfigurableProviderTokenValidator.java
index e9fda70e9..3b6ad2ae3 100644
--- a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/ConfigurableProviderTokenValidator.java
+++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/ConfigurableProviderTokenValidator.java
@@ -64,60 +64,60 @@ public class ConfigurableProviderTokenValidator implements TokenValidator {
}
else if (configurableProvider.getType().equals("saml2")) {
- Response saml2Response = null;
+ Assertion saml2Assertion = null;
try {
- Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider)configurableProvider;
- saml2Response = Saml2SSOUtils.processResponse(credentials.getTicket(), saml2ConfigurableProvider.getIdpEntityId(), saml2ConfigurableProvider.getSpEntityId(),
- saml2ConfigurableProvider.getIdpMetadataUrl(), saml2ConfigurableProvider.isResponseSigned(), saml2ConfigurableProvider.isAssertionSigned());
+ Saml2ConfigurableProvider saml2Provider = (Saml2ConfigurableProvider)configurableProvider;
+ if(saml2Provider.getBinding().equals("Redirect"))
+ saml2Assertion = Saml2SSOUtils.processResponse(credentials.getTicket(), saml2Provider);
+ else if(saml2Provider.getBinding().equals("Artifact"))
+ saml2Assertion = Saml2SSOUtils.processArtifactResponse(credentials.getTicket(), saml2Provider);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
- List assertions = saml2Response.getAssertions();
- if(assertions != null && !assertions.isEmpty()){
+ if(saml2Assertion == null)
+ return null;
- List attributeStatements = assertions.get(0).getAttributeStatements();
- if(attributeStatements != null && !attributeStatements.isEmpty()){
+ List attributeStatements = saml2Assertion.getAttributeStatements();
+ if(attributeStatements != null && !attributeStatements.isEmpty()){
- List attributes = attributeStatements.get(0).getAttributes();
- if(attributes != null && !attributes.isEmpty()){
+ List attributes = attributeStatements.get(0).getAttributes();
+ if(attributes != null && !attributes.isEmpty()){
- Saml2ConfigurableProvider.SAML2UsingFormat usingFormat = ((Saml2ConfigurableProvider)configurableProvider).getUsingFormat();
- Map attributeMapping = ((Saml2ConfigurableProvider)configurableProvider).getConfigurableUserFromAttributes();
- Map attributeType = ((Saml2ConfigurableProvider)configurableProvider).getAttributeTypes();
- Map saml2User = new HashMap<>();
- for(Attribute attribute: attributes){
+ Saml2ConfigurableProvider.SAML2UsingFormat usingFormat = ((Saml2ConfigurableProvider)configurableProvider).getUsingFormat();
+ Map attributeMapping = ((Saml2ConfigurableProvider)configurableProvider).getConfigurableUserFromAttributes();
+ Map attributeType = ((Saml2ConfigurableProvider)configurableProvider).getAttributeTypes();
+ Map saml2User = new HashMap<>();
+ for(Attribute attribute: attributes){
- String attributeName = Saml2SSOUtils.getAttributeName(attribute, usingFormat);
- if(attributeName != null && attributeMapping.containsValue(attributeName)){
+ String attributeName = Saml2SSOUtils.getAttributeName(attribute, usingFormat);
+ if(attributeName != null && attributeMapping.containsValue(attributeName)){
- Saml2ConfigurableProvider.SAML2AttributeType attrType = attributeType.get(attributeName);
- if(attribute.getAttributeValues() != null && !attribute.getAttributeValues().isEmpty() && attrType != null){
- Object attributeValue = Saml2SSOUtils.getAttributeType(attribute.getAttributeValues().get(0), attrType);
- if(attributeValue != null) {
- saml2User.put(attributeName, attributeValue);
- }
+ Saml2ConfigurableProvider.SAML2AttributeType attrType = attributeType.get(attributeName);
+ if(attribute.getAttributeValues() != null && !attribute.getAttributeValues().isEmpty() && attrType != null){
+ Object attributeValue = Saml2SSOUtils.getAttributeType(attribute.getAttributeValues().get(0), attrType);
+ if(attributeValue != null) {
+ saml2User.put(attributeName, attributeValue);
}
-
}
}
- try{
- String subjectNameId = assertions.get(0).getSubject().getNameID().getValue();
- String userId = configurableLoginId + ": " + subjectNameId;
- user.setId(userId);
- } catch(NullPointerException e){
- logger.error("Could not get Subject NameID value of assertion");
- return null;
- }
- user.setEmail((String)saml2User.get(attributeMapping.get("email")));
- user.setName((String)saml2User.get(attributeMapping.get("name")));
- user.setProvider(credentials.getProvider());
- user.setSecret(credentials.getTicket());
-
}
+ try{
+ String subjectNameId = saml2Assertion.getSubject().getNameID().getValue();
+ String userId = configurableLoginId + ": " + subjectNameId;
+ user.setId(userId);
+ } catch(NullPointerException e){
+ logger.error("Could not get Subject NameID value of assertion");
+ return null;
+ }
+ user.setEmail((String)saml2User.get(attributeMapping.get("email")));
+ user.setName((String)saml2User.get(attributeMapping.get("name")));
+ user.setProvider(credentials.getProvider());
+ user.setSecret(credentials.getTicket());
+
}
}
diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java
index 112c85b38..3f95f4505 100644
--- a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java
+++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java
@@ -1,47 +1,85 @@
package eu.eudat.logic.security.validators.configurableProvider;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
+import jakarta.xml.soap.*;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicResponseHandler;
+import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
-import org.opensaml.core.xml.io.Unmarshaller;
-import org.opensaml.core.xml.io.UnmarshallerFactory;
-import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.core.xml.io.*;
import org.opensaml.core.xml.schema.*;
+import org.opensaml.saml.common.SAMLObject;
+import org.opensaml.saml.common.SAMLObjectBuilder;
+import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.criterion.EntityRoleCriterion;
import org.opensaml.saml.criterion.ProtocolCriterion;
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.security.impl.MetadataCredentialResolver;
import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialSupport;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.security.x509.X509Credential;
+import org.opensaml.soap.common.SOAPObjectBuilder;
+import org.opensaml.soap.soap11.Body;
+import org.opensaml.soap.soap11.Envelope;
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.impl.StaticKeyInfoCredentialResolver;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.bootstrap.DOMImplementationRegistry;
+import org.w3c.dom.ls.DOMImplementationLS;
+import org.w3c.dom.ls.LSOutput;
+import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.SAXException;
+import javax.crypto.SecretKey;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
import java.io.*;
+import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.Inflater;
@@ -149,6 +187,26 @@ public class Saml2SSOUtils {
}
+ private static String marshall(XMLObject xmlObject) throws Exception {
+
+ try {
+ MarshallerFactory marshallerFactory = registry.getMarshallerFactory();
+ Marshaller marshaller = marshallerFactory.getMarshaller(xmlObject);
+ Element element = marshaller.marshall(xmlObject);
+
+ ByteArrayOutputStream byteArrayOutputStrm = new ByteArrayOutputStream();
+ DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
+ DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
+ LSSerializer writer = impl.createLSSerializer();
+ LSOutput output = impl.createLSOutput();
+ output.setByteStream(byteArrayOutputStrm);
+ writer.write(element, output);
+ return byteArrayOutputStrm.toString();
+ } catch (Exception e) {
+ throw new Exception("Error Serializing the SAML Response", e);
+ }
+ }
+
private static XMLObject unmarshall(String saml2SSOString) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
@@ -169,7 +227,234 @@ public class Saml2SSOUtils {
}
- public static Response processResponse(String saml2SSOResponse, String idPEntityId, String spEntityId, String metadataUrl, boolean responseSigned, boolean assertionSigned) throws Exception {
+ public static Assertion processArtifactResponse(String artifactString, Saml2ConfigurableProvider saml2Provider) throws Exception {
+
+ doBootstrap();
+ if (artifactString != null){
+ ArtifactResolve artifactResolve = generateArtifactResolveReq(artifactString, saml2Provider.getSpEntityId());
+ ArtifactResponse artifactResponse = sendArtifactResolveRequest(artifactResolve, saml2Provider.getIdpArtifactUrl());
+ Response saml2Response = (Response)artifactResponse.getMessage();
+ return processSSOResponse(saml2Response, saml2Provider);
+ }
+ else {
+ throw new Exception("Invalid SAML2 Artifact. SAML2 Artifact can not be null.");
+ }
+
+ }
+
+ private static ArtifactResolve generateArtifactResolveReq(String samlArtReceived, String spEntityId) {
+
+ ArtifactResolve artifactResolve = createArtifactResolveObject(samlArtReceived, spEntityId);
+// if (config.isEnableArtifactResolveSigning()) {
+// artifactResolve = signArtifactResolveReq(artifactResolve);
+// }
+ return artifactResolve;
+
+ }
+
+ private static ArtifactResolve createArtifactResolveObject(String samlArtReceived, String spEntityId) {
+
+ XMLObjectBuilderFactory builderFactory = registry.getBuilderFactory();
+
+ SAMLObjectBuilder artifactResolveBuilder =
+ (SAMLObjectBuilder) builderFactory.getBuilder(ArtifactResolve.DEFAULT_ELEMENT_NAME);
+ ArtifactResolve artifactResolve = artifactResolveBuilder.buildObject();
+ artifactResolve.setVersion(SAMLVersion.VERSION_20);
+ artifactResolve.setID(UUID.randomUUID().toString());
+ artifactResolve.setIssueInstant(Instant.now());
+
+ SAMLObjectBuilder artifactBuilder =
+ (SAMLObjectBuilder) builderFactory.getBuilder(Artifact.DEFAULT_ELEMENT_NAME);
+ Artifact artifact = artifactBuilder.buildObject();
+ artifact.setValue(samlArtReceived);
+
+ SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+ Issuer issuer = issuerBuilder.buildObject();
+ issuer.setValue(spEntityId);
+
+ artifactResolve.setIssuer(issuer);
+ artifactResolve.setArtifact(artifact);
+
+ return artifactResolve;
+
+ }
+
+ private static ArtifactResponse sendArtifactResolveRequest(ArtifactResolve artifactResolve, String idpArtifactUrl) throws Exception {
+
+ Envelope envelope = buildSOAPMessage(artifactResolve);
+ String envelopeElement;
+ try {
+ envelopeElement = marshall(envelope);
+ } catch (Exception e) {
+ throw new Exception("Encountered error marshalling SOAP message with artifact " + "resolve, into its DOM representation", e);
+ }
+
+ String artifactResponseString = sendSOAP(envelopeElement, idpArtifactUrl);
+
+ ArtifactResponse artifactResponse = extractArtifactResponse(artifactResponseString);
+ validateArtifactResponse(artifactResolve, artifactResponse);
+ return artifactResponse;
+
+ }
+
+ private static Envelope buildSOAPMessage(SAMLObject samlMessage) {
+
+ XMLObjectBuilderFactory builderFactory = registry.getBuilderFactory();
+
+ SOAPObjectBuilder envBuilder = (SOAPObjectBuilder) builderFactory.getBuilder(
+ Envelope.DEFAULT_ELEMENT_NAME);
+ Envelope envelope = envBuilder.buildObject();
+
+ SOAPObjectBuilder bodyBuilder = (SOAPObjectBuilder) builderFactory.getBuilder(
+ Body.DEFAULT_ELEMENT_NAME);
+ Body body = bodyBuilder.buildObject();
+ body.getUnknownXMLObjects().add(samlMessage);
+ envelope.setBody(body);
+ return envelope;
+
+ }
+
+ private static String sendSOAP(String message, String idpArtifactUrl) throws Exception {
+
+ if (message == null) {
+ throw new Exception("Cannot send null SOAP message.");
+ }
+ if (idpArtifactUrl == null) {
+ throw new Exception("Cannot send SOAP message to null URL.");
+ }
+
+ StringBuilder soapResponse = new StringBuilder();
+ try {
+ HttpPost httpPost = new HttpPost(idpArtifactUrl);
+ setRequestProperties(idpArtifactUrl, message, httpPost);
+ HttpClient httpClient = getHttpClient();
+ HttpResponse httpResponse = httpClient.execute(httpPost);
+
+ int responseCode = httpResponse.getStatusLine().getStatusCode();
+ if (responseCode != 200) {
+ throw new Exception("Problem in communicating with: " + idpArtifactUrl + ". Received response: " + responseCode);
+ } else {
+ soapResponse.append(getResponseBody(httpResponse));
+ }
+ } catch (UnknownHostException e) {
+ throw new Exception("Unknown targeted host: " + idpArtifactUrl, e);
+ } catch (IOException e) {
+ throw new Exception("Could not open connection with host: " + idpArtifactUrl, e);
+ }
+ return soapResponse.toString();
+
+ }
+
+ private static void setRequestProperties(String idpArtifactUrl, String message, HttpPost httpPost) {
+
+ httpPost.addHeader("Content-Type", "text/xml; charset=utf-8");
+ httpPost.addHeader("Accept", "text/xml; charset=utf-8");
+ String sbSOAPAction = "\"" + idpArtifactUrl + "\"";
+ httpPost.addHeader("SOAPAction", sbSOAPAction);
+ httpPost.addHeader("Pragma", "no-cache");
+ httpPost.addHeader("Cache-Control", "no-cache, no-store");
+
+ httpPost.setEntity(new StringEntity(message, ContentType.create("text/xml", StandardCharsets.UTF_8)));
+
+ }
+
+ private static HttpClient getHttpClient() throws Exception {
+
+ CloseableHttpClient httpClient = null;
+ SSLContextBuilder builder = new SSLContextBuilder();
+ try {
+ builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
+ SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
+ builder.build());
+ httpClient = HttpClients.custom().setSSLSocketFactory(
+ sslsf).build();
+ } catch (NoSuchAlgorithmException | KeyStoreException e) {
+ throw new Exception("Error while building trust store.", e);
+ } catch (KeyManagementException e) {
+ throw new Exception("Error while building socket factory.", e);
+ }
+
+ return httpClient;
+
+ }
+
+ private static String getResponseBody(HttpResponse response) throws Exception {
+
+ ResponseHandler responseHandler = new BasicResponseHandler();
+ String responseBody;
+ try {
+ responseBody = responseHandler.handleResponse(response);
+ } catch (IOException e) {
+ throw new Exception("Error when retrieving the HTTP response body.", e);
+ }
+ return responseBody;
+
+ }
+
+ private static ArtifactResponse extractArtifactResponse(String artifactResponseString) throws Exception {
+
+ ArtifactResponse artifactResponse = null;
+ InputStream stream = new ByteArrayInputStream(artifactResponseString.getBytes(StandardCharsets.UTF_8));
+ try {
+ MessageFactory messageFactory = MessageFactory.newInstance();
+ SOAPMessage soapMessage = messageFactory.createMessage(new MimeHeaders(), stream);
+ SOAPBody soapBody = soapMessage.getSOAPBody();
+ Iterator iterator = soapBody.getChildElements();
+
+ while (iterator.hasNext()) {
+ SOAPBodyElement artifactResponseElement = (SOAPBodyElement) iterator.next();
+
+ if (StringUtils.equals(SAMLConstants.SAML20P_NS, artifactResponseElement.getNamespaceURI()) &&
+ StringUtils.equals(ArtifactResponse.DEFAULT_ELEMENT_LOCAL_NAME,
+ artifactResponseElement.getLocalName())) {
+
+ DOMSource source = new DOMSource(artifactResponseElement);
+ StringWriter stringResult = new StringWriter();
+ TransformerFactory.newInstance().newTransformer().transform(
+ source, new StreamResult(stringResult));
+ artifactResponse = (ArtifactResponse) unmarshall(stringResult.toString());
+ } else {
+ throw new Exception("Received invalid artifact response with nameSpaceURI: " +
+ artifactResponseElement.getNamespaceURI() + " and localName: " +
+ artifactResponseElement.getLocalName());
+ }
+ }
+ } catch (SOAPException | IOException | TransformerException e) {
+ throw new Exception("Didn't receive valid artifact response.", e);
+ } catch (Exception e) {
+ throw new Exception("Encountered error unmarshalling response into SAML2 object", e);
+ }
+ return artifactResponse;
+
+ }
+
+ private static void validateArtifactResponse(ArtifactResolve artifactResolve, ArtifactResponse artifactResponse) throws Exception {
+
+ if (artifactResponse == null) {
+ throw new Exception("Received artifact response message was null.");
+ }
+
+ String artifactResolveId = artifactResolve.getID();
+ String artifactResponseInResponseTo = artifactResponse.getInResponseTo();
+ if (!artifactResolveId.equals(artifactResponseInResponseTo)) {
+ throw new Exception("Artifact resolve ID: " + artifactResolveId + " is not equal to " +
+ "artifact response InResponseTo : " + artifactResponseInResponseTo);
+ }
+
+ String artifactResponseStatus = artifactResponse.getStatus().getStatusCode().getValue();
+ if (!StatusCode.SUCCESS.equals(artifactResponseStatus)) {
+ throw new Exception("Unsuccessful artifact response with status: " +
+ artifactResponseStatus);
+ }
+
+ SAMLObject message = artifactResponse.getMessage();
+ if (message == null) {
+ throw new Exception("No SAML response embedded into the artifact response.");
+ }
+
+ }
+
+ public static Assertion processResponse(String saml2SSOResponse, Saml2ConfigurableProvider saml2Provider) throws Exception {
doBootstrap();
if (saml2SSOResponse != null) {
@@ -178,38 +463,34 @@ public class Saml2SSOUtils {
InflaterInputStream inflater = new InflaterInputStream(bytesIn, new Inflater(true));
String response = new BufferedReader(new InputStreamReader(inflater, StandardCharsets.UTF_8))
.lines().collect(Collectors.joining("\n"));
- return processSSOResponse(response, idPEntityId, spEntityId, metadataUrl, responseSigned, assertionSigned);
+ Response saml2Response = (Response) Saml2SSOUtils.unmarshall(response);
+ return processSSOResponse(saml2Response, saml2Provider);
} else {
throw new Exception("Invalid SAML2 Response. SAML2 Response can not be null.");
}
}
- private static Response processSSOResponse(String saml2SSOResponse, String idPEntityId, String spEntityId, String metadataUrl, boolean responseSigned, boolean assertionSigned) throws Exception {
-
- Response saml2Response = (Response) Saml2SSOUtils.unmarshall(saml2SSOResponse);
+ private static Assertion processSSOResponse(Response saml2Response, Saml2ConfigurableProvider saml2Provider) throws Exception {
Assertion assertion = null;
-// if (config.isAssertionEncrypted()) {
-// List encryptedAssertions = saml2Response.getEncryptedAssertions();
-// EncryptedAssertion encryptedAssertion = null;
-// if (!CollectionUtils.isEmpty(encryptedAssertions)) {
-// encryptedAssertion = encryptedAssertions.get(0);
-// try {
-// assertion = getDecryptedAssertion(encryptedAssertion);
-// } catch (Exception e) {
-// if (log.isDebugEnabled()) {
-// log.debug("Assertion decryption failure : ", e);
-// }
-// throw new Exception("Unable to decrypt the SAML2 Assertion");
-// }
-// }
-// } else {
- List assertions = saml2Response.getAssertions();
- if (assertions != null && !assertions.isEmpty()) {
- assertion = assertions.get(0);
- }
-// }
+ if (saml2Provider.isAssertionEncrypted()) {
+ List encryptedAssertions = saml2Response.getEncryptedAssertions();
+ EncryptedAssertion encryptedAssertion;
+ if (!CollectionUtils.isEmpty(encryptedAssertions)) {
+ encryptedAssertion = encryptedAssertions.get(0);
+ try {
+ assertion = getDecryptedAssertion(encryptedAssertion, saml2Provider);
+ } catch (Exception e) {
+ throw new Exception("Unable to decrypt the SAML2 Assertion");
+ }
+ }
+ } else {
+ List assertions = saml2Response.getAssertions();
+ if (assertions != null && !assertions.isEmpty()) {
+ assertion = assertions.get(0);
+ }
+ }
if (assertion == null) {
throw new Exception("SAML2 Assertion not found in the Response");
}
@@ -217,7 +498,7 @@ public class Saml2SSOUtils {
String idPEntityIdValue = assertion.getIssuer().getValue();
if (idPEntityIdValue == null || idPEntityIdValue.isEmpty()) {
throw new Exception("SAML2 Response does not contain an Issuer value");
- } else if (!idPEntityIdValue.equals(idPEntityId)) {
+ } else if (!idPEntityIdValue.equals(saml2Provider.getIdpEntityId())) {
throw new Exception("SAML2 Response Issuer verification failed");
}
@@ -230,9 +511,9 @@ public class Saml2SSOUtils {
throw new Exception("SAML2 Response does not contain the name of the subject");
}
- validateAudienceRestriction(assertion, spEntityId);
+ validateAudienceRestriction(assertion, saml2Provider.getSpEntityId());
- final HTTPMetadataResolver metadataResolver = new HTTPMetadataResolver(HttpClientBuilder.create().build(), metadataUrl);
+ final HTTPMetadataResolver metadataResolver = new HTTPMetadataResolver(HttpClientBuilder.create().build(), saml2Provider.getIdpMetadataUrl());
metadataResolver.setId(metadataResolver.getClass().getCanonicalName());
metadataResolver.setParserPool(parserPool);
metadataResolver.initialize();
@@ -249,16 +530,40 @@ public class Saml2SSOUtils {
criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
criteriaSet.add(new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
criteriaSet.add(new ProtocolCriterion(SAMLConstants.SAML20P_NS));
- criteriaSet.add(new EntityIdCriterion(idPEntityId));
+ criteriaSet.add(new EntityIdCriterion(saml2Provider.getIdpEntityId()));
Credential credential = metadataCredentialResolver.resolveSingle(criteriaSet);
- validateSignature(saml2Response, assertion, responseSigned, null, assertionSigned, credential);
+ validateSignature(saml2Response, assertion, saml2Provider.isResponseSigned(), saml2Provider.isAssertionSigned(), credential);
- return saml2Response;
+ return assertion;
}
+ private static Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion, Saml2ConfigurableProvider saml2Provider) throws Exception {
+
+ try {
+ KeyStore ks = (saml2Provider.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12");
+ String archivePassword = saml2Provider.getArchivePassword();
+ 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());
+ KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(
+ new BasicX509Credential(cert, pk));
+ EncryptedKey key = encryptedAssertion.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0);
+ Decrypter decrypter = new Decrypter(null, keyResolver, null);
+ SecretKey dkey = (SecretKey) decrypter.decryptKey(key, encryptedAssertion.getEncryptedData().getEncryptionMethod().getAlgorithm());
+ Credential shared = CredentialSupport.getSimpleCredential(dkey);
+ decrypter = new Decrypter(new StaticKeyInfoCredentialResolver(shared), null, null);
+ decrypter.setRootInNewDocument(true);
+ return decrypter.decrypt(encryptedAssertion);
+ } catch (Exception e) {
+ throw new Exception("Decrypted assertion error", e);
+
+ }
+ }
+
private static void validateAudienceRestriction(Assertion assertion, String requiredSPEntityId) throws Exception {
if (assertion != null) {
@@ -293,14 +598,14 @@ public class Saml2SSOUtils {
}
}
- private static void validateSignature(Response response, Assertion assertion, Boolean isResponseSigned, X509Credential signedResponseCredential, Boolean isAssertionSigned, Credential assertionCredential) throws Exception {
+ private static void validateSignature(Response response, Assertion assertion, Boolean isResponseSigned, Boolean isAssertionSigned, Credential credential) throws Exception {
if (isResponseSigned) {
if (response.getSignature() == null) {
throw new Exception("SAML2 Response signing is enabled, but signature element not found in SAML2 Response element");
} else {
try {
- SignatureValidator.validate(response.getSignature(), signedResponseCredential);
+ SignatureValidator.validate(response.getSignature(), credential);
} catch (Exception e) {
throw new Exception("Signature validation failed for SAML2 Response");
}
@@ -311,7 +616,7 @@ public class Saml2SSOUtils {
throw new Exception("SAML2 Assertion signing is enabled, but signature element not found in SAML2 Assertion element");
} else {
try {
- SignatureValidator.validate(assertion.getSignature(), assertionCredential);
+ SignatureValidator.validate(assertion.getSignature(), credential);
} catch (Exception e) {
throw new Exception("Signature validation failed for SAML2 Assertion");
}
diff --git a/dmp-backend/web/src/main/resources/configurableLoginProviders.json b/dmp-backend/web/src/main/resources/configurableLoginProviders.json
index 7cc036138..b3d8114b2 100644
--- a/dmp-backend/web/src/main/resources/configurableLoginProviders.json
+++ b/dmp-backend/web/src/main/resources/configurableLoginProviders.json
@@ -26,28 +26,28 @@
"logoUrl": null
},
{
- "enabled": true,
- "type": "saml2",
- "configurableLoginId": "keycloak-saml2",
- "name": "keycloak",
- "spEntityId": "argos",
- "idpEntityId": "http://localhost:8080/auth/realms/master",
- "idpUrl": "http://localhost:8080/auth/realms/master/protocol/saml",
- "idpMetadataUrl": "http://localhost:8080/auth/realms/master/protocol/saml/descriptor",
- "responseSigned": false,
- "signedResponseCredential": null,
- "assertionSigned": true,
- "usingFormat": "friendly_name",
- "configurableUserFromAttributes": {
- "email": "email",
- "name": "givenName"
- },
- "attributeTypes": {
- "email": "XSString",
- "givenName": "XSString"
- },
- "binding": "Redirect",
- "logoUrl": "https://upload.wikimedia.org/wikipedia/commons/2/29/Keycloak_Logo.png"
+ "enabled": false,
+ "type": "",
+ "configurableLoginId": "",
+ "name": "",
+ "spEntityId": "",
+ "idpEntityId": "",
+ "idpUrl": "",
+ "idpArtifactUrl": "",
+ "idpMetadataUrl": "",
+ "assertionEncrypted": null,
+ "keyFormat": "",
+ "keyAlias": "",
+ "credentialPath": "",
+ "archivePassword": "",
+ "keyPassword": "",
+ "responseSigned": null,
+ "assertionSigned": null,
+ "usingFormat": "",
+ "configurableUserFromAttributes": null,
+ "attributeTypes": null,
+ "binding": "",
+ "logoUrl": ""
}
]
}