artifact binding + decryption of assertion
This commit is contained in:
parent
8ac8f9588c
commit
6c36253f09
|
@ -170,6 +170,16 @@
|
||||||
<artifactId>xmltooling</artifactId>
|
<artifactId>xmltooling</artifactId>
|
||||||
<version>1.4.4</version>
|
<version>1.4.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.xml.soap</groupId>
|
||||||
|
<artifactId>jakarta.xml.soap-api</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.xml.messaging.saaj</groupId>
|
||||||
|
<artifactId>saaj-impl</artifactId>
|
||||||
|
<version>3.0.0-M2</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -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 spEntityId;
|
||||||
private String idpEntityId;
|
private String idpEntityId;
|
||||||
private String idpUrl;
|
private String idpUrl;
|
||||||
|
private String idpArtifactUrl;
|
||||||
private String idpMetadataUrl;
|
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 boolean responseSigned;
|
||||||
private String signedResponseCredential;
|
|
||||||
private boolean assertionSigned;
|
private boolean assertionSigned;
|
||||||
private SAML2UsingFormat usingFormat;
|
private SAML2UsingFormat usingFormat;
|
||||||
private Map<String, SAML2AttributeType> attributeTypes;
|
private Map<String, SAML2AttributeType> attributeTypes;
|
||||||
|
@ -81,6 +107,13 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
|
||||||
this.idpUrl = idpUrl;
|
this.idpUrl = idpUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getIdpArtifactUrl() {
|
||||||
|
return idpArtifactUrl;
|
||||||
|
}
|
||||||
|
public void setIdpArtifactUrl(String idpArtifactUrl) {
|
||||||
|
this.idpArtifactUrl = idpArtifactUrl;
|
||||||
|
}
|
||||||
|
|
||||||
public String getIdpMetadataUrl() {
|
public String getIdpMetadataUrl() {
|
||||||
return idpMetadataUrl;
|
return idpMetadataUrl;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +121,48 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
|
||||||
this.idpMetadataUrl = idpMetadataUrl;
|
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() {
|
public boolean isResponseSigned() {
|
||||||
return responseSigned;
|
return responseSigned;
|
||||||
}
|
}
|
||||||
|
@ -95,13 +170,6 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
|
||||||
this.responseSigned = responseSigned;
|
this.responseSigned = responseSigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSignedResponseCredential() {
|
|
||||||
return signedResponseCredential;
|
|
||||||
}
|
|
||||||
public void setSignedResponseCredential(String signedResponseCredential) {
|
|
||||||
this.signedResponseCredential = signedResponseCredential;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAssertionSigned() {
|
public boolean isAssertionSigned() {
|
||||||
return assertionSigned;
|
return assertionSigned;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,19 +64,21 @@ public class ConfigurableProviderTokenValidator implements TokenValidator {
|
||||||
}
|
}
|
||||||
else if (configurableProvider.getType().equals("saml2")) {
|
else if (configurableProvider.getType().equals("saml2")) {
|
||||||
|
|
||||||
Response saml2Response = null;
|
Assertion saml2Assertion = null;
|
||||||
try {
|
try {
|
||||||
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider)configurableProvider;
|
Saml2ConfigurableProvider saml2Provider = (Saml2ConfigurableProvider)configurableProvider;
|
||||||
saml2Response = Saml2SSOUtils.processResponse(credentials.getTicket(), saml2ConfigurableProvider.getIdpEntityId(), saml2ConfigurableProvider.getSpEntityId(),
|
if(saml2Provider.getBinding().equals("Redirect"))
|
||||||
saml2ConfigurableProvider.getIdpMetadataUrl(), saml2ConfigurableProvider.isResponseSigned(), saml2ConfigurableProvider.isAssertionSigned());
|
saml2Assertion = Saml2SSOUtils.processResponse(credentials.getTicket(), saml2Provider);
|
||||||
|
else if(saml2Provider.getBinding().equals("Artifact"))
|
||||||
|
saml2Assertion = Saml2SSOUtils.processArtifactResponse(credentials.getTicket(), saml2Provider);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Assertion> assertions = saml2Response.getAssertions();
|
if(saml2Assertion == null)
|
||||||
if(assertions != null && !assertions.isEmpty()){
|
return null;
|
||||||
|
|
||||||
List<AttributeStatement> attributeStatements = assertions.get(0).getAttributeStatements();
|
List<AttributeStatement> attributeStatements = saml2Assertion.getAttributeStatements();
|
||||||
if(attributeStatements != null && !attributeStatements.isEmpty()){
|
if(attributeStatements != null && !attributeStatements.isEmpty()){
|
||||||
|
|
||||||
List<Attribute> attributes = attributeStatements.get(0).getAttributes();
|
List<Attribute> attributes = attributeStatements.get(0).getAttributes();
|
||||||
|
@ -104,7 +106,7 @@ public class ConfigurableProviderTokenValidator implements TokenValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
String subjectNameId = assertions.get(0).getSubject().getNameID().getValue();
|
String subjectNameId = saml2Assertion.getSubject().getNameID().getValue();
|
||||||
String userId = configurableLoginId + ": " + subjectNameId;
|
String userId = configurableLoginId + ": " + subjectNameId;
|
||||||
user.setId(userId);
|
user.setId(userId);
|
||||||
} catch(NullPointerException e){
|
} catch(NullPointerException e){
|
||||||
|
@ -119,8 +121,6 @@ public class ConfigurableProviderTokenValidator implements TokenValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,85 @@
|
||||||
package eu.eudat.logic.security.validators.configurableProvider;
|
package eu.eudat.logic.security.validators.configurableProvider;
|
||||||
|
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
|
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.component.ComponentInitializationException;
|
||||||
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
|
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
|
||||||
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
|
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.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.ConfigurationService;
|
||||||
import org.opensaml.core.config.InitializationException;
|
import org.opensaml.core.config.InitializationException;
|
||||||
import org.opensaml.core.config.InitializationService;
|
import org.opensaml.core.config.InitializationService;
|
||||||
import org.opensaml.core.criterion.EntityIdCriterion;
|
import org.opensaml.core.criterion.EntityIdCriterion;
|
||||||
import org.opensaml.core.xml.XMLObject;
|
import org.opensaml.core.xml.XMLObject;
|
||||||
|
import org.opensaml.core.xml.XMLObjectBuilderFactory;
|
||||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||||
import org.opensaml.core.xml.io.Unmarshaller;
|
import org.opensaml.core.xml.io.*;
|
||||||
import org.opensaml.core.xml.io.UnmarshallerFactory;
|
|
||||||
import org.opensaml.core.xml.io.UnmarshallingException;
|
|
||||||
import org.opensaml.core.xml.schema.*;
|
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.common.xml.SAMLConstants;
|
||||||
import org.opensaml.saml.criterion.EntityRoleCriterion;
|
import org.opensaml.saml.criterion.EntityRoleCriterion;
|
||||||
import org.opensaml.saml.criterion.ProtocolCriterion;
|
import org.opensaml.saml.criterion.ProtocolCriterion;
|
||||||
import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver;
|
import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver;
|
||||||
import org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver;
|
import org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver;
|
||||||
import org.opensaml.saml.saml2.core.*;
|
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.IDPSSODescriptor;
|
||||||
import org.opensaml.saml.security.impl.MetadataCredentialResolver;
|
import org.opensaml.saml.security.impl.MetadataCredentialResolver;
|
||||||
import org.opensaml.security.credential.Credential;
|
import org.opensaml.security.credential.Credential;
|
||||||
|
import org.opensaml.security.credential.CredentialSupport;
|
||||||
import org.opensaml.security.credential.UsageType;
|
import org.opensaml.security.credential.UsageType;
|
||||||
import org.opensaml.security.criteria.UsageCriterion;
|
import org.opensaml.security.criteria.UsageCriterion;
|
||||||
|
import org.opensaml.security.x509.BasicX509Credential;
|
||||||
import org.opensaml.security.x509.X509Credential;
|
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.xml.util.Base64;
|
||||||
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
|
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
|
||||||
|
import org.opensaml.xmlsec.encryption.EncryptedKey;
|
||||||
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
|
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
|
||||||
|
import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
|
||||||
import org.opensaml.xmlsec.signature.support.SignatureValidator;
|
import org.opensaml.xmlsec.signature.support.SignatureValidator;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
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 org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
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.io.*;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.Inflater;
|
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 {
|
private static XMLObject unmarshall(String saml2SSOString) throws Exception {
|
||||||
|
|
||||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
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<ArtifactResolve> artifactResolveBuilder =
|
||||||
|
(SAMLObjectBuilder<ArtifactResolve>) 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<Artifact> artifactBuilder =
|
||||||
|
(SAMLObjectBuilder<Artifact>) builderFactory.getBuilder(Artifact.DEFAULT_ELEMENT_NAME);
|
||||||
|
Artifact artifact = artifactBuilder.buildObject();
|
||||||
|
artifact.setValue(samlArtReceived);
|
||||||
|
|
||||||
|
SAMLObjectBuilder<Issuer> issuerBuilder = (SAMLObjectBuilder<Issuer>) 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<Envelope> envBuilder = (SOAPObjectBuilder<Envelope>) builderFactory.getBuilder(
|
||||||
|
Envelope.DEFAULT_ELEMENT_NAME);
|
||||||
|
Envelope envelope = envBuilder.buildObject();
|
||||||
|
|
||||||
|
SOAPObjectBuilder<Body> bodyBuilder = (SOAPObjectBuilder<Body>) 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<String> 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<Node> 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();
|
doBootstrap();
|
||||||
if (saml2SSOResponse != null) {
|
if (saml2SSOResponse != null) {
|
||||||
|
@ -178,38 +463,34 @@ public class Saml2SSOUtils {
|
||||||
InflaterInputStream inflater = new InflaterInputStream(bytesIn, new Inflater(true));
|
InflaterInputStream inflater = new InflaterInputStream(bytesIn, new Inflater(true));
|
||||||
String response = new BufferedReader(new InputStreamReader(inflater, StandardCharsets.UTF_8))
|
String response = new BufferedReader(new InputStreamReader(inflater, StandardCharsets.UTF_8))
|
||||||
.lines().collect(Collectors.joining("\n"));
|
.lines().collect(Collectors.joining("\n"));
|
||||||
return processSSOResponse(response, idPEntityId, spEntityId, metadataUrl, responseSigned, assertionSigned);
|
Response saml2Response = (Response) Saml2SSOUtils.unmarshall(response);
|
||||||
|
return processSSOResponse(saml2Response, saml2Provider);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("Invalid SAML2 Response. SAML2 Response can not be null.");
|
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 {
|
private static Assertion processSSOResponse(Response saml2Response, Saml2ConfigurableProvider saml2Provider) throws Exception {
|
||||||
|
|
||||||
Response saml2Response = (Response) Saml2SSOUtils.unmarshall(saml2SSOResponse);
|
|
||||||
|
|
||||||
Assertion assertion = null;
|
Assertion assertion = null;
|
||||||
// if (config.isAssertionEncrypted()) {
|
if (saml2Provider.isAssertionEncrypted()) {
|
||||||
// List<EncryptedAssertion> encryptedAssertions = saml2Response.getEncryptedAssertions();
|
List<EncryptedAssertion> encryptedAssertions = saml2Response.getEncryptedAssertions();
|
||||||
// EncryptedAssertion encryptedAssertion = null;
|
EncryptedAssertion encryptedAssertion;
|
||||||
// if (!CollectionUtils.isEmpty(encryptedAssertions)) {
|
if (!CollectionUtils.isEmpty(encryptedAssertions)) {
|
||||||
// encryptedAssertion = encryptedAssertions.get(0);
|
encryptedAssertion = encryptedAssertions.get(0);
|
||||||
// try {
|
try {
|
||||||
// assertion = getDecryptedAssertion(encryptedAssertion);
|
assertion = getDecryptedAssertion(encryptedAssertion, saml2Provider);
|
||||||
// } catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// if (log.isDebugEnabled()) {
|
throw new Exception("Unable to decrypt the SAML2 Assertion");
|
||||||
// log.debug("Assertion decryption failure : ", e);
|
}
|
||||||
// }
|
}
|
||||||
// throw new Exception("Unable to decrypt the SAML2 Assertion");
|
} else {
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
List<Assertion> assertions = saml2Response.getAssertions();
|
List<Assertion> assertions = saml2Response.getAssertions();
|
||||||
if (assertions != null && !assertions.isEmpty()) {
|
if (assertions != null && !assertions.isEmpty()) {
|
||||||
assertion = assertions.get(0);
|
assertion = assertions.get(0);
|
||||||
}
|
}
|
||||||
// }
|
}
|
||||||
if (assertion == null) {
|
if (assertion == null) {
|
||||||
throw new Exception("SAML2 Assertion not found in the Response");
|
throw new Exception("SAML2 Assertion not found in the Response");
|
||||||
}
|
}
|
||||||
|
@ -217,7 +498,7 @@ public class Saml2SSOUtils {
|
||||||
String idPEntityIdValue = assertion.getIssuer().getValue();
|
String idPEntityIdValue = assertion.getIssuer().getValue();
|
||||||
if (idPEntityIdValue == null || idPEntityIdValue.isEmpty()) {
|
if (idPEntityIdValue == null || idPEntityIdValue.isEmpty()) {
|
||||||
throw new Exception("SAML2 Response does not contain an Issuer value");
|
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");
|
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");
|
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.setId(metadataResolver.getClass().getCanonicalName());
|
||||||
metadataResolver.setParserPool(parserPool);
|
metadataResolver.setParserPool(parserPool);
|
||||||
metadataResolver.initialize();
|
metadataResolver.initialize();
|
||||||
|
@ -249,16 +530,40 @@ public class Saml2SSOUtils {
|
||||||
criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
|
criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
|
||||||
criteriaSet.add(new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
|
criteriaSet.add(new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
|
||||||
criteriaSet.add(new ProtocolCriterion(SAMLConstants.SAML20P_NS));
|
criteriaSet.add(new ProtocolCriterion(SAMLConstants.SAML20P_NS));
|
||||||
criteriaSet.add(new EntityIdCriterion(idPEntityId));
|
criteriaSet.add(new EntityIdCriterion(saml2Provider.getIdpEntityId()));
|
||||||
|
|
||||||
Credential credential = metadataCredentialResolver.resolveSingle(criteriaSet);
|
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 {
|
private static void validateAudienceRestriction(Assertion assertion, String requiredSPEntityId) throws Exception {
|
||||||
|
|
||||||
if (assertion != null) {
|
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 (isResponseSigned) {
|
||||||
if (response.getSignature() == null) {
|
if (response.getSignature() == null) {
|
||||||
throw new Exception("SAML2 Response signing is enabled, but signature element not found in SAML2 Response element");
|
throw new Exception("SAML2 Response signing is enabled, but signature element not found in SAML2 Response element");
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
SignatureValidator.validate(response.getSignature(), signedResponseCredential);
|
SignatureValidator.validate(response.getSignature(), credential);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new Exception("Signature validation failed for SAML2 Response");
|
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");
|
throw new Exception("SAML2 Assertion signing is enabled, but signature element not found in SAML2 Assertion element");
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
SignatureValidator.validate(assertion.getSignature(), assertionCredential);
|
SignatureValidator.validate(assertion.getSignature(), credential);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new Exception("Signature validation failed for SAML2 Assertion");
|
throw new Exception("Signature validation failed for SAML2 Assertion");
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,28 +26,28 @@
|
||||||
"logoUrl": null
|
"logoUrl": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"enabled": true,
|
"enabled": false,
|
||||||
"type": "saml2",
|
"type": "",
|
||||||
"configurableLoginId": "keycloak-saml2",
|
"configurableLoginId": "",
|
||||||
"name": "keycloak",
|
"name": "",
|
||||||
"spEntityId": "argos",
|
"spEntityId": "",
|
||||||
"idpEntityId": "http://localhost:8080/auth/realms/master",
|
"idpEntityId": "",
|
||||||
"idpUrl": "http://localhost:8080/auth/realms/master/protocol/saml",
|
"idpUrl": "",
|
||||||
"idpMetadataUrl": "http://localhost:8080/auth/realms/master/protocol/saml/descriptor",
|
"idpArtifactUrl": "",
|
||||||
"responseSigned": false,
|
"idpMetadataUrl": "",
|
||||||
"signedResponseCredential": null,
|
"assertionEncrypted": null,
|
||||||
"assertionSigned": true,
|
"keyFormat": "",
|
||||||
"usingFormat": "friendly_name",
|
"keyAlias": "",
|
||||||
"configurableUserFromAttributes": {
|
"credentialPath": "",
|
||||||
"email": "email",
|
"archivePassword": "",
|
||||||
"name": "givenName"
|
"keyPassword": "",
|
||||||
},
|
"responseSigned": null,
|
||||||
"attributeTypes": {
|
"assertionSigned": null,
|
||||||
"email": "XSString",
|
"usingFormat": "",
|
||||||
"givenName": "XSString"
|
"configurableUserFromAttributes": null,
|
||||||
},
|
"attributeTypes": null,
|
||||||
"binding": "Redirect",
|
"binding": "",
|
||||||
"logoUrl": "https://upload.wikimedia.org/wikipedia/commons/2/29/Keycloak_Logo.png"
|
"logoUrl": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue