spring-boot-security-saml-sample icon indicating copy to clipboard operation
spring-boot-security-saml-sample copied to clipboard

Unable to sign with SHA-256

Open tyleragnew opened this issue 5 years ago • 18 comments

Describe the bug Unable to sign with SHA-256, even updating signingAlgorithm keeps it as SHA-1

To Reproduce

<samlp:Response Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
    Destination="https://contactcenter.np-mylincolnportal.com/saml/SSO"
    ID="_076f5d60-4cd4-402d-b574-c191127efbfd" InResponseTo="a15026df977c8cd4bhigg9i3h99c9g"
    IssueInstant="2019-03-18T22:37:22.543Z" Version="2.0"
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://sso.lfg.com/adfs/services/trust</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="#_076f5d60-4cd4-402d-b574-c191127efbfd">
                <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>la0uwregQ/KZXbnrhT2vbkZm6hc=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>

Where I'm updating signingAlgorithm

    // Setup advanced info about metadata
    @Bean
    public ExtendedMetadata extendedMetadata() {
        ExtendedMetadata extendedMetadata = new ExtendedMetadata();
        extendedMetadata.setIdpDiscoveryEnabled(false);
        extendedMetadata.setSigningAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
        extendedMetadata.setSignMetadata(true);
        extendedMetadata.setEcpEnabled(true);
        return extendedMetadata;
    }

Error Response

Validation of protocol message signature succeeded, message type: {urn:oasis:names:tc:SAML:2.0:protocol}Response
2019-03-18 18:20:04.615  INFO [gateway,e3fb953e205ec452,e3fb953e205ec452,false] 1 --- [io-8443-exec-10] o.s.security.saml.log.SAMLDefaultLogger  : AuthNResponse;FAILURE;10.192.16.125;lfg-cc-gateway;http://sso.lfg.com/adfs/services/trust;;;org.opensaml.common.SAMLException: Response has invalid status code urn:oasis:names:tc:SAML:2.0:status:Responder, status message is null

tyleragnew avatar Mar 18 '19 22:03 tyleragnew

Having the same problem. What was the solution?

garrit-schroeder avatar May 22 '19 09:05 garrit-schroeder

To be investigated.

vdenotaris avatar May 27 '19 09:05 vdenotaris

Hi @tyleragnew. I will soon work to replicate it, but if you already have more info about, could you please share with us the details?

vdenotaris avatar May 27 '19 09:05 vdenotaris

Hi all - I ended up signing with SHA-1 on the IDP side - so this did fix the issue. Understood that this would not fix the issue if you don't control your IDP though...

tyleragnew avatar Jun 03 '19 20:06 tyleragnew

Hi. Same problem on my side. Any news on this?

jmereaux avatar Nov 28 '19 10:11 jmereaux

Hi Any updates on this issue.

The IDP provider is reluctant to change to SHA1 and even after changing the signing and messageDigest algorithm to SHA256 the requests from SP are sent with SHA1

swatantrajain-sapiens avatar Mar 09 '20 15:03 swatantrajain-sapiens

SHA 256 should definitely be used. Security ;)

I got it working.

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

@garrit-schroeder

SHA 256 should definitely be used. Security ;)

I got it working.

@garrit-schroeder Can you help please

swatantrajain-sapiens avatar Mar 09 '20 15:03 swatantrajain-sapiens

Sure i am preparing my conf. Hang on

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

Saml Security Java File:

`@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true) public class SamlSecurity extends SAMLWebSecurityConfigurerAdapter {

private final SAMLUserDetailsServiceImpl userDetailsService;
private final MemoryUserDetailsServiceImpl memoryUserDetailsService;

private String serverName;
private int serverPort;

@Bean
public AccessDeniedHandler accessDeniedHandler() {
    return new CustomAccessDeniedHandler();
}

@Autowired
public SamlSecurity(@Value("${project.saml.server.name}") String serverName, @Value("${project.saml.server.port}")
        int serverPort, SAMLUserDetailsServiceImpl userDetailsService, MemoryUserDetailsServiceImpl memoryUserDetailsService) {
    this.serverName = serverName;
    this.serverPort = serverPort;
    this.userDetailsService = userDetailsService;
    this.memoryUserDetailsService = memoryUserDetailsService;
}


// See `SAMLConfigBean Properties` section below for more info.
@Override
protected SAMLConfigBean samlConfigBean() {
    return new SAMLConfigBeanBuilder()
            .withIdpServerName("path_xml_of_idp.xml")
            .withSpServerName(serverName)
            .withSpHttpsPort(serverPort)
            .withKeystoreResource(new DefaultResourceLoader().getResource("classpath:/saml/samlKeystore.jks"))
            .withKeystorePassword("XX1XX")
            .withKeystoreAlias("saml_key")
            .withKeystorePrivateKeyPassword("XX1XX")
            .withSuccessLoginDefaultUrl("X")
            .withSuccessLogoutUrl("X")
            .withFailedLoginDefaultUrl("X")
            .withStoreCsrfTokenInCookie(true)
            .withSamlUserDetailsService(userDetailsService)
            .withAuthnContexts(authenticationContexts())
            .build();
}

private Set<String> authenticationContexts() {
    Set<String> context = new HashSet<>();
    context.add(CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX);
    context.add(AuthnContext.PASSWORD_AUTHN_CTX);
    return context;
}

@Override
protected void configure(final HttpSecurity http) throws Exception {
    samlizedConfig(http)
            .authorizeRequests()
            .antMatchers("X").permitAll()
            .antMatchers("X")
            .hasAnyAuthority(Authority.X)
            .antMatchers("X")
            .hasAuthority(Authority.Y)
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("X")
            .failureUrl("X")
            .usernameParameter("username")
            .passwordParameter("password")
            .and()
            .exceptionHandling().accessDeniedHandler(accessDeniedHandler()).and()
            .httpBasic().and()
            .csrf().ignoringAntMatchers("/X");
}

@Override
public void configure(AuthenticationManagerBuilder auth)
        throws Exception {
    auth.authenticationProvider(samlAuthenticationProvider());
    auth.userDetailsService(memoryUserDetailsService);
}

// call samlizedConfig(web) first to decorate web with SAML configuration
// before configuring app specific web security
@Override
public void configure(final WebSecurity web) throws Exception {
    samlizedConfig(web).ignoring().antMatchers("static stuff");
}

}`

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

SAML Detail Service:

`

@Service public class SAMLUserDetailsServiceImpl extends X implements SAMLUserDetailsService {

public SAMLUserDetailsServiceImpl(X x) {
    super(x);
}

public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
    try {
        String email = credential.getAttributeAsString("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").trim();
        String firstName = credential.getAttributeAsString("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname").trim();
        String lastName = credential.getAttributeAsString("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname").trim();
        return a org.springframework.security.core.userdetails.User;
    } catch (Exception e) {
        Logger.info("saml login error: " + e.getMessage());
        throw new UsernameNotFoundException(e.getMessage(), e);
    }
}

}

`

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

Create SAML Keystore: under ./resources/saml/ for example:

` #!/bin/bash

KS_FILE=samlKeystore.jks KS_PASS=XX KS_KEY_PAIR_NAME=saml_key IDP_HOST=XX IDP_PORT=443 CERTIFICATE_FILE=file.cert

rm $KS_FILE echo "deleted old key store" keytool -genkeypair -v -keystore $KS_FILE -storepass $KS_PASS -alias $KS_KEY_PAIR_NAME -dname 'CN=test, OU=test, O=test, L=test, ST=test, C=test' -keypass $KS_PASS -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -validity 3650 echo "created store with saml key pair" openssl s_client -host $IDP_HOST -port $IDP_PORT -prexit -showcerts </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > $CERTIFICATE_FILE echo "downloaded new adfs certificate" keytool -import -alias adfs -file $CERTIFICATE_FILE -keystore $KS_FILE -storepass $KS_PASS -noprompt echo "imported new adfs certificate" rm $CERTIFICATE_FILE echo "removed downloaded certificate file" `

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

In a @ControllerAdvice class i provide the following:

@Autowired
private MetadataManager metadata;

@Controller public void blabla(){ model.put("saml_id", metadata.getHostedSPName()); }

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

Thats all. I am not setting up metadata or anything else

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

Your host should now show your certs under http://example.com/saml/metadata

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

Saml Security Java File:

`@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true) public class SamlSecurity extends SAMLWebSecurityConfigurerAdapter {

private final SAMLUserDetailsServiceImpl userDetailsService;
private final MemoryUserDetailsServiceImpl memoryUserDetailsService;

private String serverName;
private int serverPort;

@Bean
public AccessDeniedHandler accessDeniedHandler() {
    return new CustomAccessDeniedHandler();
}

@Autowired
public SamlSecurity(@Value("${project.saml.server.name}") String serverName, @Value("${project.saml.server.port}")
        int serverPort, SAMLUserDetailsServiceImpl userDetailsService, MemoryUserDetailsServiceImpl memoryUserDetailsService) {
    this.serverName = serverName;
    this.serverPort = serverPort;
    this.userDetailsService = userDetailsService;
    this.memoryUserDetailsService = memoryUserDetailsService;
}


// See `SAMLConfigBean Properties` section below for more info.
@Override
protected SAMLConfigBean samlConfigBean() {
    return new SAMLConfigBeanBuilder()
            .withIdpServerName("path_xml_of_idp.xml")
            .withSpServerName(serverName)
            .withSpHttpsPort(serverPort)
            .withKeystoreResource(new DefaultResourceLoader().getResource("classpath:/saml/samlKeystore.jks"))
            .withKeystorePassword("XX1XX")
            .withKeystoreAlias("saml_key")
            .withKeystorePrivateKeyPassword("XX1XX")
            .withSuccessLoginDefaultUrl("X")
            .withSuccessLogoutUrl("X")
            .withFailedLoginDefaultUrl("X")
            .withStoreCsrfTokenInCookie(true)
            .withSamlUserDetailsService(userDetailsService)
            .withAuthnContexts(authenticationContexts())
            .build();
}

private Set<String> authenticationContexts() {
    Set<String> context = new HashSet<>();
    context.add(CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX);
    context.add(AuthnContext.PASSWORD_AUTHN_CTX);
    return context;
}

@Override
protected void configure(final HttpSecurity http) throws Exception {
    samlizedConfig(http)
            .authorizeRequests()
            .antMatchers("X").permitAll()
            .antMatchers("X")
            .hasAnyAuthority(Authority.X)
            .antMatchers("X")
            .hasAuthority(Authority.Y)
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("X")
            .failureUrl("X")
            .usernameParameter("username")
            .passwordParameter("password")
            .and()
            .exceptionHandling().accessDeniedHandler(accessDeniedHandler()).and()
            .httpBasic().and()
            .csrf().ignoringAntMatchers("/X");
}

@Override
public void configure(AuthenticationManagerBuilder auth)
        throws Exception {
    auth.authenticationProvider(samlAuthenticationProvider());
    auth.userDetailsService(memoryUserDetailsService);
}

// call samlizedConfig(web) first to decorate web with SAML configuration
// before configuring app specific web security
@Override
public void configure(final WebSecurity web) throws Exception {
    samlizedConfig(web).ignoring().antMatchers("static stuff");
}

}`

Important is that you specify the correct server name and port. These should appear in your /saml/metadata

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

I hope that helps you. I you have further questions. Don't hesitate to ask me.

garrit-schroeder avatar Mar 09 '20 15:03 garrit-schroeder

Sorry folks, I very appreciate your passion, but this is not the right way to manage an issue. The code that have been posted does not iron out the case, but just reflect how to setup custom IdP in Spring SAML.

As stated by @tyleragnew, the SHA mismatch depends on the IdP configuration. In SAML-based authentication, IdP and SP need to agree on the cipher suite when establishing the trust relationship (see https://en.wikipedia.org/wiki/SAML_metadata).

If you run this application against a SHA-256 enabled Identity Provider, everything works accordingly (see: https://docs.spring.io/autorepo/docs/spring-security-saml/1.0.x-SNAPSHOT/reference/htmlsingle/).

Note: the issue is still open just because NO secure application must still rely on SHA-1, since it has been proved to be weak at collision attacks.

vdenotaris avatar Mar 09 '20 23:03 vdenotaris