diff --git a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ClientCache.java b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ClientCache.java
index bbab15e69c..524e27010f 100644
--- a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ClientCache.java
+++ b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ClientCache.java
@@ -131,7 +131,13 @@ public SAML2Client add(
cfg.setServiceProviderEntityId(spEntityID);
getSPMetadataPath(spEntityID).ifPresent(cfg::setServiceProviderMetadataResourceFilepath);
- SAML2Client saml2Client = new SAML2Client(cfg);
+ SAML2Client saml2Client = new SAML2Client(cfg) {
+
+ @Override
+ protected void initSignatureSigningParametersProvider() {
+ signatureSigningParametersProvider = new SAML2SP4UISignatureSigningParametersProvider(configuration);
+ }
+ };
saml2Client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
saml2Client.setCallbackUrl(callbackUrl);
saml2Client.init();
diff --git a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UISignatureSigningParametersProvider.java b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UISignatureSigningParametersProvider.java
new file mode 100644
index 0000000000..72a4e78669
--- /dev/null
+++ b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UISignatureSigningParametersProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.saml2;
+
+import java.util.List;
+import java.util.Optional;
+import org.opensaml.xmlsec.SignatureSigningConfiguration;
+import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
+import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
+import org.pac4j.saml.config.SAML2Configuration;
+import org.pac4j.saml.crypto.DefaultSignatureSigningParametersProvider;
+
+public class SAML2SP4UISignatureSigningParametersProvider extends DefaultSignatureSigningParametersProvider {
+
+ protected final SAML2Configuration configuration;
+
+ public SAML2SP4UISignatureSigningParametersProvider(final SAML2Configuration configuration) {
+ super(configuration);
+ this.configuration = configuration;
+ }
+
+ @Override
+ protected SignatureSigningConfiguration getSignatureSigningConfiguration() {
+ BasicSignatureSigningConfiguration ssc =
+ DefaultSecurityConfigurationBootstrap.buildDefaultSignatureSigningConfiguration();
+
+ Optional.ofNullable(configuration.getBlackListedSignatureSigningAlgorithms()).
+ ifPresent(ssc::setExcludedAlgorithms);
+
+ Optional.ofNullable(configuration.getSignatureAlgorithms()).
+ ifPresent(ssc::setSignatureAlgorithms);
+
+ Optional.ofNullable(configuration.getSignatureCanonicalizationAlgorithm()).
+ ifPresent(ssc::setSignatureCanonicalizationAlgorithm);
+
+ Optional.ofNullable(configuration.getSignatureReferenceDigestMethods()).
+ ifPresent(ssc::setSignatureReferenceDigestMethods);
+
+ ssc.setSigningCredentials(List.of(configuration.getCredentialProvider().getCredential()));
+ return ssc;
+ }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
index 6eb86ee2b1..26cacc3291 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
@@ -37,6 +37,7 @@
import org.apache.syncope.sra.security.saml2.SAML2MetadataEndpoint;
import org.apache.syncope.sra.security.saml2.SAML2SecurityConfigUtils;
import org.apache.syncope.sra.security.saml2.SAML2WebSsoAuthenticationWebFilter;
+import org.apache.syncope.sra.security.saml2.SRASignatureSigningParametersProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
import org.pac4j.saml.client.SAML2Client;
@@ -293,7 +294,13 @@ public InputStream retrieve() throws Exception {
cfg.setSessionLogoutHandler(new NoOpSessionLogoutHandler());
- SAML2Client saml2Client = new SAML2Client(cfg);
+ SAML2Client saml2Client = new SAML2Client(cfg) {
+
+ @Override
+ protected void initSignatureSigningParametersProvider() {
+ signatureSigningParametersProvider = new SRASignatureSigningParametersProvider(configuration);
+ }
+ };
saml2Client.setName(SRAProperties.AMType.SAML2.name());
saml2Client.setCallbackUrl(props.getSaml2().getEntityId()
+ SAML2WebSsoAuthenticationWebFilter.FILTER_PROCESSES_URI);
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SRASignatureSigningParametersProvider.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SRASignatureSigningParametersProvider.java
new file mode 100644
index 0000000000..4b2ad75a11
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SRASignatureSigningParametersProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.security.saml2;
+
+import java.util.List;
+import java.util.Optional;
+import org.opensaml.xmlsec.SignatureSigningConfiguration;
+import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
+import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
+import org.pac4j.saml.config.SAML2Configuration;
+import org.pac4j.saml.crypto.DefaultSignatureSigningParametersProvider;
+
+public class SRASignatureSigningParametersProvider extends DefaultSignatureSigningParametersProvider {
+
+ protected final SAML2Configuration configuration;
+
+ public SRASignatureSigningParametersProvider(final SAML2Configuration configuration) {
+ super(configuration);
+ this.configuration = configuration;
+ }
+
+ @Override
+ protected SignatureSigningConfiguration getSignatureSigningConfiguration() {
+ BasicSignatureSigningConfiguration ssc =
+ DefaultSecurityConfigurationBootstrap.buildDefaultSignatureSigningConfiguration();
+
+ Optional.ofNullable(configuration.getBlackListedSignatureSigningAlgorithms()).
+ ifPresent(ssc::setExcludedAlgorithms);
+
+ Optional.ofNullable(configuration.getSignatureAlgorithms()).
+ ifPresent(ssc::setSignatureAlgorithms);
+
+ Optional.ofNullable(configuration.getSignatureCanonicalizationAlgorithm()).
+ ifPresent(ssc::setSignatureCanonicalizationAlgorithm);
+
+ Optional.ofNullable(configuration.getSignatureReferenceDigestMethods()).
+ ifPresent(ssc::setSignatureReferenceDigestMethods);
+
+ ssc.setSigningCredentials(List.of(configuration.getCredentialProvider().getCredential()));
+ return ssc;
+ }
+}
diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml
index c93e899206..4c80b4c1b9 100644
--- a/wa/starter/pom.xml
+++ b/wa/starter/pom.xml
@@ -203,6 +203,10 @@ under the License.
org.apereo.cas
cas-server-support-saml-idp-core
+
+ org.apereo.cas
+ cas-server-support-saml-idp-web
+
org.apereo.cas
cas-server-support-oidc
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java
index 8871cfb159..ca2823f745 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java
@@ -61,6 +61,7 @@
import org.apache.syncope.wa.starter.oidc.WAOIDCJWKSGeneratorService;
import org.apache.syncope.wa.starter.pac4j.saml.WASAML2ClientCustomizer;
import org.apache.syncope.wa.starter.saml.idp.WASamlIdPCasEventListener;
+import org.apache.syncope.wa.starter.saml.idp.WASamlIdPObjectSigner;
import org.apache.syncope.wa.starter.saml.idp.metadata.WASamlIdPMetadataGenerator;
import org.apache.syncope.wa.starter.saml.idp.metadata.WASamlIdPMetadataLocator;
import org.apache.syncope.wa.starter.services.WAServiceRegistry;
@@ -89,11 +90,13 @@
import org.apereo.cas.support.saml.idp.metadata.generator.SamlIdPMetadataGeneratorConfigurationContext;
import org.apereo.cas.support.saml.idp.metadata.locator.SamlIdPMetadataLocator;
import org.apereo.cas.support.saml.services.idp.metadata.SamlIdPMetadataDocument;
+import org.apereo.cas.support.saml.web.idp.profile.builders.enc.SamlIdPObjectSigner;
import org.apereo.cas.util.DateTimeUtils;
import org.apereo.cas.util.LdapUtils;
import org.apereo.cas.util.crypto.CipherExecutor;
import org.apereo.cas.webauthn.storage.WebAuthnCredentialRepository;
import org.ldaptive.ConnectionFactory;
+import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.pac4j.core.client.Client;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -259,10 +262,19 @@ public SamlIdPMetadataLocator samlIdPMetadataLocator(
final Cache samlIdPMetadataCache,
final WARestClient waRestClient) {
- return new WASamlIdPMetadataLocator(
- cipherExecutor,
- samlIdPMetadataCache,
- waRestClient);
+ return new WASamlIdPMetadataLocator(cipherExecutor, samlIdPMetadataCache, waRestClient);
+ }
+
+ @Bean
+ @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+ public SamlIdPObjectSigner samlObjectSigner(
+ final CasConfigurationProperties casProperties,
+ @Qualifier("casSamlIdPMetadataResolver")
+ final MetadataResolver casSamlIdPMetadataResolver,
+ @Qualifier("samlIdPMetadataLocator")
+ final SamlIdPMetadataLocator samlIdPMetadataLocator) {
+
+ return new WASamlIdPObjectSigner(casSamlIdPMetadataResolver, casProperties, samlIdPMetadataLocator);
}
@Bean
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/WASamlIdPObjectSigner.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/WASamlIdPObjectSigner.java
new file mode 100644
index 0000000000..a56c962796
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/WASamlIdPObjectSigner.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.wa.starter.saml.idp;
+
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import org.apache.commons.lang3.StringUtils;
+import org.apereo.cas.configuration.CasConfigurationProperties;
+import org.apereo.cas.support.saml.SamlIdPUtils;
+import org.apereo.cas.support.saml.idp.metadata.locator.SamlIdPMetadataCredentialResolver;
+import org.apereo.cas.support.saml.idp.metadata.locator.SamlIdPMetadataLocator;
+import org.apereo.cas.support.saml.idp.metadata.locator.SamlIdPSamlRegisteredServiceCriterion;
+import org.apereo.cas.support.saml.services.SamlRegisteredService;
+import org.apereo.cas.support.saml.web.idp.profile.builders.enc.DefaultSamlIdPObjectSigner;
+import org.apereo.cas.util.DigestUtils;
+import org.apereo.cas.util.RegexUtils;
+import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.saml.criterion.EntityRoleCriterion;
+import org.opensaml.saml.metadata.criteria.entity.impl.EvaluableEntityRoleEntityDescriptorCriterion;
+import org.opensaml.saml.metadata.resolver.MetadataResolver;
+import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
+import org.opensaml.security.credential.AbstractCredential;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.xmlsec.SignatureSigningConfiguration;
+import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
+import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WASamlIdPObjectSigner extends DefaultSamlIdPObjectSigner {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(WASamlIdPObjectSigner.class);
+
+ protected static boolean doesCredentialFingerprintMatch(
+ final AbstractCredential credential,
+ final SamlRegisteredService samlRegisteredService) {
+
+ var fingerprint = samlRegisteredService.getSigningCredentialFingerprint();
+ if (StringUtils.isNotBlank(fingerprint)) {
+ var digest = DigestUtils.digest("SHA-1", Objects.requireNonNull(credential.getPublicKey()).getEncoded());
+ var pattern = RegexUtils.createPattern(fingerprint, Pattern.CASE_INSENSITIVE);
+ LOG.debug("Matching credential fingerprint [{}] against filter [{}] for service [{}]",
+ digest, fingerprint, samlRegisteredService.getName());
+ return pattern.matcher(digest).find();
+ }
+ return true;
+ }
+
+ protected final MetadataResolver samlIdPMetadataResolver;
+
+ protected final CasConfigurationProperties casProperties;
+
+ protected final SamlIdPMetadataLocator samlIdPMetadataLocator;
+
+ public WASamlIdPObjectSigner(
+ final MetadataResolver samlIdPMetadataResolver,
+ final CasConfigurationProperties casProperties,
+ final SamlIdPMetadataLocator samlIdPMetadataLocator) {
+
+ super(samlIdPMetadataResolver, casProperties, samlIdPMetadataLocator);
+ this.samlIdPMetadataResolver = samlIdPMetadataResolver;
+ this.casProperties = casProperties;
+ this.samlIdPMetadataLocator = samlIdPMetadataLocator;
+ }
+
+ @Override
+ protected SignatureSigningConfiguration getSignatureSigningConfiguration(final SamlRegisteredService service)
+ throws Exception {
+
+ var config = configureSignatureSigningSecurityConfiguration(service);
+
+ var samlIdp = casProperties.getAuthn().getSamlIdp();
+ var privateKey = getSigningPrivateKey(service);
+
+ var mdCredentialResolver = new SamlIdPMetadataCredentialResolver();
+ var roleDescriptorResolver = SamlIdPUtils.getRoleDescriptorResolver(
+ samlIdPMetadataResolver,
+ samlIdp.getMetadata().getCore().isRequireValidMetadata());
+ mdCredentialResolver.setRoleDescriptorResolver(roleDescriptorResolver);
+ mdCredentialResolver.setKeyInfoCredentialResolver(
+ DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
+ mdCredentialResolver.initialize();
+
+ var criteriaSet = new CriteriaSet();
+ criteriaSet.add(new SignatureSigningConfigurationCriterion(config));
+ criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
+
+ var entityIdCriteriaSet = new CriteriaSet(
+ new EvaluableEntityRoleEntityDescriptorCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME),
+ new SamlIdPSamlRegisteredServiceCriterion(service));
+ LOG.trace("Resolving entity id from SAML2 IdP metadata for signature signing configuration is [{}]", service.
+ getName());
+ var entityId = Objects.requireNonNull(samlIdPMetadataResolver.resolveSingle(entityIdCriteriaSet)).getEntityID();
+ LOG.trace("Resolved entity id from SAML2 IdP metadata is [{}]", entityId);
+ criteriaSet.add(new EntityIdCriterion(entityId));
+ criteriaSet.add(new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
+ criteriaSet.add(new SamlIdPSamlRegisteredServiceCriterion(service));
+
+ LOG.trace("Resolved signing credentials based on criteria [{}]", criteriaSet);
+ var credentials = Sets.newLinkedHashSet(mdCredentialResolver.resolve(criteriaSet));
+ LOG.trace("Resolved [{}] signing credentials", credentials.size());
+
+ var finalCredentials = new ArrayList();
+ credentials.stream()
+ .map(creds -> getResolvedSigningCredential(creds, privateKey, service))
+ .filter(Objects::nonNull)
+ .filter(creds -> doesCredentialFingerprintMatch(creds, service))
+ .forEach(finalCredentials::add);
+
+ if (finalCredentials.isEmpty()) {
+ LOG.error("Unable to locate any signing credentials for service [{}]", service.getName());
+ throw new IllegalArgumentException("Unable to locate signing credentials");
+ }
+
+ config.setSigningCredentials(finalCredentials);
+ LOG.trace("Signature signing credentials configured with [{}] credentials", finalCredentials.size());
+ return config;
+ }
+}