package org.apereo.cas.support.saml;
import net.shibboleth.idp.profile.spring.factory.BasicResourceCredentialFactoryBean;
import net.shibboleth.idp.profile.spring.factory.BasicX509CredentialFactoryBean;
import org.apereo.cas.util.ResourceUtils;
import org.cryptacular.util.CertUtil;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.saml.metadata.resolver.filter.impl.SignatureValidationFilter;
import org.opensaml.security.credential.BasicCredential;
import org.opensaml.security.credential.impl.StaticCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.DEREncodedKeyValueProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider;
import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.w3c.dom.Element;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
/**
* This is {@link SamlUtils}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public final class SamlUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(SamlUtils.class);
private SamlUtils() {
}
/**
* Read certificate x 509 certificate.
*
* @param resource the resource
* @return the x 509 certificate
*/
public static X509Certificate readCertificate(final Resource resource) {
try (InputStream in = resource.getInputStream()) {
return CertUtil.readCertificate(in);
} catch (final Exception e) {
throw new RuntimeException("Error reading certificate " + resource, e);
}
}
/**
* Transform saml object to String.
*
* @param configBean the config bean
* @param samlObject the saml object
* @return the string
* @throws SamlException the saml exception
*/
public static StringWriter transformSamlObject(final OpenSamlConfigBean configBean, final XMLObject samlObject) throws SamlException {
final StringWriter writer = new StringWriter();
try {
final Marshaller marshaller = configBean.getMarshallerFactory().getMarshaller(samlObject.getElementQName());
if (marshaller != null) {
final Element element = marshaller.marshall(samlObject);
final DOMSource domSource = new DOMSource(element);
final StreamResult result = new StreamResult(writer);
final TransformerFactory tf = TransformerFactory.newInstance();
final Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
}
} catch (final Exception e) {
throw new SamlException(e.getMessage(), e);
}
return writer;
}
/**
* Build signature validation filter if needed.
*
* @param signatureResourceLocation the signature resource location
* @return the metadata filter
* @throws Exception the exception
*/
public static SignatureValidationFilter buildSignatureValidationFilter(final String signatureResourceLocation) throws Exception {
final AbstractResource resource = ResourceUtils.getResourceFrom(signatureResourceLocation);
return buildSignatureValidationFilter(resource);
}
/**
* Build signature validation filter if needed.
*
* @param resourceLoader the resource loader
* @param signatureResourceLocation the signature resource location
* @return the metadata filter
* @throws Exception the exception
*/
public static SignatureValidationFilter buildSignatureValidationFilter(final ResourceLoader resourceLoader,
final String signatureResourceLocation) throws Exception {
try {
final Resource resource = resourceLoader.getResource(signatureResourceLocation);
return buildSignatureValidationFilter(resource);
} catch (final Exception e){
LOGGER.debug(e.getMessage(), e);
}
return null;
}
/**
* Build signature validation filter if needed.
*
* @param signatureResourceLocation the signature resource location
* @return the metadata filter
* @throws Exception the exception
*/
public static SignatureValidationFilter buildSignatureValidationFilter(final Resource signatureResourceLocation) throws Exception {
if (!ResourceUtils.doesResourceExist(signatureResourceLocation)) {
LOGGER.warn("Resource [{}] cannot be located", signatureResourceLocation);
return null;
}
final List<KeyInfoProvider> keyInfoProviderList = new ArrayList<>();
keyInfoProviderList.add(new RSAKeyValueProvider());
keyInfoProviderList.add(new DSAKeyValueProvider());
keyInfoProviderList.add(new DEREncodedKeyValueProvider());
keyInfoProviderList.add(new InlineX509DataProvider());
LOGGER.debug("Attempting to resolve credentials from [{}]", signatureResourceLocation);
final BasicCredential credential = buildCredentialForMetadataSignatureValidation(signatureResourceLocation);
LOGGER.info("Successfully resolved credentials from [{}]", signatureResourceLocation);
LOGGER.debug("Configuring credential resolver for key signature trust engine @ [{}]", credential.getCredentialType().getSimpleName());
final StaticCredentialResolver resolver = new StaticCredentialResolver(credential);
final BasicProviderKeyInfoCredentialResolver keyInfoResolver = new BasicProviderKeyInfoCredentialResolver(keyInfoProviderList);
final ExplicitKeySignatureTrustEngine trustEngine = new ExplicitKeySignatureTrustEngine(resolver, keyInfoResolver);
LOGGER.debug("Adding signature validation filter based on the configured trust engine");
final SignatureValidationFilter signatureValidationFilter = new SignatureValidationFilter(trustEngine);
signatureValidationFilter.setRequireSignedRoot(false);
LOGGER.debug("Added metadata SignatureValidationFilter with signature from [{}]", signatureResourceLocation);
return signatureValidationFilter;
}
/**
* Build credential for metadata signature validation basic credential.
*
* @param resource the resource
* @return the basic credential
* @throws Exception the exception
*/
public static BasicCredential buildCredentialForMetadataSignatureValidation(final Resource resource) throws Exception {
try {
final BasicX509CredentialFactoryBean x509FactoryBean = new BasicX509CredentialFactoryBean();
x509FactoryBean.setCertificateResource(resource);
x509FactoryBean.afterPropertiesSet();
return x509FactoryBean.getObject();
} catch (final Exception e) {
LOGGER.trace(e.getMessage(), e);
LOGGER.debug("Credential cannot be extracted from [{}] via X.509. Treating it as a public key to locate credential...",
resource);
final BasicResourceCredentialFactoryBean credentialFactoryBean = new BasicResourceCredentialFactoryBean();
credentialFactoryBean.setPublicKeyInfo(resource);
credentialFactoryBean.afterPropertiesSet();
return credentialFactoryBean.getObject();
}
}
/**
* Log saml object.
*
* @param configBean the config bean
* @param samlObject the saml object
* @throws SamlException the saml exception
*/
public static void logSamlObject(final OpenSamlConfigBean configBean, final XMLObject samlObject) throws SamlException {
LOGGER.debug("Logging [{}]\n\n[{}]", samlObject.getClass().getName(), transformSamlObject(configBean, samlObject));
}
}