Prefer cert over pubkey in SAML metadata
If SAML key material was given as a certificate, consistently expose the certificate rather than just the public key when presenting SAML metadata info. This change ensures that the client obtains sufficient information (eg. issuer) to close the trust chain. Closes: #17549 Signed-off-by: Daniel Kobras <kobras@puzzle-itc.de>
This commit is contained in:
parent
142bb30f66
commit
a45b5dcd90
4 changed files with 48 additions and 12 deletions
|
@ -134,14 +134,11 @@ public class SAML2Signature {
|
||||||
dto.setSignatureMethod(signatureMethod);
|
dto.setSignatureMethod(signatureMethod);
|
||||||
dto.setReferenceURI(referenceURI);
|
dto.setReferenceURI(referenceURI);
|
||||||
dto.setNextSibling(sibling);
|
dto.setNextSibling(sibling);
|
||||||
|
dto.setX509Certificate(x509Certificate);
|
||||||
if (x509Certificate != null) {
|
|
||||||
dto.setX509Certificate(x509Certificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return XMLSignatureUtil.sign(dto, canonicalizationMethodType);
|
return XMLSignatureUtil.sign(dto, canonicalizationMethodType);
|
||||||
}
|
}
|
||||||
return XMLSignatureUtil.sign(doc, keyName, keyPair, digestMethod, signatureMethod, referenceURI, canonicalizationMethodType);
|
return XMLSignatureUtil.sign(doc, keyName, keyPair, digestMethod, signatureMethod, referenceURI, x509Certificate, canonicalizationMethodType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -224,4 +221,4 @@ public class SAML2Signature {
|
||||||
element.setIdAttribute(JBossSAMLConstants.ID.get(), true);
|
element.setIdAttribute(JBossSAMLConstants.ID.get(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -723,9 +723,7 @@ public class XMLSignatureUtil {
|
||||||
|
|
||||||
if (x509Certificate != null) {
|
if (x509Certificate != null) {
|
||||||
items.add(keyInfoFactory.newX509Data(Collections.singletonList(x509Certificate)));
|
items.add(keyInfoFactory.newX509Data(Collections.singletonList(x509Certificate)));
|
||||||
}
|
} else if (publicKey != null) {
|
||||||
|
|
||||||
if (publicKey != null) {
|
|
||||||
items.add(keyInfoFactory.newKeyValue(publicKey));
|
items.add(keyInfoFactory.newKeyValue(publicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@ import javax.xml.stream.XMLStreamWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -457,13 +458,15 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
if (getConfig().isSignSpMetadata())
|
if (getConfig().isSignSpMetadata())
|
||||||
{
|
{
|
||||||
KeyManager.ActiveRsaKey activeKey = session.keys().getActiveRsaKey(realm);
|
KeyManager.ActiveRsaKey activeKey = session.keys().getActiveRsaKey(realm);
|
||||||
String keyName = getConfig().getXmlSigKeyInfoKeyNameTransformer().getKeyName(activeKey.getKid(), activeKey.getCertificate());
|
X509Certificate certificate = activeKey.getCertificate();
|
||||||
|
String keyName = getConfig().getXmlSigKeyInfoKeyNameTransformer().getKeyName(activeKey.getKid(), certificate);
|
||||||
KeyPair keyPair = new KeyPair(activeKey.getPublicKey(), activeKey.getPrivateKey());
|
KeyPair keyPair = new KeyPair(activeKey.getPublicKey(), activeKey.getPrivateKey());
|
||||||
|
|
||||||
Document metadataDocument = DocumentUtil.getDocument(descriptor);
|
Document metadataDocument = DocumentUtil.getDocument(descriptor);
|
||||||
SAML2Signature signatureHelper = new SAML2Signature();
|
SAML2Signature signatureHelper = new SAML2Signature();
|
||||||
signatureHelper.setSignatureMethod(getSignatureAlgorithm().getXmlSignatureMethod());
|
signatureHelper.setSignatureMethod(getSignatureAlgorithm().getXmlSignatureMethod());
|
||||||
signatureHelper.setDigestMethod(getSignatureAlgorithm().getXmlSignatureDigestMethod());
|
signatureHelper.setDigestMethod(getSignatureAlgorithm().getXmlSignatureDigestMethod());
|
||||||
|
signatureHelper.setX509Certificate(certificate);
|
||||||
|
|
||||||
Node nextSibling = metadataDocument.getDocumentElement().getFirstChild();
|
Node nextSibling = metadataDocument.getDocumentElement().getFirstChild();
|
||||||
signatureHelper.setNextSibling(nextSibling);
|
signatureHelper.setNextSibling(nextSibling);
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
||||||
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
@ -41,15 +42,19 @@ import org.keycloak.representations.idm.ErrorRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderMapperTypeRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderMapperTypeRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
import org.keycloak.saml.common.util.DocumentUtil;
|
import org.keycloak.saml.common.util.DocumentUtil;
|
||||||
|
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
|
||||||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||||
|
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
import org.keycloak.testsuite.broker.OIDCIdentityProviderConfigRep;
|
import org.keycloak.testsuite.broker.OIDCIdentityProviderConfigRep;
|
||||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||||
|
import org.keycloak.testsuite.util.KeyUtils;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
@ -67,7 +72,11 @@ import java.net.URL;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -93,6 +102,8 @@ import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.XMLDSIG_NSURI;
|
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.XMLDSIG_NSURI;
|
||||||
|
|
||||||
|
import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
|
@ -1044,7 +1055,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSamlExportSignatureOn() throws URISyntaxException, IOException, ConfigurationException, ParsingException, ProcessingException {
|
public void testSamlExportSignatureOn() throws URISyntaxException, IOException, ConfigurationException, ParsingException, ProcessingException, CertificateEncodingException {
|
||||||
// Use import-config to convert IDPSSODescriptor file into key value pairs
|
// Use import-config to convert IDPSSODescriptor file into key value pairs
|
||||||
// to use when creating a SAML Identity Provider
|
// to use when creating a SAML Identity Provider
|
||||||
MultipartFormDataOutput form = new MultipartFormDataOutput();
|
MultipartFormDataOutput form = new MultipartFormDataOutput();
|
||||||
|
@ -1059,6 +1070,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
|
|
||||||
// Explicitly enable SP Metadata Signature
|
// Explicitly enable SP Metadata Signature
|
||||||
result.put(SAMLIdentityProviderConfig.SIGN_SP_METADATA, "true");
|
result.put(SAMLIdentityProviderConfig.SIGN_SP_METADATA, "true");
|
||||||
|
result.put(SAMLIdentityProviderConfig.XML_SIG_KEY_INFO_KEY_NAME_TRANSFORMER, XmlKeyInfoKeyNameTransformer.CERT_SUBJECT.name());
|
||||||
|
|
||||||
// Create new SAML identity provider using configuration retrieved from import-config
|
// Create new SAML identity provider using configuration retrieved from import-config
|
||||||
IdentityProviderRepresentation idpRep = createRep("saml", "saml", true, result);
|
IdentityProviderRepresentation idpRep = createRep("saml", "saml", true, result);
|
||||||
|
@ -1073,6 +1085,32 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
Document document = DocumentUtil.getDocument(body);
|
Document document = DocumentUtil.getDocument(body);
|
||||||
|
|
||||||
Element signatureElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), XMLDSIG_NSURI.get(), "Signature");
|
Element signatureElement = DocumentUtil.getDirectChildElement(document.getDocumentElement(), XMLDSIG_NSURI.get(), "Signature");
|
||||||
Assert.assertNotNull(signatureElement);
|
Assert.assertThat("Signature not null", signatureElement, notNullValue());
|
||||||
|
|
||||||
|
Element keyInfoElement = DocumentUtil.getDirectChildElement(signatureElement, XMLDSIG_NSURI.get(), "KeyInfo");
|
||||||
|
Assert.assertThat("KeyInfo not null", keyInfoElement, notNullValue());
|
||||||
|
|
||||||
|
Element x509DataElement = DocumentUtil.getDirectChildElement(keyInfoElement, XMLDSIG_NSURI.get(), "X509Data");
|
||||||
|
Assert.assertThat("X509Data not null", x509DataElement, notNullValue());
|
||||||
|
|
||||||
|
Element x509CertificateElement = DocumentUtil.getDirectChildElement(x509DataElement, XMLDSIG_NSURI.get(), "X509Certificate");
|
||||||
|
Assert.assertThat("X509Certificate not null", x509CertificateElement, notNullValue());
|
||||||
|
|
||||||
|
Element keyNameElement = DocumentUtil.getDirectChildElement(keyInfoElement, XMLDSIG_NSURI.get(), "KeyName");
|
||||||
|
Assert.assertThat("KeyName not null", keyNameElement, notNullValue());
|
||||||
|
|
||||||
|
String activeSigCert = KeyUtils.findActiveSigningKey(realm, Constants.DEFAULT_SIGNATURE_ALGORITHM).getCertificate();
|
||||||
|
Assert.assertThat("activeSigCert not null", activeSigCert, notNullValue());
|
||||||
|
|
||||||
|
X509Certificate activeX509SigCert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(activeSigCert);
|
||||||
|
Assert.assertThat("KeyName matches subject DN",
|
||||||
|
keyNameElement.getTextContent().trim(), equalTo(activeX509SigCert.getSubjectDN().getName()));
|
||||||
|
|
||||||
|
Assert.assertThat("Signing cert matches active realm cert",
|
||||||
|
x509CertificateElement.getTextContent().trim(), equalTo(Base64.getEncoder().encodeToString(activeX509SigCert.getEncoded())));
|
||||||
|
|
||||||
|
PublicKey activePublicSigKey = activeX509SigCert.getPublicKey();
|
||||||
|
Assert.assertThat("Metadata signature is valid",
|
||||||
|
new SAML2Signature().validate(document, new HardcodedKeyLocator(activePublicSigKey)), is(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue