artifact binding + decryption of assertion
This commit is contained in:
parent
8ac8f9588c
commit
6c36253f09
|
@ -170,6 +170,16 @@
|
|||
<artifactId>xmltooling</artifactId>
|
||||
<version>1.4.4</version>
|
||||
</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>
|
||||
|
||||
<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 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<String, SAML2AttributeType> 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;
|
||||
}
|
||||
|
|
|
@ -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<Assertion> assertions = saml2Response.getAssertions();
|
||||
if(assertions != null && !assertions.isEmpty()){
|
||||
if(saml2Assertion == null)
|
||||
return null;
|
||||
|
||||
List<AttributeStatement> attributeStatements = assertions.get(0).getAttributeStatements();
|
||||
if(attributeStatements != null && !attributeStatements.isEmpty()){
|
||||
List<AttributeStatement> attributeStatements = saml2Assertion.getAttributeStatements();
|
||||
if(attributeStatements != null && !attributeStatements.isEmpty()){
|
||||
|
||||
List<Attribute> attributes = attributeStatements.get(0).getAttributes();
|
||||
if(attributes != null && !attributes.isEmpty()){
|
||||
List<Attribute> attributes = attributeStatements.get(0).getAttributes();
|
||||
if(attributes != null && !attributes.isEmpty()){
|
||||
|
||||
Saml2ConfigurableProvider.SAML2UsingFormat usingFormat = ((Saml2ConfigurableProvider)configurableProvider).getUsingFormat();
|
||||
Map<String, String> attributeMapping = ((Saml2ConfigurableProvider)configurableProvider).getConfigurableUserFromAttributes();
|
||||
Map<String, Saml2ConfigurableProvider.SAML2AttributeType> attributeType = ((Saml2ConfigurableProvider)configurableProvider).getAttributeTypes();
|
||||
Map<String, Object> saml2User = new HashMap<>();
|
||||
for(Attribute attribute: attributes){
|
||||
Saml2ConfigurableProvider.SAML2UsingFormat usingFormat = ((Saml2ConfigurableProvider)configurableProvider).getUsingFormat();
|
||||
Map<String, String> attributeMapping = ((Saml2ConfigurableProvider)configurableProvider).getConfigurableUserFromAttributes();
|
||||
Map<String, Saml2ConfigurableProvider.SAML2AttributeType> attributeType = ((Saml2ConfigurableProvider)configurableProvider).getAttributeTypes();
|
||||
Map<String, Object> 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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<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();
|
||||
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<EncryptedAssertion> 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<Assertion> assertions = saml2Response.getAssertions();
|
||||
if (assertions != null && !assertions.isEmpty()) {
|
||||
assertion = assertions.get(0);
|
||||
}
|
||||
// }
|
||||
if (saml2Provider.isAssertionEncrypted()) {
|
||||
List<EncryptedAssertion> 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<Assertion> 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");
|
||||
}
|
||||
|
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue