vdenotaris / spring-boot-security-saml-sample

SBS3 — A sample SAML 2.0 Service Provider built on Spring Boot.
https://sbs3.vdenotaris.com
Apache License 2.0
563 stars 351 forks source link

Unable to sign with SHA-256 #66

Open tyleragnew opened 5 years ago

tyleragnew commented 5 years ago

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
garrit-schroeder commented 5 years ago

Having the same problem. What was the solution?

vdenotaris commented 5 years ago

To be investigated.

vdenotaris commented 5 years ago

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?

tyleragnew commented 5 years ago

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

jmereaux commented 4 years ago

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

swatantrajain-sapiens commented 4 years ago

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

garrit-schroeder commented 4 years ago

SHA 256 should definitely be used. Security ;)

I got it working.

swatantrajain-sapiens commented 4 years ago

@garrit-schroeder

SHA 256 should definitely be used. Security ;)

I got it working.

@garrit-schroeder Can you help please

garrit-schroeder commented 4 years ago

Sure i am preparing my conf. Hang on

garrit-schroeder commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

In a @ControllerAdvice class i provide the following:

@Autowired
private MetadataManager metadata;

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

garrit-schroeder commented 4 years ago

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

garrit-schroeder commented 4 years ago

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

garrit-schroeder commented 4 years ago

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 commented 4 years ago

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

vdenotaris commented 4 years ago

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.