Category Archives: Spring Security SAML

Spring Security SAML: Replacing SHA-1 with SHA-256 on Signature and Digest Algorithms

PROBLEM

By default, Spring Security SAML’s SAMLBootstrap uses SHA1withRSA for signature algorithm and SHA-1 for digest algorithm.

@Configuration
@EnableWebSecurity
public abstract class AppSAMLConfig extends WebSecurityConfigurerAdapter {
	...

    @Bean
    public static SAMLBootstrap SAMLBootstrap() {
        return new SAMLBootstrap();
    }
	
	...
}

For example, the above configuration will generate the following SAML request payload when using HTTP-POST binding:-

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
    AssertionConsumerServiceURL="https://server/app/saml/SSO"
    Destination="https://adfs-server/adfs/ls/" ForceAuthn="true"
    ID="a3bj4e05i70f6946gi85299i51i02a" IsPassive="false"
    IssueInstant="2016-02-23T15:10:26.414Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://server/app/saml/metadata</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <ds:Reference URI="#a3bj4e05i70f6946gi85299i51i02a">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <ds:DigestValue>u25hV7rk8hIpXYLJQs0aZjkueP0=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>YDR9ybi...</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MIICxz...</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <saml2p:RequestedAuthnContext Comparison="exact">
        <saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
    </saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>

Unfortunately, SHA-1 is now deemed insecure due to “Freestart Collision” attack.

Further, most modern browsers have ceased to trust SHA-1 code signing certificates starting January 2016 and will eventually stop accepting these certificates by January 2017.

SOLUTION

To fix this, we could replace SHA-1 with stronger secure hash algorithm, such as SHA-256.

To do so, create a class that extends SAMLBootstrap that uses SHA256withRSA for signature algorithm and SHA-256 for digest algorithm.

public final class CustomSAMLBootstrap extends SAMLBootstrap {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        super.postProcessBeanFactory(beanFactory);
        BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
        config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
        config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
    }
}

Then, return CustomSAMLBootstrap instead of SAMLBootstrap

@Configuration
@EnableWebSecurity
public abstract class AppSAMLConfig extends WebSecurityConfigurerAdapter {
	...

    @Bean
    public static SAMLBootstrap SAMLBootstrap() {
        return new CustomSAMLBootstrap();
    }
	
	...
}

Now, the generated SAML request payload using HTTP-POST binding looks like this:-

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
    AssertionConsumerServiceURL="https://server/app/saml/SSO"
    Destination="https://adfs-server/adfs/ls/" ForceAuthn="true"
    ID="a2e7f98agfaec7d253714fjdbcf8a83" IsPassive="false"
    IssueInstant="2016-02-23T15:18:43.452Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://server/app/saml/metadata</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#a2e7f98agfaec7d253714fjdbcf8a83">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                <ds:DigestValue>w4qHFsBxFGifzemEJCYcuGOt+oZJ9N2DQM+Q2aEqJFI=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>YDR9ybi...</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MIICxz...</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <saml2p:RequestedAuthnContext Comparison="exact">
        <saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
    </saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>
Advertisements

Spring Security SAML: Handling IdP’s Public Certificate When Loading Metadata Over HTTPS

PROBLEM

By default, when loading IdP’s metadata over HTTPS (ex: https://adfs-server/federationmetadata/2007-06/federationmetadata.xml), Spring Security SAML will perform the trust verification configured in JDK.

However, there are times we do not have direct access to JDK home directory especially if the web apps are hosted on someone else’s JEE or PaaS servers.

SOLUTION

To fix this, the IdP’s public certificate can be imported into the app’s keystore instead of JDK’s keystore.

keytool -importcert -file adfs-server.cer -keystore app-keystore.jks -alias "adfs-server"

Then, configure Spring Security SAML to use TLSProtocolConfigurer, which will use all public certificates stored in the app’s keystore as trust anchors for PKIX validation.

@Configuration
@EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {

    ...
	
	// in this case, `app-keystore.jks` contains the app's public/private keys and 
	// the imported IdP's public certificate
    @Bean
    public KeyManager keyManager() {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        Resource storeFile = loader.getResource("classpath:app-keystore.jks");
        Map<String, String> passwords = new HashMap<>();
        passwords.put("app_alias", "app_password");
        return new JKSKeyManager(storeFile, "app_password", passwords, "app_alias");
    }

    @Bean
    public TLSProtocolConfigurer tlsProtocolConfigurer() {
        return new TLSProtocolConfigurer();
    }

    @Bean
    public ProtocolSocketFactory protocolSocketFactory(KeyManager keyManager) {
        return new TLSProtocolSocketFactory(keyManager, null, "default");
    }

    @Bean
    public Protocol protocol(ProtocolSocketFactory protocolSocketFactory) {
        return new Protocol("https", protocolSocketFactory, 443);
    }

    @Bean
    public MethodInvokingFactoryBean socketFactoryInitialization(Protocol protocol) {
        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setTargetClass(Protocol.class);
        methodInvokingFactoryBean.setTargetMethod("registerProtocol");
        Object[] args = {"https", protocol};
        methodInvokingFactoryBean.setArguments(args);
        return methodInvokingFactoryBean;
    }

    ...
}

Doing so makes the app more portable so that it can be deployed into any JEE or PaaS servers without any problem.

Spring Security SAML: Configuring Binding for Sending SAML Messages to IdP

PROBLEM

Depending on each institution’s Identity Provider (IdP) configuration, the Service Provider (Sp) may need to configure the correct binding for sending SAML messages to IdP.

SOLUTION

Using Spring Security SAML, the binding configuration is highlighted below:-

@Configuration
@EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {
    
	...
	
    @Bean
    public WebSSOProfileOptions webSSOProfileOptions() {
        WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
        webSSOProfileOptions.setIncludeScoping(false);
        webSSOProfileOptions.setBinding(...);
    }
	
    @Bean
    public SAMLEntryPoint samlEntryPoint(WebSSOProfileOptions webSSOProfileOptions) {
        SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
        samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
        return samlEntryPoint;
    }

	...
}

HTTP-POST Binding

Configuration:-

webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);

Using HTTP-POST binding, the SAML message to IdP will contain the signature information:-

<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response
        Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
        Destination="https://server/my-app/saml/SSO"
        ID="_370d6ba5-177c-494b-9147-2eafd9ecb6c9"
        InResponseTo="a5c5dja1i5fgb2bf2e66f6g9g5398gj"
        IssueInstant="2016-02-18T15:28:43.473Z"
        Version="2.0"
        xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://adfs-server/adfs/services/trust</Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <EncryptedAssertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
                    <e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
                        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    </e:EncryptionMethod>
                    <KeyInfo>
                        <ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                            <ds:X509IssuerSerial>
                                <ds:X509IssuerName>CN=server</ds:X509IssuerName>
                                <ds:X509SerialNumber>1822784706</ds:X509SerialNumber>
                            </ds:X509IssuerSerial>
                        </ds:X509Data>
                    </KeyInfo>
                    <e:CipherData>
                        <e:CipherValue>isG83fVk50fJRI...</e:CipherValue>
                    </e:CipherData>
                </e:EncryptedKey>
            </KeyInfo>
            <xenc:CipherData>
                <xenc:CipherValue>+b2o6HNxaxsse7rkB...</xenc:CipherValue>
            </xenc:CipherData>
        </xenc:EncryptedData>
    </EncryptedAssertion>
</samlp:Response>

HTTP-Redirect Binding

Configuration:-

webSSOProfileOptions.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);

Using SAML2_REDIRECT_BINDING_URI binding, the signature will be removed before the message is delivered. The signature is then performed on the serialized request and sent as a GET parameter.

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
                     AssertionConsumerServiceURL="https://server/my-app/saml/SSO"
                     Destination="https://adfs-server/adfs/ls/"
                     ForceAuthn="false"
                     ID="a4719398gd37jgg464505g70i40a49"
                     IsPassive="false"
                     IssueInstant="2016-02-18T15:24:59.036Z"
                     ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                     Version="2.0">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://server/my-app/saml/metadata</saml2:Issuer>
</saml2p:AuthnRequest>