diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java new file mode 100644 index 0000000000..c2e85ccebf --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java @@ -0,0 +1,200 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.saml2.core; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +import org.springframework.util.Assert; + +import static java.util.Arrays.asList; +import static org.springframework.util.Assert.notEmpty; +import static org.springframework.util.Assert.notNull; +import static org.springframework.util.Assert.state; + +/** + * An object for holding a public certificate, any associated private key, and its intended + * + * usages + * + * (Line 584, Section 4.3 Credentials). + * + * @since 5.4 + * @author Filip Hanik + * @author Josh Cummings + */ +public final class Saml2X509Credential { + public enum Saml2X509CredentialType { + VERIFICATION, + ENCRYPTION, + SIGNING, + DECRYPTION, + } + + private final PrivateKey privateKey; + private final X509Certificate certificate; + private final Set credentialTypes; + + /** + * Creates a {@link Saml2X509Credential} using the provided parameters + * + * @param certificate the credential's public certificiate + * @param types the credential's intended usages, must be one of {@link Saml2X509CredentialType#VERIFICATION} or + * {@link Saml2X509CredentialType#ENCRYPTION} or both. + */ + public Saml2X509Credential(X509Certificate certificate, Saml2X509CredentialType... types) { + this(null, false, certificate, types); + validateUsages(types, Saml2X509CredentialType.VERIFICATION, Saml2X509CredentialType.ENCRYPTION); + } + + /** + * Creates a {@link Saml2X509Credential} using the provided parameters + * + * @param privateKey the credential's private key + * @param certificate the credential's public certificate + * @param types the credential's intended usages, must be one of {@link Saml2X509CredentialType#SIGNING} or + * {@link Saml2X509CredentialType#DECRYPTION} or both. + */ + public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Saml2X509CredentialType... types) { + this(privateKey, true, certificate, types); + validateUsages(types, Saml2X509CredentialType.SIGNING, Saml2X509CredentialType.DECRYPTION); + } + + /** + * Creates a {@link Saml2X509Credential} using the provided parameters + * + * @param privateKey the credential's private key + * @param certificate the credential's public certificate + * @param types the credential's intended usages + */ + public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Set types) { + Assert.notNull(certificate, "certificate cannot be null"); + Assert.notNull(types, "credentialTypes cannot be null"); + this.privateKey = privateKey; + this.certificate = certificate; + this.credentialTypes = types; + } + + private Saml2X509Credential( + PrivateKey privateKey, + boolean keyRequired, + X509Certificate certificate, + Saml2X509CredentialType... types) { + notNull(certificate, "certificate cannot be null"); + notEmpty(types, "credentials types cannot be empty"); + if (keyRequired) { + notNull(privateKey, "privateKey cannot be null"); + } + this.privateKey = privateKey; + this.certificate = certificate; + this.credentialTypes = new LinkedHashSet<>(asList(types)); + } + + /** + * Get the private key for this credential + * + * @return the private key, may be null + * @see {@link #Saml2X509Credential(PrivateKey, X509Certificate, Saml2X509CredentialType...)} + */ + public PrivateKey getPrivateKey() { + return this.privateKey; + } + + /** + * Get the public certificate for this credential + * + * @return the public certificate + */ + public X509Certificate getCertificate() { + return this.certificate; + } + + /** + * Indicate whether this credential can be used for signing + * + * @return true if the credential has a {@link Saml2X509CredentialType#SIGNING} type + */ + public boolean isSigningCredential() { + return getCredentialTypes().contains(Saml2X509CredentialType.SIGNING); + } + + /** + * Indicate whether this credential can be used for decryption + * + * @return true if the credential has a {@link Saml2X509CredentialType#DECRYPTION} type + */ + public boolean isDecryptionCredential() { + return getCredentialTypes().contains(Saml2X509CredentialType.DECRYPTION); + } + + /** + * Indicate whether this credential can be used for verification + * + * @return true if the credential has a {@link Saml2X509CredentialType#VERIFICATION} type + */ + public boolean isVerificationCredential() { + return getCredentialTypes().contains(Saml2X509CredentialType.VERIFICATION); + } + + /** + * Indicate whether this credential can be used for encryption + * + * @return true if the credential has a {@link Saml2X509CredentialType#ENCRYPTION} type + */ + public boolean isEncryptionCredential() { + return getCredentialTypes().contains(Saml2X509CredentialType.ENCRYPTION); + } + + /** + * List all this credential's intended usages + * + * @return the set of this credential's intended usages + */ + public Set getCredentialTypes() { + return this.credentialTypes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Saml2X509Credential that = (Saml2X509Credential) o; + return Objects.equals(this.privateKey, that.privateKey) && + this.certificate.equals(that.certificate) && + this.credentialTypes.equals(that.credentialTypes); + } + + @Override + public int hashCode() { + return Objects.hash(this.privateKey, this.certificate, this.credentialTypes); + } + + private void validateUsages(Saml2X509CredentialType[] usages, Saml2X509CredentialType... validUsages) { + for (Saml2X509CredentialType usage : usages) { + boolean valid = false; + for (Saml2X509CredentialType validUsage : validUsages) { + if (usage == validUsage) { + valid = true; + break; + } + } + state(valid, () -> usage +" is not a valid usage for this credential"); + } + } +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java index 6b570010cb..a83fc06e75 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,11 @@ package org.springframework.security.saml2.credentials; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.LinkedHashSet; +import java.util.Objects; import java.util.Set; +import org.springframework.util.Assert; + import static java.util.Arrays.asList; import static org.springframework.util.Assert.notEmpty; import static org.springframework.util.Assert.notNull; @@ -32,8 +35,14 @@ import static org.springframework.util.Assert.state; * Line: 584, Section 4.3 Credentials Used for both signing, signature verification and encryption/decryption * * @since 5.2 + * @deprecated Use {@link org.springframework.security.saml2.core.Saml2X509Credential} instead */ +@Deprecated public class Saml2X509Credential { + /** + * @deprecated Use {@link org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType} instead + */ + @Deprecated public enum Saml2X509CredentialType { VERIFICATION, ENCRYPTION, @@ -70,6 +79,14 @@ public class Saml2X509Credential { validateUsages(types, Saml2X509CredentialType.SIGNING, Saml2X509CredentialType.DECRYPTION); } + public Saml2X509Credential(PrivateKey privateKey, X509Certificate certificate, Set types) { + Assert.notNull(certificate, "certificate cannot be null"); + Assert.notEmpty(types, "credentialTypes cannot be empty"); + this.privateKey = privateKey; + this.certificate = certificate; + this.credentialTypes = types; + } + private Saml2X509Credential( PrivateKey privateKey, boolean keyRequired, @@ -147,6 +164,21 @@ public class Saml2X509Credential { return this.certificate; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Saml2X509Credential that = (Saml2X509Credential) o; + return Objects.equals(this.privateKey, that.privateKey) && + this.certificate.equals(that.certificate) && + this.credentialTypes.equals(that.credentialTypes); + } + + @Override + public int hashCode() { + return Objects.hash(this.privateKey, this.certificate, this.credentialTypes); + } + private void validateUsages(Saml2X509CredentialType[] usages, Saml2X509CredentialType... validUsages) { for (Saml2X509CredentialType usage : usages) { boolean valid = false; diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java index 8dc7fb1385..e93f5f89ec 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java @@ -99,7 +99,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.credentials.Saml2X509Credential; +import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -480,7 +480,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi private SignatureTrustEngine buildSignatureTrustEngine(Saml2AuthenticationToken token) { Set credentials = new HashSet<>(); - for (Saml2X509Credential key : token.getRelyingPartyRegistration().getVerificationCredentials()) { + Collection keys = token.getRelyingPartyRegistration().getAssertingPartyDetails().getVerificationX509Credentials(); + for (Saml2X509Credential key : keys) { BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); cred.setUsageType(UsageType.SIGNING); cred.setEntityId(token.getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId()); @@ -536,8 +537,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi return encrypted -> { Saml2AuthenticationException last = authException(DECRYPTION_ERROR, "No valid decryption credentials found."); - List decryptionCredentials = token.getRelyingPartyRegistration().getDecryptionCredentials(); - for (Saml2X509Credential key : decryptionCredentials) { + Collection keys = token.getRelyingPartyRegistration().getDecryptionX509Credentials(); + for (Saml2X509Credential key : keys) { Decrypter decrypter = getDecrypter(key); try { return decrypter.decrypt(encrypted); @@ -618,7 +619,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi private SignatureTrustEngine buildSignatureTrustEngine(Saml2AuthenticationToken token) { Set credentials = new HashSet<>(); - for (Saml2X509Credential key : token.getRelyingPartyRegistration().getVerificationCredentials()) { + Collection keys = token.getRelyingPartyRegistration().getAssertingPartyDetails().getVerificationX509Credentials(); + for (Saml2X509Credential key : keys) { BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); cred.setUsageType(UsageType.SIGNING); cred.setEntityId(token.getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId()); @@ -730,8 +732,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi return encrypted -> { Saml2AuthenticationException last = authException(DECRYPTION_ERROR, "No valid decryption credentials found."); - List decryptionCredentials = token.getRelyingPartyRegistration().getDecryptionCredentials(); - for (Saml2X509Credential key : decryptionCredentials) { + Collection keys = token.getRelyingPartyRegistration().getDecryptionX509Credentials(); + for (Saml2X509Credential key : keys) { Decrypter decrypter = getDecrypter(key); try { return (NameID) decrypter.decrypt(encrypted); 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 f554258b3a..ee7cb1bff0 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 @@ -16,22 +16,36 @@ package org.springframework.security.saml2.provider.service.authentication; +import java.security.PrivateKey; import java.time.Clock; import java.time.Instant; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; +import java.security.cert.X509Certificate; import org.joda.time.DateTime; +import org.opensaml.core.xml.io.MarshallingException; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.Issuer; +import org.opensaml.security.SecurityException; +import org.opensaml.security.credential.BasicCredential; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.CredentialSupport; +import org.opensaml.security.credential.UsageType; +import org.opensaml.xmlsec.SignatureSigningParameters; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.opensaml.xmlsec.signature.support.SignatureException; +import org.opensaml.xmlsec.signature.support.SignatureSupport; import org.springframework.core.convert.converter.Converter; -import org.springframework.security.saml2.credentials.Saml2X509Credential; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.Builder; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.util.Assert; import static java.nio.charset.StandardCharsets.UTF_8; @@ -62,7 +76,14 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication AuthnRequest authnRequest = createAuthnRequest(request.getIssuer(), request.getDestination(), request.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(null)); - return this.saml.serialize(authnRequest, request.getCredentials()); + for (org.springframework.security.saml2.credentials.Saml2X509Credential credential : request.getCredentials()) { + if (credential.isSigningCredential()) { + Credential cred = getSigningCredential(credential.getCertificate(), credential.getPrivateKey(), request.getIssuer()); + signAuthnRequest(authnRequest, cred); + return this.saml.serialize(authnRequest); + } + } + throw new IllegalArgumentException("No signing credential provided"); } /** @@ -72,7 +93,7 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) { AuthnRequest authnRequest = createAuthnRequest(context); String xml = context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned() ? - this.saml.serialize(authnRequest, context.getRelyingPartyRegistration().getSigningCredentials()) : + signThenSerialize(authnRequest, context.getRelyingPartyRegistration()) : this.saml.serialize(authnRequest); return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context) @@ -93,7 +114,7 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication .relayState(context.getRelayState()); if (context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned()) { - List signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials(); + Collection signingCredentials = context.getRelyingPartyRegistration().getSigningX509Credentials(); Map signedParams = this.saml.signQueryParameters( signingCredentials, deflatedAndEncoded, @@ -178,4 +199,34 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication } this.protocolBindingResolver = context -> protocolBinding; } + + private String signThenSerialize(AuthnRequest authnRequest, RelyingPartyRegistration relyingPartyRegistration) { + for (Saml2X509Credential credential : relyingPartyRegistration.getSigningX509Credentials()) { + Credential cred = getSigningCredential( + credential.getCertificate(), credential.getPrivateKey(), relyingPartyRegistration.getEntityId()); + signAuthnRequest(authnRequest, cred); + return this.saml.serialize(authnRequest); + } + throw new IllegalArgumentException("No signing credential provided"); + } + + private void signAuthnRequest(AuthnRequest authnRequest, Credential credential) { + SignatureSigningParameters parameters = new SignatureSigningParameters(); + parameters.setSigningCredential(credential); + parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256); + parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + try { + SignatureSupport.signObject(authnRequest, parameters); + } catch (MarshallingException | SignatureException | SecurityException e) { + throw new Saml2Exception(e); + } + } + + private Credential getSigningCredential(X509Certificate certificate, PrivateKey privateKey, String entityId) { + BasicCredential cred = CredentialSupport.getSimpleCredential(certificate, privateKey); + cred.setEntityId(entityId); + cred.setUsageType(UsageType.SIGNING); + return cred; + } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java index 25dd4388e4..55d867e754 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java @@ -19,9 +19,9 @@ package org.springframework.security.saml2.provider.service.authentication; import java.io.ByteArrayInputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.namespace.QName; @@ -62,7 +62,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.credentials.Saml2X509Credential; +import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.util.Assert; import org.springframework.web.util.UriUtils; @@ -187,13 +187,6 @@ final class OpenSamlImplementation { } } - String serialize(AuthnRequest authnRequest, List signingCredentials) { - if (hasSigningCredential(signingCredentials) != null) { - signAuthnRequest(authnRequest, signingCredentials); - } - return serialize(authnRequest); - } - /** * Returns query parameter after creating a Query String signature * All return values are unencoded and will need to be encoded prior to sending @@ -205,7 +198,7 @@ final class OpenSamlImplementation { * */ Map signQueryParameters( - List signingCredentials, + Collection signingCredentials, String samlRequest, String relayState) { Assert.notNull(samlRequest, "samlRequest cannot be null"); @@ -280,7 +273,7 @@ final class OpenSamlImplementation { } - private Saml2X509Credential hasSigningCredential(List credentials) { + private Saml2X509Credential hasSigningCredential(Collection credentials) { for (Saml2X509Credential c : credentials) { if (c.isSigningCredential()) { return c; @@ -289,7 +282,7 @@ final class OpenSamlImplementation { return null; } - private Credential getSigningCredential(List signingCredential, + private Credential getSigningCredential(Collection signingCredential, String localSpEntityId ) { Saml2X509Credential credential = hasSigningCredential(signingCredential); @@ -302,7 +295,7 @@ final class OpenSamlImplementation { return cred; } - private void signAuthnRequest(AuthnRequest authnRequest, List signingCredentials) { + private void signAuthnRequest(AuthnRequest authnRequest, Collection signingCredentials) { SignatureSigningParameters parameters = new SignatureSigningParameters(); Credential credential = getSigningCredential(signingCredentials, authnRequest.getIssuer().getValue()); parameters.setSigningCredential(credential); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java index 9ae7c284e4..b70a4fe69c 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java @@ -16,12 +16,12 @@ package org.springframework.security.saml2.provider.service.authentication; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; - import java.nio.charset.StandardCharsets; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; + import static org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequest.withAuthenticationRequestContext; import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate; import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode; 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 0afe03f026..5d934ed0f0 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,18 @@ package org.springframework.security.saml2.provider.service.registration; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; -import org.springframework.security.saml2.credentials.Saml2X509Credential; -import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType; +import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.util.Assert; /** @@ -51,11 +54,11 @@ import org.springframework.util.Assert; * RelyingPartyRegistration rp = RelyingPartyRegistration.withRegistrationId(registrationId) * .entityId(relyingPartyEntityId) * .assertionConsumerServiceLocation(assertingConsumerServiceLocation) - * .credentials(c -> c.add(relyingPartySigningCredential)) + * .signingX509Credentials(c -> c.add(relyingPartySigningCredential)) * .assertingPartyDetails(details -> details * .entityId(assertingPartyEntityId)); * .singleSignOnServiceLocation(singleSignOnServiceLocation)) - * .credentials(c -> c.add(assertingPartyVerificationCredential)) + * .verifyingX509Credentials(c -> c.add(assertingPartyVerificationCredential)) * .build(); * * @@ -70,7 +73,9 @@ public class RelyingPartyRegistration { private final String assertionConsumerServiceLocation; private final Saml2MessageBinding assertionConsumerServiceBinding; private final ProviderDetails providerDetails; - private final List credentials; + private final List credentials; + private final Collection decryptionX509Credentials; + private final Collection signingX509Credentials; private RelyingPartyRegistration( String registrationId, @@ -78,7 +83,9 @@ public class RelyingPartyRegistration { String assertionConsumerServiceLocation, Saml2MessageBinding assertionConsumerServiceBinding, ProviderDetails providerDetails, - List credentials) { + Collection credentials, + Collection decryptionX509Credentials, + Collection signingX509Credentials) { Assert.hasText(registrationId, "registrationId cannot be empty"); Assert.hasText(entityId, "entityId cannot be empty"); @@ -86,15 +93,29 @@ public class RelyingPartyRegistration { Assert.notNull(assertionConsumerServiceBinding, "assertionConsumerServiceBinding cannot be null"); Assert.notNull(providerDetails, "providerDetails cannot be null"); Assert.notEmpty(credentials, "credentials cannot be empty"); - for (Saml2X509Credential c : credentials) { + for (org.springframework.security.saml2.credentials.Saml2X509Credential c : credentials) { Assert.notNull(c, "credentials cannot contain null elements"); } + Assert.notNull(decryptionX509Credentials, "decryptionX509Credentials cannot be null"); + for (Saml2X509Credential c : decryptionX509Credentials) { + Assert.notNull(c, "decryptionX509Credentials cannot contain null elements"); + Assert.isTrue(c.isDecryptionCredential(), + "All decryptionX509Credentials must have a usage of DECRYPTION set"); + } + Assert.notNull(signingX509Credentials, "signingX509Credentials cannot be null"); + for (Saml2X509Credential c : signingX509Credentials) { + Assert.notNull(c, "signingX509Credentials cannot contain null elements"); + Assert.isTrue(c.isSigningCredential(), + "All signingX509Credentials must have a usage of SIGNING set"); + } this.registrationId = registrationId; this.entityId = entityId; this.assertionConsumerServiceLocation = assertionConsumerServiceLocation; this.assertionConsumerServiceBinding = assertionConsumerServiceBinding; this.providerDetails = providerDetails; this.credentials = Collections.unmodifiableList(new LinkedList<>(credentials)); + this.decryptionX509Credentials = Collections.unmodifiableList(new LinkedList<>(decryptionX509Credentials)); + this.signingX509Credentials = Collections.unmodifiableList(new LinkedList<>(signingX509Credentials)); } /** @@ -154,6 +175,26 @@ public class RelyingPartyRegistration { return this.assertionConsumerServiceBinding; } + /** + * Get the {@link Collection} of decryption {@link Saml2X509Credential}s associated with this relying party + * + * @return the {@link Collection} of decryption {@link Saml2X509Credential}s associated with this relying party + * @since 5.4 + */ + public Collection getDecryptionX509Credentials() { + return this.decryptionX509Credentials; + } + + /** + * Get the {@link Collection} of signing {@link Saml2X509Credential}s associated with this relying party + * + * @return the {@link Collection} of signing {@link Saml2X509Credential}s associated with this relying party + * @since 5.4 + */ + public Collection getSigningX509Credentials() { + return this.signingX509Credentials; + } + /** * Get the configuration details for the Asserting Party * @@ -225,50 +266,62 @@ public class RelyingPartyRegistration { * Returns a list of configured credentials to be used in message exchanges between relying party, SP, and * asserting party, IDP. * @return a list of credentials + * @deprecated Instead of retrieving all credentials, use the appropriate method for obtaining the correct type */ - public List getCredentials() { + @Deprecated + public List getCredentials() { return this.credentials; } /** * @return a filtered list containing only credentials of type - * {@link Saml2X509CredentialType#VERIFICATION}. + * {@link org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType#VERIFICATION}. * Returns an empty list of credentials are not found + * @deprecated Use {@link #getAssertingPartyDetails().getSigningX509Credentials()} instead */ - public List getVerificationCredentials() { - return filterCredentials(c -> c.isSignatureVerficationCredential()); + @Deprecated + public List getVerificationCredentials() { + return filterCredentials(org.springframework.security.saml2.credentials.Saml2X509Credential::isSignatureVerficationCredential); } /** * @return a filtered list containing only credentials of type - * {@link Saml2X509CredentialType#SIGNING}. + * {@link org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType#SIGNING}. * Returns an empty list of credentials are not found + * @deprecated Use {@link #getSigningX509Credentials()} instead */ - public List getSigningCredentials() { - return filterCredentials(c -> c.isSigningCredential()); + @Deprecated + public List getSigningCredentials() { + return filterCredentials(org.springframework.security.saml2.credentials.Saml2X509Credential::isSigningCredential); } /** * @return a filtered list containing only credentials of type - * {@link Saml2X509CredentialType#ENCRYPTION}. + * {@link org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType#ENCRYPTION}. * Returns an empty list of credentials are not found + * @deprecated Use {@link AssertingPartyDetails#getEncryptionX509Credentials()} instead */ - public List getEncryptionCredentials() { - return filterCredentials(c -> c.isEncryptionCredential()); + @Deprecated + public List getEncryptionCredentials() { + return filterCredentials(org.springframework.security.saml2.credentials.Saml2X509Credential::isEncryptionCredential); } /** * @return a filtered list containing only credentials of type - * {@link Saml2X509CredentialType#DECRYPTION}. + * {@link org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType#DECRYPTION}. * Returns an empty list of credentials are not found + * @deprecated Use {@link #getDecryptionX509Credentials()} instead */ - public List getDecryptionCredentials() { - return filterCredentials(c -> c.isDecryptionCredential()); + @Deprecated + public List getDecryptionCredentials() { + return filterCredentials(org.springframework.security.saml2.credentials.Saml2X509Credential::isDecryptionCredential); } - private List filterCredentials(Function filter) { - List result = new LinkedList<>(); - for (Saml2X509Credential c : getCredentials()) { + private List filterCredentials( + Function filter) { + + List result = new LinkedList<>(); + for (org.springframework.security.saml2.credentials.Saml2X509Credential c : this.credentials) { if (filter.apply(c)) { result.add(c); } @@ -295,15 +348,18 @@ public class RelyingPartyRegistration { Assert.notNull(registration, "registration cannot be null"); return withRegistrationId(registration.getRegistrationId()) .entityId(registration.getEntityId()) + .signingX509Credentials(c -> c.addAll(registration.getSigningX509Credentials())) + .decryptionX509Credentials(c -> c.addAll(registration.getDecryptionX509Credentials())) .assertionConsumerServiceLocation(registration.getAssertionConsumerServiceLocation()) .assertionConsumerServiceBinding(registration.getAssertionConsumerServiceBinding()) - .assertingPartyDetails(c -> c + .assertingPartyDetails(assertingParty -> assertingParty .entityId(registration.getAssertingPartyDetails().getEntityId()) .wantAuthnRequestsSigned(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) + .verificationX509Credentials(c -> c.addAll(registration.getAssertingPartyDetails().getVerificationX509Credentials())) + .encryptionX509Credentials(c -> c.addAll(registration.getAssertingPartyDetails().getEncryptionX509Credentials())) .singleSignOnServiceLocation(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()) .singleSignOnServiceBinding(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) - ) - .credentials(c -> c.addAll(registration.getCredentials())); + ); } @@ -315,20 +371,38 @@ public class RelyingPartyRegistration { public final static class AssertingPartyDetails { private final String entityId; private final boolean wantAuthnRequestsSigned; + private final Collection verificationX509Credentials; + private final Collection encryptionX509Credentials; private final String singleSignOnServiceLocation; private final Saml2MessageBinding singleSignOnServiceBinding; private AssertingPartyDetails( String entityId, boolean wantAuthnRequestsSigned, + Collection verificationX509Credentials, + Collection encryptionX509Credentials, String singleSignOnServiceLocation, Saml2MessageBinding singleSignOnServiceBinding) { Assert.hasText(entityId, "entityId cannot be null or empty"); + Assert.notNull(verificationX509Credentials, "verificationX509Credentials cannot be null"); + for (Saml2X509Credential credential : verificationX509Credentials) { + Assert.notNull(credential, "verificationX509Credentials cannot have null values"); + Assert.isTrue(credential.isVerificationCredential(), + "All verificationX509Credentials must have a usage of VERIFICATION set"); + } + Assert.notNull(encryptionX509Credentials, "encryptionX509Credentials cannot be null"); + for (Saml2X509Credential credential : encryptionX509Credentials) { + Assert.notNull(credential, "encryptionX509Credentials cannot have null values"); + Assert.isTrue(credential.isEncryptionCredential(), + "All encryptionX509Credentials must have a usage of ENCRYPTION set"); + } Assert.notNull(singleSignOnServiceLocation, "singleSignOnServiceLocation cannot be null"); Assert.notNull(singleSignOnServiceBinding, "singleSignOnServiceBinding cannot be null"); this.entityId = entityId; this.wantAuthnRequestsSigned = wantAuthnRequestsSigned; + this.verificationX509Credentials = verificationX509Credentials; + this.encryptionX509Credentials = encryptionX509Credentials; this.singleSignOnServiceLocation = singleSignOnServiceLocation; this.singleSignOnServiceBinding = singleSignOnServiceBinding; } @@ -362,6 +436,26 @@ public class RelyingPartyRegistration { return this.wantAuthnRequestsSigned; } + /** + * Get all verification {@link Saml2X509Credential}s associated with this asserting party + * + * @return all verification {@link Saml2X509Credential}s associated with this asserting party + * @since 5.4 + */ + public Collection getVerificationX509Credentials() { + return this.verificationX509Credentials; + } + + /** + * Get all encryption {@link Saml2X509Credential}s associated with this asserting party + * + * @return all encryption {@link Saml2X509Credential}s associated with this asserting party + * @since 5.4 + */ + public Collection getEncryptionX509Credentials() { + return this.encryptionX509Credentials; + } + /** * Get the * SingleSignOnService @@ -395,6 +489,8 @@ public class RelyingPartyRegistration { public final static class Builder { private String entityId; private boolean wantAuthnRequestsSigned = true; + private Collection verificationX509Credentials = new HashSet<>(); + private Collection encryptionX509Credentials = new HashSet<>(); private String singleSignOnServiceLocation; private Saml2MessageBinding singleSignOnServiceBinding = Saml2MessageBinding.REDIRECT; @@ -424,6 +520,30 @@ public class RelyingPartyRegistration { return this; } + /** + * Apply this {@link Consumer} to the list of {@link Saml2X509Credential}s + * + * @param credentialsConsumer a {@link Consumer} of the {@link List} of {@link Saml2X509Credential}s + * @return the {@link RelyingPartyRegistration.Builder} for further configuration + * @since 5.4 + */ + public Builder verificationX509Credentials(Consumer> credentialsConsumer) { + credentialsConsumer.accept(this.verificationX509Credentials); + return this; + } + + /** + * Apply this {@link Consumer} to the list of {@link Saml2X509Credential}s + * + * @param credentialsConsumer a {@link Consumer} of the {@link List} of {@link Saml2X509Credential}s + * @return the {@link RelyingPartyRegistration.Builder} for further configuration + * @since 5.4 + */ + public Builder encryptionX509Credentials(Consumer> credentialsConsumer) { + credentialsConsumer.accept(this.encryptionX509Credentials); + return this; + } + /** * Set the * SingleSignOnService @@ -466,6 +586,8 @@ public class RelyingPartyRegistration { return new AssertingPartyDetails( this.entityId, this.wantAuthnRequestsSigned, + this.verificationX509Credentials, + this.encryptionX509Credentials, this.singleSignOnServiceLocation, this.singleSignOnServiceBinding ); @@ -591,10 +713,12 @@ public class RelyingPartyRegistration { public final static class Builder { private String registrationId; private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}"; + private Collection signingX509Credentials = new HashSet<>(); + private Collection decryptionX509Credentials = new HashSet<>(); private String assertionConsumerServiceLocation; private Saml2MessageBinding assertionConsumerServiceBinding = Saml2MessageBinding.POST; private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder(); - private List credentials = new LinkedList<>(); + private Collection credentials = new HashSet<>(); private Builder(String registrationId) { this.registrationId = registrationId; @@ -629,6 +753,32 @@ public class RelyingPartyRegistration { return this; } + /** + * Apply this {@link Consumer} to the {@link Collection} of {@link Saml2X509Credential}s + * for the purposes of modifying the {@link Collection} + * + * @param credentialsConsumer - the {@link Consumer} for modifying the {@link Collection} + * @return the {@link Builder} for further configuration + * @since 5.4 + */ + public Builder signingX509Credentials(Consumer> credentialsConsumer) { + credentialsConsumer.accept(this.signingX509Credentials); + return this; + } + + /** + * Apply this {@link Consumer} to the {@link Collection} of {@link Saml2X509Credential}s + * for the purposes of modifying the {@link Collection} + * + * @param credentialsConsumer - the {@link Consumer} for modifying the {@link Collection} + * @return the {@link Builder} for further configuration + * @since 5.4 + */ + public Builder decryptionX509Credentials(Consumer> credentialsConsumer) { + credentialsConsumer.accept(this.decryptionX509Credentials); + return this; + } + /** * Set the AssertionConsumerService * Location. @@ -693,8 +843,12 @@ public class RelyingPartyRegistration { * * @param credentials - a consumer that can modify the collection of credentials * @return this object + * @deprecated Use {@link #signingX509Credentials} or {@link #decryptionX509Credentials} instead + * for relying party keys or {@link AssertingPartyDetails.Builder#verificationX509Credentials} or + * {@link AssertingPartyDetails.Builder#encryptionX509Credentials} for asserting party keys */ - public Builder credentials(Consumer> credentials) { + @Deprecated + public Builder credentials(Consumer> credentials) { credentials.accept(this.credentials); return this; } @@ -769,15 +923,85 @@ public class RelyingPartyRegistration { * @return a RelyingPartyRegistration instance */ public RelyingPartyRegistration build() { + for (org.springframework.security.saml2.credentials.Saml2X509Credential credential : this.credentials) { + Saml2X509Credential mapped = fromDeprecated(credential); + if (credential.isSigningCredential()) { + signingX509Credentials(c -> c.add(mapped)); + } + if (credential.isDecryptionCredential()) { + decryptionX509Credentials(c -> c.add(mapped)); + } + if (credential.isSignatureVerficationCredential()) { + this.providerDetails.assertingPartyDetailsBuilder + .verificationX509Credentials(c -> c.add(mapped)); + } + if (credential.isEncryptionCredential()) { + this.providerDetails.assertingPartyDetailsBuilder + .encryptionX509Credentials(c -> c.add(mapped)); + } + } + + for (Saml2X509Credential credential : this.signingX509Credentials) { + this.credentials.add(toDeprecated(credential)); + } + for (Saml2X509Credential credential : this.decryptionX509Credentials) { + this.credentials.add(toDeprecated(credential)); + } + for (Saml2X509Credential credential : this.providerDetails.assertingPartyDetailsBuilder.verificationX509Credentials) { + this.credentials.add(toDeprecated(credential)); + } + for (Saml2X509Credential credential : this.providerDetails.assertingPartyDetailsBuilder.encryptionX509Credentials) { + this.credentials.add(toDeprecated(credential)); + } + return new RelyingPartyRegistration( this.registrationId, this.entityId, this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding, this.providerDetails.build(), - this.credentials + this.credentials, + this.decryptionX509Credentials, + this.signingX509Credentials ); } } + private static Saml2X509Credential fromDeprecated(org.springframework.security.saml2.credentials.Saml2X509Credential credential) { + PrivateKey privateKey = credential.getPrivateKey(); + X509Certificate certificate = credential.getCertificate(); + Set credentialTypes = new HashSet<>(); + if (credential.isSigningCredential()) { + credentialTypes.add(Saml2X509Credential.Saml2X509CredentialType.SIGNING); + } + if (credential.isSignatureVerficationCredential()) { + credentialTypes.add(Saml2X509Credential.Saml2X509CredentialType.VERIFICATION); + } + if (credential.isEncryptionCredential()) { + credentialTypes.add(Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION); + } + if (credential.isDecryptionCredential()) { + credentialTypes.add(Saml2X509Credential.Saml2X509CredentialType.DECRYPTION); + } + return new Saml2X509Credential(privateKey, certificate, credentialTypes); + } + + private static org.springframework.security.saml2.credentials.Saml2X509Credential toDeprecated(Saml2X509Credential credential) { + PrivateKey privateKey = credential.getPrivateKey(); + X509Certificate certificate = credential.getCertificate(); + Set credentialTypes = new HashSet<>(); + if (credential.isSigningCredential()) { + credentialTypes.add(org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING); + } + if (credential.isVerificationCredential()) { + credentialTypes.add(org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION); + } + if (credential.isEncryptionCredential()) { + credentialTypes.add(org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION); + } + if (credential.isDecryptionCredential()) { + credentialTypes.add(org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION); + } + return new org.springframework.security.saml2.credentials.Saml2X509Credential(privateKey, certificate, credentialTypes); + } } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java new file mode 100644 index 0000000000..f2d711882e --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java @@ -0,0 +1,179 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.saml2.core; + +import java.io.ByteArrayInputStream; +import java.security.KeyException; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.opensaml.security.crypto.KeySupport; + +import org.springframework.security.saml2.Saml2Exception; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION; +import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION; +import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.SIGNING; +import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION; + +public final class TestSaml2X509Credentials { + public static Saml2X509Credential assertingPartySigningCredential() { + return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), SIGNING); + } + + public static Saml2X509Credential assertingPartyEncryptingCredential() { + return new Saml2X509Credential(spCertificate(), ENCRYPTION); + } + + public static Saml2X509Credential assertingPartyPrivateCredential() { + return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), SIGNING, DECRYPTION); + } + + public static Saml2X509Credential relyingPartyVerifyingCredential() { + return new Saml2X509Credential(idpCertificate(), VERIFICATION); + } + + public static Saml2X509Credential relyingPartySigningCredential() { + return new Saml2X509Credential(spPrivateKey(), spCertificate(), SIGNING); + } + + public static Saml2X509Credential relyingPartyDecryptingCredential() { + return new Saml2X509Credential(spPrivateKey(), spCertificate(), DECRYPTION); + } + + private static X509Certificate certificate(String cert) { + ByteArrayInputStream certBytes = new ByteArrayInputStream(cert.getBytes()); + try { + return (X509Certificate) CertificateFactory + .getInstance("X.509") + .generateCertificate(certBytes); + } + catch (CertificateException e) { + throw new Saml2Exception(e); + } + } + + private static PrivateKey privateKey(String key) { + try { + return KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]); + } + catch (KeyException e) { + throw new Saml2Exception(e); + } + } + + private static X509Certificate idpCertificate() { + return certificate("-----BEGIN CERTIFICATE-----\n" + + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n" + + "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n" + + "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n" + + "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n" + + "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n" + + "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n" + + "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n" + + "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n" + + "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n" + + "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n" + + "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n" + + "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n" + + "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n" + + "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n" + + "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n" + + "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n" + + "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n" + + "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n" + + "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n" + + "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n" + + "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n" + + "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + "-----END CERTIFICATE-----\n"); + } + + + private static PrivateKey idpPrivateKey() { + return privateKey("-----BEGIN PRIVATE KEY-----\n" + + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4cn62E1xLqpN3\n" + + "4PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZX\n" + + "W+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHE\n" + + "fDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7h\n" + + "Z6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/T\n" + + "Xy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7\n" + + "I+J5lS8VAgMBAAECggEBAKyxBlIS7mcp3chvq0RF7B3PHFJMMzkwE+t3pLJcs4cZ\n" + + "nezh/KbREfP70QjXzk/llnZCvxeIs5vRu24vbdBm79qLHqBuHp8XfHHtuo2AfoAQ\n" + + "l4h047Xc/+TKMivnPQ0jX9qqndKDLqZDf5wnbslDmlskvF0a/MjsLU0TxtOfo+dB\n" + + "t55FW11cGqxZwhS5Gnr+cbw3OkHz23b9gEOt9qfwPVepeysbmm9FjU+k4yVa7rAN\n" + + "xcbzVb6Y7GCITe2tgvvEHmjB9BLmWrH3mZ3Af17YU/iN6TrpPd6Sj3QoS+2wGtAe\n" + + "HbUs3CKJu7bIHcj4poal6Kh8519S+erJTtqQ8M0ZiEECgYEA43hLYAPaUueFkdfh\n" + + "9K/7ClH6436CUH3VdizwUXi26fdhhV/I/ot6zLfU2mgEHU22LBECWQGtAFm8kv0P\n" + + "zPn+qjaR3e62l5PIlSYbnkIidzoDZ2ztu4jF5LgStlTJQPteFEGgZVl5o9DaSZOq\n" + + "Yd7G3XqXuQ1VGMW58G5FYJPtA1cCgYEAz5TPUtK+R2KXHMjUwlGY9AefQYRYmyX2\n" + + "Tn/OFgKvY8lpAkMrhPKONq7SMYc8E9v9G7A0dIOXvW7QOYSapNhKU+np3lUafR5F\n" + + "4ZN0bxZ9qjHbn3AMYeraKjeutHvlLtbHdIc1j3sxe/EzltRsYmiqLdEBW0p6hwWg\n" + + "tyGhYWVyaXMCgYAfDOKtHpmEy5nOCLwNXKBWDk7DExfSyPqEgSnk1SeS1HP5ctPK\n" + + "+1st6sIhdiVpopwFc+TwJWxqKdW18tlfT5jVv1E2DEnccw3kXilS9xAhWkfwrEvf\n" + + "V5I74GydewFl32o+NZ8hdo9GL1I8zO1rIq/et8dSOWGuWf9BtKu/vTGTTQKBgFxU\n" + + "VjsCnbvmsEwPUAL2hE/WrBFaKocnxXx5AFNt8lEyHtDwy4Sg1nygGcIJ4sD6koQk\n" + + "RdClT3LkvR04TAiSY80bN/i6ZcPNGUwSaDGZEWAIOSWbkwZijZNFnSGOEgxZX/IG\n" + + "yd39766vREEMTwEeiMNEOZQ/dmxkJm4OOVe25cLdAoGACOtPnq1Fxay80UYBf4rQ\n" + + "+bJ9yX1ulB8WIree1hD7OHSB2lRHxrVYWrglrTvkh63Lgx+EcsTV788OsvAVfPPz\n" + + "BZrn8SdDlQqalMxUBYEFwnsYD3cQ8yOUnijFVC4xNcdDv8OIqVgSk4KKxU5AshaA\n" + "xk6Mox+u8Cc2eAK12H13i+8=\n" + + "-----END PRIVATE KEY-----\n"); + } + + private static X509Certificate spCertificate() { + + return certificate("-----BEGIN CERTIFICATE-----\n" + + "MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC\n" + + "VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG\n" + + "A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD\n" + + "DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1\n" + + "MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES\n" + + "MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN\n" + + "TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos\n" + + "vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM\n" + + "+U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG\n" + + "y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi\n" + + "XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+\n" + + "qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD\n" + + "RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B\n" + + "-----END CERTIFICATE-----"); + } + + private static PrivateKey spPrivateKey() { + return privateKey("-----BEGIN PRIVATE KEY-----\n" + + "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE\n" + + "VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK\n" + + "cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6\n" + + "Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn\n" + + "x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5\n" + + "wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd\n" + + "vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY\n" + + "8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX\n" + + "oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx\n" + + "EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0\n" + + "KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt\n" + + "YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr\n" + + "9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM\n" + + "INrtuLp4YHbgk1mi\n" + + "-----END PRIVATE KEY-----"); + } + +} diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementationTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementationTests.java index 7e1015c2e8..5fbe68c8fd 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementationTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import java.util.Map; import org.junit.Test; import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.springframework.security.saml2.credentials.Saml2X509Credential; +import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.web.util.UriUtils; import static java.nio.charset.StandardCharsets.ISO_8859_1; @@ -30,8 +30,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; import static org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects.getSigningCredential; -import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.assertingPartySigningCredential; -import static org.springframework.security.saml2.credentials.TestSaml2X509Credentials.relyingPartyVerifyingCredential; +import static org.springframework.security.saml2.core.TestSaml2X509Credentials.assertingPartySigningCredential; +import static org.springframework.security.saml2.core.TestSaml2X509Credentials.relyingPartyVerifyingCredential; public class OpenSamlImplementationTests { 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 79b8823141..878b52a134 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 @@ -74,7 +74,7 @@ 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.credentials.Saml2X509Credential; +import org.springframework.security.saml2.core.Saml2X509Credential; final class TestOpenSamlObjects { private static OpenSamlImplementation saml = OpenSamlImplementation.getInstance(); @@ -178,6 +178,13 @@ final class TestOpenSamlObjects { return cred; } + static Credential getSigningCredential(org.springframework.security.saml2.credentials.Saml2X509Credential credential, String entityId) { + BasicCredential cred = getBasicCredential(credential); + cred.setEntityId(entityId); + cred.setUsageType(UsageType.SIGNING); + return cred; + } + static BasicCredential getBasicCredential(Saml2X509Credential credential) { return CredentialSupport.getSimpleCredential( credential.getCertificate(), @@ -185,6 +192,13 @@ final class TestOpenSamlObjects { ); } + static BasicCredential getBasicCredential(org.springframework.security.saml2.credentials.Saml2X509Credential credential) { + return CredentialSupport.getSimpleCredential( + credential.getCertificate(), + credential.getPrivateKey() + ); + } + static T signed(T signable, Saml2X509Credential credential, String entityId) { SignatureSigningParameters parameters = new SignatureSigningParameters(); Credential signingCredential = getSigningCredential(credential, entityId); @@ -201,6 +215,22 @@ final class TestOpenSamlObjects { return signable; } + static T signed(T signable, org.springframework.security.saml2.credentials.Saml2X509Credential credential, String entityId) { + SignatureSigningParameters parameters = new SignatureSigningParameters(); + Credential signingCredential = getSigningCredential(credential, entityId); + parameters.setSigningCredential(signingCredential); + parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256); + parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + try { + SignatureSupport.signObject(signable, parameters); + } catch (MarshallingException | SignatureException | SecurityException e) { + throw new Saml2Exception(e); + } + + return signable; + } + static EncryptedAssertion encrypted(Assertion assertion, Saml2X509Credential credential) { X509Certificate certificate = credential.getCertificate(); Encrypter encrypter = getEncrypter(certificate); @@ -212,6 +242,17 @@ final class TestOpenSamlObjects { } } + static EncryptedAssertion encrypted(Assertion assertion, org.springframework.security.saml2.credentials.Saml2X509Credential credential) { + X509Certificate certificate = credential.getCertificate(); + Encrypter encrypter = getEncrypter(certificate); + try { + return encrypter.encrypt(assertion); + } + catch (EncryptionException e) { + throw new Saml2Exception("Unable to encrypt assertion.", e); + } + } + static EncryptedID encrypted(NameID nameId, Saml2X509Credential credential) { X509Certificate certificate = credential.getCertificate(); Encrypter encrypter = getEncrypter(certificate); @@ -223,6 +264,17 @@ final class TestOpenSamlObjects { } } + static EncryptedID encrypted(NameID nameId, org.springframework.security.saml2.credentials.Saml2X509Credential credential) { + X509Certificate certificate = credential.getCertificate(); + Encrypter encrypter = getEncrypter(certificate); + try { + return encrypter.encrypt(nameId); + } + catch (EncryptionException e) { + throw new Saml2Exception("Unable to encrypt nameID.", e); + } + } + private static Encrypter getEncrypter(X509Certificate certificate) { String dataAlgorithm = XMLCipherParameters.AES_256; String keyAlgorithm = XMLCipherParameters.RSA_1_5; diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java index 72db62ce6e..d8cd44acd5 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java @@ -81,6 +81,14 @@ public class RelyingPartyRegistrationTests { .isFalse(); assertThat(copy.getAssertionConsumerServiceBinding()) .isEqualTo(registration.getAssertionConsumerServiceBinding()); + assertThat(copy.getDecryptionX509Credentials()) + .isEqualTo(registration.getDecryptionX509Credentials()); + assertThat(copy.getSigningX509Credentials()) + .isEqualTo(registration.getSigningX509Credentials()); + assertThat(copy.getAssertingPartyDetails().getEncryptionX509Credentials()) + .isEqualTo(registration.getAssertingPartyDetails().getEncryptionX509Credentials()); + assertThat(copy.getAssertingPartyDetails().getVerificationX509Credentials()) + .isEqualTo(registration.getAssertingPartyDetails().getVerificationX509Credentials()); } @Test diff --git a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index ddc28ca4af..67d2257485 100644 --- a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -26,14 +26,14 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.converter.RsaKeyConverters; -import org.springframework.security.saml2.credentials.Saml2X509Credential; +import org.springframework.security.saml2.core.Saml2X509Credential; 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.servlet.filter.Saml2WebSsoAuthenticationFilter; -import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION; -import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING; -import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION; +import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION; +import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.SIGNING; +import static org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION; @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -56,11 +56,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { return RelyingPartyRegistration.withRegistrationId(registrationId) .entityId(localEntityIdTemplate) .assertionConsumerServiceLocation(acsUrlTemplate) - .credentials(c -> c.add(signingCredential)) + .signingX509Credentials(c -> c.add(signingCredential)) .assertingPartyDetails(config -> config .entityId(idpEntityId) - .singleSignOnServiceLocation(webSsoEndpoint)) - .credentials(c -> c.add(idpVerificationCertificate)) + .singleSignOnServiceLocation(webSsoEndpoint) + .verificationX509Credentials(c -> c.add(idpVerificationCertificate))) .build(); }