package org.apereo.cas.support.saml.web.idp.profile.builders.enc;
import com.google.common.base.Throwables;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.saml.idp.SamlIdPProperties;
import org.apereo.cas.support.saml.SamlException;
import org.apereo.cas.support.saml.SamlIdPUtils;
import org.apereo.cas.support.saml.services.idp.metadata.SamlRegisteredServiceServiceProviderMetadataFacade;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
import org.opensaml.saml.common.messaging.context.SAMLProtocolContext;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.criterion.EntityRoleCriterion;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.metadata.resolver.RoleDescriptorResolver;
import org.opensaml.saml.metadata.resolver.impl.BasicRoleDescriptorResolver;
import org.opensaml.saml.saml2.binding.security.impl.SAML2HTTPRedirectDeflateSignatureSecurityHandler;
import org.opensaml.saml.saml2.core.RequestAbstractType;
import org.opensaml.saml.saml2.metadata.RoleDescriptor;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml.security.impl.MetadataCredentialResolver;
import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialResolver;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.credential.impl.StaticCredentialResolver;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.xmlsec.SignatureValidationConfiguration;
import org.opensaml.xmlsec.SignatureValidationParameters;
import org.opensaml.xmlsec.config.DefaultSecurityConfigurationBootstrap;
import org.opensaml.xmlsec.context.SecurityParametersContext;
import org.opensaml.xmlsec.criterion.SignatureValidationConfigurationCriterion;
import org.opensaml.xmlsec.impl.BasicSignatureValidationConfiguration;
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* This is {@link SamlObjectSignatureValidator}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
public class SamlObjectSignatureValidator {
private static final Logger LOGGER = LoggerFactory.getLogger(SamlObjectSignatureValidator.class);
/**
* The Override signature reference digest methods.
*/
protected List overrideSignatureReferenceDigestMethods;
/**
* The Override signature algorithms.
*/
protected List overrideSignatureAlgorithms;
/**
* The Override black listed signature algorithms.
*/
protected List overrideBlackListedSignatureAlgorithms;
/**
* The Override white listed signature signing algorithms.
*/
protected List overrideWhiteListedAlgorithms;
@Autowired
private CasConfigurationProperties casProperties;
public SamlObjectSignatureValidator(final List overrideSignatureReferenceDigestMethods, final List overrideSignatureAlgorithms,
final List overrideBlackListedSignatureAlgorithms, final List overrideWhiteListedAlgorithms) {
this.overrideSignatureReferenceDigestMethods = overrideSignatureReferenceDigestMethods;
this.overrideSignatureAlgorithms = overrideSignatureAlgorithms;
this.overrideBlackListedSignatureAlgorithms = overrideBlackListedSignatureAlgorithms;
this.overrideWhiteListedAlgorithms = overrideWhiteListedAlgorithms;
}
/**
* Verify saml profile request if needed.
*
* @param profileRequest the profile request
* @param resolver the resolver
* @param request the request
* @param context the context
* @throws Exception the exception
*/
public void verifySamlProfileRequestIfNeeded(final RequestAbstractType profileRequest,
final MetadataResolver resolver,
final HttpServletRequest request,
final MessageContext context) throws Exception {
final RoleDescriptorResolver roleDescriptorResolver = getRoleDescriptorResolver(resolver, context, profileRequest);
LOGGER.debug("Validating signature for [{}]", profileRequest.getClass().getName());
final Signature signature = profileRequest.getSignature();
if (signature != null) {
validateSignatureOnProfileRequest(profileRequest, signature, roleDescriptorResolver);
} else {
validateSignatureOnAuthenticationRequest(profileRequest, request, context, roleDescriptorResolver);
}
}
/**
* Validate authn request signature.
*
* @param profileRequest the authn request
* @param adaptor the adaptor
* @param request the request
* @param context the context
* @throws Exception the exception
*/
public void verifySamlProfileRequestIfNeeded(final RequestAbstractType profileRequest,
final SamlRegisteredServiceServiceProviderMetadataFacade adaptor,
final HttpServletRequest request,
final MessageContext context) throws Exception {
verifySamlProfileRequestIfNeeded(profileRequest, adaptor.getMetadataResolver(), request, context);
}
/**
* Gets role descriptor resolver.
*
* @param resolver the resolver
* @param context the context
* @param profileRequest the profile request
* @return the role descriptor resolver
* @throws Exception the exception
*/
protected RoleDescriptorResolver getRoleDescriptorResolver(final MetadataResolver resolver,
final MessageContext context,
final RequestAbstractType profileRequest) throws Exception {
final BasicRoleDescriptorResolver roleDescriptorResolver = new BasicRoleDescriptorResolver(resolver);
roleDescriptorResolver.initialize();
return roleDescriptorResolver;
}
private void validateSignatureOnAuthenticationRequest(final RequestAbstractType profileRequest, final HttpServletRequest request,
final MessageContext context,
final RoleDescriptorResolver roleDescriptorResolver) throws Exception {
final SAML2HTTPRedirectDeflateSignatureSecurityHandler handler = new SAML2HTTPRedirectDeflateSignatureSecurityHandler();
final SAMLPeerEntityContext peer = context.getSubcontext(SAMLPeerEntityContext.class, true);
peer.setEntityId(SamlIdPUtils.getIssuerFromSamlRequest(profileRequest));
LOGGER.debug("Validating request signature for [{}] via [{}]...", peer.getEntityId(), handler.getClass().getSimpleName());
LOGGER.debug("Resolving role descriptor for [{}]", peer.getEntityId());
final RoleDescriptor roleDescriptor = roleDescriptorResolver.resolveSingle(
new CriteriaSet(new EntityIdCriterion(peer.getEntityId()),
new EntityRoleCriterion(SPSSODescriptor.DEFAULT_ELEMENT_NAME)));
peer.setRole(roleDescriptor.getElementQName());
final SAMLProtocolContext protocol = context.getSubcontext(SAMLProtocolContext.class, true);
protocol.setProtocol(SAMLConstants.SAML20P_NS);
LOGGER.debug("Building security parameters context for signature validation of [{}]", peer.getEntityId());
final SecurityParametersContext secCtx = context.getSubcontext(SecurityParametersContext.class, true);
final SignatureValidationParameters validationParams = new SignatureValidationParameters();
if (overrideBlackListedSignatureAlgorithms != null && !overrideBlackListedSignatureAlgorithms.isEmpty()) {
validationParams.setBlacklistedAlgorithms(this.overrideBlackListedSignatureAlgorithms);
LOGGER.debug("Validation override blacklisted algorithms are [{}]", this.overrideWhiteListedAlgorithms);
}
if (overrideWhiteListedAlgorithms != null && !overrideWhiteListedAlgorithms.isEmpty()) {
validationParams.setWhitelistedAlgorithms(this.overrideWhiteListedAlgorithms);
LOGGER.debug("Validation override whitelisted algorithms are [{}]", this.overrideWhiteListedAlgorithms);
}
LOGGER.debug("Resolving signing credentials for [{}]", peer.getEntityId());
final Credential credential = getSigningCredential(roleDescriptorResolver, profileRequest);
if (credential == null) {
throw new SamlException("Signing credential for validation could not be resolved");
}
final CredentialResolver resolver = new StaticCredentialResolver(credential);
final KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(credential);
final SignatureTrustEngine trustEngine = new ExplicitKeySignatureTrustEngine(resolver, keyResolver);
validationParams.setSignatureTrustEngine(trustEngine);
secCtx.setSignatureValidationParameters(validationParams);
handler.setHttpServletRequest(request);
LOGGER.debug("Initializing [{}] to execute signature validation for [{}]", handler.getClass().getSimpleName(), peer.getEntityId());
handler.initialize();
LOGGER.debug("Invoking [{}] to handle signature validation for [{}]", handler.getClass().getSimpleName(), peer.getEntityId());
handler.invoke(context);
LOGGER.debug("Successfully validated request signature for [{}].", profileRequest.getIssuer());
}
private void validateSignatureOnProfileRequest(final RequestAbstractType profileRequest,
final Signature signature,
final RoleDescriptorResolver roleDescriptorResolver) throws Exception {
final SAMLSignatureProfileValidator validator = new SAMLSignatureProfileValidator();
LOGGER.debug("Validating profile signature for [{}] via [{}]...", profileRequest.getIssuer(),
validator.getClass().getSimpleName());
validator.validate(signature);
LOGGER.debug("Successfully validated profile signature for [{}].", profileRequest.getIssuer());
final Credential credential = getSigningCredential(roleDescriptorResolver, profileRequest);
if (credential == null) {
throw new SamlException("Signing credential for validation could not be resolved");
}
LOGGER.debug("Validating signature using credentials for [{}]", credential.getEntityId());
SignatureValidator.validate(signature, credential);
LOGGER.info("Successfully validated the request signature.");
}
private Credential getSigningCredential(final RoleDescriptorResolver resolver, final RequestAbstractType profileRequest) {
try {
final MetadataCredentialResolver kekCredentialResolver = new MetadataCredentialResolver();
final SignatureValidationConfiguration config = getSignatureValidationConfiguration();
kekCredentialResolver.setRoleDescriptorResolver(resolver);
kekCredentialResolver.setKeyInfoCredentialResolver(
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
kekCredentialResolver.initialize();
final CriteriaSet criteriaSet = new CriteriaSet();
criteriaSet.add(new SignatureValidationConfigurationCriterion(config));
criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
buildEntityCriteriaForSigningCredential(profileRequest, criteriaSet);
return kekCredentialResolver.resolveSingle(criteriaSet);
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Build entity criteria for signing credential.
*
* @param profileRequest the profile request
* @param criteriaSet the criteria set
*/
protected void buildEntityCriteriaForSigningCredential(final RequestAbstractType profileRequest, final CriteriaSet criteriaSet) {
criteriaSet.add(new EntityIdCriterion(SamlIdPUtils.getIssuerFromSamlRequest(profileRequest)));
criteriaSet.add(new EntityRoleCriterion(SPSSODescriptor.DEFAULT_ELEMENT_NAME));
}
/**
* Gets signature validation configuration.
*
* @return the signature validation configuration
* @throws Exception the exception
*/
protected SignatureValidationConfiguration getSignatureValidationConfiguration() throws Exception {
final BasicSignatureValidationConfiguration config =
DefaultSecurityConfigurationBootstrap.buildDefaultSignatureValidationConfiguration();
final SamlIdPProperties samlIdp = casProperties.getAuthn().getSamlIdp();
if (this.overrideBlackListedSignatureAlgorithms != null
&& !samlIdp.getAlgs().getOverrideBlackListedSignatureSigningAlgorithms().isEmpty()) {
config.setBlacklistedAlgorithms(this.overrideBlackListedSignatureAlgorithms);
config.setWhitelistMerge(true);
}
if (this.overrideWhiteListedAlgorithms != null && !this.overrideWhiteListedAlgorithms.isEmpty()) {
config.setWhitelistedAlgorithms(this.overrideWhiteListedAlgorithms);
config.setBlacklistMerge(true);
}
LOGGER.debug("Signature validation blacklisted algorithms: [{}]", config.getBlacklistedAlgorithms());
LOGGER.debug("Signature validation whitelisted algorithms: [{}]", config.getWhitelistedAlgorithms());
return config;
}
}