From 84b36f837222efbd3160bd41a9437058e1c5f2ff Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Mon, 22 May 2023 08:34:20 +0200 Subject: chore(saml2): optimize metadata signature-verification filter Select trusted X509 certificates based on KeyInfo from XML-Signature, if possible --- .../SimpleMetadataSignatureVerificationFilter.java | 79 +++++++++++++++++++--- .../pvp2/test/metadata/MetadataResolverTest.java | 53 +++++++++++++++ 2 files changed, 121 insertions(+), 11 deletions(-) diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java index 5a97924f..f4b008af 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/validation/metadata/SimpleMetadataSignatureVerificationFilter.java @@ -25,17 +25,24 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.xml.security.keys.KeyInfo; +import org.apache.xml.security.keys.keyresolver.KeyResolverException; import org.opensaml.saml.common.SignableSAMLObject; import org.opensaml.saml.saml2.metadata.EntitiesDescriptor; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.signature.Signature; +import org.opensaml.xmlsec.signature.impl.SignatureImpl; import org.opensaml.xmlsec.signature.support.SignatureException; import org.opensaml.xmlsec.signature.support.SignatureValidator; @@ -124,28 +131,78 @@ public class SimpleMetadataSignatureVerificationFilter extends AbstractMetadataS } // perform cryptographic signature verification - boolean isTrusted = false; - for (final BasicX509Credential cred : getTrustedCertificates()) { - log.trace("Validating signature with credential: {} ... ", - cred.getEntityCertificate().getSubjectDN()); + if (!performCryptographicCheck(signedElement.getSignature())) { + log.info("PVP2 metadata: " + metadataUrl + " are NOT trusted!"); + throw new SamlMetadataSignatureException(metadataUrl, ERROR_MSG_SIGNOTVALID); + + } + } + + private boolean performCryptographicCheck(Signature signature) throws EaafConfigurationException { + + // extract trusted signer certificate from signature + BasicX509Credential x509FromSig = extractTrustedX509FromSignature(signature); + if (x509FromSig != null) { try { - SignatureValidator.validate(signedElement.getSignature(), cred); - isTrusted = true; + SignatureValidator.validate(signature, x509FromSig); + return true; } catch (final SignatureException e) { log.debug("Failed to verfiy Signature with cert: {} Reason: {}", - cred.getEntityCertificate().getSubjectDN(), e.getMessage()); + x509FromSig.getEntityCertificate().getSubjectDN(), e.getMessage()); + + } + } else { + for (final BasicX509Credential cred : getTrustedCertificates()) { + log.trace("Validating signature with credential: {} ... ", + cred.getEntityCertificate().getSubjectDN()); + try { + SignatureValidator.validate(signature, cred); + return true; + + } catch (final SignatureException e) { + log.debug("Failed to verfiy Signature with cert: {} Reason: {}", + cred.getEntityCertificate().getSubjectDN(), e.getMessage()); + + } } } - if (!isTrusted) { - log.info("PVP2 metadata: " + metadataUrl + " are NOT trusted!"); - throw new SamlMetadataSignatureException(metadataUrl, ERROR_MSG_SIGNOTVALID); + return false; + + } + + private BasicX509Credential extractTrustedX509FromSignature(Signature signature) { + try { + KeyInfo keyInfo = ((SignatureImpl) signature).getXMLSignature().getKeyInfo(); + + byte[] encSigCert = keyInfo.getX509Certificate() != null + ? keyInfo.getX509Certificate().getEncoded() + : ArrayUtils.EMPTY_BYTE_ARRAY; + + return getTrustedCertificates().stream() + .filter(el -> { + try { + return MessageDigest.isEqual(el.getEntityCertificate().getEncoded(), encSigCert); + + } catch (CertificateEncodingException e) { + log.warn("Can not match certificates.", e); + return false; + + } + }) + .findFirst() + .orElse(null); + + } catch (KeyResolverException | EaafConfigurationException | CertificateEncodingException e) { + log.info("Can not extract X509 certificate from XML signature. Reason: {}", e.getMessage()); } + return null; + } - + private List getTrustedCertificates() throws EaafConfigurationException { try { final List certs = diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/test/java/at/gv/egiz/eaaf/modules/pvp2/test/metadata/MetadataResolverTest.java b/eaaf_modules/eaaf_module_pvp2_core/src/test/java/at/gv/egiz/eaaf/modules/pvp2/test/metadata/MetadataResolverTest.java index 036d682b..511b7283 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/test/java/at/gv/egiz/eaaf/modules/pvp2/test/metadata/MetadataResolverTest.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/test/java/at/gv/egiz/eaaf/modules/pvp2/test/metadata/MetadataResolverTest.java @@ -336,6 +336,59 @@ public class MetadataResolverTest { } + @Test + public void metadataSignatureValidCredentialsNoX509() throws CertificateException, Pvp2MetadataException, + ResolverException, XMLParserException, UnmarshallingException, SamlSigningException, + CredentialsNotAvailableException, MarshallingException, TransformerException, IOException, + KeyStoreException, NoSuchAlgorithmException { + + mockWebServer.shutdown(); + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/metadata"); + + final EntityDescriptor metadata = (EntityDescriptor) XMLObjectSupport.unmarshallFromInputStream( + XMLObjectProviderRegistrySupport.getParserPool(), + MetadataResolverTest.class.getResourceAsStream("/data/pvp_metadata_moaid_test.xml")); + metadata.setValidUntil(Instant.now().plus(Duration.ofDays(1))); + metadata.setSignature(null); + metadata.setEntityID(RandomStringUtils.randomAlphabetic(10)); + final EntityDescriptor signedMatadata = + Saml2Utils.signSamlObject(metadata, credentialProvider.getMetaDataSigningCredential(), false); + final Element metadataElement = XMLObjectSupport.marshall(signedMatadata); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody(SerializeSupport.nodeToString(metadataElement)) + .setHeader("Content-Type", "text/html;charset=utf-8")); + + final KeyStore keystore = KeyStore.getInstance("JKS"); + keystore.load(null, "junit".toCharArray()); + final CertificateFactory fact = CertificateFactory.getInstance("X.509"); + keystore.setCertificateEntry("1", fact.generateCertificate( + MetadataResolverTest.class.getResourceAsStream("/data/metadata_sig_cert.crt"))); + keystore.setCertificateEntry("2", fact.generateCertificate( + MetadataResolverTest.class.getResourceAsStream("/data/assertion_sig_cert.crt"))); + keystore.setCertificateEntry("3", fact.generateCertificate( + MetadataResolverTest.class.getResourceAsStream("/data/junit_metadata_sig_cert.crt"))); + + final List filterList = new ArrayList<>(); + filterList.add(new SchemaValidationFilter(true)); + filterList.add(new SimpleMetadataSignatureVerificationFilter( + keystore, + mockServerUrl.url().toString())); + filterList.add(new PvpEntityCategoryFilter(true)); + + final MetadataFilterChain filterChain = new MetadataFilterChain(); + filterChain.setFilters(filterList); + + final IPvp2MetadataProvider mdResolver = metadataResolverFactory.createMetadataProvider( + mockServerUrl.url().toString(), + filterChain, "jUnit test", httpClientFactory.getHttpClient()); + + final EntityDescriptor entityIdNotExists = mdResolver.getEntityDescriptor( + metadata.getEntityID()); + Assert.assertNotNull("No EntityDescripter", entityIdNotExists); + + } + @Test public void metadataSignatureValidCredentialsSecond() throws CertificateException, Pvp2MetadataException, ResolverException, XMLParserException, UnmarshallingException, SamlSigningException, -- cgit v1.2.3