From 097cebd803192686cc1abd7160861ab5f21becf8 Mon Sep 17 00:00:00 2001 From: Diana Krepinska Date: Mon, 2 Oct 2023 12:47:14 +0200 Subject: [PATCH] [JBWS-4389] Use TwoWayPassword in a UsernameToken profile instead of ClearPassword --- .../org/jboss/wsf/stack/cxf/i18n/Loggers.java | 4 + .../authentication/SubjectCreator.java | 21 ++- .../cli/jbws-testsuite-default-elytron.cli | 19 +++ ...onTextDigestedPropertiesRealmTestCase.java | 130 ++++++++++++++++++ .../jaas/digest/WEB-INF/jboss-web-digest.xml | 7 + .../WEB-INF/jbossws-users-digest.properties | 2 + 6 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 modules/testsuite/cxf-tests/src/test/java/org/jboss/test/ws/jaxws/samples/wsse/policy/jaas/UsernameAuthorizationTextDigestedPropertiesRealmTestCase.java create mode 100644 modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jboss-web-digest.xml create mode 100644 modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jbossws-users-digest.properties diff --git a/modules/client/src/main/java/org/jboss/wsf/stack/cxf/i18n/Loggers.java b/modules/client/src/main/java/org/jboss/wsf/stack/cxf/i18n/Loggers.java index 32845a56b..75764fb27 100644 --- a/modules/client/src/main/java/org/jboss/wsf/stack/cxf/i18n/Loggers.java +++ b/modules/client/src/main/java/org/jboss/wsf/stack/cxf/i18n/Loggers.java @@ -260,4 +260,8 @@ public interface Loggers extends BasicLogger @LogMessage(level = ERROR) @Message(id = 24115, value = "Failed to compute UsernameToken profile digest from expected password") void failedToComputeUsernameTokenProfileDigest(); + + @LogMessage(level = TRACE) + @Message(id = 24116, value = "Original password must be recoverable when UsernameToken Profile is used with PasswordDigest") + void originalPasswordMustBeRecoverable(); } diff --git a/modules/server/src/main/java/org/jboss/wsf/stack/cxf/security/authentication/SubjectCreator.java b/modules/server/src/main/java/org/jboss/wsf/stack/cxf/security/authentication/SubjectCreator.java index d46c63318..446e8bc49 100644 --- a/modules/server/src/main/java/org/jboss/wsf/stack/cxf/security/authentication/SubjectCreator.java +++ b/modules/server/src/main/java/org/jboss/wsf/stack/cxf/security/authentication/SubjectCreator.java @@ -27,7 +27,10 @@ import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.security.Principal; +import java.security.spec.InvalidKeySpecException; import java.util.Base64; import java.util.Calendar; import java.util.TimeZone; @@ -44,7 +47,10 @@ import org.wildfly.security.auth.server.RealmUnavailableException; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.credential.PasswordCredential; +import org.wildfly.security.password.PasswordFactory; +import org.wildfly.security.password.TwoWayPassword; import org.wildfly.security.password.interfaces.ClearPassword; +import org.wildfly.security.password.spec.ClearPasswordSpec; /** * Creates Subject instances after having authenticated / authorized the provided @@ -100,12 +106,17 @@ public Subject createSubject(SecurityDomainContext ctx, String name, String pass } if (isDigest && created != null && nonce != null) { // username token profile is using digest // verify client's digest - ClearPassword clearPassword = identity.getCredential(PasswordCredential.class).getPassword(ClearPassword.class); - // only realms supporting getCredential with clear password can be used with Username Token profile - if (clearPassword == null) { + TwoWayPassword recoveredTwoWayPassword = identity.getCredential(PasswordCredential.class).getPassword(TwoWayPassword.class); + if (recoveredTwoWayPassword == null) { + SECURITY_LOGGER.originalPasswordMustBeRecoverable(); + throw MESSAGES.authenticationFailed(principal.getName()); + } + PasswordFactory passwordFactory = PasswordFactory.getInstance(recoveredTwoWayPassword.getAlgorithm()); + String expectedPassword = new String(passwordFactory.getKeySpec(passwordFactory.translate(recoveredTwoWayPassword), ClearPasswordSpec.class).getEncodedPassword()); + // only realms supporting getCredential with plain text password can be used with PasswordDigest type + if (expectedPassword.isEmpty()) { throw MESSAGES.authenticationFailed(principal.getName()); } - String expectedPassword = new String(clearPassword.getPassword()); if (!getUsernameTokenPasswordDigest(nonce, created, expectedPassword).equals(password)) { throw MESSAGES.authenticationFailed(principal.getName()); } @@ -119,7 +130,7 @@ public Subject createSubject(SecurityDomainContext ctx, String name, String pass } } - } catch (RealmUnavailableException e) { + } catch (RealmUnavailableException | InvalidKeyException | InvalidKeySpecException | NoSuchAlgorithmException e) { throw MESSAGES.authenticationFailed(principal.getName()); } diff --git a/modules/testsuite/cxf-tests/src/test/cli/jbws-testsuite-default-elytron.cli b/modules/testsuite/cxf-tests/src/test/cli/jbws-testsuite-default-elytron.cli index 5c410d641..4aea303a8 100644 --- a/modules/testsuite/cxf-tests/src/test/cli/jbws-testsuite-default-elytron.cli +++ b/modules/testsuite/cxf-tests/src/test/cli/jbws-testsuite-default-elytron.cli @@ -95,6 +95,25 @@ echo JBossWSDigest domain config ./subsystem=undertow/application-security-domain=JBossWSDigest \ :add(http-authentication-factory=JBossWSDigest) +##----- JAASJBossWSDigestPropertiesRealm domain config --------- +##----- JAASJBossWSDigestPropertiesRealm domain config --------- +echo JBossWSDigest domain config +# 1. +./subsystem=elytron/properties-realm=JAASJBossWSDigestPropertiesRealm \ + :add(users-properties={path=$testResourcesDir/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jbossws-users-digest.properties, plain-text=false}, \ + groups-properties={path=$testResourcesDir/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jbossws-roles.properties}) +# 2. +./subsystem=elytron/security-domain=JBossWSDigestPropertiesDomain \ + :add(default-realm=JAASJBossWSDigestPropertiesRealm, permission-mapper=default-permission-mapper, \ + realms=[{realm=JAASJBossWSDigestPropertiesRealm,role-decoder=groups-to-roles}]) +# 3. +./subsystem=elytron/http-authentication-factory=JBossWSDigestProperties \ + :add(security-domain=JBossWSDigestPropertiesDomain, http-server-mechanism-factory=global, \ + mechanism-configurations=[{mechanism-name=BASIC, \ + mechanism-realm-configurations=[{realm-name=JAASJBossWSDigestPropertiesRealm}] }]) +# 4. +./subsystem=undertow/application-security-domain=JBossWSDigestPropertiesDomain \ + :add(http-authentication-factory=JBossWSDigestProperties) ##----- JAASJBossWS domain config --------- echo JAASJBossWS domain config diff --git a/modules/testsuite/cxf-tests/src/test/java/org/jboss/test/ws/jaxws/samples/wsse/policy/jaas/UsernameAuthorizationTextDigestedPropertiesRealmTestCase.java b/modules/testsuite/cxf-tests/src/test/java/org/jboss/test/ws/jaxws/samples/wsse/policy/jaas/UsernameAuthorizationTextDigestedPropertiesRealmTestCase.java new file mode 100644 index 000000000..5c6b5c57d --- /dev/null +++ b/modules/testsuite/cxf-tests/src/test/java/org/jboss/test/ws/jaxws/samples/wsse/policy/jaas/UsernameAuthorizationTextDigestedPropertiesRealmTestCase.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.jboss.test.ws.jaxws.samples.wsse.policy.jaas; + +import jakarta.xml.ws.BindingProvider; +import jakarta.xml.ws.Service; +import org.apache.cxf.ws.security.SecurityConstants; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.wsf.test.JBossWSTest; +import org.jboss.wsf.test.JBossWSTestHelper; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.xml.namespace.QName; +import java.io.File; +import java.net.URL; + +/** + * WS-Security Policy username test case leveraging JAAS container integration and using clear text client passwords + * with server side properties-realm configured to store digested password. + * WS-SecurityPolicy 1.2 used for policies in the included wsdl contract. + * + */ +@RunWith(Arquillian.class) +public final class UsernameAuthorizationTextDigestedPropertiesRealmTestCase extends JBossWSTest +{ + @ArquillianResource + private URL baseURL; + + @Deployment(testable = false) + public static WebArchive createDeployment() { + WebArchive archive = ShrinkWrap.create(WebArchive.class, "jaxws-samples-wsse-policy-username-jaas.war"); + archive + .setManifest(new StringAsset("Manifest-Version: 1.0\n" + + "Dependencies: org.jboss.ws.cxf.jbossws-cxf-client\n")) + .addClass(org.jboss.test.ws.jaxws.samples.wsse.policy.jaas.POJOEndpointAuthorizationInterceptor.class) + .addClass(org.jboss.test.ws.jaxws.samples.wsse.policy.jaas.ServiceIface.class) + .addClass(org.jboss.test.ws.jaxws.samples.wsse.policy.jaas.ServiceImpl.class) + .addClass(org.jboss.test.ws.jaxws.samples.wsse.policy.jaxws.GreetMe.class) + .addClass(org.jboss.test.ws.jaxws.samples.wsse.policy.jaxws.GreetMeResponse.class) + .addClass(org.jboss.test.ws.jaxws.samples.wsse.policy.jaxws.SayHello.class) + .addClass(org.jboss.test.ws.jaxws.samples.wsse.policy.jaxws.SayHelloResponse.class) + .addAsWebInfResource(new File(JBossWSTestHelper.getTestResourcesDir() + "/jaxws/samples/wsse/policy/jaas/auth/WEB-INF/jaxws-endpoint-config.xml"), "jaxws-endpoint-config.xml") + .addAsWebInfResource(new File(JBossWSTestHelper.getTestResourcesDir() + "/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jboss-web-digest.xml"), "jboss-web.xml") + .addAsWebInfResource(new File(JBossWSTestHelper.getTestResourcesDir() + "/jaxws/samples/wsse/policy/jaas/auth/WEB-INF/wsdl/SecurityService.wsdl"), "wsdl/SecurityService.wsdl") + .addAsWebInfResource(new File(JBossWSTestHelper.getTestResourcesDir() + "/jaxws/samples/wsse/policy/jaas/auth/WEB-INF/wsdl/SecurityService_schema1.xsd"), "wsdl/SecurityService_schema1.xsd") + .setWebXML(new File(JBossWSTestHelper.getTestResourcesDir() + "/jaxws/samples/wsse/policy/jaas/auth/WEB-INF/web.xml")); + return archive; + } + + @Test + @RunAsClient + public void test() throws Exception + { + QName serviceName = new QName("http://www.jboss.org/jbossws/ws-extensions/wssecuritypolicy", "SecurityService"); + URL wsdlURL = new URL(baseURL + "/jaxws-samples-wsse-policy-username-jaas?wsdl"); + Service service = Service.create(wsdlURL, serviceName); + ServiceIface proxy = (ServiceIface)service.getPort(ServiceIface.class); + setupWsse(proxy, "kermit"); + assertEquals("Secure Hello World!", proxy.sayHello()); + } + + @Test + @RunAsClient + public void testUnauthenticated() throws Exception + { + QName serviceName = new QName("http://www.jboss.org/jbossws/ws-extensions/wssecuritypolicy", "SecurityService"); + URL wsdlURL = new URL(baseURL + "/jaxws-samples-wsse-policy-username-jaas?wsdl"); + Service service = Service.create(wsdlURL, serviceName); + ServiceIface proxy = (ServiceIface)service.getPort(ServiceIface.class); + setupWsse(proxy, "snoopy"); + try + { + proxy.sayHello(); + fail("User snoopy shouldn't be authenticated."); + } + catch (Exception e) + { + //OK + } + } + + @Test + @RunAsClient + public void testUnauthorized() throws Exception + { + QName serviceName = new QName("http://www.jboss.org/jbossws/ws-extensions/wssecuritypolicy", "SecurityService"); + URL wsdlURL = new URL(baseURL + "/jaxws-samples-wsse-policy-username-jaas?wsdl"); + Service service = Service.create(wsdlURL, serviceName); + ServiceIface proxy = (ServiceIface)service.getPort(ServiceIface.class); + setupWsse(proxy, "kermit"); + try + { + proxy.greetMe(); + fail("User kermit shouldn't be authorized to call greetMe()."); + } + catch (Exception e) + { + assertEquals("Unauthorized", e.getMessage()); + } + } + + private void setupWsse(ServiceIface proxy, String username) + { + ((BindingProvider)proxy).getRequestContext().put(SecurityConstants.USERNAME, username); + ((BindingProvider)proxy).getRequestContext().put(SecurityConstants.CALLBACK_HANDLER, "org.jboss.test.ws.jaxws.samples.wsse.policy.jaas.UsernamePasswordCallback"); + } +} diff --git a/modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jboss-web-digest.xml b/modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jboss-web-digest.xml new file mode 100644 index 000000000..67938a7c3 --- /dev/null +++ b/modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jboss-web-digest.xml @@ -0,0 +1,7 @@ + + + + + + java:/jaas/JBossWSDigestPropertiesDomain + diff --git a/modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jbossws-users-digest.properties b/modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jbossws-users-digest.properties new file mode 100644 index 000000000..372513925 --- /dev/null +++ b/modules/testsuite/cxf-tests/src/test/resources/jaxws/samples/wsse/policy/jaas/digest/WEB-INF/jbossws-users-digest.properties @@ -0,0 +1,2 @@ +#$REALM_NAME=ApplicationRealm$ This line is used by the add-user utility to identify the realm name already used in this file. +kermit=95f7b13fffe05d817aa31138ab8252e4 \ No newline at end of file