3 changed files with 18 additions and 330 deletions
@ -1,247 +0,0 @@
@@ -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 @@
@@ -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(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue