I'm trying to setup POC using Spring Security, Spring Security SAML and Keycloak. For this, I'm using the simple-service-provider sample which is provided by the Spring SAML core project.
I managed to get the SAML setup working as long as Keycloak does not require the client signature (Client Signature Required disabled). When I enable this option, I get the following error and stacktrace in Keycloak
07:07:40,385 WARN [org.keycloak.events] (default task-8) type=LOGIN_ERROR, realmId=ea-localhost, clientId=null, userId=null, ipAddress=172.17.0.1, error=invalid_signature
07:07:44,961 ERROR [org.keycloak.protocol.saml.SamlService] (default task-7) request validation failed: org.keycloak.common.VerificationException: SigAlg was null
at org.keycloak.protocol.saml.SamlProtocolUtils.verifyRedirectSignature(SamlProtocolUtils.java:135)
at org.keycloak.protocol.saml.SamlService$RedirectBindingProtocol.verifySignature(SamlService.java:518)
at org.keycloak.protocol.saml.SamlService$BindingProtocol.handleSamlRequest(SamlService.java:233)
at org.keycloak.protocol.saml.SamlService$BindingProtocol.execute(SamlService.java:478)
at org.keycloak.protocol.saml.SamlService.redirectBinding(SamlService.java:553)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
The Spring configuration is as follows
spring:
thymeleaf:
cache: false
security:
saml2:
service-provider:
entity-id: spring-saml-test
sign-metadata: false
sign-requests: true
want-assertions-signed: true
keys:
active:
- name: key
type: SIGNING
private-key: |
-----BEGIN RSA PRIVATE KEY-----
SNIP
-----END RSA PRIVATE KEY-----
passphrase: SNIP
certificate: |
-----BEGIN CERTIFICATE-----
SNIP
-----END CERTIFICATE-----
- name: key
type: ENCRYPTION
private-key: |
-----BEGIN RSA PRIVATE KEY-----
SNIP
-----END RSA PRIVATE KEY-----
passphrase: SNIP
certificate: |
-----BEGIN CERTIFICATE-----
SNIP
-----END CERTIFICATE-----
providers:
- name: keycloak
metadata: |
<?xml version="1.0" encoding="UTF-8"?>
<EntityDescriptor entityID="http://localhost:8090/auth/realms/ea-localhost"
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8090/auth/realms/ea-localhost/protocol/saml" />
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8090/auth/realms/ea-localhost/protocol/saml" />
<KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:KeyName>tURdSWRSOIQXpkOdAZzAevrtov8QU5ea0dqJpng2hSY</dsig:KeyName>
<dsig:X509Data>
<dsig:X509Certificate>SNIP</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
</IDPSSODescriptor>
</EntityDescriptor>
link-text: KEYCLOAK LOCAL IDP
name-id: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
This is a sample request which is sent to keycloak
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest AssertionConsumerServiceIndex="0" AssertionConsumerServiceURL="http://localhost:8080/sample-sp/saml/sp/SSO" Destination="http://localhost:8090/auth/realms/ea-localhost/protocol/saml" ForceAuthn="false" ID="1a072bcf-2822-424d-98ea-5e1f0c3b83b7" IsPassive="false" IssueInstant="2018-05-31T08:00:42.939Z" 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">spring-saml-test</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="#1a072bcf-2822-424d-98ea-5e1f0c3b83b7">
<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>R/D3Qk8KmcZdTwBZypDDq+D8lcMpvYcElmiwg01dYK0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
gldzIzX2Ti+nHhz99jQgLwLQ1IZnNJBGM39MpBo7pcFmWQ83Y4R4Bv+OfGbdmqO8GTbZo86zjRM0
+c1w+/QFvbZv4hEudIFuuDbzgCcTG2tyFau525+T7IZcBuPXexYEE+JX/y9cZifo7ws7EolfUC/V
e3qHlYGzOx/cPx6qPem6QawDaU8X46WkYDIOjAJGxrbqGY8fR3YC+PGndD4/+47Zrcp58REBUDPH
X680RZJP+06nnOIS5seKuIOyzEYmz8FLrsN2RLy0QnR3Qws+aWoP0ut04CFgmpcV5JmmNpMXASIT
86Xy53N6q1XvXqAhZuwG1WUriJBZD0mCPDmDhA==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>SNIP</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<saml2p:RequestedAuthnContext Comparison="exact"/>
</saml2p:AuthnRequest>
I've tried different combinations but as soon as Keycloak verifies the signature, the exception is thrown. The code in Keycloak which causes the exception is this:
public static void verifyRedirectSignature(SAMLDocumentHolder documentHolder, KeyLocator locator, UriInfo uriInformation, String paramKey) throws VerificationException {
MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
String request = encodedParams.getFirst(paramKey);
String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
String relayState = encodedParams.getFirst(GeneralConstants.RELAY_STATE);
String decodedAlgorithm = uriInformation.getQueryParameters(true).getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
if (request == null) throw new VerificationException("SAM was null");
if (algorithm == null) throw new VerificationException("SigAlg was null");
if (signature == null) throw new VerificationException("Signature was null");
What am I doing wrong here ? Or is it a bug in Keycloak of Spring Saml ? I'm using Keycloak 4.0.0.Beta2 but the same issue occurs in the latest v3. I've pulled the latest spring-security-saml code from github (2.0.0.BUILD-SNAPSHOT, 48ccd77ebabb5465af81a53b7095cdfb466a62b5).