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": "" } ] }