KEYCLOAK-13698 - SAML Client - Add certificate info to signature

Adds the X509Data tag to the XML Document signature in AuthnRequests
This commit is contained in:
Luca Leonardo Scorcia 2020-07-10 23:06:37 +02:00 committed by GitHub
parent 7087c081f0
commit f8a4f66d6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 16 deletions

View file

@ -52,7 +52,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -110,12 +109,10 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
if (getConfig().isWantAuthnRequestsSigned()) {
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
String keyName = getConfig().getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
binding.signWith(keyName, keypair);
binding.signatureAlgorithm(getSignatureAlgorithm());
binding.signDocument();
binding.signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(getSignatureAlgorithm())
.signDocument();
if (! postBinding && getConfig().isAddExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
authnRequestBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keyName));
}

View file

@ -53,6 +53,7 @@ import java.security.Key;
import java.security.KeyManagementException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@ -123,16 +124,21 @@ public class SamlClient {
@Override
public HttpPost createSamlUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest) {
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_REQUEST_KEY, null, null);
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_REQUEST_KEY, null, null, null);
}
@Override
public HttpPost createSamlUnsignedResponse(URI samlEndpoint, String relayState, Document samlRequest) {
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_RESPONSE_KEY, null, null);
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_RESPONSE_KEY, null, null, null);
}
@Override
public HttpUriRequest createSamlSignedResponse(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey) {
return createSamlSignedResponse(samlEndpoint, relayState, samlRequest, realmPrivateKey, realmPublicKey, null);
}
@Override
public HttpUriRequest createSamlSignedResponse(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey, String certificateStr) {
return null;
}
@ -146,10 +152,15 @@ public class SamlClient {
@Override
public HttpPost createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey) {
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_REQUEST_KEY, realmPrivateKey, realmPublicKey);
return createSamlSignedRequest(samlEndpoint, relayState, samlRequest, realmPrivateKey, realmPublicKey, null);
}
private HttpPost createSamlPostMessage(URI samlEndpoint, String relayState, Document samlRequest, String messageType, String privateKeyStr, String publicKeyStr) {
@Override
public HttpPost createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey, String certificateStr) {
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_REQUEST_KEY, realmPrivateKey, realmPublicKey, certificateStr);
}
private HttpPost createSamlPostMessage(URI samlEndpoint, String relayState, Document samlRequest, String messageType, String privateKeyStr, String publicKeyStr, String certificateStr) {
HttpPost post = new HttpPost(samlEndpoint);
List<NameValuePair> parameters = new LinkedList<>();
@ -161,9 +172,10 @@ public class SamlClient {
if (privateKeyStr != null && publicKeyStr != null) {
PrivateKey privateKey = org.keycloak.testsuite.util.KeyUtils.privateKeyFromString(privateKeyStr);
PublicKey publicKey = org.keycloak.testsuite.util.KeyUtils.publicKeyFromString(publicKeyStr);
X509Certificate cert = org.keycloak.common.util.PemUtils.decodeCertificate(certificateStr);
binding
.signatureAlgorithm(SignatureAlgorithm.RSA_SHA256)
.signWith(KeyUtils.createKeyId(privateKey), privateKey, publicKey)
.signWith(KeyUtils.createKeyId(privateKey), privateKey, publicKey, cert)
.signDocument();
}
@ -242,6 +254,11 @@ public class SamlClient {
@Override
public HttpUriRequest createSamlSignedResponse(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey) {
return createSamlSignedResponse(samlEndpoint, relayState, samlRequest, realmPrivateKey, realmPublicKey, null);
}
@Override
public HttpUriRequest createSamlSignedResponse(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey, String certificateStr) {
try {
BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
@ -249,9 +266,10 @@ public class SamlClient {
if (realmPrivateKey != null && realmPublicKey != null) {
PrivateKey privateKey = org.keycloak.testsuite.util.KeyUtils.privateKeyFromString(realmPrivateKey);
PublicKey publicKey = org.keycloak.testsuite.util.KeyUtils.publicKeyFromString(realmPublicKey);
X509Certificate cert = org.keycloak.common.util.PemUtils.decodeCertificate(certificateStr);
binding
.signatureAlgorithm(SignatureAlgorithm.RSA_SHA256)
.signWith(KeyUtils.createKeyId(privateKey), privateKey, publicKey)
.signWith(KeyUtils.createKeyId(privateKey), privateKey, publicKey, cert)
.signDocument();
}
@ -273,13 +291,19 @@ public class SamlClient {
@Override
public HttpUriRequest createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String privateKeyStr, String publicKeyStr) {
return createSamlSignedRequest(samlEndpoint, relayState, samlRequest, privateKeyStr, publicKeyStr, null);
}
@Override
public HttpUriRequest createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String privateKeyStr, String publicKeyStr, String certificateStr) {
try {
BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder().relayState(relayState);
if (privateKeyStr != null && publicKeyStr != null) {
PrivateKey privateKey = org.keycloak.testsuite.util.KeyUtils.privateKeyFromString(privateKeyStr);
PublicKey publicKey = org.keycloak.testsuite.util.KeyUtils.publicKeyFromString(publicKeyStr);
X509Certificate cert = org.keycloak.common.util.PemUtils.decodeCertificate(certificateStr);
binding.signatureAlgorithm(SignatureAlgorithm.RSA_SHA256)
.signWith(KeyUtils.createKeyId(privateKey), privateKey, publicKey)
.signWith(KeyUtils.createKeyId(privateKey), privateKey, publicKey, cert)
.signDocument();
}
return new HttpGet(binding.redirectBinding(samlRequest).requestURI(samlEndpoint.toString()));
@ -299,12 +323,16 @@ public class SamlClient {
public abstract HttpUriRequest createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey);
public abstract HttpUriRequest createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey, String certificateStr);
public abstract URI getBindingUri();
public abstract HttpUriRequest createSamlUnsignedResponse(URI samlEndpoint, String relayState, Document samlRequest);
public abstract HttpUriRequest createSamlSignedResponse(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey);
public abstract HttpUriRequest createSamlSignedResponse(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey, String certificateStr);
public abstract String extractRelayState(CloseableHttpResponse response) throws IOException;
}
@ -510,4 +538,4 @@ public class SamlClient {
protected HttpClientBuilder createHttpClientBuilderInstance() {
return HttpClientBuilder.create();
}
}
}

View file

@ -43,6 +43,7 @@ public class CreateAuthnRequestStepBuilder extends SamlDocumentStepBuilder<Authn
private final String assertionConsumerURL;
private String signingPublicKeyPem; // TODO: should not be needed
private String signingPrivateKeyPem;
private String signingCertificate;
private final Document forceLoginRequestDocument;
@ -80,8 +81,13 @@ public class CreateAuthnRequestStepBuilder extends SamlDocumentStepBuilder<Authn
}
public CreateAuthnRequestStepBuilder signWith(String signingPrivateKeyPem, String signingPublicKeyPem) {
return signWith(signingPrivateKeyPem, signingPublicKeyPem, null);
}
public CreateAuthnRequestStepBuilder signWith(String signingPrivateKeyPem, String signingPublicKeyPem, String signingCertificate) {
this.signingPrivateKeyPem = signingPrivateKeyPem;
this.signingPublicKeyPem = signingPublicKeyPem;
this.signingCertificate = signingCertificate;
return this;
}
@ -100,7 +106,7 @@ public class CreateAuthnRequestStepBuilder extends SamlDocumentStepBuilder<Authn
String relayState = this.relayState == null ? null : this.relayState.get();
return this.signingPrivateKeyPem == null
? requestBinding.createSamlUnsignedRequest(authServerSamlUrl, relayState, samlDoc)
: requestBinding.createSamlSignedRequest(authServerSamlUrl, relayState, samlDoc, signingPrivateKeyPem, signingPublicKeyPem);
: requestBinding.createSamlSignedRequest(authServerSamlUrl, relayState, samlDoc, signingPrivateKeyPem, signingPublicKeyPem, signingCertificate);
}
protected Document createLoginRequestDocument() {

View file

@ -44,6 +44,7 @@ public class CreateLogoutRequestStepBuilder extends SamlDocumentStepBuilder<Logo
private Supplier<String> relayState = () -> null;
private String signingPublicKeyPem; // TODO: should not be needed
private String signingPrivateKeyPem;
private String signingCertificate;
public CreateLogoutRequestStepBuilder(URI authServerSamlUrl, String issuer, Binding requestBinding, SamlClientBuilder clientBuilder) {
super(clientBuilder);
@ -95,8 +96,13 @@ public class CreateLogoutRequestStepBuilder extends SamlDocumentStepBuilder<Logo
}
public CreateLogoutRequestStepBuilder signWith(String signingPrivateKeyPem, String signingPublicKeyPem) {
return signWith(signingPrivateKeyPem, signingPublicKeyPem, null);
}
public CreateLogoutRequestStepBuilder signWith(String signingPrivateKeyPem, String signingPublicKeyPem, String signingCertificate) {
this.signingPrivateKeyPem = signingPrivateKeyPem;
this.signingPublicKeyPem = signingPublicKeyPem;
this.signingCertificate = signingCertificate;
return this;
}
@ -117,7 +123,7 @@ public class CreateLogoutRequestStepBuilder extends SamlDocumentStepBuilder<Logo
return this.signingPrivateKeyPem == null
? requestBinding.createSamlUnsignedRequest(authServerSamlUrl, relayState(), DocumentUtil.getDocument(transformed))
: requestBinding.createSamlSignedRequest(authServerSamlUrl, relayState(), DocumentUtil.getDocument(transformed), signingPrivateKeyPem, signingPublicKeyPem);
: requestBinding.createSamlSignedRequest(authServerSamlUrl, relayState(), DocumentUtil.getDocument(transformed), signingPrivateKeyPem, signingPublicKeyPem, signingCertificate);
}
}

View file

@ -448,4 +448,62 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
}, producerSignDocument, producerSignAssertions, producerEncryptAssertions);
}
@Test
public void testSignatureDataWhenWantsRequestsSigned() throws Exception {
// Verifies that an AuthnRequest contains the KeyInfo/X509Data element when
// client AuthnRequest signature is requested
String providerCert = KeyUtils.getActiveKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue());
String consumerCert = KeyUtils.getActiveKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(consumerCert, Matchers.notNullValue());
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.VALIDATE_SIGNATURE, Boolean.toString(true))
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_SIGNED, Boolean.toString(true))
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, Boolean.toString(false))
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "true")
.setAttribute(SAMLIdentityProviderConfig.SIGNING_CERTIFICATE_KEY, AbstractSamlTest.SAML_CLIENT_SALES_POST_SIG_EXPIRED_CERTIFICATE)
.update();
Closeable clientUpdater = ClientAttributeUpdater.forClient(adminClient, bc.providerRealmName(), bc.getIDPClientIdInProviderRealm())
.setAttribute(SamlConfigAttributes.SAML_ENCRYPT, Boolean.toString(false))
.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true")
.setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, Boolean.toString(true))
.setAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "false")
.update())
{
// Build the login request document
AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(AbstractSamlTest.SAML_CLIENT_ID_SALES_POST + ".dot/ted", getConsumerRoot() + "/sales-post/saml", null);
Document doc = SAML2Request.convert(loginRep);
new SamlClientBuilder()
.authnRequest(getConsumerSamlEndpoint(bc.consumerRealmName()), doc, Binding.POST)
.build() // Request to consumer IdP
.login().idp(bc.getIDPAlias()).build()
.processSamlResponse(Binding.POST) // AuthnRequest to producer IdP
.targetAttributeSamlRequest()
.transformDocument(this::extractNamespacesToTopLevelElement)
.transformDocument((document) -> {
try
{
// Find the Signature element
Element signatureElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), XMLSignature.XMLNS, "Signature");
Assert.assertThat("Signature element not found in request document", signatureElement, Matchers.notNullValue());
// Find the KeyInfo element
Element keyInfoElement = DocumentUtil.getDirectChildElement(signatureElement, XMLSignature.XMLNS, "KeyInfo");
Assert.assertThat("KeyInfo element not found in request Signature element", keyInfoElement, Matchers.notNullValue());
// Find the X509Data element
Element x509DataElement = DocumentUtil.getDirectChildElement(keyInfoElement, XMLSignature.XMLNS, "X509Data");
Assert.assertThat("X509Data element not found in request Signature/KeyInfo element", x509DataElement, Matchers.notNullValue());
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
})
.build()
.execute();
}
}
}