vdenotaris / spring-boot-security-saml-sample

SBS3 — A sample SAML 2.0 Service Provider built on Spring Boot.

Home Page:https://sbs3.vdenotaris.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to sign with SHA-256

tyleragnew opened this issue · 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

Having the same problem. What was the solution?

To be investigated.

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?

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...

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

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

SHA 256 should definitely be used. Security ;)

I got it working.

@garrit-schroeder

SHA 256 should definitely be used. Security ;)

I got it working.

@garrit-schroeder Can you help please

Sure i am preparing my conf. Hang on

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");
}

}`

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);
    }
}

}

`

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"
`

In a @ControllerAdvice class i provide the following:

@Autowired
private MetadataManager metadata;

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

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

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

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

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

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.