Browse Source
Fixes gh-7377 Fixes gh-7375 https://github.com/spring-projects/spring-security/issues/7377 https://github.com/spring-projects/spring-security/issues/7375 Clean up code - Authentication request factory should only throw Saml2Exception - OpenSamlImplementation should only throw Saml2Exception - Move the OpenSamlImplementation package private methods to the right sectionpull/7477/head
13 changed files with 1616 additions and 188 deletions
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
/* |
||||
* Copyright 2002-2019 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 org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* This exception is thrown for all SAML 2.0 related {@link Authentication} errors. |
||||
* |
||||
* <p> |
||||
* There are a number of scenarios where an error may occur, for example: |
||||
* <ul> |
||||
* <li>The response or assertion request is missing or malformed</li> |
||||
* <li>Missing or invalid subject</li> |
||||
* <li>Missing or invalid signatures</li> |
||||
* <li>The time period validation for the assertion fails</li> |
||||
* <li>One of the assertion conditions was not met</li> |
||||
* <li>Decryption failed</li> |
||||
* <li>Unable to locate a subject identifier, commonly known as username</li> |
||||
* </ul> |
||||
* |
||||
* @since 5.2 |
||||
*/ |
||||
public class Saml2AuthenticationException extends AuthenticationException { |
||||
private Saml2Error error; |
||||
|
||||
/** |
||||
* Constructs a {@code Saml2AuthenticationException} using the provided parameters. |
||||
* |
||||
* @param error the {@link Saml2Error SAML 2.0 Error} |
||||
*/ |
||||
public Saml2AuthenticationException(Saml2Error error) { |
||||
this(error, error.getDescription()); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a {@code Saml2AuthenticationException} using the provided parameters. |
||||
* |
||||
* @param error the {@link Saml2Error SAML 2.0 Error} |
||||
* @param cause the root cause |
||||
*/ |
||||
public Saml2AuthenticationException(Saml2Error error, Throwable cause) { |
||||
this(error, cause.getMessage(), cause); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a {@code Saml2AuthenticationException} using the provided parameters. |
||||
* |
||||
* @param error the {@link Saml2Error SAML 2.0 Error} |
||||
* @param message the detail message |
||||
*/ |
||||
public Saml2AuthenticationException(Saml2Error error, String message) { |
||||
super(message); |
||||
this.setError(error); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a {@code Saml2AuthenticationException} using the provided parameters. |
||||
* |
||||
* @param error the {@link Saml2Error SAML 2.0 Error} |
||||
* @param message the detail message |
||||
* @param cause the root cause |
||||
*/ |
||||
public Saml2AuthenticationException(Saml2Error error, String message, Throwable cause) { |
||||
super(message, cause); |
||||
this.setError(error); |
||||
} |
||||
|
||||
/** |
||||
* Returns the {@link Saml2Error SAML 2.0 Error}. |
||||
* |
||||
* @return the {@link Saml2Error} |
||||
*/ |
||||
public Saml2Error getError() { |
||||
return this.error; |
||||
} |
||||
|
||||
private void setError(Saml2Error error) { |
||||
Assert.notNull(error, "error cannot be null"); |
||||
this.error = error; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
final StringBuffer sb = new StringBuffer("Saml2AuthenticationException{"); |
||||
sb.append("error=").append(error); |
||||
sb.append('}'); |
||||
return sb.toString(); |
||||
} |
||||
} |
||||
@ -0,0 +1,75 @@
@@ -0,0 +1,75 @@
|
||||
/* |
||||
* Copyright 2002-2019 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 org.springframework.security.core.SpringSecurityCoreVersion; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* A representation of an SAML 2.0 Error. |
||||
* |
||||
* <p> |
||||
* At a minimum, an error response will contain an error code. |
||||
* The commonly used error code are defined in this class
|
||||
* or a new codes can be defined in the future as arbitrary strings. |
||||
* </p> |
||||
* @since 5.2 |
||||
*/ |
||||
public class Saml2Error implements Serializable { |
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; |
||||
|
||||
private final String errorCode; |
||||
private final String description; |
||||
|
||||
/** |
||||
* Constructs a {@code Saml2Error} using the provided parameters. |
||||
* |
||||
* @param errorCode the error code |
||||
* @param description the error description |
||||
*/ |
||||
public Saml2Error(String errorCode, String description) { |
||||
Assert.hasText(errorCode, "errorCode cannot be empty"); |
||||
this.errorCode = errorCode; |
||||
this.description = description; |
||||
} |
||||
|
||||
/** |
||||
* Returns the error code. |
||||
* |
||||
* @return the error code |
||||
*/ |
||||
public final String getErrorCode() { |
||||
return this.errorCode; |
||||
} |
||||
|
||||
/** |
||||
* Returns the error description. |
||||
* |
||||
* @return the error description |
||||
*/ |
||||
public final String getDescription() { |
||||
return this.description; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "[" + this.getErrorCode() + "] " + |
||||
(this.getDescription() != null ? this.getDescription() : ""); |
||||
} |
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
/* |
||||
* Copyright 2002-2019 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; |
||||
|
||||
/** |
||||
* A list of SAML known 2 error codes used during SAML authentication. |
||||
* |
||||
* @since 5.2 |
||||
*/ |
||||
public interface Saml2ErrorCodes { |
||||
/** |
||||
* SAML Data does not represent a SAML 2 Response object. |
||||
* A valid XML object was received, but that object was not a |
||||
* SAML 2 Response object of type {@code ResponseType} per specification |
||||
* https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=46
|
||||
*/ |
||||
String UNKNOWN_RESPONSE_CLASS = "unknown_response_class"; |
||||
/** |
||||
* The response data is malformed or incomplete. |
||||
* An invalid XML object was received, and XML unmarshalling failed. |
||||
*/ |
||||
String MALFORMED_RESPONSE_DATA = "malformed_response_data"; |
||||
/** |
||||
* Response destination does not match the request URL. |
||||
* A SAML 2 response object was received at a URL that |
||||
* did not match the URL stored in the {code Destination} attribute |
||||
* in the Response object. |
||||
* https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38
|
||||
*/ |
||||
String INVALID_DESTINATION = "invalid_destination"; |
||||
/** |
||||
* The assertion was not valid. |
||||
* The assertion used for authentication failed validation. |
||||
* Details around the failure will be present in the error description. |
||||
*/ |
||||
String INVALID_ASSERTION = "invalid_assertion"; |
||||
/** |
||||
* The signature of response or assertion was invalid. |
||||
* Either the response or the assertion was missing a signature |
||||
* or the signature could not be verified using the system's |
||||
* configured credentials. Most commonly the IDP's |
||||
* X509 certificate. |
||||
*/ |
||||
String INVALID_SIGNATURE = "invalid_signature"; |
||||
/** |
||||
* The assertion did not contain a subject element. |
||||
* The subject element, type SubjectType, contains |
||||
* a {@code NameID} or an {@code EncryptedID} that is used |
||||
* to assign the authenticated principal an identifier, |
||||
* typically a username. |
||||
* |
||||
* https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=18
|
||||
*/ |
||||
String SUBJECT_NOT_FOUND = "subject_not_found"; |
||||
/** |
||||
* The subject did not contain a user identifier |
||||
* The assertion contained a subject element, but the subject |
||||
* element did not have a {@code NameID} or {@code EncryptedID} |
||||
* element |
||||
* |
||||
* https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=18
|
||||
*/ |
||||
String USERNAME_NOT_FOUND = "username_not_found"; |
||||
/** |
||||
* The system failed to decrypt an assertion or a name identifier. |
||||
* This error code will be thrown if the decryption of either a |
||||
* {@code EncryptedAssertion} or {@code EncryptedID} fails. |
||||
* https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=17
|
||||
*/ |
||||
String DECRYPTION_ERROR = "decryption_error"; |
||||
/** |
||||
* An Issuer element contained a value that didn't |
||||
* https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=15
|
||||
*/ |
||||
String INVALID_ISSUER = "invalid_issuer"; |
||||
/** |
||||
* An error happened during validation. |
||||
* Used when internal, non classified, errors are caught during the |
||||
* authentication process. |
||||
*/ |
||||
String INTERNAL_VALIDATION_ERROR = "internal_validation_error"; |
||||
} |
||||
@ -0,0 +1,149 @@
@@ -0,0 +1,149 @@
|
||||
/* |
||||
* Copyright 2002-2019 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.credentials; |
||||
|
||||
import org.springframework.security.converter.RsaKeyConverters; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.security.PrivateKey; |
||||
import java.security.cert.CertificateFactory; |
||||
import java.security.cert.X509Certificate; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION; |
||||
|
||||
public class Saml2X509CredentialTests { |
||||
|
||||
@Rule |
||||
public ExpectedException exception = ExpectedException.none(); |
||||
|
||||
private Saml2X509Credential credential; |
||||
private PrivateKey key; |
||||
private X509Certificate certificate; |
||||
|
||||
@Before |
||||
public void setup() throws Exception { |
||||
String keyData = "-----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-----"; |
||||
key = RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(keyData.getBytes(UTF_8))); |
||||
final CertificateFactory factory = CertificateFactory.getInstance("X.509"); |
||||
String certificateData = "-----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-----"; |
||||
certificate = (X509Certificate) factory |
||||
.generateCertificate(new ByteArrayInputStream(certificateData.getBytes(UTF_8))); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenRelyingPartyWithCredentialsThenItSucceeds() { |
||||
new Saml2X509Credential(key, certificate, SIGNING); |
||||
new Saml2X509Credential(key, certificate, SIGNING, DECRYPTION); |
||||
new Saml2X509Credential(key, certificate, DECRYPTION); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenAssertingPartyWithCredentialsThenItSucceeds() { |
||||
new Saml2X509Credential(certificate, VERIFICATION); |
||||
new Saml2X509Credential(certificate, VERIFICATION, ENCRYPTION); |
||||
new Saml2X509Credential(certificate, ENCRYPTION); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenRelyingPartyWithoutCredentialsThenItFails() { |
||||
exception.expect(IllegalArgumentException.class); |
||||
new Saml2X509Credential(null, (X509Certificate) null, SIGNING); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenRelyingPartyWithoutPrivateKeyThenItFails() { |
||||
exception.expect(IllegalArgumentException.class); |
||||
new Saml2X509Credential(null, certificate, SIGNING); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenRelyingPartyWithoutCertificateThenItFails() { |
||||
exception.expect(IllegalArgumentException.class); |
||||
new Saml2X509Credential(key, null, SIGNING); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenAssertingPartyWithoutCertificateThenItFails() { |
||||
exception.expect(IllegalArgumentException.class); |
||||
new Saml2X509Credential(null, SIGNING); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenRelyingPartyWithEncryptionUsageThenItFails() { |
||||
exception.expect(IllegalStateException.class); |
||||
new Saml2X509Credential(key, certificate, ENCRYPTION); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenRelyingPartyWithVerificationUsageThenItFails() { |
||||
exception.expect(IllegalStateException.class); |
||||
new Saml2X509Credential(key, certificate, VERIFICATION); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenAssertingPartyWithSigningUsageThenItFails() { |
||||
exception.expect(IllegalStateException.class); |
||||
new Saml2X509Credential(certificate, SIGNING); |
||||
} |
||||
|
||||
@Test |
||||
public void constructorWhenAssertingPartyWithDecryptionUsageThenItFails() { |
||||
exception.expect(IllegalStateException.class); |
||||
new Saml2X509Credential(certificate, DECRYPTION); |
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,357 @@
@@ -0,0 +1,357 @@
|
||||
/* |
||||
* Copyright 2002-2019 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 org.springframework.security.core.Authentication; |
||||
|
||||
import org.hamcrest.BaseMatcher; |
||||
import org.hamcrest.Description; |
||||
import org.joda.time.DateTime; |
||||
import org.joda.time.Duration; |
||||
import org.junit.Before; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
import org.opensaml.core.xml.XMLObject; |
||||
import org.opensaml.saml.saml2.core.Assertion; |
||||
import org.opensaml.saml.saml2.core.EncryptedAssertion; |
||||
import org.opensaml.saml.saml2.core.EncryptedID; |
||||
import org.opensaml.saml.saml2.core.NameID; |
||||
import org.opensaml.saml.saml2.core.Response; |
||||
|
||||
import static java.util.Collections.emptyList; |
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationObjects.assertion; |
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationObjects.response; |
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2CryptoTestSupport.encryptAssertion; |
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2CryptoTestSupport.encryptNameId; |
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2CryptoTestSupport.signXmlObject; |
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.assertingPartyCredentials; |
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.relyingPartyCredentials; |
||||
import static org.springframework.test.util.AssertionErrors.assertTrue; |
||||
import static org.springframework.util.StringUtils.hasText; |
||||
|
||||
public class OpenSamlAuthenticationProviderTests { |
||||
|
||||
private static String username = "test@saml.user"; |
||||
private static String recipientUri = "https://localhost/login/saml2/sso/idp-alias"; |
||||
private static String recipientEntityId = "https://localhost/saml2/service-provider-metadata/idp-alias"; |
||||
private static String idpEntityId = "https://some.idp.test/saml2/idp"; |
||||
|
||||
private OpenSamlAuthenticationProvider provider; |
||||
private OpenSamlImplementation saml; |
||||
private Saml2AuthenticationToken token; |
||||
|
||||
@Rule |
||||
public ExpectedException exception = ExpectedException.none(); |
||||
|
||||
@Before |
||||
public void setup() { |
||||
saml = OpenSamlImplementation.getInstance(); |
||||
provider = new OpenSamlAuthenticationProvider(); |
||||
token = new Saml2AuthenticationToken( |
||||
"responseXml", |
||||
recipientUri, |
||||
idpEntityId, |
||||
recipientEntityId, |
||||
relyingPartyCredentials() |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void supportsWhenSaml2AuthenticationTokenThenReturnTrue() { |
||||
|
||||
assertTrue( |
||||
OpenSamlAuthenticationProvider.class + "should support " + token.getClass(), |
||||
provider.supports(token.getClass()) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void supportsWhenNotSaml2AuthenticationTokenThenReturnFalse() { |
||||
assertTrue( |
||||
OpenSamlAuthenticationProvider.class + "should not support " + Authentication.class, |
||||
!provider.supports(Authentication.class) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenUnknownDataClassThenThrowAuthenticationException() { |
||||
Assertion assertion = defaultAssertion(); |
||||
token = responseXml(assertion, idpEntityId); |
||||
exception.expect(authenticationMatcher(Saml2ErrorCodes.UNKNOWN_RESPONSE_CLASS)); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenXmlErrorThenThrowAuthenticationException() { |
||||
token = new Saml2AuthenticationToken( |
||||
"invalid xml string", |
||||
recipientUri, |
||||
idpEntityId, |
||||
recipientEntityId, |
||||
relyingPartyCredentials() |
||||
); |
||||
exception.expect(authenticationMatcher(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() { |
||||
Response response = response(recipientUri + "invalid", idpEntityId); |
||||
token = responseXml(response, idpEntityId); |
||||
exception.expect(authenticationMatcher(Saml2ErrorCodes.INVALID_DESTINATION)); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
token = responseXml(response, idpEntityId); |
||||
exception.expect( |
||||
authenticationMatcher( |
||||
Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, |
||||
"No assertions found in response." |
||||
) |
||||
); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
response.getAssertions().add(assertion); |
||||
token = responseXml(response, idpEntityId); |
||||
exception.expect( |
||||
authenticationMatcher( |
||||
Saml2ErrorCodes.INVALID_SIGNATURE |
||||
) |
||||
); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() throws Exception { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
assertion |
||||
.getSubject() |
||||
.getSubjectConfirmations() |
||||
.get(0) |
||||
.getSubjectConfirmationData() |
||||
.setNotOnOrAfter(DateTime.now().minus(Duration.standardDays(3))); |
||||
signXmlObject( |
||||
assertion, |
||||
assertingPartyCredentials(), |
||||
recipientEntityId |
||||
); |
||||
response.getAssertions().add(assertion); |
||||
token = responseXml(response, idpEntityId); |
||||
|
||||
exception.expect( |
||||
authenticationMatcher( |
||||
Saml2ErrorCodes.INVALID_ASSERTION |
||||
) |
||||
); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenMissingSubjectThenThrowAuthenticationException() { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
assertion.setSubject(null); |
||||
signXmlObject( |
||||
assertion, |
||||
assertingPartyCredentials(), |
||||
recipientEntityId |
||||
); |
||||
response.getAssertions().add(assertion); |
||||
token = responseXml(response, idpEntityId); |
||||
|
||||
exception.expect( |
||||
authenticationMatcher( |
||||
Saml2ErrorCodes.SUBJECT_NOT_FOUND |
||||
) |
||||
); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenUsernameMissingThenThrowAuthenticationException() throws Exception { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
assertion |
||||
.getSubject() |
||||
.getNameID() |
||||
.setValue(null); |
||||
signXmlObject( |
||||
assertion, |
||||
assertingPartyCredentials(), |
||||
recipientEntityId |
||||
); |
||||
response.getAssertions().add(assertion); |
||||
token = responseXml(response, idpEntityId); |
||||
|
||||
exception.expect( |
||||
authenticationMatcher( |
||||
Saml2ErrorCodes.USERNAME_NOT_FOUND |
||||
) |
||||
); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenEncryptedAssertionWithoutSignatureThenItSucceeds() throws Exception { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
EncryptedAssertion encryptedAssertion = encryptAssertion(assertion, assertingPartyCredentials()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
token = responseXml(response, idpEntityId); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() throws Exception { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
NameID nameId = assertion.getSubject().getNameID(); |
||||
EncryptedID encryptedID = encryptNameId(nameId, assertingPartyCredentials()); |
||||
assertion.getSubject().setNameID(null); |
||||
assertion.getSubject().setEncryptedID(encryptedID); |
||||
signXmlObject( |
||||
assertion, |
||||
assertingPartyCredentials(), |
||||
recipientEntityId |
||||
); |
||||
response.getAssertions().add(assertion); |
||||
token = responseXml(response, idpEntityId); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() throws Exception { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
EncryptedAssertion encryptedAssertion = encryptAssertion(assertion, assertingPartyCredentials()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
token = responseXml(response, idpEntityId); |
||||
|
||||
token = new Saml2AuthenticationToken( |
||||
token.getSaml2Response(), |
||||
recipientUri, |
||||
idpEntityId, |
||||
recipientEntityId, |
||||
emptyList() |
||||
); |
||||
|
||||
exception.expect( |
||||
authenticationMatcher( |
||||
Saml2ErrorCodes.DECRYPTION_ERROR, |
||||
"No valid decryption credentials found." |
||||
) |
||||
); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() throws Exception { |
||||
Response response = response(recipientUri, idpEntityId); |
||||
Assertion assertion = defaultAssertion(); |
||||
EncryptedAssertion encryptedAssertion = encryptAssertion(assertion, assertingPartyCredentials()); |
||||
response.getEncryptedAssertions().add(encryptedAssertion); |
||||
token = responseXml(response, idpEntityId); |
||||
|
||||
token = new Saml2AuthenticationToken( |
||||
token.getSaml2Response(), |
||||
recipientUri, |
||||
idpEntityId, |
||||
recipientEntityId, |
||||
assertingPartyCredentials() |
||||
); |
||||
|
||||
exception.expect( |
||||
authenticationMatcher( |
||||
Saml2ErrorCodes.DECRYPTION_ERROR, |
||||
"Failed to decrypt EncryptedData" |
||||
) |
||||
); |
||||
provider.authenticate(token); |
||||
} |
||||
|
||||
private Assertion defaultAssertion() { |
||||
return assertion( |
||||
username, |
||||
idpEntityId, |
||||
recipientEntityId, |
||||
recipientUri |
||||
); |
||||
} |
||||
|
||||
private Saml2AuthenticationToken responseXml( |
||||
XMLObject object, |
||||
String issuerEntityId |
||||
) { |
||||
String xml = saml.toXml(object, emptyList(), issuerEntityId); |
||||
return new Saml2AuthenticationToken( |
||||
xml, |
||||
recipientUri, |
||||
idpEntityId, |
||||
recipientEntityId, |
||||
relyingPartyCredentials() |
||||
); |
||||
|
||||
} |
||||
|
||||
private BaseMatcher<Saml2AuthenticationException> authenticationMatcher(String code) { |
||||
return authenticationMatcher(code, null); |
||||
} |
||||
|
||||
private BaseMatcher<Saml2AuthenticationException> authenticationMatcher(String code, String description) { |
||||
return new BaseMatcher<Saml2AuthenticationException>() { |
||||
private Object value = null; |
||||
|
||||
@Override |
||||
public boolean matches(Object item) { |
||||
if (!(item instanceof Saml2AuthenticationException)) { |
||||
value = item; |
||||
return false; |
||||
} |
||||
Saml2AuthenticationException ex = (Saml2AuthenticationException) item; |
||||
if (!code.equals(ex.getError().getErrorCode())) { |
||||
value = item; |
||||
return false; |
||||
} |
||||
if (hasText(description)) { |
||||
if (!description.equals(ex.getError().getDescription())) { |
||||
value = item; |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void describeTo(Description desc) { |
||||
String excepting = "Saml2AuthenticationException[code="+code+"; description="+description+"]"; |
||||
desc.appendText(excepting); |
||||
|
||||
} |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,169 @@
@@ -0,0 +1,169 @@
|
||||
/* |
||||
* Copyright 2002-2019 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 org.springframework.security.saml2.Saml2Exception; |
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential; |
||||
|
||||
import org.apache.xml.security.algorithms.JCEMapper; |
||||
import org.apache.xml.security.encryption.XMLCipherParameters; |
||||
import org.opensaml.core.xml.io.MarshallingException; |
||||
import org.opensaml.saml.common.SignableSAMLObject; |
||||
import org.opensaml.saml.saml2.core.Assertion; |
||||
import org.opensaml.saml.saml2.core.EncryptedAssertion; |
||||
import org.opensaml.saml.saml2.core.EncryptedID; |
||||
import org.opensaml.saml.saml2.core.NameID; |
||||
import org.opensaml.saml.saml2.encryption.Encrypter; |
||||
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.encryption.support.DataEncryptionParameters; |
||||
import org.opensaml.xmlsec.encryption.support.EncryptionException; |
||||
import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters; |
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants; |
||||
import org.opensaml.xmlsec.signature.support.SignatureException; |
||||
import org.opensaml.xmlsec.signature.support.SignatureSupport; |
||||
|
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.security.NoSuchProviderException; |
||||
import java.security.cert.X509Certificate; |
||||
import java.util.List; |
||||
import javax.crypto.SecretKey; |
||||
|
||||
import static java.util.Arrays.asList; |
||||
import static org.opensaml.security.crypto.KeySupport.generateKey; |
||||
|
||||
final class Saml2CryptoTestSupport { |
||||
static void signXmlObject(SignableSAMLObject object, List<Saml2X509Credential> signingCredentials, String entityId) { |
||||
SignatureSigningParameters parameters = new SignatureSigningParameters(); |
||||
Credential credential = getSigningCredential(signingCredentials, entityId); |
||||
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(object, parameters); |
||||
} catch (MarshallingException | SignatureException | SecurityException e) { |
||||
throw new Saml2Exception(e); |
||||
} |
||||
|
||||
} |
||||
|
||||
static EncryptedAssertion encryptAssertion(Assertion assertion, List<Saml2X509Credential> encryptionCredentials) { |
||||
X509Certificate certificate = getEncryptionCertificate(encryptionCredentials); |
||||
Encrypter encrypter = getEncrypter(certificate); |
||||
try { |
||||
Encrypter.KeyPlacement keyPlacement = Encrypter.KeyPlacement.valueOf("PEER"); |
||||
encrypter.setKeyPlacement(keyPlacement); |
||||
return encrypter.encrypt(assertion); |
||||
} |
||||
catch (EncryptionException e) { |
||||
throw new Saml2Exception("Unable to encrypt assertion.", e); |
||||
} |
||||
} |
||||
|
||||
static EncryptedID encryptNameId(NameID nameID, List<Saml2X509Credential> encryptionCredentials) { |
||||
X509Certificate certificate = getEncryptionCertificate(encryptionCredentials); |
||||
Encrypter encrypter = getEncrypter(certificate); |
||||
try { |
||||
Encrypter.KeyPlacement keyPlacement = Encrypter.KeyPlacement.valueOf("PEER"); |
||||
encrypter.setKeyPlacement(keyPlacement); |
||||
return encrypter.encrypt(nameID); |
||||
} |
||||
catch (EncryptionException e) { |
||||
throw new Saml2Exception("Unable to encrypt nameID.", e); |
||||
} |
||||
} |
||||
|
||||
private static Encrypter getEncrypter(X509Certificate certificate) { |
||||
Credential credential = CredentialSupport.getSimpleCredential(certificate, null); |
||||
final String dataAlgorithm = XMLCipherParameters.AES_256; |
||||
final String keyAlgorithm = XMLCipherParameters.RSA_1_5; |
||||
SecretKey secretKey = generateKeyFromURI(dataAlgorithm); |
||||
BasicCredential dataCredential = new BasicCredential(secretKey); |
||||
DataEncryptionParameters dataEncryptionParameters = new DataEncryptionParameters(); |
||||
dataEncryptionParameters.setEncryptionCredential(dataCredential); |
||||
dataEncryptionParameters.setAlgorithm(dataAlgorithm); |
||||
|
||||
KeyEncryptionParameters keyEncryptionParameters = new KeyEncryptionParameters(); |
||||
keyEncryptionParameters.setEncryptionCredential(credential); |
||||
keyEncryptionParameters.setAlgorithm(keyAlgorithm); |
||||
|
||||
Encrypter encrypter = new Encrypter(dataEncryptionParameters, asList(keyEncryptionParameters)); |
||||
|
||||
return encrypter; |
||||
} |
||||
|
||||
private static SecretKey generateKeyFromURI(String algoURI) { |
||||
try { |
||||
String jceAlgorithmName = JCEMapper.getJCEKeyAlgorithmFromURI(algoURI); |
||||
int keyLength = JCEMapper.getKeyLengthFromURI(algoURI); |
||||
return generateKey(jceAlgorithmName, keyLength, null); |
||||
} |
||||
catch (NoSuchAlgorithmException | NoSuchProviderException e) { |
||||
throw new Saml2Exception(e); |
||||
} |
||||
} |
||||
|
||||
private static X509Certificate getEncryptionCertificate(List<Saml2X509Credential> encryptionCredentials) { |
||||
X509Certificate certificate = null; |
||||
for (Saml2X509Credential credential : encryptionCredentials) { |
||||
if (credential.isEncryptionCredential()) { |
||||
certificate = credential.getCertificate(); |
||||
break; |
||||
} |
||||
} |
||||
if (certificate == null) { |
||||
throw new Saml2Exception("No valid encryption certificate found"); |
||||
} |
||||
return certificate; |
||||
} |
||||
|
||||
private static Saml2X509Credential hasSigningCredential(List<Saml2X509Credential> credentials) { |
||||
for (Saml2X509Credential c : credentials) { |
||||
if (c.isSigningCredential()) { |
||||
return c; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private static Credential getSigningCredential(List<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 static BasicX509Credential getBasicCredential(Saml2X509Credential credential) { |
||||
return CredentialSupport.getSimpleCredential( |
||||
credential.getCertificate(), |
||||
credential.getPrivateKey() |
||||
); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
/* |
||||
* Copyright 2002-2019 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 org.joda.time.DateTime; |
||||
import org.joda.time.Duration; |
||||
import org.opensaml.saml.common.SAMLVersion; |
||||
import org.opensaml.saml.saml2.core.Assertion; |
||||
import org.opensaml.saml.saml2.core.Conditions; |
||||
import org.opensaml.saml.saml2.core.Issuer; |
||||
import org.opensaml.saml.saml2.core.NameID; |
||||
import org.opensaml.saml.saml2.core.Response; |
||||
import org.opensaml.saml.saml2.core.Subject; |
||||
import org.opensaml.saml.saml2.core.SubjectConfirmation; |
||||
import org.opensaml.saml.saml2.core.SubjectConfirmationData; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
final class TestSaml2AuthenticationObjects { |
||||
private static OpenSamlImplementation saml = OpenSamlImplementation.getInstance(); |
||||
|
||||
static Response response(String destination, String issuerEntityId) { |
||||
Response response = saml.buildSAMLObject(Response.class); |
||||
response.setID("R"+UUID.randomUUID().toString()); |
||||
response.setIssueInstant(DateTime.now()); |
||||
response.setVersion(SAMLVersion.VERSION_20); |
||||
response.setID("_" + UUID.randomUUID().toString()); |
||||
response.setDestination(destination); |
||||
response.setIssuer(issuer(issuerEntityId)); |
||||
return response; |
||||
} |
||||
static Assertion assertion( |
||||
String username, |
||||
String issuerEntityId, |
||||
String recipientEntityId, |
||||
String recipientUri |
||||
) { |
||||
Assertion assertion = saml.buildSAMLObject(Assertion.class); |
||||
assertion.setID("A"+ UUID.randomUUID().toString()); |
||||
assertion.setIssueInstant(DateTime.now()); |
||||
assertion.setVersion(SAMLVersion.VERSION_20); |
||||
assertion.setIssueInstant(DateTime.now()); |
||||
assertion.setIssuer(issuer(issuerEntityId)); |
||||
assertion.setSubject(subject(username)); |
||||
assertion.setConditions(conditions()); |
||||
|
||||
SubjectConfirmation subjectConfirmation = subjectConfirmation(); |
||||
subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER); |
||||
SubjectConfirmationData confirmationData = subjectConfirmationData(recipientEntityId); |
||||
confirmationData.setRecipient(recipientUri); |
||||
subjectConfirmation.setSubjectConfirmationData(confirmationData); |
||||
assertion.getSubject().getSubjectConfirmations().add(subjectConfirmation); |
||||
return assertion; |
||||
} |
||||
|
||||
|
||||
static Issuer issuer(String entityId) { |
||||
Issuer issuer = saml.buildSAMLObject(Issuer.class); |
||||
issuer.setValue(entityId); |
||||
return issuer; |
||||
} |
||||
|
||||
static Subject subject(String principalName) { |
||||
Subject subject = saml.buildSAMLObject(Subject.class); |
||||
|
||||
if (principalName != null) { |
||||
subject.setNameID(nameId(principalName)); |
||||
} |
||||
|
||||
return subject; |
||||
} |
||||
|
||||
static NameID nameId(String principalName) { |
||||
NameID nameId = saml.buildSAMLObject(NameID.class); |
||||
nameId.setValue(principalName); |
||||
return nameId; |
||||
} |
||||
|
||||
static SubjectConfirmation subjectConfirmation() { |
||||
return saml.buildSAMLObject(SubjectConfirmation.class); |
||||
} |
||||
|
||||
static SubjectConfirmationData subjectConfirmationData(String recipient) { |
||||
SubjectConfirmationData subject = saml.buildSAMLObject(SubjectConfirmationData.class); |
||||
subject.setRecipient(recipient); |
||||
subject.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000))); |
||||
subject.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000))); |
||||
return subject; |
||||
} |
||||
|
||||
static Conditions conditions() { |
||||
Conditions conditions = saml.buildSAMLObject(Conditions.class); |
||||
conditions.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000))); |
||||
conditions.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000))); |
||||
return conditions; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,190 @@
@@ -0,0 +1,190 @@
|
||||
/* |
||||
* Copyright 2002-2019 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 org.springframework.security.saml2.Saml2Exception; |
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential; |
||||
|
||||
import org.opensaml.security.crypto.KeySupport; |
||||
|
||||
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 java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING; |
||||
import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION; |
||||
|
||||
final class TestSaml2X509Credentials { |
||||
static List<Saml2X509Credential> assertingPartyCredentials() { |
||||
return Arrays.asList( |
||||
new Saml2X509Credential( |
||||
idpPrivateKey(), |
||||
idpCertificate(), |
||||
SIGNING, |
||||
DECRYPTION |
||||
), |
||||
new Saml2X509Credential( |
||||
spCertificate(), |
||||
ENCRYPTION, |
||||
VERIFICATION |
||||
) |
||||
); |
||||
} |
||||
|
||||
static List<Saml2X509Credential> relyingPartyCredentials() { |
||||
return Arrays.asList( |
||||
new Saml2X509Credential( |
||||
spPrivateKey(), |
||||
spCertificate(), |
||||
SIGNING, |
||||
DECRYPTION |
||||
), |
||||
new Saml2X509Credential( |
||||
idpCertificate(), |
||||
ENCRYPTION, |
||||
VERIFICATION |
||||
) |
||||
); |
||||
} |
||||
|
||||
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-----"); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue