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:
parent
7087c081f0
commit
f8a4f66d6c
5 changed files with 111 additions and 16 deletions
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue