Hot questions for Spring Security SAML

Top 10 Java Open Source / Spring / Spring Security SAML

Question:

I have generated testIdp.cer file by copying 509 entry of the IDP I am planning to connect. Then I created JKS file by executing the following command

keytool -importcert -alias adfssigning -keystore C:\Users\user\Desktop\samlKeystore.jks -file    C:\Users\user\Desktop\testIdp.cer

When executed it has asked to enter a password for which I have given a password. For the question "Trust this certificate? [no]:", I have given "y" as input. Message came out as "Certificate was added to keystore".

Then I have configured the following details in securityContext.xml

<bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">
    <constructor-arg value="classpath:security/samlKeystore.jks"/>
    <constructor-arg type="java.lang.String" value="mypassword"/>
    <constructor-arg>
        <map>
            <entry key="adfssigning" value="mypassword"/>
        </map>
    </constructor-arg>
    <constructor-arg type="java.lang.String" value="adfssigning"/>
</bean>

<bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
  <property name="alias" value="adfssigning" />
  <property name="signingKey" value="adfssigning"/>     
</bean>

But when I run the application, I get the following two exceptions when the server is starting and when I load the homepage of the application. Can anyone let me know if I am missing anything else.

This exception is occuring when I start the server

Caused by: org.opensaml.saml2.metadata.provider.FilterException: Signature trust establishment failed for metadata entry
at org.opensaml.saml2.metadata.provider.SignatureValidationFilter.verifySignature(SignatureValidationFilter.java:327)
at org.opensaml.saml2.metadata.provider.SignatureValidationFilter.processEntityGroup(SignatureValidationFilter.java:240)
at org.opensaml.saml2.metadata.provider.SignatureValidationFilter.doFilter(SignatureValidationFilter.java:158)
at org.opensaml.saml2.metadata.provider.AbstractMetadataProvider.filterMetadata(AbstractMetadataProvider.java:493)
at org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider.processNonExpiredMetadata(AbstractReloadingMetadataProvider.java:395)

This exception is occuring when I run the homepage of my application

java.lang.UnsupportedOperationException: trusted certificate entries are not password-protected
at java.security.KeyStoreSpi.engineGetEntry(Unknown Source)
at java.security.KeyStore.getEntry(Unknown Source)
at org.opensaml.xml.security.credential.KeyStoreCredentialResolver.resolveFromSource(KeyStoreCredentialResolver.java:132)

Answer:

Your .cer certificate contains only a public key, you mustn't define <entry key="adfssigning" value="mypassword"/> for public keys; it can only be used for private ones. Simply take out the adfssigning entry and make sure to include a private key instead - just like in the Spring SAML sample application.

The SAML keystore can contain two basic types of keys - public and private ones (plus their certificates). Each key has an alias which is used to refer to it. The keystore itself can be protected by a password (provided in the second constructor parameter), plus each private key can be also protected by an additional password (these are defined in third parameter of the constructor in a map of alias->password). The public keys which you import to the keystore (just like you did with the command above) mustn't be defined in this map. They will be automatically available after being imported without additional declarations. For Spring SAML to work, the keystore must contain at least one private key (the sample application contains private key with alias apollo) and its alias needs to be provided in the third parameter of the constructor.

Your example above fails, because you have imported a public key, but included it in the map which can only be used for private keys.

Question:

I am trying to integrate SAML SSO with Spring Security using Spring Security SAML extension. Before, I succeeded to run a proof of concept found here: https://github.com/vdenotaris/spring-boot-security-saml-sample. Unfortunately, after moving the configuration to my project it is not working correctly.

After analyzing the logs, I figured out that my application (SP) is correctly downloading the IdP metadata from provided URL. However, after trying to download metadata of my SP by trying https://localhost:8443/saml/metadata in browser, the following exception is thrown:

javax.servlet.ServletException: Error initializing metadata
at org.springframework.security.saml.metadata.MetadataDisplayFilter.processMetadataDisplay(MetadataDisplayFilter.java:120)
at org.springframework.security.saml.metadata.MetadataDisplayFilter.doFilter(MetadataDisplayFilter.java:88)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1645)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:564)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:578)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1111)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:498)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:183)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1045)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:98)
at org.eclipse.jetty.server.Server.handle(Server.java:461)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:284)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:244)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:534)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.opensaml.saml2.metadata.provider.MetadataProviderException: No hosted service provider is configured and no alias was selected
    at org.springframework.security.saml.context.SAMLContextProviderImpl.populateLocalEntity(SAMLContextProviderImpl.java:311)
    at org.springframework.security.saml.context.SAMLContextProviderImpl.populateLocalContext(SAMLContextProviderImpl.java:216)
    at org.springframework.security.saml.context.SAMLContextProviderImpl.getLocalEntity(SAMLContextProviderImpl.java:107)
    at org.springframework.security.saml.metadata.MetadataDisplayFilter.processMetadataDisplay(MetadataDisplayFilter.java:114)
    ... 24 more

After debugging, I was not able to figure out why Spring is not able to figure out entity ID of my application. I am setting it like this:

// Filter automatically generates default SP metadata
@Bean
public MetadataGenerator metadataGenerator() {
    MetadataGenerator metadataGenerator = new MetadataGenerator();
    metadataGenerator.setEntityId(environment.getRequiredProperty("saml.entity-id"));
    metadataGenerator.setEntityBaseURL("URL is here");
    metadataGenerator.setExtendedMetadata(extendedMetadata());
    metadataGenerator.setIncludeDiscoveryExtension(false);
    metadataGenerator.setKeyManager(keyManager());
    return metadataGenerator;
}

Of course the saml.entity-id property is correctly downloaded from my configuration. Whole security config is here: https://gist.github.com/mc-suchecki/671ecb4d5ae4bae17f81

Order of the filters is correct - the Metadata Generator Filter is before the SAML Filter. I am not sure that is relevant - I suppose not - but my application is not using Spring Boot - and the sample application (the source of the configuration) is.

Thank you in advance for any help.


Answer:

I found the issue this week. There was a problem with filters. One of the methods was creating the 'samlFilter', like this:

public FilterChainProxy samlFilter() throws Exception {
    List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
        metadataDisplayFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
        samlWebSSOProcessingFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
        samlWebSSOHoKProcessingFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
        samlLogoutProcessingFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"), samlIDPDiscovery()));
    return new FilterChainProxy(chains);
}

After that, another method was setting the whole filter chain for Spring, like so:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic().authenticationEntryPoint(samlEntryPoint());
    http.csrf().disable();
    http.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
        .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
    http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/error").permitAll()
        .antMatchers("/saml/**").permitAll().anyRequest().authenticated();
    http.logout().logoutSuccessUrl("/");
}

That was perfectly correct. However, when I was launching the application using Jetty server, I was trying to connect only the 'samlFilter' to the application context. Because of that, the 'metadataGeneratorFilter' that is required to be before the 'metadataDisplayFilter' was not added to the filter chain at all. When I changed 'samlFilter' to 'springSecurityFilter', everything started working. That was not easy to find, because of my non-standard use of Jetty.

Thank you for your help!

Question:

We are using ADFS as an IDP and our application acts as SP. Below is a sample Auth response

<?xml version="1.0" encoding="UTF-8"?>
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_82062d3d-897f-473e-90ad-0bb351d63b22" IssueInstant="2015-04-29T20:39:17.240Z" Version="2.0">
   <Issuer>http://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="#_82062d3d-897f-473e-90ad-0bb351d63b22">
            <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 />
         </ds:Reference>
      </ds:SignedInfo>
      <ds:SignatureValue />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
         <ds:X509Data>
            <ds:X509Certificate>certificate..... </ds:X509Certificate>
         </ds:X509Data>
      </KeyInfo>
   </ds:Signature>
   <Subject>
      <NameID>username</NameID>
      <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
         <SubjectConfirmationData InResponseTo="923ki0eg8h7g7y2243fi9jbdd1977j9" NotOnOrAfter="2015-04-29T20:44:17.240Z" Recipient="https://localhost/saml/SSO" />
      </SubjectConfirmation>
   </Subject>
   <Conditions NotBefore="2015-04-29T20:39:17.240Z" NotOnOrAfter="2015-04-29T21:39:17.240Z">
      <AudienceRestriction>
         <Audience>https://localhost/saml/metadata</Audience>
      </AudienceRestriction>
   </Conditions>
   <AuthnStatement AuthnInstant="2015-04-29T20:39:17.162Z" SessionIndex="_92062g3d-897f-473e-90ad-0aa351d63b22">
      <AuthnContext>
         <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
      </AuthnContext>
   </AuthnStatement>
</Assertion>

The problem I face can be categorized into two scenarios:

  1. After 1 hour of Idle time, I logout the user locally. The server session expiry is a default value of 30min. I have my code to send heartbeat pings every 10min, if the user is actively working on something. Now, the problem is that when the user tries to login after the session expiry of 1 hour, I get the below exception

    Caused by: org.springframework.security.authentication.CredentialsExpiredException: Authentication statement is too old to be used with value 2015-05-28T17:41:52.648Z
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAuthenticationStatement(WebSSOProfileConsumerImpl.java:538)
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAssertion(WebSSOProfileConsumerImpl.java:306)
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:214)
        ... 77 more
    

The question here is.. Why would our application try to validate the instance of when the token was issued? It could be granted anytime..

  1. I keep getting SAMLException with message "Local entity is not the intended audience of the assertion in at least one AudienceRestriction". The trace is as below

    Caused by: org.opensaml.common.SAMLException: Local entity is not the intended audience of the assertion in at least one AudienceRestriction
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAudience(WebSSOProfileConsumerImpl.java:506)
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAssertionConditions(WebSSOProfileConsumerImpl.java:458)
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.verifyAssertion(WebSSOProfileConsumerImpl.java:303)
        at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:214)
        ... 77 more
    

I dont understand why this exception occurs.

Please help me to understand the concept.

Thanks!


Answer:

Your IDP is re-using information that user has authenticated earlier (at time identified by Authentication Instant) and Spring SAML is by default configured to not let user login if she's been authenticated more than 7200 seconds ago.

It's a security measure - if it's a long time ago since the computer has authenticated the user, it's hard to guarantee that it's still the same person operating the computer. Spring SAML provides you some means to configure what level of security will be acceptable - for example by making this configurable.

You can increase this value by setting property maxAuthenticationAge on the WebSSOProfileConsumerImpl bean.

The audience error should happen only when the assertion contains Audience elements and none of them matches the entity ID of your application. I don't think the Response in your question is the one triggering this error?

Question:

In order to fetch metadata from a remote source, I defined an ExtendedMetadataDelegate bean as follows:

@Bean
@Qualifier("replyMeta")
public ExtendedMetadataDelegate replyMetadataProvider() throws MetadataProviderException {
    String metadataURL = "https://ststest.mydomain.it/FederationMetadata/2007-06/FederationMetadata.xml";
    final Timer backgroundTaskTimer = new Timer(true);
    HTTPMetadataProvider provider = new HTTPMetadataProvider(
            backgroundTaskTimer, httpClient(), metadataURL);
    provider.setParserPool(parserPool());
    ExtendedMetadataDelegate emd = new ExtendedMetadataDelegate(
            provider, new ExtendedMetadata());
    return emd;
}

To ensure the signature trust establishment, I added the related key both in JDK keystore and application keystore (the second step might not be enough); despite that, an error occurs by running the webapp.

[2014-08-18 14:36:47.200] boot - 6000 DEBUG [localhost-startStop-1] --- SignatureValidator: Attempting to validate signature using key from supplied credential
[2014-08-18 14:36:47.200] boot - 6000 DEBUG [localhost-startStop-1] --- SignatureValidator: Creating XMLSignature object
[2014-08-18 14:36:47.206] boot - 6000 DEBUG [localhost-startStop-1] --- SignatureValidator: Validating signature with signature algorithm URI: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
[2014-08-18 14:36:47.207] boot - 6000 DEBUG [localhost-startStop-1] --- SignatureValidator: Validation credential key algorithm 'RSA', key instance class 'sun.security.rsa.RSAPublicKeyImpl'
[2014-08-18 14:36:47.329] boot - 6000 DEBUG [localhost-startStop-1] --- SignatureValidator: Signature validated with key from supplied credential
[2014-08-18 14:36:47.329] boot - 6000 DEBUG [localhost-startStop-1] --- BaseSignatureTrustEngine: Signature validation using candidate credential was successful
[2014-08-18 14:36:47.330] boot - 6000 DEBUG [localhost-startStop-1] --- BaseSignatureTrustEngine: Successfully verified signature using KeyInfo-derived credential
[2014-08-18 14:36:47.330] boot - 6000 DEBUG [localhost-startStop-1] --- BaseSignatureTrustEngine: Attempting to establish trust of KeyInfo-derived credential
[2014-08-18 14:36:47.330] boot - 6000 DEBUG [localhost-startStop-1] --- BasicX509CredentialNameEvaluator: Supplied trusted names are null or empty, skipping name evaluation
[2014-08-18 14:36:47.331] boot - 6000 DEBUG [localhost-startStop-1] --- MetadataCredentialResolver: Attempting PKIX path validation on untrusted credential: [subjectName='CN=ADFS Signing - ststest-replynet.reply.it']
[2014-08-18 14:36:47.346] boot - 6000 ERROR [localhost-startStop-1] --- MetadataCredentialResolver: PKIX path construction failed for untrusted credential: [subjectName='CN=ADFS Signing - ststest-replynet.reply.it']: unable to find valid certification path to requested target
[2014-08-18 14:36:47.347] boot - 6000 DEBUG [localhost-startStop-1] --- PKIXSignatureTrustEngine: Signature trust could not be established via PKIX validation of signing credential
[2014-08-18 14:36:47.347] boot - 6000 DEBUG [localhost-startStop-1] --- BaseSignatureTrustEngine: Failed to establish trust of KeyInfo-derived credential
[2014-08-18 14:36:47.347] boot - 6000 DEBUG [localhost-startStop-1] --- BaseSignatureTrustEngine: Failed to verify signature and/or establish trust using any KeyInfo-derived credentials
[2014-08-18 14:36:47.347] boot - 6000 DEBUG [localhost-startStop-1] --- PKIXSignatureTrustEngine: PKIX validation of signature failed, unable to resolve valid and trusted signing key
[2014-08-18 14:36:47.347] boot - 6000 ERROR [localhost-startStop-1] --- SignatureValidationFilter: Signature trust establishment failed for metadata entry http://ststest-replynet.reply.it/adfs/services/trust
[2014-08-18 14:36:47.349] boot - 6000 ERROR [localhost-startStop-1] --- AbstractReloadingMetadataProvider: Error filtering metadata from https://ststest-replynet.reply.it/FederationMetadata/2007-06/FederationMetadata.xml
org.opensaml.saml2.metadata.provider.FilterException: Signature trust establishment failed for metadata entry

The error disappears by setting:

emd.setMetadataTrustCheck(false); 

... but I'd like to check used metadata.

Is there a way to resolve this error?


Update:

I tried to setup the ExtendedMetadata as follows but the error persists.

em.setAlias("defaultAlias");
em.setSigningKey("*.mydomain.it (Go Daddy Secure Certification Authority)");

Answer:

You have most likely imported the HTTPS certificate, but not the certificate which is used to create the signature - they differ. You should:

  1. Create file signature.cer with the following content taken from the metadata:

    -----BEGIN CERTIFICATE-----
    MIIC7jCCAdagAwIBAgIQa+pSaOoDP6ZL3qAi564CxzANBgkqhkiG9w0BAQs
    FADAzMTEwLwYDVQQDEyhBREZTIFNpZ25pbmcgLSBzdHN0ZXN0LXJlcGx5bm
    V0LnJlcGx5Lml0MB4XDTE0MDQyMTAwMzUyNVoXDTE1MDQyMTAwMzUyNVowM
    zExMC8GA1UEAxMoQURGUyBTaWduaW5nIC0gc3RzdGVzdC1yZXBseW5ldC5y
    ZXBseS5pdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJyI7Se
    +UGGHOGRcwHF8LrmivtcPJyNKyicPJ8UJ8pIsEmgYBnJIrpS05RkYtdUdk+
    aumDlc3ACt23FxGDLy9hkJJlRbZwklzh4W3RqGC3W5Y+t7KeIuB8d7ZrrLb
    2AoJpVHICRagsLjjHMwz9sJUt+PZdUFFc0pZckHba3TY2Y+MgPYVsyjlEkf
    QrwL0ggh23g9Pe1VQ9HaInXZvwVMGWZ1oL4Uk0cW11URa8x53ZOWMQSsksi
    MUlquItssiuJjRnI9Df+GaDxbQJi51esY2EF1o2JxqGJSA71Apy9EahDho8
    eFkfOS0fYbVNBU5X/Wn7BKsf2Rmg3r6mQM94+gAA8CAwEAATANBgkqhkiG9
    w0BAQsFAAOCAQEAIX5FEt5JWtINzy4C0LtTtta3DMOsLIBH3raRr53+6MKG
    sPP75VAt7fYUutopuk5Y2o++sVPuEuTzcogz5Dj8eglDESkPwR0PrlClVcG
    FLFEx9qOOidYIEa90g462niIOgkNkIpb1JRrmZEFo+yrYYdFSR2iXzC3O1f
    7JAhNwi+d4a8cOTrqynqL6p1z+hiWEub39FlWDPacELw9HSDIYY151hiiPz
    vIRQDBOjDg3Ws8fRwYNjJH4ElwjP2z+1r+sktD/kkh8jj3iWhT37JnQG72D
    7c63ovYICwEZUqS4L3vepO0pv6xewkUbfX4KBQbUPaVVgmVUcSecj85mvMx
    42g==
    -----END CERTIFICATE-----
    
  2. Import the certificate to your samlKeystore.jks with:

     keytool -importcert -alias adfssigning -keystore samlKeystore.jks -file signature.cer
    

This should be all you need, just restart Tomcat and your metadata loading should now pass.

You don't need to include the HTTPS certificate in your JDK's cacerts in case you include the following bean which configures the HTTP client (available in Spring SAML 1.0.0.RELEASE):

 <bean class="org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer"/>

Question:

Usually my Spring SAML-based Service Provider (SP) implementation works fine, but sometimes it returns this error:

[2014-07-17 16:00:58.767] boot - 1078 DEBUG [http-bio-80-exec-1] --- BaseMessageDecoder:     Successfully decoded message.
[2014-07-17 16:00:58.767] boot - 1078 DEBUG [http-bio-80-exec-1] --- BaseSAMLMessageDecoder: Checking SAML message intended destination endpoint against receiver endpoint
[2014-07-17 16:00:58.768] boot - 1078 DEBUG [http-bio-80-exec-1] --- BaseSAMLMessageDecoder: Intended message destination endpoint: https://prismasp.cloud.reply.eu:443/MIUR_PRISMA-2.1-WEBUI/saml/SSO/alias/defaultAlias
[2014-07-17 16:00:58.768] boot - 1078 DEBUG [http-bio-80-exec-1] --- BaseSAMLMessageDecoder: Actual message receiver endpoint: http://prismasp.cloud.reply.eu:443/MIUR_PRISMA-2.1-WEBUI/saml/SSO/alias/defaultAlias
[2014-07-17 16:00:58.768] boot - 1078 ERROR [http-bio-80-exec-1] --- BaseSAMLMessageDecoder: SAML message intended destination endpoint 'https://prismasp.cloud.reply.eu:443/MIUR_PRISMA-2.1-WEBUI/saml/SSO/alias/defaultAlias' did not match the recipient endpoint 'http://prismasp.cloud.reply.eu:443/MIUR_PRISMA-2.1-WEBUI/saml/SSO/alias/defaultAlias'
[2014-07-17 16:00:58.782] boot - 1078 DEBUG [http-bio-80-exec-1] --- SAMLProcessingFilter: Incoming SAML message is invalid
org.opensaml.xml.security.SecurityException: SAML message intended destination endpoint did not match recipient endpoint
...

I'm using (as default setting of Spring Security) the HTTP Strict Transport Security (HSTS) on Tomcat 7 with SSL enabled.

Is there a way to fix this error?


Note: sample source code is on Github: vdenotaris/spring-boot-security-saml-sample.


Answer:

I don't know why is your problem occurring randomly, but at least one way to fix it is by configuring SAMLContextProviderLB instead of your current SAMLContextProviderImpl.

The SAMLContextProviderLB is typically used to tell Spring SAML public about the public URL used on a reverse proxy or load balancer, but in this case you can use to force Spring SAML to think it's using HTTPS. You can find details in chapter 10.1 Advanced Configuration of the Spring SAML manual.

You should also make sure to properly set property entityBaseURL on your MetadataGenerator bean, because without doing so the generated metadata will depend on whether you made first request to your application using http or https. Again, all of this is documented.

Question:

We have a Spring MVC (4.0.5) application with Spring Security (3.2.4) which includes CSRF protection which works fine. We are now adding the SAML security extension (spring-security-saml2-core 1.0.0) which causes an issue with CSRF protection.

The metadata has been configured on SSOCircle and trying to access http://localhost:8080/myapp directs to the login page on SSOCircle. After authentication, the browser redirects to http://localhost:8080/myapp/saml/SSO and generates an error:

HTTP Status 403 - Expected CSRF token not found. Has your session expired?

If we turn off CSRF protection, everything works. How can we maintain CSRF protection and still use the SAML extension?

Before setting up the SAML extension, we used a login form and CSRF protection worked and we did not receive an error on the login JSP and it did not have the token.

Code before SAML:

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.authorizeRequests()
            .antMatchers("/login", "/login.request", "/logout").permitAll()
            .anyRequest()
                .hasAnyAuthority("MyRole")
                    .and().formLogin()
            .loginPage("/login.request").loginProcessingUrl("/login")
            .failureUrl("/login.request?error").permitAll().and().logout()
            .logoutUrl("/logout").permitAll()
            .logoutSuccessUrl("/login.request");
}

Code with SAML:

@Override
protected void configure(HttpSecurity http) throws Exception {
    //http.csrf().disable();

    http.httpBasic().authenticationEntryPoint(samlEntryPoint());

    http.addFilterBefore(metadataGeneratorFilter(),
            ChannelProcessingFilter.class).addFilterAfter(samlFilter(),
            BasicAuthenticationFilter.class);

    http
        .authorizeRequests()
            .antMatchers("/error").permitAll()
            .antMatchers("/saml/**").permitAll()
            .anyRequest()
                .hasAnyAuthority("MyRole")
            .anyRequest().authenticated();

    http.logout().logoutSuccessUrl("/");
}

UPDATE

After re-enabling CSRF protection and setting logging to DEBUG, here are the logs that occur right after the successful authentication:

22.10.2014 16:54:17.374 [http-bio-8080-exec-8] DEBUG o.s.w.m.support.MultipartFilter -
                Using MultipartResolver 'filterMultipartResolver' for MultipartFilter

22.10.2014 16:54:17.377 [http-bio-8080-exec-8] DEBUG o.s.b.f.s.DefaultListableBeanFactory -
                Returning cached instance of singleton bean 'filterMultipartResolver'

22.10.2014 16:54:17.788 [http-bio-8080-exec-8] DEBUG o.s.w.m.support.MultipartFilter -
                Request [/epass/saml/SSO] is not a multipart request

22.10.2014 16:54:17.790 [http-bio-8080-exec-8] DEBUG o.s.s.w.u.m.AntPathRequestMatcher -
                Checking match of request : '/saml/sso'; against '/resources/**'

22.10.2014 16:54:17.791 [http-bio-8080-exec-8] DEBUG o.s.security.web.FilterChainProxy -
                /saml/SSO at position 1 of 14 in additional filter chain; firing Filter: 'MetadataGeneratorFilter'

22.10.2014 16:54:17.793 [http-bio-8080-exec-8] DEBUG o.s.security.web.FilterChainProxy -
                /saml/SSO at position 2 of 14 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'

22.10.2014 16:54:17.795 [http-bio-8080-exec-8] DEBUG o.s.security.web.FilterChainProxy -
                /saml/SSO at position 3 of 14 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'

22.10.2014 16:54:17.797 [http-bio-8080-exec-8] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository -
                HttpSession returned null object for SPRING_SECURITY_CONTEXT

22.10.2014 16:54:17.798 [http-bio-8080-exec-8] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository -
                No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@b08c9c9. A new one will be created.

22.10.2014 16:54:17.800 [http-bio-8080-exec-8] DEBUG o.s.security.web.FilterChainProxy -
                /saml/SSO at position 4 of 14 in additional filter chain; firing Filter: 'HeaderWriterFilter'

22.10.2014 16:54:17.801 [http-bio-8080-exec-8] DEBUG o.s.s.w.h.writers.HstsHeaderWriter -
                Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@244a79ef

22.10.2014 16:54:17.802 [http-bio-8080-exec-8] DEBUG o.s.security.web.FilterChainProxy -
                /saml/SSO at position 5 of 14 in additional filter chain; firing Filter: 'CsrfFilter'
22.10.2014 16:54:17.805 [http-bio-8080-exec-8] DEBUG o.s.security.web.csrf.CsrfFilter -
                Invalid CSRF token found for `http://localhost:8080/myapp/saml/SSO`

22.10.2014 16:54:17.807 [http-bio-8080-exec-8] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository -
                SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.

22.10.2014 16:54:17.808 [http-bio-8080-exec-8] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter -
                SecurityContextHolder now cleared, as request processing completed

Answer:

You have at least two options.

One is to implement a custom RequestMatcher (org.springframework.security.web.util.RequestMatcher) which will not match on Spring SAML URLs and provide this to the csrf configuration with:

http.csrf().requireCsrfProtectionMatcher(matcher);

The other, easier is to define Spring SAML endpoints in a separate http configuration which will not have the csrf protection enabled.

The XML configuration to do this can be similar to:

<!-- SAML processing endpoints -->
<security:http pattern="/saml/**" entry-point-ref="samlEntryPoint">
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
    <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</security:http>

<!-- Secured pages with SAML as entry point -->
<security:http entry-point-ref="samlEntryPoint">
    <security:csrf />
    <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
</security:http>

For Java configuration something like this should work:

@Configuration
@EnableWebSecurity
public class MutlipleHttpConfigurationConfig {

    @Configuration
    @Order(1)
    public static class SAMLWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/saml/**");
            http.csrf().disable();
            http.httpBasic().authenticationEntryPoint(samlEntryPoint());
            http.addFilterBefore(metadataGeneratorFilter(),
                    ChannelProcessingFilter.class).addFilterAfter(samlFilter(),
                    BasicAuthenticationFilter.class);
        }
    }

    @Configuration
    public static class BasicWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        protected void configure(HttpSecurity http) throws Exception {
            http.httpBasic().authenticationEntryPoint(samlEntryPoint());
            http.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class);
            http
                    .authorizeRequests()
                    .antMatchers("/error").permitAll()
                    .anyRequest()
                    .hasAnyAuthority("MyRole")
                    .anyRequest().authenticated();

            http.logout().logoutSuccessUrl("/");
        }
    }
}

Details on defining multiple http configuration with Java configuration can be found in the Spring Security manual.

Question:

I am working on integrating Spring SAML Extension within our appliaction and for SSO with one of our client's ADFS2.0 as the IDP we have generated Service provider meta data from our appliaction and imported ADFS meta data into our appliaction.When i select the clients idp and click on start single sign and give the proper clients credentials we are seeing the the SAML response as follows:

Saml Response.

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"  Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"  
Destination="https://sso.spire2grow.com:8443/<our application>/saml/SSO" ID="_d7fa7cb7-a858-4d4e-aa4c-bf7a5d11e485" 
InResponseTo="a2icei36d347di68gi33534cc13fd1" IssueInstant="2014-09-30T14:17:21.819Z" Version="2.0"><Issuer 
xmlns="urn:oasis:names:tc:SAML:2.0:assertion"><Clients ADFS trust services URL></Issuer><samlp:Status><samlp:StatusCode 
Value="urn:oasis:names:tc:SAML:2.0:status:Responder"></samlp:StatusCode></samlp:Status></samlp:Response>

but also i am seeing following exception being thrown as the service provider is not able to validate the message.

Exception message:

[351545]2014-09-30 19:47:21,714 DEBUG - SAML message intended destination endpoint matched recipient endpoint
[351545]2014-09-30 19:47:21,714 DEBUG - Authentication attempt using org.springframework.security.saml.SAMLAuthenticationProvider
[351545]2014-09-30 19:47:21,715 DEBUG - Error validating SAML message
org.opensaml.common.SAMLException: Response has invalid status code urn:oasis:names:tc:SAML:2.0:status:Responder, status message is null
    at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:113)
    at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:82)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
    at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:84)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:195)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)

Could any one please point out if i mising any thing here.

UPDATE:

After seeing the answer provided for this question I saw the following error from ADFS.

Microsoft.IdentityServer.Protocols.Saml.SamlProtocolSignatureAlgorithmMismatchException: MSIS7093: The message is not signed with expected signature algorithm. Message is signed with signature algorithm http://www.w3.org/2000/09/xmldsig#rsa-sha1. Expected signature algorithm http://www.w3.org/2001/04/xmldsig-more#rsa-sha256. at Microsoft.IdentityServer.Web.Protocols.Saml.SamlProtocolManager.ValidateSignatureRequirements(SamlMessage samlMessage) at Microsoft.IdentityServer.Web.Protocols.Saml.SamlProtocolManager.Issue(HttpSamlRequestMessage httpSamlRequestMessage, SecurityTokenElement onBehalfOf, String sessionState, String relayState, String& newSamlSession, String& samlpAuthenticationProvider, Boolean isUrlTranslationNeeded, WrappedHttpListenerContext context, Boolean isKmsiRequested)

But after seeing this we did change the Signing algorithm on the relying trust party to rsa-sha256 but still its displaying the same message .

Do we need a genuine certificate for rsa-sha256? Will the self-signed certificate properly work?


Answer:

The exception from ADFS complains that the SAML message was not signed with RSA-SHA256 which it expects, but with RSA-SHA1.

Make sure to set signing algorithm of the Spring SAML's Relaying Party in ADFS to SHA-1. You can find details in the last bullet point of http://docs.spring.io/autorepo/docs/spring-security-saml/1.0.x-SNAPSHOT/reference/htmlsingle/#chapter-idp-guide-adfs-sp

Question:

I've implemented SSO using Spring SAML and everything is working fine. It worked with the following IDP's till now: 1) idp.ssocircle.com 2) openidp.feide.no

Now I'm testing with salesforce.com as my Identity Provider. As there is no provision to upload Service Provider Metadata I've done the following configuration settings at its IdP:

Gave my entityID and Assertion Consumer Service URL. I also uploaded my SP certificate. I've downloaded its metadata (idp metadata) which is as follows (hiding the sensitive information):

<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://ABC-dev-ed.my.salesforce.com" validUntil="2024-04-11T13:55:57.307Z">
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
  <md:KeyDescriptor use="signing">
     <ds:KeyInfo>
        <ds:X509Data>
           <ds:X509Certificate>XXXXXXXXX</ds:X509Certificate>
        </ds:X509Data>
     </ds:KeyInfo>
  </md:KeyDescriptor>
  <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
  <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ABC-dev-ed.my.salesforce.com/idp/endpoint/HttpPost"/>
  <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://ABC-dev-ed.my.salesforce.com/idp/endpoint/HttpRedirect"/>

Now when I tried to test my SP, first it redirected me to the IDP(salesforce) asking for credentials where I entered them but then after that I was redirected back to my Assertion consumer Service URL(which is my SP) but here an exception was generated saying that

HTTP Status 401 - This request requires HTTP authentication(Authentication Failed: Incoming SAML message is invalid).

I've tried the following but didn't work :( - Though not necessary, I've downloaded the certificate file from the salesforce and imported it to my keystore.jks so that to make sure that key is used for signature validation.(Not necessary due to the certificate info already present in IDP metadata).

Here is what I found in my log file(Adding necessary info only after successful AuthnRequest):

AuthNRequest;SUCCESS;127.0.0.1
.....STARTED_FAILING_HERE.....
Attempting to extract credential from an X509Data
Found 1 X509Certificates
Found 0 X509CRLs
Single certificate was present, treating as end-entity certificate
Credentials successfully extracted from child {http://www.w3.org/2000/09/xmldsig#}X509Data by provider org.opensaml.xml.security.keyinfo.provider.InlineX509DataProvider
A total of 1 credentials were resolved
Registry could not locate evaluable criteria for criteria class org.opensaml.xml.security.keyinfo.KeyInfoCriteria
Attempting to validate signature using key from supplied credential
Creating XMLSignature object
Validating signature with signature algorithm URI: http://www.w3.org/2000/09/xmldsig#rsa-sha1
Validation credential key algorithm 'RSA', key instance class 'sun.security.rsa.RSAPublicKeyImpl'
Signature validated with key from supplied credential
Signature validation using candidate credential was successful
Successfully verified signature using KeyInfo-derived credential
Attempting to establish trust of KeyInfo-derived credential
Failed to validate untrusted credential against trusted key
Failed to establish trust of KeyInfo-derived credential
Failed to verify signature and/or establish trust using any KeyInfo-derived credentials
Attempting to verify signature using trusted credentials
Attempting to validate signature using key from supplied credential
Creating XMLSignature object
Validating signature with signature algorithm URI: http://www.w3.org/2000/09/xmldsig#rsa-sha1
Validation credential key algorithm 'RSA', key instance class 'sun.security.rsa.RSAPublicKeyImpl'
Signature did not validate against the credential's key
Signature validation using candidate validation credential failed
org.opensaml.xml.validation.ValidationException: Signature did not validate against the credential's key
at org.opensaml.xml.signature.SignatureValidator.validate(SignatureValidator.java:79)
at org.opensaml.xml.signature.impl.BaseSignatureTrustEngine.verifySignature(BaseSignatureTrustEngine.java:142)
at org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine.validate(ExplicitKeySignatureTrustEngine.java:110)
at org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine.validate(ExplicitKeySignatureTrustEngine.java:49)
at org.opensaml.ws.security.provider.BaseTrustEngineRule.evaluate(BaseTrustEngineRule.java:104)
at org.opensaml.ws.security.provider.BaseTrustEngineRule.evaluate(BaseTrustEngineRule.java:91)
at org.opensaml.common.binding.security.SAMLProtocolMessageXMLSignatureSecurityPolicyRule.doEvaluate(SAMLProtocolMessageXMLSignatureSecurityPolicyRule.java:128)
at org.opensaml.common.binding.security.SAMLProtocolMessageXMLSignatureSecurityPolicyRule.evaluate(SAMLProtocolMessageXMLSignatureSecurityPolicyRule.java:107)
at org.opensaml.ws.security.provider.BasicSecurityPolicy.evaluate(BasicSecurityPolicy.java:51)
at org.opensaml.ws.message.decoder.BaseMessageDecoder.processSecurityPolicy(BaseMessageDecoder.java:132)
at org.opensaml.ws.message.decoder.BaseMessageDecoder.decode(BaseMessageDecoder.java:83)
at org.opensaml.saml2.binding.decoding.BaseSAML2MessageDecoder.decode(BaseSAML2MessageDecoder.java:70)
at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:105)
at org.springframework.security.saml.processor.SAMLProcessorImpl.retrieveMessage(SAMLProcessorImpl.java:172)
at org.springframework.security.saml.SAMLProcessingFilter.attemptAuthentication(SAMLProcessingFilter.java:77)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:195)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:166)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:462)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:403)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:301)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:162)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:140)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:309)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Failed to verify signature using either KeyInfo-derived or directly trusted credentials
Validation of protocol message signature failed for context issuer 'https://ABC-dev-ed.my.salesforce.com', message type: {urn:oasis:names:tc:SAML:2.0:protocol}Response
Authentication request failed: org.springframework.security.authentication.AuthenticationServiceException: Incoming SAML message is invalid
Updated SecurityContextHolder to contain null Authentication
Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@153a591

Can someone please tell me what is going wrong by looking at the above log. Any help would be highly appreciated.

Thanks,

Abhilash


Answer:

Your IDP is using a different key for digital signatures than it defines in metadata.

You should inspect the SAML message you received and look for element X509Certificate inside element Signature. Extract the content of the certificate into a separate file, e.g. sales-force-sign.cer

You then need to import the certificate into your samlKeystore.jks, you can find details on how to do it in chapter 4.5 (Key management) of the Spring SAML manual. Make sure to note the alias you import the key with.

As last step you need to tell Spring SAML to use the newly imported key for signature verifications for your IDP, for that you should update your securityContext.xml and update your ExtendedMetadta for your IDP with property signingKey and value of the alias you used earlier to import the key. It will look similar to:

  <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
      <constructor-arg>
          <bean class="org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider">
              <constructor-arg>
                  <value type="java.io.File">classpath:salesforce_metadata.xml</value>
              </constructor-arg>
              <property name="parserPool" ref="parserPool"/>
          </bean>
      </constructor-arg>
      <constructor-arg>
          <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
              <property name="signingKey" value="sf-proxy"/>
          </bean>
      </constructor-arg>
  </bean>

Again you can find details on all of this in the manual.

Alternatively you can simply add the key you extracted from the message into your IDP metadata. Just manualy update the XML file and add another KeyDescriptor with use="signing". It might be faster to do.

Question:

I'm using the Spring Security SAML 2.0 sample webapp on Tomcat 7 and have modified it to try to get it to authenticate against a Ping Identity service. The webapp is talking to the service and it's sending back an assertion, but it's failing when trying to verify the signature, as shown by the debug output below:

- Attempting to verify signature and establish trust using KeyInfo-derived credentials
- Signature contained no KeyInfo element, could not resolve verification credentials
- Failed to verify signature and/or establish trust using any KeyInfo-derived credentials
- Attempting to verify signature using trusted credentials
- Failed to verify signature using either KeyInfo-derived or directly trusted credentials
- Validation of received assertion failed, assertion will be skipped
org.opensaml.xml.validation.ValidationException: Signature is not trusted or invalid

I understand that it's not able to verify the signature, and I have been given a certificate by the Ping Identity admins to use, but I'm unsure of how to include it in the application. I've tried adding it to the JKS (keystore) that comes with the sample application using the JDK's keytool program, but it can't seem to find it in there. I've also tried adding it to the service provider's metadata xml file like this:

<md:KeyDescriptor use="signing">
    <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:X509Data>
            <ds:X509Certificate>
                [Certificate is here...]
            </ds:X509Certificate>
        </ds:X509Data>
    </ds:KeyInfo>
</md:KeyDescriptor>

However it still returns the same error.

Is there a specific place I should put the certificate in order to validate the signature? I'm relatively new to SAML and application security in general, so I apologise if I'm using the wrong terminology.


Answer:

Finally got this to work. Turns out I'd missed out a line of configuration in the security context file, and that (it appears as though) no X509 certificate definition was needed in the service provider's metadata XML file.

Basically I'd already imported the public key I'd been provided with into the existing JKS (using keytool), but I hadn't told the application to specifically use this. In order to do this, I had to go into the security context file (in my case "securityContext.xml") and add the following line to the ExtendedMetadata bean definition for the SP's metadata xml file:

<property name="signingKey" value="[alias of the provided key in the JKS goes here]"/>

Hence after this modification, the ExtendedMetadataDelegate bean definition looked like this:

<bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
  <constructor-arg>
    <bean class="org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider">
      <constructor-arg>
        <value type="java.io.File">classpath:security/[Path to SP metadata xml file].xml</value>
      </constructor-arg>
      <property name="parserPool" ref="parserPool" />
    </bean>
  </constructor-arg>
  <constructor-arg>
    <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
      <property name="alias" value="[SP alias goes here]" />
      <property name="signingKey" value="[alias of the provided key in the JKS goes here]"/>
    </bean>
  </constructor-arg>
</bean>

Hope this helps anyone who might be in a similar situation.