From 29e4cf0b5abf934726cdd996d3f32f53511a1965 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Sun, 5 Nov 2023 19:25:45 +0100 Subject: [PATCH] Test security.saml-callback-handler WS-Security configuration option #1052 --- .../includes/quarkus-cxf-rt-ws-security.adoc | 4 - .../deployment/OpenSamlProcessor.java | 37 +++++ .../cxf/ws/security/CxfWsSecurityConfig.java | 5 +- .../it/security/policy/CryptoProducers.java | 26 ++- .../policy/Saml1PolicyHelloService.java | 16 ++ .../policy/Saml1PolicyHelloServiceImpl.java | 15 ++ .../policy/Saml2PolicyHelloService.java | 16 ++ .../policy/Saml2PolicyHelloServiceImpl.java | 15 ++ .../it/security/policy/SamlBeanProducers.java | 155 ++++++++++++++++++ .../policy/SecurityPolicyResource.java | 14 ++ .../src/main/resources/application.properties | 40 +++++ .../src/main/resources/saml1-policy.xml | 84 ++++++++++ .../src/main/resources/saml2-policy.xml | 84 ++++++++++ ...stractUsernameTokenSecurityPolicyTest.java | 71 +++++++- .../policy/UsernameTokenSecurityPolicyIT.java | 6 + .../UsernameTokenSecurityPolicyStaxIT.java | 6 + .../UsernameTokenSecurityPolicyStaxTest.java | 27 +++ .../UsernameTokenSecurityPolicyTest.java | 28 ++++ 18 files changed, 624 insertions(+), 25 deletions(-) create mode 100644 integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloService.java create mode 100644 integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloServiceImpl.java create mode 100644 integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloService.java create mode 100644 integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloServiceImpl.java create mode 100644 integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SamlBeanProducers.java create mode 100644 integration-tests/ws-security-policy/src/main/resources/saml1-policy.xml create mode 100644 integration-tests/ws-security-policy/src/main/resources/saml2-policy.xml diff --git a/docs/modules/ROOT/pages/includes/quarkus-cxf-rt-ws-security.adoc b/docs/modules/ROOT/pages/includes/quarkus-cxf-rt-ws-security.adoc index 891b6a456..cca1710ed 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-cxf-rt-ws-security.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-cxf-rt-ws-security.adoc @@ -125,8 +125,6 @@ a| [[quarkus-cxf-rt-ws-security_quarkus.cxf.client.-clients-.security.saml-callb -- A link:../../user-guide/configuration.html#beanRefs[reference] to a `javax.security.auth.callback.CallbackHandler` implementation used to construct SAML Assertions. -This option is experimental, because it is link:https://github.com/quarkiverse/quarkus-cxf/issues/1052[not covered by tests] yet. - ifdef::add-copy-button-to-env-var[] Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__SECURITY_SAML_CALLBACK_HANDLER+++[] endif::add-copy-button-to-env-var[] @@ -1296,8 +1294,6 @@ a| [[quarkus-cxf-rt-ws-security_quarkus.cxf.endpoint.-endpoints-.security.saml-c -- A link:../../user-guide/configuration.html#beanRefs[reference] to a `javax.security.auth.callback.CallbackHandler` implementation used to construct SAML Assertions. -This option is experimental, because it is link:https://github.com/quarkiverse/quarkus-cxf/issues/1052[not covered by tests] yet. - ifdef::add-copy-button-to-env-var[] Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_ENDPOINT__ENDPOINTS__SECURITY_SAML_CALLBACK_HANDLER+++[] endif::add-copy-button-to-env-var[] diff --git a/extensions/ws-security/deployment/src/main/java/io/quarkiverse/cxf/ws/security/deployment/OpenSamlProcessor.java b/extensions/ws-security/deployment/src/main/java/io/quarkiverse/cxf/ws/security/deployment/OpenSamlProcessor.java index 84d3b7e78..b585acce2 100644 --- a/extensions/ws-security/deployment/src/main/java/io/quarkiverse/cxf/ws/security/deployment/OpenSamlProcessor.java +++ b/extensions/ws-security/deployment/src/main/java/io/quarkiverse/cxf/ws/security/deployment/OpenSamlProcessor.java @@ -50,6 +50,41 @@ void indexDependencies(BuildProducer indexDependencies @BuildStep NativeImageResourceBuildItem nativeImageResource() { return new NativeImageResourceBuildItem( + "opensaml-config.properties", + + // from org.apache.wss4j.common.saml.OpenSAMLBootstrap + "default-config.xml", + "schema-config.xml", + "saml1-assertion-config.xml", + "saml1-metadata-config.xml", + "saml1-protocol-config.xml", + "saml2-assertion-config.xml", + "saml2-assertion-delegation-restriction-config.xml", + "saml2-ecp-config.xml", + "saml2-metadata-algorithm-config.xml", + "saml2-metadata-attr-config.xml", + "saml2-metadata-config.xml", + "saml2-metadata-idp-discovery-config.xml", + "saml2-metadata-query-config.xml", + "saml2-metadata-reqinit-config.xml", + "saml2-metadata-ui-config.xml", + "saml2-metadata-rpi-config.xml", + "saml2-protocol-config.xml", + "saml2-protocol-thirdparty-config.xml", + "saml2-protocol-aslo-config.xml", + "saml2-channel-binding-config.xml", + "saml-ec-gss-config.xml", + "signature-config.xml", + "wss4j-signature-config.xml", + "encryption-config.xml", + "xacml20-context-config.xml", + "xacml20-policy-config.xml", + "xacml10-saml2-profile-config.xml", + "xacml11-saml2-profile-config.xml", + "xacml2-saml2-profile-config.xml", + "xacml3-saml2-profile-config.xml", + "saml2-xacml2-profile.xml", + "schema/xmltooling-config.xsd", "schema/datatypes.dtd", "schema/xml.xsd", @@ -79,6 +114,8 @@ void reflectiveClasses( @BuildStep void registerServices(BuildProducer serviceProvider) { Stream.of( + "org.opensaml.core.config.Configuration", + "org.opensaml.core.config.ConfigurationPropertiesSource", "org.opensaml.core.config.Initializer", "org.opensaml.xmlsec.signature.support.SignerProvider", "org.opensaml.xmlsec.algorithm.AlgorithmDescriptor", diff --git a/extensions/ws-security/runtime/src/main/java/io/quarkiverse/cxf/ws/security/CxfWsSecurityConfig.java b/extensions/ws-security/runtime/src/main/java/io/quarkiverse/cxf/ws/security/CxfWsSecurityConfig.java index 862596ea1..baec9f61f 100644 --- a/extensions/ws-security/runtime/src/main/java/io/quarkiverse/cxf/ws/security/CxfWsSecurityConfig.java +++ b/extensions/ws-security/runtime/src/main/java/io/quarkiverse/cxf/ws/security/CxfWsSecurityConfig.java @@ -129,9 +129,6 @@ public interface SecurityConfig { /** * A reference to a * {@code javax.security.auth.callback.CallbackHandler} implementation used to construct SAML Assertions. - *

- * This option is experimental, because it is - * not covered by tests yet. * * @since 2.5.0 */ @@ -275,7 +272,7 @@ public interface SecurityConfig { boolean scFromJaasSubject(); /** - * If {@code}, then if the SAML Token contains Audience Restriction URIs, one of them must match one of the + * If {@code true}, then if the SAML Token contains Audience Restriction URIs, one of them must match one of the * values in {@code audience.restrictions}; otherwise the SAML AudienceRestriction validation is disabled. *

* This option is experimental, because it is diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/CryptoProducers.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/CryptoProducers.java index 9371622d7..430cc3e93 100644 --- a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/CryptoProducers.java +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/CryptoProducers.java @@ -13,7 +13,7 @@ public class CryptoProducers { - private final PasswordEncryptor dummyPasswordEncryptor = new PasswordEncryptor() { + private static final PasswordEncryptor dummyPasswordEncryptor = new PasswordEncryptor() { @Override public String encrypt(String password) { @@ -30,29 +30,23 @@ public String decrypt(String encryptedPassword) { @ApplicationScoped @Named public Crypto bobCrypto() { - Properties props = new Properties(); - props.put("org.apache.ws.security.crypto.provider", "org.apache.ws.security.components.crypto.Merlin"); - props.put("org.apache.ws.security.crypto.merlin.keystore.type", "jks"); - props.put("org.apache.ws.security.crypto.merlin.keystore.password", "password"); - props.put("org.apache.ws.security.crypto.merlin.keystore.alias", "bob"); - props.put("org.apache.ws.security.crypto.merlin.file", "bob.jks"); - try { - return CryptoFactory.getInstance(props, CryptoFactory.class.getClassLoader(), dummyPasswordEncryptor); - } catch (WSSecurityException e) { - throw new RuntimeException(e); - } + return createCrypto("jks", "bob", "password", "bob.jks"); } @Produces @ApplicationScoped @Named public Crypto aliceCrypto() { + return createCrypto("jks", "alice", "password", "alice.jks"); + } + + public static Crypto createCrypto(String type, String alias, String password, String keyStoreFile) { Properties props = new Properties(); props.put("org.apache.ws.security.crypto.provider", "org.apache.ws.security.components.crypto.Merlin"); - props.put("org.apache.ws.security.crypto.merlin.keystore.type", "jks"); - props.put("org.apache.ws.security.crypto.merlin.keystore.password", "password"); - props.put("org.apache.ws.security.crypto.merlin.keystore.alias", "alice"); - props.put("org.apache.ws.security.crypto.merlin.file", "alice.jks"); + props.put("org.apache.ws.security.crypto.merlin.keystore.type", type); + props.put("org.apache.ws.security.crypto.merlin.keystore.password", password); + props.put("org.apache.ws.security.crypto.merlin.keystore.alias", alias); + props.put("org.apache.ws.security.crypto.merlin.file", keyStoreFile); try { return CryptoFactory.getInstance(props, CryptoFactory.class.getClassLoader(), dummyPasswordEncryptor); } catch (WSSecurityException e) { diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloService.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloService.java new file mode 100644 index 000000000..d77b50b04 --- /dev/null +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloService.java @@ -0,0 +1,16 @@ +package io.quarkiverse.cxf.it.security.policy; + +import jakarta.jws.WebMethod; +import jakarta.jws.WebService; + +import org.apache.cxf.annotations.Policy; + +/** + */ +@WebService(serviceName = "Saml1PolicyHelloService") +@Policy(placement = Policy.Placement.BINDING, uri = "saml1-policy.xml") +public interface Saml1PolicyHelloService extends AbstractHelloService { + @WebMethod + @Override + String hello(String text); +} diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloServiceImpl.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloServiceImpl.java new file mode 100644 index 000000000..23931cacd --- /dev/null +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml1PolicyHelloServiceImpl.java @@ -0,0 +1,15 @@ +package io.quarkiverse.cxf.it.security.policy; + +import jakarta.jws.WebMethod; +import jakarta.jws.WebService; + +@WebService +public class Saml1PolicyHelloServiceImpl implements Saml1PolicyHelloService { + + @WebMethod + @Override + public String hello(String text) { + return "Hello " + text + " from helloSaml1!"; + } + +} diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloService.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloService.java new file mode 100644 index 000000000..b1d51d408 --- /dev/null +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloService.java @@ -0,0 +1,16 @@ +package io.quarkiverse.cxf.it.security.policy; + +import jakarta.jws.WebMethod; +import jakarta.jws.WebService; + +import org.apache.cxf.annotations.Policy; + +/** + */ +@WebService(serviceName = "Saml2PolicyHelloService") +@Policy(placement = Policy.Placement.BINDING, uri = "saml2-policy.xml") +public interface Saml2PolicyHelloService extends AbstractHelloService { + @WebMethod + @Override + String hello(String text); +} diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloServiceImpl.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloServiceImpl.java new file mode 100644 index 000000000..4f48c56d1 --- /dev/null +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/Saml2PolicyHelloServiceImpl.java @@ -0,0 +1,15 @@ +package io.quarkiverse.cxf.it.security.policy; + +import jakarta.jws.WebMethod; +import jakarta.jws.WebService; + +@WebService +public class Saml2PolicyHelloServiceImpl implements Saml2PolicyHelloService { + + @WebMethod + @Override + public String hello(String text) { + return "Hello " + text + " from helloSaml2!"; + } + +} diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SamlBeanProducers.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SamlBeanProducers.java new file mode 100644 index 000000000..4dba3a5bd --- /dev/null +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SamlBeanProducers.java @@ -0,0 +1,155 @@ +package io.quarkiverse.cxf.it.security.policy; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.Collections; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Named; + +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.CryptoType; +import org.apache.wss4j.common.saml.SAMLCallback; +import org.apache.wss4j.common.saml.bean.AttributeBean; +import org.apache.wss4j.common.saml.bean.AttributeStatementBean; +import org.apache.wss4j.common.saml.bean.ConditionsBean; +import org.apache.wss4j.common.saml.bean.KeyInfoBean; +import org.apache.wss4j.common.saml.bean.KeyInfoBean.CERT_IDENTIFIER; +import org.apache.wss4j.common.saml.bean.SubjectBean; +import org.apache.wss4j.common.saml.bean.Version; +import org.apache.wss4j.common.saml.builder.SAML1Constants; +import org.apache.wss4j.common.saml.builder.SAML2Constants; +import org.apache.wss4j.dom.WSConstants; + +public class SamlBeanProducers { + + @Produces + @ApplicationScoped + @Named + public CallbackHandler saml2CallbackHandler() { + return new SamlCallbackHandler(Version.SAML_20); + } + + @Produces + @ApplicationScoped + @Named + public CallbackHandler saml1CallbackHandler() { + return new SamlCallbackHandler(Version.SAML_11); + } + + /** + * A CallbackHandler instance that is used by the STS to mock up a SAML Attribute Assertion. + *

+ * Adapted from https://github.com/apache/cxf/blob/cxf-4.0.3/systests/ws-security/src/test/java/org/apache/cxf/systest/ws/saml/client/SamlCallbackHandler.java + */ + public static class SamlCallbackHandler implements CallbackHandler { + private final Version samlVersion; + private String confirmationMethod; + private CERT_IDENTIFIER keyInfoIdentifier = CERT_IDENTIFIER.X509_CERT; + private final boolean signAssertion = false; + private ConditionsBean conditions; + private String cryptoAlias = "alice"; + private String cryptoPassword = "password"; + private String cryptoKeystoreFile = "alice.jks"; + private String signatureAlgorithm = WSConstants.RSA_SHA1; + private String digestAlgorithm = WSConstants.SHA1; + + public SamlCallbackHandler(Version samlVersion) { + this.samlVersion = samlVersion; + switch (samlVersion) { + case SAML_20: + this.confirmationMethod = SAML2Constants.CONF_SENDER_VOUCHES; + break; + case SAML_11: + case SAML_10: + this.confirmationMethod = SAML1Constants.CONF_SENDER_VOUCHES; + break; + default: + throw new IllegalStateException("Unexpected " + Version.class.getName() + ": " + samlVersion); + } + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof SAMLCallback) { + SAMLCallback callback = (SAMLCallback) callbacks[i]; + callback.setSamlVersion(samlVersion); + if (conditions != null) { + callback.setConditions(conditions); + } + + callback.setIssuer("sts"); + String subjectName = "uid=sts-client,o=mock-sts.com"; + String subjectQualifier = "www.mock-sts.com"; + SubjectBean subjectBean = new SubjectBean( + subjectName, subjectQualifier, confirmationMethod); + if (SAML2Constants.CONF_HOLDER_KEY.equals(confirmationMethod) + || SAML1Constants.CONF_HOLDER_KEY.equals(confirmationMethod)) { + try { + KeyInfoBean keyInfo = createKeyInfo(); + subjectBean.setKeyInfo(keyInfo); + } catch (Exception ex) { + throw new IOException("Problem creating KeyInfo: " + ex.getMessage()); + } + } + callback.setSubject(subjectBean); + + AttributeStatementBean attrBean = new AttributeStatementBean(); + attrBean.setSubject(subjectBean); + + AttributeBean attributeBean = new AttributeBean(); + switch (samlVersion) { + case SAML_20: + attributeBean.setQualifiedName("subject-role"); + break; + case SAML_11: + case SAML_10: + attributeBean.setSimpleName("subject-role"); + attributeBean.setQualifiedName("http://custom-ns"); + break; + default: + throw new IllegalStateException("Unexpected " + Version.class.getName() + ": " + samlVersion); + } + attributeBean.addAttributeValue("system-user"); + attrBean.setSamlAttributes(Collections.singletonList(attributeBean)); + callback.setAttributeStatementData(Collections.singletonList(attrBean)); + callback.setSignatureAlgorithm(signatureAlgorithm); + callback.setSignatureDigestAlgorithm(digestAlgorithm); + + Crypto crypto = io.quarkiverse.cxf.it.security.policy.CryptoProducers.createCrypto("jks", cryptoAlias, + cryptoPassword, cryptoKeystoreFile); + callback.setIssuerCrypto(crypto); + callback.setIssuerKeyName(cryptoAlias); + callback.setIssuerKeyPassword(cryptoPassword); + callback.setSignAssertion(signAssertion); + } + } + } + + protected KeyInfoBean createKeyInfo() throws Exception { + Crypto crypto = io.quarkiverse.cxf.it.security.policy.CryptoProducers.createCrypto("jks", cryptoAlias, + cryptoPassword, cryptoKeystoreFile); + CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS); + cryptoType.setAlias(cryptoAlias); + X509Certificate[] certs = crypto.getX509Certificates(cryptoType); + + KeyInfoBean keyInfo = new KeyInfoBean(); + keyInfo.setCertIdentifer(keyInfoIdentifier); + if (keyInfoIdentifier == CERT_IDENTIFIER.X509_CERT) { + keyInfo.setCertificate(certs[0]); + } else if (keyInfoIdentifier == CERT_IDENTIFIER.KEY_VALUE) { + keyInfo.setPublicKey(certs[0].getPublicKey()); + } + + return keyInfo; + } + + } + +} diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java index f78ec9bca..45ce59ef0 100644 --- a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java @@ -73,6 +73,14 @@ public class SecurityPolicyResource { @CXFClient("helloEncryptSignCrypto") EncryptSignPolicyHelloService helloEncryptSignCrypto; + @Inject + @CXFClient("helloSaml1") + Saml1PolicyHelloService helloSaml1; + + @Inject + @CXFClient("helloSaml2") + Saml2PolicyHelloService helloSaml2; + @Inject @Named("messageCollector") MessageCollector messageCollector; @@ -140,6 +148,12 @@ public Response hello(@PathParam("client") String client, String body) { case "helloEncryptSignCrypto": service = helloEncryptSignCrypto; break; + case "helloSaml1": + service = helloSaml1; + break; + case "helloSaml2": + service = helloSaml2; + break; default: throw new IllegalStateException("Unexpected client " + client); } diff --git a/integration-tests/ws-security-policy/src/main/resources/application.properties b/integration-tests/ws-security-policy/src/main/resources/application.properties index b20f25ef6..d25072182 100644 --- a/integration-tests/ws-security-policy/src/main/resources/application.properties +++ b/integration-tests/ws-security-policy/src/main/resources/application.properties @@ -55,6 +55,26 @@ quarkus.cxf.endpoint."/helloEncryptSignCrypto".security.signature.crypto = #bobC quarkus.cxf.endpoint."/helloEncryptSignCrypto".security.encryption.username = alice quarkus.cxf.endpoint."/helloEncryptSignCrypto".security.encryption.crypto = #bobCrypto +quarkus.cxf.endpoint."/helloSaml1".implementor = io.quarkiverse.cxf.it.security.policy.Saml1PolicyHelloServiceImpl +quarkus.cxf.endpoint."/helloSaml1".security.return.security.error = true +quarkus.cxf.endpoint."/helloSaml1".security.signature.username = bob +quarkus.cxf.endpoint."/helloSaml1".security.signature.password = password +quarkus.cxf.endpoint."/helloSaml1".security.signature.properties."org.apache.ws.security.crypto.provider" = org.apache.ws.security.components.crypto.Merlin +quarkus.cxf.endpoint."/helloSaml1".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.type" = jks +quarkus.cxf.endpoint."/helloSaml1".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.password" = password +quarkus.cxf.endpoint."/helloSaml1".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.alias" = bob +quarkus.cxf.endpoint."/helloSaml1".security.signature.properties."org.apache.ws.security.crypto.merlin.file" = bob.jks + +quarkus.cxf.endpoint."/helloSaml2".implementor = io.quarkiverse.cxf.it.security.policy.Saml2PolicyHelloServiceImpl +quarkus.cxf.endpoint."/helloSaml2".security.return.security.error = true +quarkus.cxf.endpoint."/helloSaml2".security.signature.username = bob +quarkus.cxf.endpoint."/helloSaml2".security.signature.password = password +quarkus.cxf.endpoint."/helloSaml2".security.signature.properties."org.apache.ws.security.crypto.provider" = org.apache.ws.security.components.crypto.Merlin +quarkus.cxf.endpoint."/helloSaml2".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.type" = jks +quarkus.cxf.endpoint."/helloSaml2".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.password" = password +quarkus.cxf.endpoint."/helloSaml2".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.alias" = bob +quarkus.cxf.endpoint."/helloSaml2".security.signature.properties."org.apache.ws.security.crypto.merlin.file" = bob.jks + # Clients quarkus.cxf.client.hello.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/hello quarkus.cxf.client.hello.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService @@ -157,3 +177,23 @@ quarkus.cxf.client.helloEncryptSignCrypto.security.signature.password = password quarkus.cxf.client.helloEncryptSignCrypto.security.signature.crypto = #aliceCrypto quarkus.cxf.client.helloEncryptSignCrypto.security.encryption.username = bob quarkus.cxf.client.helloEncryptSignCrypto.security.encryption.crypto = #aliceCrypto + +quarkus.cxf.client.helloSaml1.service-interface = io.quarkiverse.cxf.it.security.policy.Saml1PolicyHelloService +quarkus.cxf.client.helloSaml1.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/helloSaml1 +quarkus.cxf.client.helloSaml1.trust-store = client-truststore.jks +quarkus.cxf.client.helloSaml1.trust-store-password = password +quarkus.cxf.client.helloSaml1.features = #messageCollector +quarkus.cxf.client.helloSaml1.security.signature.username = alice +quarkus.cxf.client.helloSaml1.security.signature.password = password +quarkus.cxf.client.helloSaml1.security.signature.crypto = #aliceCrypto +quarkus.cxf.client.helloSaml1.security.saml-callback-handler = #saml1CallbackHandler + +quarkus.cxf.client.helloSaml2.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/helloSaml2 +quarkus.cxf.client.helloSaml2.service-interface = io.quarkiverse.cxf.it.security.policy.Saml2PolicyHelloService +quarkus.cxf.client.helloSaml2.trust-store = client-truststore.jks +quarkus.cxf.client.helloSaml2.trust-store-password = password +quarkus.cxf.client.helloSaml2.features = #messageCollector +quarkus.cxf.client.helloSaml2.security.signature.username = alice +quarkus.cxf.client.helloSaml2.security.signature.password = password +quarkus.cxf.client.helloSaml2.security.signature.crypto = #aliceCrypto +quarkus.cxf.client.helloSaml2.security.saml-callback-handler = #saml2CallbackHandler diff --git a/integration-tests/ws-security-policy/src/main/resources/saml1-policy.xml b/integration-tests/ws-security-policy/src/main/resources/saml1-policy.xml new file mode 100644 index 000000000..3f87ea03b --- /dev/null +++ b/integration-tests/ws-security-policy/src/main/resources/saml1-policy.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //saml1:Assertion + + + + + + + + + + + + + + + + diff --git a/integration-tests/ws-security-policy/src/main/resources/saml2-policy.xml b/integration-tests/ws-security-policy/src/main/resources/saml2-policy.xml new file mode 100644 index 000000000..a0436df80 --- /dev/null +++ b/integration-tests/ws-security-policy/src/main/resources/saml2-policy.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //saml2:Assertion + + + + + + + + + + + + + + + + diff --git a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/AbstractUsernameTokenSecurityPolicyTest.java b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/AbstractUsernameTokenSecurityPolicyTest.java index c1daa5afa..f03691855 100644 --- a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/AbstractUsernameTokenSecurityPolicyTest.java +++ b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/AbstractUsernameTokenSecurityPolicyTest.java @@ -6,6 +6,8 @@ import java.util.List; import org.assertj.core.api.Assertions; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -191,7 +193,6 @@ void helloUsernameTokenNoMustUnderstand() { @Test void helloEncryptSign() { encryptSign("helloEncryptSign"); - } @Test @@ -249,4 +250,72 @@ void encryptSign(String endpoint) { abstract Matcher unsignedUnencryptedErrorMessage(); + @Test + void helloSaml1() { + saml("helloSaml1"); + } + + @Test + void helloSaml2() { + saml("helloSaml2"); + } + + void saml(String endpoint) { + PolicyTestUtils.drainMessages("drainMessages", -1); + + final String requestPayload = "random saml person"; + final String responsePayload = "Hello random saml person from " + endpoint + "!"; + RestAssured.given() + .config(RestAssured.config().sslConfig(new SSLConfig().with().trustStore("client-truststore.jks", "password"))) + .body(requestPayload) + .post("/cxf/security-policy/" + endpoint) + .then() + .statusCode(200) + .body(is(responsePayload)); + + final List messages = PolicyTestUtils.drainMessages("drainMessages", 2); + + final String req = messages.get(0); + if ("helloSaml1".equals(endpoint)) { + Assertions.assertThat(req).contains("\"urn:oasis:names:tc:SAML:1.0:assertion\""); + Assertions.assertThat(req).contains("NameIdentifier Format=\"urn:oasis:names:tc:SAML:1.1"); + } else if ("helloSaml2".equals(endpoint)) { + Assertions.assertThat(req).contains("\"urn:oasis:names:tc:SAML:2.0:assertion\""); + Assertions.assertThat(req).contains("NameFormat=\"urn:oasis:names:tc:SAML:2.0"); + } else { + throw new IllegalStateException("Unexpected endpoint " + endpoint); + } + Assertions.assertThat(req).contains(":Signature "); + Assertions.assertThat(req).contains(":SignatureValue>"); + + final String resp = messages.get(1); + Assertions.assertThat(resp).contains(":Signature "); + Assertions.assertThat(resp).contains(":SignatureValue>"); + + /* Unsigned and unencrypted message must fail */ + final String unsignedUnencrypted = "\n" + + " \n" + + " \n" + + " " + requestPayload + "\n" + + " \n" + + " \n" + + ""; + RestAssured.given() + .config(RestAssured.config().sslConfig(new SSLConfig().with().trustStore("client-truststore.jks", "password"))) + .contentType("text/xml") + .body(unsignedUnencrypted) + .post("https://localhost:" + getPort() + "/services/" + endpoint) + .then() + .statusCode(500) + .body(missingSamlErrorMessage(endpoint)); + + } + + abstract Matcher missingSamlErrorMessage(String endpoint); + + protected int getPort() { + final Config config = ConfigProvider.getConfig(); + return config.getValue("quarkus.http.test-ssl-port", Integer.class); + } + } diff --git a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyIT.java b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyIT.java index 0f0d48e1d..b77e0c4c4 100644 --- a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyIT.java +++ b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyIT.java @@ -4,4 +4,10 @@ @QuarkusIntegrationTest public class UsernameTokenSecurityPolicyIT extends UsernameTokenSecurityPolicyTest { + @Override + protected int getPort() { + // final Config config = ConfigProvider.getConfig(); + // does not seem to work return config.getValue("quarkus.http.test-ssl-port", Integer.class); + return 8444; + } } diff --git a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxIT.java b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxIT.java index 188a6cc3a..ba7903fc1 100644 --- a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxIT.java +++ b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxIT.java @@ -4,5 +4,11 @@ @QuarkusIntegrationTest public class UsernameTokenSecurityPolicyStaxIT extends UsernameTokenSecurityPolicyStaxTest { + @Override + protected int getPort() { + // final Config config = ConfigProvider.getConfig(); + // does not seem to work return config.getValue("quarkus.http.test-ssl-port", Integer.class); + return 8444; + } } diff --git a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxTest.java b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxTest.java index bfd7954a9..259e708c3 100644 --- a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxTest.java +++ b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyStaxTest.java @@ -6,6 +6,8 @@ import java.util.Map; import org.hamcrest.Matcher; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.QuarkusTestProfile; @@ -24,12 +26,16 @@ public Map getConfigOverrides() { map.put("quarkus.cxf.endpoint.\"/helloUsernameTokenUncachedNonce\".security.enable.streaming", "true"); map.put("quarkus.cxf.endpoint.\"/helloEncryptSign\".security.enable.streaming", "true"); map.put("quarkus.cxf.endpoint.\"/helloEncryptSignCrypto\".security.enable.streaming", "true"); + map.put("quarkus.cxf.endpoint.\"/helloSaml1\".security.enable.streaming", "true"); + map.put("quarkus.cxf.endpoint.\"/helloSaml2\".security.enable.streaming", "true"); map.put("quarkus.cxf.client.helloUsernameToken.security.enable.streaming", "true"); map.put("quarkus.cxf.client.helloUsernameTokenAlt.security.enable.streaming", "true"); map.put("quarkus.cxf.client.helloUsernameTokenNoMustUnderstand.security.enable.streaming", "true"); map.put("quarkus.cxf.client.helloNoUsernameToken.security.enable.streaming", "true"); map.put("quarkus.cxf.client.helloEncryptSign.security.enable.streaming", "true"); map.put("quarkus.cxf.client.helloEncryptSignCrypto.security.enable.streaming", "true"); + map.put("quarkus.cxf.client.helloSaml1.security.enable.streaming", "true"); + map.put("quarkus.cxf.client.helloSaml2.security.enable.streaming", "true"); return map; } @@ -73,4 +79,25 @@ Matcher unsignedUnencryptedErrorMessage() { /* The Stax implmentation does not honor security.return.security.error = true */ return containsString("XML_STREAM_EXC"); } + + @Override + Matcher missingSamlErrorMessage(final String endpoint) { + /* The Stax implmentation does not honor security.return.security.error = true */ + return containsString("An error was discovered processing the <wsse:Security> header"); + } + + @Disabled("https://github.com/quarkiverse/quarkus-cxf/issues/1095") + @Override + @Test + void helloSaml1() { + super.helloSaml1(); + } + + @Disabled("https://github.com/quarkiverse/quarkus-cxf/issues/1095") + @Override + @Test + void helloSaml2() { + super.helloSaml1(); + } + } diff --git a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyTest.java b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyTest.java index 67d85f52c..3fbabf04b 100644 --- a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyTest.java +++ b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/UsernameTokenSecurityPolicyTest.java @@ -49,4 +49,32 @@ Matcher unsignedUnencryptedErrorMessage() { containsString("Soap Body is not SIGNED")); } + @Override + Matcher missingSamlErrorMessage(final String endpoint) { + final String samlMajor; + final String samlMinor; + if ("helloSaml1".equals(endpoint)) { + samlMajor = "1"; + samlMinor = "1"; + } else if ("helloSaml2".equals(endpoint)) { + samlMajor = "2"; + samlMinor = "0"; + } else { + throw new IllegalStateException("Unexpected endpoint " + endpoint); + } + + return Matchers.allOf( + containsString("These policy alternatives can not be satisfied"), + /* + * https://github.com/rest-assured/rest-assured/issues/1744 + * The searched substring cannot contain XPath because otherwise RestAssured thinks the matcher is + * an XPath matcher and will pass a DOM body instead of String + */ + containsString("No SIGNED element found matching one of the XPat"), + containsString("aths [//saml" + samlMajor + ":Assertion]"), + containsString("SamlToken: The received token does not match the token inclusion requirement"), + containsString("{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}WssSamlV" + samlMajor + samlMinor + + "Token11")); + } + }