artifact binding + decryption of assertion

This commit is contained in:
Bernaldo Mihasi 2022-05-11 10:38:10 +03:00
parent 8ac8f9588c
commit 6c36253f09
5 changed files with 487 additions and 104 deletions

View File

@ -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>

View File

@ -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;
} }

View File

@ -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;

View File

@ -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");
} }

View File

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