diff --git a/dependencies.gradle b/dependencies.gradle index d4e12916ce3..a9f6b3c7903 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -12,7 +12,7 @@ versions.springBootVersion = "3.5.13" versions.guavaVersion = "33.5.0-jre" versions.seleniumVersion = "4.43.0" versions.braveVersion = "6.3.1" -versions.opensaml = "4.0.1" +versions.opensaml = "4.3.2" // Versions we're overriding from the Spring Boot Bom (Dependabot does not issue PRs to bump these versions, so we need to manually bump them) ext["selenium.version"] = "${versions.seleniumVersion}" // Selenium for integration tests only diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProvider.java index d245b787942..4f33625a1f0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProvider.java @@ -69,7 +69,6 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; @@ -113,7 +112,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProvider, ZoneAware { static { - OpenSamlInitializationService.initialize(); + SamlConfiguration.setupOpenSaml(); } private final Log logger = LogFactory.getLog(this.getClass()); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java index 2aa607f4bf7..37381cf0111 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java @@ -37,7 +37,6 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; @@ -73,7 +72,7 @@ public final class Saml2BearerGrantAuthenticationConverter implements AuthenticationConverter { static { - OpenSamlInitializationService.initialize(); + SamlConfiguration.setupOpenSaml(); } private static final AssertionUnmarshaller assertionUnmarshaller; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfiguration.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfiguration.java index 3e93f6c3a23..c4bf6903fb9 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfiguration.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfiguration.java @@ -8,16 +8,31 @@ import org.cloudfoundry.identity.uaa.util.TimeService; import org.cloudfoundry.identity.uaa.util.TimeServiceImpl; import org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils; +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.config.InitializationException; +import org.opensaml.core.config.Initializer; +import org.opensaml.security.config.GlobalNamedCurveRegistryInitializer; +import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.web.client.RestTemplate; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap.CONFIG_PROPERTY_ECDH_DEFAULT_KDF; + @Slf4j @EnableConfigurationProperties({SamlConfigProps.class}) +@DependsOn({"setUpBouncyCastle", "setupOpenSaml"}) @Configuration @Data public class SamlConfiguration { @@ -54,6 +69,31 @@ public class SamlConfiguration { @Value("${login.saml.socket.soTimeout:10000}") private int socketReadTimeout = 10_000; + private static final AtomicBoolean samlInitialized = new AtomicBoolean(false); + + @Bean + public static Boolean setupOpenSaml() { + if (samlInitialized.compareAndSet(false, true)) { + Properties props = ConfigurationService.getConfigurationProperties(); + props.put(CONFIG_PROPERTY_ECDH_DEFAULT_KDF, DefaultSecurityConfigurationBootstrap.PBKDF2); + Class toSkip = GlobalNamedCurveRegistryInitializer.class; + ServiceLoader.load(Initializer.class).stream().filter((provider) -> provider.type() != toSkip).forEach((provider) -> init(provider)); + try { + OpenSamlInitializationService.initialize(); + } catch (NoClassDefFoundError | NoSuchMethodError e) { + // ignore + } + } + return Boolean.TRUE; + } + + private static void init(ServiceLoader.Provider provider) { + try { + provider.get().init(); + } catch (InitializationException ex) { + throw new Saml2Exception(ex); + } + } @Bean public String samlEntityID() { return samlEntityID; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfig.java index 6d15c1c33a4..b736d84c9a4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfig.java @@ -13,7 +13,6 @@ import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; @@ -21,6 +20,7 @@ import static org.cloudfoundry.identity.uaa.provider.saml.SamlMetadataEndpoint.DEFAULT_REGISTRATION_ID; +@DependsOn({"setUpBouncyCastle", "setupOpenSaml"}) @Configuration @Slf4j public class SamlRelyingPartyRegistrationRepositoryConfig { @@ -44,7 +44,6 @@ public SamlRelyingPartyRegistrationRepositoryConfig(@Qualifier("samlEntityID") S this.signatureAlgorithms = signatureAlgorithms; } - @DependsOn({"setUpBouncyCastle"}) @Bean RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(SamlIdentityProviderConfigurator samlIdentityProviderConfigurator) { SamlKeyManagerFactory.SamlConfigPropsSamlKeyManagerImpl samlKeyManager = new SamlKeyManagerFactory.SamlConfigPropsSamlKeyManagerImpl(samlConfigProps); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java index dd29182fdf8..27327ccbc75 100755 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java @@ -153,6 +153,7 @@ public class BootstrapSamlIdentityProviderDataTests { @BeforeEach void beforeEach() { + SamlConfiguration.setupOpenSaml(); bootstrap = new BootstrapSamlIdentityProviderData(new SamlIdentityProviderConfigurator(mock(JdbcIdentityProviderProvisioning.class), new IdentityZoneManagerImpl(), mock(FixedHttpMetaDataProvider.class))); singleAdd = new SamlIdentityProviderDefinition() .setMetaDataLocation(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID.formatted(new RandomValueStringGenerator().generate())) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUaaTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUaaTests.java index 59ce0da1fec..1f654be1ee2 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUaaTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUaaTests.java @@ -45,7 +45,6 @@ import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.opensaml.core.config.InitializationException; -import org.opensaml.core.config.InitializationService; import org.opensaml.saml.saml2.core.AuthnContext; import org.opensaml.saml.saml2.core.Response; import org.springframework.beans.factory.annotation.Autowired; @@ -183,7 +182,7 @@ void configureProvider() throws SecurityException, SQLException, InitializationE RequestContextHolder.setRequestAttributes(servletWebRequest); DbUtils dbUtils = new DbUtils(); - InitializationService.initialize(); + SamlConfiguration.setupOpenSaml(); ScimGroupProvisioning groupProvisioning = new JdbcScimGroupProvisioning( namedJdbcTemplate, new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUnitTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUnitTests.java index a53d7a5910c..41b861f109f 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUnitTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUnitTests.java @@ -6,6 +6,7 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; @@ -93,6 +94,11 @@ class OpenSaml4AuthenticationProviderUnitTests { private final OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + @BeforeEach + void setUp() { + SamlConfiguration.setupOpenSaml(); + } + @AfterEach void cleanup() { IdentityZoneHolder.clear(); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java index 8419385ab8c..07574b620b0 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java @@ -40,8 +40,6 @@ import org.opensaml.saml.saml2.core.AttributeValue; import org.w3c.dom.Element; -import org.springframework.security.saml2.core.OpenSamlInitializationService; - /** * This was copied from Spring Security Test Classes *

@@ -51,7 +49,7 @@ public final class TestCustomOpenSamlObjects { static { - OpenSamlInitializationService.initialize(); + SamlConfiguration.setupOpenSaml(); XMLObjectProviderRegistrySupport.getMarshallerFactory() .registerMarshaller(CustomOpenSamlObject.TYPE_NAME, new TestCustomOpenSamlObjects.CustomSamlObjectMarshaller()); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java index 1af4d83316e..36720470fab 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java @@ -63,7 +63,6 @@ import org.opensaml.xmlsec.signature.support.SignatureException; import org.opensaml.xmlsec.signature.support.SignatureSupport; import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -101,7 +100,7 @@ public final class TestOpenSamlObjects { public static final String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; static { - OpenSamlInitializationService.initialize(); + SamlConfiguration.setupOpenSaml(); } private TestOpenSamlObjects() { @@ -133,6 +132,7 @@ static Response signedResponseWithOneAssertion() { static Response signedResponseWithOneAssertion(Consumer responseConsumer) { Response response = response(); + response.setIssueInstant(Instant.now()); response.getAssertions().add(assertion()); responseConsumer.accept(response); return signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); @@ -156,6 +156,7 @@ public static Assertion assertion(String username, String issuerEntityId, String Assertion assertion = build(Assertion.DEFAULT_ELEMENT_NAME); assertion.setID("A" + UUID.randomUUID()); assertion.setVersion(SAMLVersion.VERSION_20); + assertion.setIssueInstant(Instant.now()); assertion.setIssuer(issuer(issuerEntityId)); assertion.setSubject(subject(username)); assertion.setConditions(conditions()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java index 934b15e75e2..d65064c3175 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java @@ -5,6 +5,7 @@ import org.cloudfoundry.identity.uaa.db.beans.JdbcUrlCustomizer; import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; +import org.cloudfoundry.identity.uaa.provider.saml.SamlConfiguration; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.zone.ZoneContextPathSessionFilter; import org.cloudfoundry.identity.uaa.zone.ZonePathContextRewritingFilter; @@ -71,6 +72,7 @@ class TestPropertyInitializer implements ApplicationContextInitializer