diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java index 3b93c84353..9c6a5d83f1 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java @@ -274,7 +274,7 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication private SignatureSigningParameters resolveSigningParameters(RelyingPartyRegistration relyingPartyRegistration) { List credentials = resolveSigningCredentials(relyingPartyRegistration); - List algorithms = Collections.singletonList(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + List algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningMethodAlgorithms(); List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java index 137e97f88d..cdbef9e544 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java @@ -18,6 +18,8 @@ package org.springframework.security.saml2.provider.service.registration; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -27,6 +29,8 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; +import org.opensaml.xmlsec.signature.support.SignatureConstants; + import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.util.Assert; @@ -438,10 +442,12 @@ public final class RelyingPartyRegistration { private final Saml2MessageBinding singleSignOnServiceBinding; + private List signingMethodAlgorithms; + private AssertingPartyDetails(String entityId, boolean wantAuthnRequestsSigned, Collection verificationX509Credentials, Collection encryptionX509Credentials, String singleSignOnServiceLocation, - Saml2MessageBinding singleSignOnServiceBinding) { + Saml2MessageBinding singleSignOnServiceBinding, List signingMethodAlgorithms) { Assert.hasText(entityId, "entityId cannot be null or empty"); Assert.notNull(verificationX509Credentials, "verificationX509Credentials cannot be null"); for (Saml2X509Credential credential : verificationX509Credentials) { @@ -457,12 +463,14 @@ public final class RelyingPartyRegistration { } Assert.notNull(singleSignOnServiceLocation, "singleSignOnServiceLocation cannot be null"); Assert.notNull(singleSignOnServiceBinding, "singleSignOnServiceBinding cannot be null"); + Assert.notEmpty(signingMethodAlgorithms, "signingMethodAlgorithms cannot be empty"); this.entityId = entityId; this.wantAuthnRequestsSigned = wantAuthnRequestsSigned; this.verificationX509Credentials = verificationX509Credentials; this.encryptionX509Credentials = encryptionX509Credentials; this.singleSignOnServiceLocation = singleSignOnServiceLocation; this.singleSignOnServiceBinding = singleSignOnServiceBinding; + this.signingMethodAlgorithms = signingMethodAlgorithms; } /** @@ -542,6 +550,15 @@ public final class RelyingPartyRegistration { return this.singleSignOnServiceBinding; } + /** + * Return the list of preferred signature algorithm URIs, in preference order. + * @return the list of signature algorithm URIs + * @since 5.5 + */ + public List getSigningMethodAlgorithms() { + return this.signingMethodAlgorithms; + } + public static final class Builder { private String entityId; @@ -556,6 +573,8 @@ public final class RelyingPartyRegistration { private Saml2MessageBinding singleSignOnServiceBinding = Saml2MessageBinding.REDIRECT; + private List signingMethodAlgorithms = new ArrayList<>(); + /** * Set the asserting party's EntityID. @@ -639,15 +658,31 @@ public final class RelyingPartyRegistration { return this; } + /** + * Apply this {@link Consumer} to the list of signature algorithm URIs + * @param signingMethodAlgorithmsConsumer a {@link Consumer} of the list of + * signature algorithm URIs + * @return this {@code Builder} + * @since 5.5 + */ + public Builder signingMethodAlgorithms(Consumer> signingMethodAlgorithmsConsumer) { + signingMethodAlgorithmsConsumer.accept(this.signingMethodAlgorithms); + return this; + } + /** * Creates an immutable ProviderDetails object representing the configuration * for an Identity Provider, IDP * @return immutable ProviderDetails object */ public AssertingPartyDetails build() { + List signingMethodAlgorithmsCopy = this.signingMethodAlgorithms.isEmpty() + ? Arrays.asList(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256) + : Collections.unmodifiableList(this.signingMethodAlgorithms); + return new AssertingPartyDetails(this.entityId, this.wantAuthnRequestsSigned, this.verificationX509Credentials, this.encryptionX509Credentials, - this.singleSignOnServiceLocation, this.singleSignOnServiceBinding); + this.singleSignOnServiceLocation, this.singleSignOnServiceBinding, signingMethodAlgorithmsCopy); } } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java index 9581ea3aa0..fd24ebd511 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java @@ -52,6 +52,7 @@ import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder; import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder; import org.opensaml.saml.saml2.core.impl.NameIDBuilder; import org.opensaml.xmlsec.encryption.impl.EncryptedDataBuilder; +import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.w3c.dom.Element; import org.springframework.core.convert.converter.Converter; @@ -463,6 +464,17 @@ public class OpenSamlAuthenticationProviderTests { verify(context, atLeastOnce()).getStaticParameters(); } + @Test + public void authenticateWithSHA1SignatureThenItSucceeds() throws Exception { + Response response = TestOpenSamlObjects.response(); + Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID, + SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + this.provider.authenticate(token); + } + @Test public void setAssertionValidatorWhenNullThenIllegalArgument() { // @formatter:off diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java index 5b378808b9..67bf4a6803 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java @@ -240,6 +240,22 @@ public class OpenSamlAuthenticationRequestFactoryTests { assertThat(inflated).contains("ProtocolBinding=\"" + SAMLConstants.SAML2_REDIRECT_BINDING_URI + "\""); } + @Test + public void createRedirectAuthenticationRequestWhenSHA1SignRequestThenSignatureIsPresent() { + RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationBuilder + .assertingPartyDetails( + (a) -> a.signingMethodAlgorithms((c) -> c.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1))) + .build(); + Saml2AuthenticationRequestContext context = this.contextBuilder.relayState("Relay State Value") + .relyingPartyRegistration(relyingPartyRegistration).build(); + Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(context); + assertThat(result.getSamlRequest()).isNotEmpty(); + assertThat(result.getRelayState()).isEqualTo("Relay State Value"); + assertThat(result.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); + assertThat(result.getSignature()).isNotNull(); + assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); + } + private AuthnRequest getAuthNRequest(Saml2MessageBinding binding) { AbstractSaml2AuthenticationRequest result = (binding == Saml2MessageBinding.REDIRECT) ? this.factory.createRedirectAuthenticationRequest(this.context) diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java index 3c76804f56..120b031906 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java @@ -205,11 +205,12 @@ public final class TestOpenSamlObjects { return CredentialSupport.getSimpleCredential(credential.getCertificate(), credential.getPrivateKey()); } - static T signed(T signable, Saml2X509Credential credential, String entityId) { + static T signed(T signable, Saml2X509Credential credential, String entityId, + String signAlgorithmUri) { SignatureSigningParameters parameters = new SignatureSigningParameters(); Credential signingCredential = getSigningCredential(credential, entityId); parameters.setSigningCredential(signingCredential); - parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + parameters.setSignatureAlgorithm(signAlgorithmUri); parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256); parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); try { @@ -221,6 +222,10 @@ public final class TestOpenSamlObjects { return signable; } + static T signed(T signable, Saml2X509Credential credential, String entityId) { + return signed(signable, credential, entityId, SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + } + static EncryptedAssertion encrypted(Assertion assertion, Saml2X509Credential credential) { X509Certificate certificate = credential.getCertificate(); Encrypter encrypter = getEncrypter(certificate);