package org.apereo.cas.support.saml.web.idp.profile.builders.enc; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import org.apache.commons.lang3.StringUtils; 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.SamlUtils; import org.apereo.cas.support.saml.services.SamlRegisteredService; import org.apereo.cas.support.saml.services.idp.metadata.SamlRegisteredServiceServiceProviderMetadataFacade; import org.apereo.cas.util.crypto.PrivateKeyFactoryBean; import org.opensaml.messaging.context.MessageContext; import org.opensaml.saml.common.SAMLException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.binding.impl.SAMLOutboundDestinationHandler; import org.opensaml.saml.common.binding.security.impl.EndpointURLSchemeSecurityHandler; import org.opensaml.saml.common.binding.security.impl.SAMLOutboundProtocolMessageSigningHandler; import org.opensaml.saml.criterion.RoleDescriptorCriterion; import org.opensaml.saml.saml2.metadata.RoleDescriptor; import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; import org.opensaml.security.credential.Credential; import org.opensaml.security.x509.BasicX509Credential; import org.opensaml.xmlsec.SignatureSigningConfiguration; import org.opensaml.xmlsec.SignatureSigningParameters; import org.opensaml.xmlsec.config.DefaultSecurityConfigurationBootstrap; import org.opensaml.xmlsec.context.SecurityParametersContext; import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; /** * This is {@link BaseSamlObjectSigner}. * * @author Misagh Moayyed * @since 5.0.0 */ public class BaseSamlObjectSigner { private static final Logger LOGGER = LoggerFactory.getLogger(BaseSamlObjectSigner.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 BaseSamlObjectSigner(final List overrideSignatureReferenceDigestMethods, final List overrideSignatureAlgorithms, final List overrideBlackListedSignatureAlgorithms, final List overrideWhiteListedAlgorithms) { this.overrideSignatureReferenceDigestMethods = overrideSignatureReferenceDigestMethods; this.overrideSignatureAlgorithms = overrideSignatureAlgorithms; this.overrideBlackListedSignatureAlgorithms = overrideBlackListedSignatureAlgorithms; this.overrideWhiteListedAlgorithms = overrideWhiteListedAlgorithms; } /** * Encode a given saml object by invoking a number of outbound security handlers on the context. * * @param <T> the type parameter * @param samlObject the saml object * @param service the service * @param adaptor the adaptor * @param response the response * @param request the request * @param binding the binding * @return the t * @throws SamlException the saml exception */ public <T extends SAMLObject> T encode(final T samlObject, final SamlRegisteredService service, final SamlRegisteredServiceServiceProviderMetadataFacade adaptor, final HttpServletResponse response, final HttpServletRequest request, final String binding) throws SamlException { try { LOGGER.debug("Attempting to encode [{}] for [{}]", samlObject.getClass().getName(), adaptor.getEntityId()); final MessageContext<T> outboundContext = new MessageContext<>(); prepareOutboundContext(samlObject, adaptor, outboundContext, binding); prepareSecurityParametersContext(adaptor, outboundContext); prepareEndpointURLSchemeSecurityHandler(outboundContext); prepareSamlOutboundDestinationHandler(outboundContext); prepareSamlOutboundProtocolMessageSigningHandler(outboundContext); return samlObject; } catch (final Exception e) { throw new SamlException(e.getMessage(), e); } } /** * Prepare saml outbound protocol message signing handler. * * @param <T> the type parameter * @param outboundContext the outbound context * @throws Exception the exception */ protected <T extends SAMLObject> void prepareSamlOutboundProtocolMessageSigningHandler(final MessageContext<T> outboundContext) throws Exception { LOGGER.debug("Attempting to sign the outbound SAML message..."); final SAMLOutboundProtocolMessageSigningHandler handler = new SAMLOutboundProtocolMessageSigningHandler(); handler.setSignErrorResponses(casProperties.getAuthn().getSamlIdp().getResponse().isSignError()); handler.invoke(outboundContext); LOGGER.debug("Signed SAML message successfully"); } /** * Prepare saml outbound destination handler. * * @param <T> the type parameter * @param outboundContext the outbound context * @throws Exception the exception */ protected <T extends SAMLObject> void prepareSamlOutboundDestinationHandler(final MessageContext<T> outboundContext) throws Exception { final SAMLOutboundDestinationHandler handlerDest = new SAMLOutboundDestinationHandler(); handlerDest.initialize(); handlerDest.invoke(outboundContext); } /** * Prepare endpoint url scheme security handler. * * @param <T> the type parameter * @param outboundContext the outbound context * @throws Exception the exception */ protected <T extends SAMLObject> void prepareEndpointURLSchemeSecurityHandler(final MessageContext<T> outboundContext) throws Exception { final EndpointURLSchemeSecurityHandler handlerEnd = new EndpointURLSchemeSecurityHandler(); handlerEnd.initialize(); handlerEnd.invoke(outboundContext); } /** * Prepare security parameters context. * * @param <T> the type parameter * @param adaptor the adaptor * @param outboundContext the outbound context * @throws SAMLException the saml exception */ protected <T extends SAMLObject> void prepareSecurityParametersContext(final SamlRegisteredServiceServiceProviderMetadataFacade adaptor, final MessageContext<T> outboundContext) throws SAMLException { final SecurityParametersContext secParametersContext = outboundContext.getSubcontext(SecurityParametersContext.class, true); if (secParametersContext == null) { throw new RuntimeException("No signature signing parameters could be determined"); } final SignatureSigningParameters signingParameters = buildSignatureSigningParameters(adaptor.getSsoDescriptor()); secParametersContext.setSignatureSigningParameters(signingParameters); } /** * Prepare outbound context. * * @param <T> the type parameter * @param samlObject the saml object * @param adaptor the adaptor * @param outboundContext the outbound context * @param binding the binding * @throws SamlException the saml exception */ protected <T extends SAMLObject> void prepareOutboundContext(final T samlObject, final SamlRegisteredServiceServiceProviderMetadataFacade adaptor, final MessageContext<T> outboundContext, final String binding) throws SamlException { LOGGER.debug("Outbound saml object to use is [{}]", samlObject.getClass().getName()); outboundContext.setMessage(samlObject); SamlIdPUtils.preparePeerEntitySamlEndpointContext(outboundContext, adaptor, binding); } /** * Build signature signing parameters signature signing parameters. * * @param descriptor the descriptor * @return the signature signing parameters * @throws SAMLException the saml exception */ protected SignatureSigningParameters buildSignatureSigningParameters(final RoleDescriptor descriptor) throws SAMLException { try { final CriteriaSet criteria = new CriteriaSet(); criteria.add(new SignatureSigningConfigurationCriterion(getSignatureSigningConfiguration())); criteria.add(new RoleDescriptorCriterion(descriptor)); final SAMLMetadataSignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); LOGGER.debug("Resolving signature signing parameters for [{}]", descriptor.getElementQName().getLocalPart()); final SignatureSigningParameters params = resolver.resolveSingle(criteria); if (params == null) { throw new SAMLException("No signature signing parameter is available"); } LOGGER.debug("Created signature signing parameters." + "\nSignature algorithm: [{}]" + "\nSignature canonicalization algorithm: [{}]" + "\nSignature reference digest methods: [{}]", params.getSignatureAlgorithm(), params.getSignatureCanonicalizationAlgorithm(), params.getSignatureReferenceDigestMethod()); return params; } catch (final Exception e) { throw new SAMLException(e.getMessage(), e); } } /** * Gets signature signing configuration. * * @return the signature signing configuration * @throws Exception the exception */ protected SignatureSigningConfiguration getSignatureSigningConfiguration() throws Exception { final BasicSignatureSigningConfiguration config = DefaultSecurityConfigurationBootstrap.buildDefaultSignatureSigningConfiguration(); final SamlIdPProperties samlIdp = casProperties.getAuthn().getSamlIdp(); if (this.overrideBlackListedSignatureAlgorithms != null && !samlIdp.getAlgs().getOverrideBlackListedSignatureSigningAlgorithms().isEmpty()) { config.setBlacklistedAlgorithms(this.overrideBlackListedSignatureAlgorithms); } if (this.overrideSignatureAlgorithms != null && !this.overrideSignatureAlgorithms.isEmpty()) { config.setSignatureAlgorithms(this.overrideSignatureAlgorithms); } if (this.overrideSignatureReferenceDigestMethods != null && !this.overrideSignatureReferenceDigestMethods.isEmpty()) { config.setSignatureReferenceDigestMethods(this.overrideSignatureReferenceDigestMethods); } if (this.overrideWhiteListedAlgorithms != null && !this.overrideWhiteListedAlgorithms.isEmpty()) { config.setWhitelistedAlgorithms(this.overrideWhiteListedAlgorithms); } if (StringUtils.isNotBlank(samlIdp.getAlgs().getOverrideSignatureCanonicalizationAlgorithm())) { config.setSignatureCanonicalizationAlgorithm(samlIdp.getAlgs().getOverrideSignatureCanonicalizationAlgorithm()); } LOGGER.debug("Signature signing blacklisted algorithms: [{}]", config.getBlacklistedAlgorithms()); LOGGER.debug("Signature signing signature algorithms: [{}]", config.getSignatureAlgorithms()); LOGGER.debug("Signature signing signature canonicalization algorithm: [{}]", config.getSignatureCanonicalizationAlgorithm()); LOGGER.debug("Signature signing whitelisted algorithms: [{}]", config.getWhitelistedAlgorithms()); LOGGER.debug("Signature signing reference digest methods: [{}]", config.getSignatureReferenceDigestMethods()); final PrivateKey privateKey = getSigningPrivateKey(); final X509Certificate certificate = getSigningCertificate(); final List<Credential> creds = new ArrayList<>(); creds.add(new BasicX509Credential(certificate, privateKey)); config.setSigningCredentials(creds); LOGGER.debug("Signature signing credentials configured"); return config; } /** * Gets signing certificate. * * @return the signing certificate * @throws Exception the exception */ protected X509Certificate getSigningCertificate() throws Exception { final SamlIdPProperties samlIdp = casProperties.getAuthn().getSamlIdp(); LOGGER.debug("Locating signature signing certificate file from [{}]", samlIdp.getMetadata().getSigningCertFile()); return SamlUtils.readCertificate(new FileSystemResource(samlIdp.getMetadata().getSigningCertFile().getFile())); } /** * Gets signing private key. * * @return the signing private key * @throws Exception the exception */ protected PrivateKey getSigningPrivateKey() throws Exception { final SamlIdPProperties samlIdp = casProperties.getAuthn().getSamlIdp(); final PrivateKeyFactoryBean privateKeyFactoryBean = new PrivateKeyFactoryBean(); privateKeyFactoryBean.setLocation(new FileSystemResource(samlIdp.getMetadata().getSigningKeyFile().getFile())); privateKeyFactoryBean.setAlgorithm(samlIdp.getMetadata().getPrivateKeyAlgName()); privateKeyFactoryBean.setSingleton(false); LOGGER.debug("Locating signature signing key file from [{}]", samlIdp.getMetadata().getSigningKeyFile()); return privateKeyFactoryBean.getObject(); } }