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 deleted file mode 100644 index 04a929044e..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * 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.provider.service.authentication; - -import java.io.ByteArrayInputStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import net.shibboleth.utilities.java.support.xml.XMLParserException; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilderFactory; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.MarshallerFactory; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.io.UnmarshallingException; -import org.opensaml.saml.saml2.core.AuthnRequest; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -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.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureException; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriUtils; - -import static java.util.Arrays.asList; -import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getParserPool; -import static org.springframework.util.StringUtils.hasText; - -/** - * @since 5.2 - */ -final class OpenSamlImplementation { - static { - OpenSamlInitializationService.initialize(); - } - - private static OpenSamlImplementation instance = new OpenSamlImplementation(); - private static XMLObjectBuilderFactory xmlObjectBuilderFactory = - XMLObjectProviderRegistrySupport.getBuilderFactory(); - - private final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - asList( - new InlineEncryptedKeyResolver(), - new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver() - ) - ); - - /* - * ============================================================== - * PUBLIC METHODS - * ============================================================== - */ - static OpenSamlImplementation getInstance() { - return instance; - } - - EncryptedKeyResolver getEncryptedKeyResolver() { - return this.encryptedKeyResolver; - } - - T buildSamlObject(QName qName) { - return (T) xmlObjectBuilderFactory.getBuilder(qName).buildObject(qName); - } - - XMLObject resolve(String xml) { - return resolve(xml.getBytes(StandardCharsets.UTF_8)); - } - - String serialize(XMLObject xmlObject) { - final MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory(); - try { - Element element = marshallerFactory.getMarshaller(xmlObject).marshall(xmlObject); - return SerializeSupport.nodeToString(element); - } catch (MarshallingException e) { - throw new Saml2Exception(e); - } - } - - /** - * Returns query parameter after creating a Query String signature - * All return values are unencoded and will need to be encoded prior to sending - * The methods {@link UriUtils#encode(String, Charset)} and {@link UriUtils#decode(String, Charset)} - * with the {@link StandardCharsets#ISO_8859_1} character set are used for all URL encoding/decoding. - * @param signingCredentials - credentials to be used for signature - * @return a map of unencoded query parameters with the following keys: - * {@code {SAMLRequest, RelayState (may be null)}, SigAlg, Signature} - * - */ - Map signQueryParameters( - Collection signingCredentials, - String samlRequest, - String relayState) { - Assert.notNull(samlRequest, "samlRequest cannot be null"); - String algorithmUri = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; - StringBuilder queryString = new StringBuilder(); - queryString - .append("SAMLRequest") - .append("=") - .append(UriUtils.encode(samlRequest, StandardCharsets.ISO_8859_1)) - .append("&"); - if (hasText(relayState)) { - queryString - .append("RelayState") - .append("=") - .append(UriUtils.encode(relayState, StandardCharsets.ISO_8859_1)) - .append("&"); - } - queryString - .append("SigAlg") - .append("=") - .append(UriUtils.encode(algorithmUri, StandardCharsets.ISO_8859_1)); - - try { - byte[] rawSignature = XMLSigningUtil.signWithURI( - getSigningCredential(signingCredentials, ""), - algorithmUri, - queryString.toString().getBytes(StandardCharsets.UTF_8) - ); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - - Map result = new LinkedHashMap<>(); - result.put("SAMLRequest", samlRequest); - if (hasText(relayState)) { - result.put("RelayState", relayState); - } - result.put("SigAlg", algorithmUri); - result.put("Signature", b64Signature); - return result; - } - catch (SecurityException e) { - throw new Saml2Exception(e); - } - } - - /* - * ============================================================== - * PRIVATE METHODS - * ============================================================== - */ - - private XMLObject resolve(byte[] xml) { - XMLObject parsed = parse(xml); - if (parsed != null) { - return parsed; - } - throw new Saml2Exception("Deserialization not supported for given data set"); - } - - private XMLObject parse(byte[] xml) { - try { - Document document = getParserPool().parse(new ByteArrayInputStream(xml)); - Element element = document.getDocumentElement(); - return getUnmarshallerFactory().getUnmarshaller(element).unmarshall(element); - } - catch (UnmarshallingException | XMLParserException e) { - throw new Saml2Exception(e); - } - } - - private UnmarshallerFactory getUnmarshallerFactory() { - return XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - } - - - private Saml2X509Credential hasSigningCredential(Collection credentials) { - for (Saml2X509Credential c : credentials) { - if (c.isSigningCredential()) { - return c; - } - } - return null; - } - - private Credential getSigningCredential(Collection signingCredential, - String localSpEntityId - ) { - Saml2X509Credential credential = hasSigningCredential(signingCredential); - if (credential == null) { - throw new Saml2Exception("no signing credential configured"); - } - BasicCredential cred = getBasicCredential(credential); - cred.setEntityId(localSpEntityId); - cred.setUsageType(UsageType.SIGNING); - return cred; - } - - private void signAuthnRequest(AuthnRequest authnRequest, Collection signingCredentials) { - SignatureSigningParameters parameters = new SignatureSigningParameters(); - Credential credential = getSigningCredential(signingCredentials, authnRequest.getIssuer().getValue()); - 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 BasicX509Credential getBasicCredential(Saml2X509Credential credential) { - return CredentialSupport.getSimpleCredential( - credential.getCertificate(), - credential.getPrivateKey() - ); - } - -} 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 deleted file mode 100644 index 5fbe68c8fd..0000000000 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementationTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.provider.service.authentication; - -import java.util.Arrays; -import java.util.Map; - -import org.junit.Test; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; - -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.web.util.UriUtils; - -import static java.nio.charset.StandardCharsets.ISO_8859_1; -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.core.TestSaml2X509Credentials.assertingPartySigningCredential; -import static org.springframework.security.saml2.core.TestSaml2X509Credentials.relyingPartyVerifyingCredential; - -public class OpenSamlImplementationTests { - - @Test - public void getInstance() { - OpenSamlImplementation.getInstance(); - } - - @Test - public void signQueryParametersWhenDataSuppliedReturnsValidSignature() throws Exception { - OpenSamlImplementation impl = OpenSamlImplementation.getInstance(); - Saml2X509Credential signingCredential = assertingPartySigningCredential(); - Saml2X509Credential verifyingCredential = relyingPartyVerifyingCredential(); - String samlRequest = "saml-request-example"; - String encoded = Saml2Utils.samlEncode(samlRequest.getBytes(UTF_8)); - String relayState = "test relay state"; - Map parameters = impl.signQueryParameters(Arrays.asList(signingCredential), encoded, relayState); - - String queryString = "SAMLRequest=" + - UriUtils.encode(encoded, ISO_8859_1) + - "&RelayState=" + - UriUtils.encode(relayState, ISO_8859_1) + - "&SigAlg=" + - UriUtils.encode(ALGO_ID_SIGNATURE_RSA_SHA256, ISO_8859_1); - - - byte[] signature = Saml2Utils.samlDecode(parameters.get("Signature")); - boolean result = XMLSigningUtil.verifyWithURI( - getSigningCredential(verifyingCredential, "local-sp-entity-id"), - ALGO_ID_SIGNATURE_RSA_SHA256, - signature, - queryString.getBytes(UTF_8) - ); - assertThat(result).isTrue(); - } -} 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 17a66e5857..9267ef93dd 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 @@ -17,18 +17,19 @@ package org.springframework.security.saml2.provider.service.authentication; import java.security.cert.X509Certificate; -import java.util.Base64; -import java.util.UUID; -import java.util.List; import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.UUID; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import javax.xml.namespace.QName; import org.apache.xml.security.encryption.XMLCipherParameters; import org.joda.time.DateTime; import org.joda.time.Duration; +import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.io.MarshallingException; - import org.opensaml.core.xml.schema.XSAny; import org.opensaml.core.xml.schema.XSBoolean; import org.opensaml.core.xml.schema.XSBooleanValue; @@ -77,13 +78,13 @@ import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2X509Credential; +import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getBuilderFactory; + final class TestOpenSamlObjects { static { OpenSamlInitializationService.initialize(); } - private static OpenSamlImplementation saml = OpenSamlImplementation.getInstance(); - private static String USERNAME = "test@saml.user"; private static String DESTINATION = "https://localhost/login/saml2/sso/idp-alias"; private static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; @@ -96,7 +97,7 @@ final class TestOpenSamlObjects { } static Response response(String destination, String issuerEntityId) { - Response response = saml.buildSamlObject(Response.DEFAULT_ELEMENT_NAME); + Response response = build(Response.DEFAULT_ELEMENT_NAME); response.setID("R"+UUID.randomUUID().toString()); response.setIssueInstant(DateTime.now()); response.setVersion(SAMLVersion.VERSION_20); @@ -116,7 +117,7 @@ final class TestOpenSamlObjects { String recipientEntityId, String recipientUri ) { - Assertion assertion = saml.buildSamlObject(Assertion.DEFAULT_ELEMENT_NAME); + Assertion assertion = build(Assertion.DEFAULT_ELEMENT_NAME); assertion.setID("A"+ UUID.randomUUID().toString()); assertion.setIssueInstant(DateTime.now()); assertion.setVersion(SAMLVersion.VERSION_20); @@ -136,13 +137,13 @@ final class TestOpenSamlObjects { static Issuer issuer(String entityId) { - Issuer issuer = saml.buildSamlObject(Issuer.DEFAULT_ELEMENT_NAME); + Issuer issuer = build(Issuer.DEFAULT_ELEMENT_NAME); issuer.setValue(entityId); return issuer; } static Subject subject(String principalName) { - Subject subject = saml.buildSamlObject(Subject.DEFAULT_ELEMENT_NAME); + Subject subject = build(Subject.DEFAULT_ELEMENT_NAME); if (principalName != null) { subject.setNameID(nameId(principalName)); @@ -152,17 +153,17 @@ final class TestOpenSamlObjects { } static NameID nameId(String principalName) { - NameID nameId = saml.buildSamlObject(NameID.DEFAULT_ELEMENT_NAME); + NameID nameId = build(NameID.DEFAULT_ELEMENT_NAME); nameId.setValue(principalName); return nameId; } static SubjectConfirmation subjectConfirmation() { - return saml.buildSamlObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME); + return build(SubjectConfirmation.DEFAULT_ELEMENT_NAME); } static SubjectConfirmationData subjectConfirmationData(String recipient) { - SubjectConfirmationData subject = saml.buildSamlObject(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); + SubjectConfirmationData subject = build(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); subject.setRecipient(recipient); subject.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000))); subject.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000))); @@ -170,7 +171,7 @@ final class TestOpenSamlObjects { } static Conditions conditions() { - Conditions conditions = saml.buildSamlObject(Conditions.DEFAULT_ELEMENT_NAME); + Conditions conditions = build(Conditions.DEFAULT_ELEMENT_NAME); conditions.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000))); conditions.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000))); return conditions; @@ -362,4 +363,8 @@ final class TestOpenSamlObjects { return attributeStatements; } + + static T build(QName qName) { + return (T) getBuilderFactory().getBuilder(qName).buildObject(qName); + } }