argos/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/configurableProvider/Saml2SSOUtils.java

322 lines
15 KiB
Java

package eu.eudat.logic.security.validators.configurableProvider;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
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.http.impl.client.HttpClientBuilder;
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.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.schema.*;
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.metadata.IDPSSODescriptor;
import org.opensaml.saml.security.impl.MetadataCredentialResolver;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.xml.util.Base64;
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
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.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
public class Saml2SSOUtils {
private static final Logger logger = LoggerFactory.getLogger(Saml2SSOUtils.class);
private static boolean isBootStrapped = false;
private static BasicParserPool parserPool;
private static XMLObjectProviderRegistry registry;
private Saml2SSOUtils() {
}
private static void doBootstrap() throws Exception {
if (!isBootStrapped) {
try {
boostrap();
isBootStrapped = true;
} catch (Exception e) {
throw new Exception("Error in bootstrapping the OpenSAML2 library", e);
}
}
}
private static void boostrap(){
parserPool = new BasicParserPool();
parserPool.setMaxPoolSize(100);
parserPool.setCoalescing(true);
parserPool.setIgnoreComments(true);
parserPool.setIgnoreElementContentWhitespace(true);
parserPool.setNamespaceAware(true);
parserPool.setExpandEntityReferences(false);
parserPool.setXincludeAware(false);
final Map<String, Boolean> features = new HashMap<String, Boolean>();
features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE);
features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE);
parserPool.setBuilderFeatures(features);
parserPool.setBuilderAttributes(new HashMap<String, Object>());
try {
parserPool.initialize();
} catch (ComponentInitializationException e) {
logger.error(e.getMessage(), e);
}
registry = new XMLObjectProviderRegistry();
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
registry.setParserPool(parserPool);
try {
InitializationService.initialize();
} catch (InitializationException e) {
logger.error(e.getMessage(), e);
}
}
public static String getAttributeName(Attribute attribute, Saml2ConfigurableProvider.SAML2UsingFormat usingFormat){
String friendlyName = attribute.getFriendlyName();
String name = attribute.getName();
if(usingFormat.getName().equals(Saml2ConfigurableProvider.SAML2UsingFormat.FRIENDLY_NAME.getName())){
return (friendlyName != null) ? friendlyName : name;
}
else{
return (name != null) ? name : friendlyName;
}
}
public static Object getAttributeType(XMLObject attribute, Saml2ConfigurableProvider.SAML2AttributeType attributeType){
if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSSTRING.getType())){
return ((XSString)attribute).getValue();
}
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSINTEGER.getType())){
return ((XSInteger)attribute).getValue();
}
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSDATETIME.getType())){
return ((XSDateTime)attribute).getValue();
}
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSBOOLEAN.getType())){
return ((XSBoolean)attribute).getValue();
}
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSBASE64BINARY.getType())){
return ((XSBase64Binary)attribute).getValue();
}
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSURI.getType())){
return ((XSURI)attribute).getURI();
}
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSQNAME.getType())){
return ((XSQName)attribute).getValue();
}
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSANY.getType())){
return ((XSAny)attribute).getTextContent();
}
else {
return null;
}
}
private static XMLObject unmarshall(String saml2SSOString) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//documentBuilderFactory.setExpandEntityReferences(false);
documentBuilderFactory.setNamespaceAware(true);
try {
DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
ByteArrayInputStream is = new ByteArrayInputStream(saml2SSOString.getBytes(StandardCharsets.UTF_8));
Document document = docBuilder.parse(is);
Element element = document.getDocumentElement();
UnmarshallerFactory unmarshallerFactory = registry.getUnmarshallerFactory();
Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
return unmarshaller.unmarshall(element);
} catch (ParserConfigurationException | UnmarshallingException | SAXException | IOException e) {
throw new Exception("Error in unmarshalling SAML2SSO Request from the encoded String", e);
}
}
public static Response processResponse(String saml2SSOResponse, String idPEntityId, String spEntityId, String metadataUrl, boolean responseSigned, boolean assertionSigned) throws Exception {
doBootstrap();
if (saml2SSOResponse != null) {
byte[] decodedResponse = Base64.decode(saml2SSOResponse);
ByteArrayInputStream bytesIn = new ByteArrayInputStream(decodedResponse);
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);
} 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);
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 (assertion == null) {
throw new Exception("SAML2 Assertion not found in the Response");
}
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)) {
throw new Exception("SAML2 Response Issuer verification failed");
}
String subject = null;
if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
subject = assertion.getSubject().getNameID().getValue();
}
if (subject == null) {
throw new Exception("SAML2 Response does not contain the name of the subject");
}
validateAudienceRestriction(assertion, spEntityId);
final HTTPMetadataResolver metadataResolver = new HTTPMetadataResolver(HttpClientBuilder.create().build(), metadataUrl);
metadataResolver.setId(metadataResolver.getClass().getCanonicalName());
metadataResolver.setParserPool(parserPool);
metadataResolver.initialize();
final MetadataCredentialResolver metadataCredentialResolver = new MetadataCredentialResolver();
final PredicateRoleDescriptorResolver roleResolver = new PredicateRoleDescriptorResolver(metadataResolver);
final KeyInfoCredentialResolver keyResolver = DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver();
metadataCredentialResolver.setKeyInfoCredentialResolver(keyResolver);
metadataCredentialResolver.setRoleDescriptorResolver(roleResolver);
metadataCredentialResolver.initialize();
roleResolver.initialize();
CriteriaSet criteriaSet = new CriteriaSet();
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));
Credential credential = metadataCredentialResolver.resolveSingle(criteriaSet);
validateSignature(saml2Response, assertion, responseSigned, null, assertionSigned, credential);
return saml2Response;
}
private static void validateAudienceRestriction(Assertion assertion, String requiredSPEntityId) throws Exception {
if (assertion != null) {
Conditions conditions = assertion.getConditions();
if (conditions != null) {
List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
boolean audienceFound = false;
for (AudienceRestriction audienceRestriction : audienceRestrictions) {
if (audienceRestriction.getAudiences() != null && !audienceRestriction.getAudiences().isEmpty()
) {
for (Audience audience : audienceRestriction.getAudiences()) {
if (requiredSPEntityId.equals(audience.getURI())) {
audienceFound = true;
break;
}
}
}
if (audienceFound) {
break;
}
}
if (!audienceFound) {
throw new Exception("SAML2 Assertion Audience Restriction validation failed");
}
} else {
throw new Exception("SAML2 Response doesn't contain AudienceRestrictions");
}
} else {
throw new Exception("SAML2 Response doesn't contain Conditions");
}
}
}
private static void validateSignature(Response response, Assertion assertion, Boolean isResponseSigned, X509Credential signedResponseCredential, Boolean isAssertionSigned, Credential assertionCredential) 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);
} catch (Exception e) {
throw new Exception("Signature validation failed for SAML2 Response");
}
}
}
if (isAssertionSigned) {
if (assertion.getSignature() == null) {
throw new Exception("SAML2 Assertion signing is enabled, but signature element not found in SAML2 Assertion element");
} else {
try {
SignatureValidator.validate(assertion.getSignature(), assertionCredential);
} catch (Exception e) {
throw new Exception("Signature validation failed for SAML2 Assertion");
}
}
}
}
}