parent
5779121da6
commit
08849e2652
|
@ -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> 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<String, String> signQueryParameters(
|
||||
Collection<Saml2X509Credential> 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<String, String> 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<Saml2X509Credential> credentials) {
|
||||
for (Saml2X509Credential c : credentials) {
|
||||
if (c.isSigningCredential()) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Credential getSigningCredential(Collection<Saml2X509Credential> 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<Saml2X509Credential> 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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, String> 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();
|
||||
}
|
||||
}
|
|
@ -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 extends XMLObject> T build(QName qName) {
|
||||
return (T) getBuilderFactory().getBuilder(qName).buildObject(qName);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue