You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2026/09/21 08:24:33 UTC
(ws-wss4j) branch coheigea/rsa15 created (now 0d1fca09f)
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a change to branch coheigea/rsa15
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
at 0d1fca09f Properly wire UsernameToken + RSA15 flags for the streaming layer
This branch includes the following new commits:
new 0d1fca09f Properly wire UsernameToken + RSA15 flags for the streaming layer
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(ws-wss4j) 01/01: Properly wire UsernameToken + RSA15 flags for the streaming layer
Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch coheigea/rsa15
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
commit 0d1fca09f41e6b962da532f27ffcf2eaed62e466
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Mon Sep 21 09:23:52 2026 +0100
Properly wire UsernameToken + RSA15 flags for the streaming layer
---
THREAT-MODEL.md | 27 ++++
src/site/asciidoc/streaming.adoc | 8 ++
.../wss4j/policy/stax/enforcer/PolicyEnforcer.java | 124 ++++++++++++++---
.../policy/stax/enforcer/PolicyInputProcessor.java | 16 ++-
.../policy/stax/test/ScopedEngineDefaultsTest.java | 155 +++++++++++++++++++++
.../testdata/wsdl/mixedPasswordPolicies.wsdl | 98 +++++++++++++
6 files changed, 404 insertions(+), 24 deletions(-)
diff --git a/THREAT-MODEL.md b/THREAT-MODEL.md
index 5852447bf..0ea98b153 100644
--- a/THREAT-MODEL.md
+++ b/THREAT-MODEL.md
@@ -573,6 +573,33 @@ on each is captured in §14 Q10–Q11.
- *(documented:
`ws-security-policy-stax/src/test/java/.../VulnerabliltyVectorsTest.java`)*
+### P12 — An engine default relaxed by policy is scoped to the operation the message invokes (StAX policy mode)
+
+- **Condition**: streaming engine in policy mode (`PolicyInputProcessor`),
+ where the caller has not itself set `AllowUsernameTokenNoPassword` or
+ `AllowRSA15KeyTransportAlgorithm`. Those are explicit overrides by the
+ caller and are left alone.
+- **Violation symptom**: one operation of an endpoint asks for
+ `sp:NoPassword`, or for an AlgorithmSuite whose asymmetric key wrap is
+ rsa-1_5, and the engine's corresponding hardened default is thereby
+ lowered for every other operation of that endpoint — so a
+ password-less UsernameToken is accepted for an operation whose policy
+ names no UsernameToken at all, and is examined by no assertion.
+- **Mechanism**: the relaxation is decided while the security header is
+ read, before the Body names the operation. It is therefore scoped to
+ the operation's own policy where SOAPAction already selected it, and
+ otherwise granted across the policy set and reimposed once the
+ operation is known.
+- **Residual**: an rsa-1_5 unwrap granted across the policy set is
+ performed before the operation is known. The message is rejected, but
+ the unwrap has happened, so the oracle surface of §8 P4 is reachable
+ for an operation whose own policy forbids rsa-1_5. Only a
+ SOAPAction-selected operation avoids that.
+- **Severity**: **security-critical** for the UsernameToken case;
+ `VALID-HARDENING` for the rsa-1_5 case.
+- *(documented:
+ `ws-security-policy-stax/src/test/java/.../ScopedEngineDefaultsTest.java`)*
+
## §9 Security properties the project does *not* provide
State each plainly so a triager can route an inbound report to the
diff --git a/src/site/asciidoc/streaming.adoc b/src/site/asciidoc/streaming.adoc
index 3d5484c85..705fc26e1 100644
--- a/src/site/asciidoc/streaming.adoc
+++ b/src/site/asciidoc/streaming.adoc
@@ -87,6 +87,14 @@ nothing on its own. Check the effective policy if you rely on a nested
assertion being enforced.
* Where a compact policy offers several alternatives within a nested policy,
only the first is read.
+ * A hardened engine default that WS-SecurityPolicy relaxes - accepting a
+password-less UsernameToken for an sp:NoPassword policy, or rsa-1_5 key transport
+for an AlgorithmSuite that asks for it - has to be decided while the security
+header is read, before the Body says which operation the message invokes. Where
+SOAPAction has already selected the operation only that operation's policy is
+consulted. Otherwise the relaxation is granted across the endpoint's operations
+and reimposed once the operation is known, which rejects the message but, for
+rsa-1_5, only after the key has been unwrapped.
* The REQUIRE_SIGNED_ENCRYPTED_DATA_ELEMENTS ("requireSignedEncryptedDataElements")
configuration tag is not read by the streaming code and is not enforced by it.
The tag is shared between both stacks, and the streaming ConfigurationConverter
diff --git a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java
index db1322d96..c3280c74f 100644
--- a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java
+++ b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java
@@ -104,8 +104,10 @@ import org.apache.wss4j.policy.stax.assertionStates.X509TokenAssertionState;
import org.apache.wss4j.stax.ext.WSSConstants;
import org.apache.wss4j.stax.securityEvent.NoSecuritySecurityEvent;
import org.apache.wss4j.stax.securityEvent.OperationSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.UsernameTokenSecurityEvent;
import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.stax.securityEvent.AlgorithmSuiteSecurityEvent;
import org.apache.xml.security.stax.securityEvent.SecurityEvent;
import org.apache.xml.security.stax.securityEvent.SecurityEventConstants;
import org.apache.xml.security.stax.securityEvent.SecurityEventListener;
@@ -156,6 +158,8 @@ public class PolicyEnforcer implements SecurityEventListener {
private boolean faultOccurred;
private final PolicyAsserter policyAsserter;
private boolean soap12;
+ private boolean usernameTokenNoPasswordRelaxedForAllOperations;
+ private boolean rsa15KeyTransportRelaxedForAllOperations;
public PolicyEnforcer(List<OperationPolicy> operationPolicies, String soapAction, boolean initiator,
String actorOrRole, int attachmentCount, PolicyAsserter policyAsserter, boolean soap12
@@ -892,6 +896,11 @@ public class PolicyEnforcer implements SecurityEventListener {
new IllegalArgumentException(message));
}
+ //The operation is known now, so an engine default relaxed on the strength of
+ //the whole policy set can be reimposed where this operation's policy does not
+ //ask for it.
+ verifyRelaxedEngineDefaults();
+
try {
Iterator<SecurityEvent> securityEventIterator = securityEventQueue.descendingIterator();
while (securityEventIterator.hasNext()) {
@@ -923,18 +932,25 @@ public class PolicyEnforcer implements SecurityEventListener {
}
/**
- * Returns true if any configured operation policy contains a UsernameToken assertion
- * that explicitly allows password-less tokens (sp:NoPassword). Used to decide whether
- * the engine's hardened default (rejecting password-less UsernameTokens) may be
- * relaxed in policy mode.
+ * Returns true if the policy that governs this message contains a UsernameToken
+ * assertion that explicitly allows password-less tokens (sp:NoPassword). Used to
+ * decide whether the engine's hardened default (rejecting password-less
+ * UsernameTokens) may be relaxed in policy mode.
+ *
+ * This is asked while the security header is being read, so the operation is known
+ * only where SOAPAction already selected its policy. Failing that the question can be
+ * answered across the configured operations and no more, which relaxes the default for
+ * a message that may turn out to invoke an operation whose own policy does not allow
+ * it. Record that, so that verifyRelaxedEngineDefaults() can reimpose the default once
+ * the operation is known.
*/
public boolean isUsernameTokenNoPasswordAllowedByPolicy() {
+ if (effectivePolicy != null) {
+ return allowsUsernameTokenNoPassword(effectivePolicy);
+ }
for (OperationPolicy operationPolicy : operationPolicies) {
- org.apache.neethi.Policy policy = operationPolicy.getPolicy();
- if (policy != null && policyContains(policy,
- assertion -> assertion instanceof UsernameToken
- && ((UsernameToken)assertion).getPasswordType()
- == UsernameToken.PasswordType.NoPassword)) {
+ if (allowsUsernameTokenNoPassword(operationPolicy)) {
+ usernameTokenNoPasswordRelaxedForAllOperations = true;
return true;
}
}
@@ -942,28 +958,92 @@ public class PolicyEnforcer implements SecurityEventListener {
}
/**
- * Returns true if any configured operation policy contains an AlgorithmSuite whose
- * asymmetric key wrap is RSA v1.5. Used to decide whether the engine's hardened
- * default (rejecting rsa-1_5 key transport) may be relaxed in policy mode.
+ * Returns true if the policy that governs this message contains an AlgorithmSuite whose
+ * asymmetric key wrap is RSA v1.5. Used to decide whether the engine's hardened default
+ * (rejecting rsa-1_5 key transport) may be relaxed in policy mode. Scoped to the
+ * operation, and recorded when it cannot be, exactly as above.
*/
public boolean isRSA15KeyTransportAllowedByPolicy() {
+ if (effectivePolicy != null) {
+ return allowsRSA15KeyTransport(effectivePolicy);
+ }
for (OperationPolicy operationPolicy : operationPolicies) {
- org.apache.neethi.Policy policy = operationPolicy.getPolicy();
- if (policy != null && policyContains(policy, assertion -> {
- if (!(assertion instanceof AlgorithmSuite)) {
- return false;
- }
- AlgorithmSuite.AlgorithmSuiteType algorithmSuiteType =
- ((AlgorithmSuite)assertion).getAlgorithmSuiteType();
- return algorithmSuiteType != null
- && SPConstants.KW_RSA15.equals(algorithmSuiteType.getAsymmetricKeyWrap());
- })) {
+ if (allowsRSA15KeyTransport(operationPolicy)) {
+ rsa15KeyTransportRelaxedForAllOperations = true;
return true;
}
}
return false;
}
+ private static boolean allowsUsernameTokenNoPassword(OperationPolicy operationPolicy) {
+ Policy policy = operationPolicy.getPolicy();
+ return policy != null && policyContains(policy,
+ assertion -> assertion instanceof UsernameToken
+ && ((UsernameToken)assertion).getPasswordType()
+ == UsernameToken.PasswordType.NoPassword);
+ }
+
+ private static boolean allowsRSA15KeyTransport(OperationPolicy operationPolicy) {
+ Policy policy = operationPolicy.getPolicy();
+ return policy != null && policyContains(policy, assertion -> {
+ if (!(assertion instanceof AlgorithmSuite)) {
+ return false;
+ }
+ AlgorithmSuite.AlgorithmSuiteType algorithmSuiteType =
+ ((AlgorithmSuite)assertion).getAlgorithmSuiteType();
+ return algorithmSuiteType != null
+ && SPConstants.KW_RSA15.equals(algorithmSuiteType.getAsymmetricKeyWrap());
+ });
+ }
+
+ /**
+ * An engine default that was relaxed on the strength of the whole set of operation
+ * policies - because the operation was not yet known when the security header was read
+ * - must still hold for the operation the message turned out to invoke. Otherwise one
+ * operation that asks for sp:NoPassword or for rsa-1_5 lowers the engine's floor for
+ * every other operation of the endpoint, and whether that is caught depends on whether
+ * the effective policy happens to carry an assertion that rejects what was accepted: a
+ * password-less UsernameToken is not examined at all by a policy that names no
+ * UsernameToken.
+ */
+ private void verifyRelaxedEngineDefaults() throws WSSecurityException {
+ if (usernameTokenNoPasswordRelaxedForAllOperations
+ && !allowsUsernameTokenNoPassword(effectivePolicy)) {
+ for (SecurityEvent securityEvent : securityEventQueue) {
+ if (securityEvent instanceof UsernameTokenSecurityEvent
+ && ((UsernameTokenSecurityEvent)securityEvent).getSecurityToken() != null
+ && WSSConstants.UsernameTokenPasswordType.PASSWORD_NONE
+ == ((UsernameTokenSecurityEvent)securityEvent).getUsernameTokenPasswordType()) {
+ rejectRelaxedEngineDefault("a UsernameToken with no password");
+ }
+ }
+ }
+ if (rsa15KeyTransportRelaxedForAllOperations
+ && !allowsRSA15KeyTransport(effectivePolicy)) {
+ for (SecurityEvent securityEvent : securityEventQueue) {
+ if (securityEvent instanceof AlgorithmSuiteSecurityEvent) {
+ AlgorithmSuiteSecurityEvent algorithmSuiteSecurityEvent =
+ (AlgorithmSuiteSecurityEvent)securityEvent;
+ if (WSSConstants.Asym_Key_Wrap.equals(algorithmSuiteSecurityEvent.getAlgorithmUsage())
+ && WSSConstants.NS_XENC_RSA15.equals(algorithmSuiteSecurityEvent.getAlgorithmURI())) {
+ rejectRelaxedEngineDefault("rsa-1_5 key transport");
+ }
+ }
+ }
+ }
+ }
+
+ private void rejectRelaxedEngineDefault(String what) throws WSSecurityException {
+ String message = "The message uses " + what + ", which the policy for operation "
+ + effectivePolicy.getOperationName() + " does not allow";
+ LOG.warn("{}; rejecting the message", message);
+ securityEventQueue.clear();
+ throw new WSSecurityException(
+ WSSecurityException.ErrorCode.INVALID_SECURITY,
+ new IllegalArgumentException(message));
+ }
+
private static boolean policyContains(PolicyComponent policyComponent, Predicate<Assertion> predicate) {
if (policyComponent instanceof PolicyOperator) {
for (PolicyComponent childComponent
diff --git a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java
index 78a4d3ef2..90d7264fd 100644
--- a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java
+++ b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java
@@ -254,11 +254,23 @@ public class PolicyInputProcessor extends AbstractInputProcessor {
// unconditionally, which silently reversed both defaults even when the
// configured policy contained no assertion that re-imposes the check. Only
// relax an engine default when the policy actually covers it.
- if (policyEnforcer.isRSA15KeyTransportAllowedByPolicy()) {
+ // Where the caller has already relaxed a default itself there is nothing to
+ // grant, and asking would arm a re-check against a policy the caller has
+ // deliberately overruled, so leave that case alone. The properties are
+ // optional on this processor - a caller may construct it with none - and then
+ // the caller has relaxed nothing.
+ XMLSecurityProperties properties = getSecurityProperties();
+ WSSSecurityProperties securityProperties =
+ properties instanceof WSSSecurityProperties ? (WSSSecurityProperties) properties : null;
+ boolean callerAllowsRSA15 =
+ securityProperties != null && securityProperties.isAllowRSA15KeyTransportAlgorithm();
+ boolean callerAllowsNoPassword =
+ securityProperties != null && securityProperties.isAllowUsernameTokenNoPassword();
+ if (!callerAllowsRSA15 && policyEnforcer.isRSA15KeyTransportAllowedByPolicy()) {
inputProcessorChain.getSecurityContext().put(
WSSConstants.PROP_ALLOW_RSA15_KEYTRANSPORT_ALGORITHM, Boolean.TRUE);
}
- if (policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy()) {
+ if (!callerAllowsNoPassword && policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy()) {
inputProcessorChain.getSecurityContext().put(
WSSConstants.PROP_ALLOW_USERNAMETOKEN_NOPASSWORD, Boolean.TRUE.toString());
}
diff --git a/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/ScopedEngineDefaultsTest.java b/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/ScopedEngineDefaultsTest.java
new file mode 100644
index 000000000..b97605567
--- /dev/null
+++ b/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/ScopedEngineDefaultsTest.java
@@ -0,0 +1,155 @@
+/**
+ * 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.apache.wss4j.policy.stax.test;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+
+import javax.xml.namespace.QName;
+
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.util.DateUtil;
+import org.apache.wss4j.policy.stax.enforcer.PolicyEnforcer;
+import org.apache.wss4j.policy.stax.enforcer.PolicyEnforcerFactory;
+import org.apache.wss4j.stax.ext.WSSConstants;
+import org.apache.wss4j.stax.impl.securityToken.UsernameSecurityTokenImpl;
+import org.apache.wss4j.stax.securityEvent.OperationSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.UsernameTokenSecurityEvent;
+import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.stax.impl.util.IDGenerator;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The engine's hardened defaults - rejecting password-less UsernameTokens and rsa-1_5 key
+ * transport - are relaxed in policy mode so that the corresponding policy assertions can
+ * take over. The relaxation belongs to the operation whose policy asks for it, not to every
+ * operation of the endpoint.
+ *
+ * The WSDL behind these tests has two operations: one asks for sp:NoPassword, the other
+ * names no UsernameToken at all, so nothing in its own policy would reject a password-less
+ * token that the engine has already accepted.
+ */
+public class ScopedEngineDefaultsTest extends AbstractPolicyTestBase {
+
+ private static final String NAMESPACE = "http://www.example.net/MixedPolicyService";
+ private static final QName NO_PASSWORD_OPERATION = new QName(NAMESPACE, "noPasswordOperation");
+ private static final QName PASSWORD_OPERATION = new QName(NAMESPACE, "passwordOperation");
+
+ /**
+ * Where SOAPAction has already selected the operation, only that operation's policy
+ * decides. The operation that asks for no password gets the relaxation; the one that
+ * does not, does not.
+ */
+ @Test
+ public void testRelaxationIsScopedToTheOperationSelectedBySOAPAction() throws Exception {
+ assertTrue(newPolicyEnforcer("noPasswordOperationAction").isUsernameTokenNoPasswordAllowedByPolicy());
+ assertFalse(newPolicyEnforcer("passwordOperationAction").isUsernameTokenNoPasswordAllowedByPolicy());
+ }
+
+ /**
+ * Without a SOAPAction the operation is not known while the security header is being
+ * read, so the question can only be answered across every configured operation. The
+ * engine default is still relaxed, as before, or a deployment whose operation cannot be
+ * determined that early would stop working.
+ */
+ @Test
+ public void testRelaxationStillGrantedWhenTheOperationIsNotYetKnown() throws Exception {
+ assertTrue(newPolicyEnforcer("").isUsernameTokenNoPasswordAllowedByPolicy());
+ }
+
+ /**
+ * ...but once the operation turns out to be the one whose policy says nothing about
+ * UsernameTokens, a password-less token accepted under that relaxation is rejected.
+ */
+ @Test
+ public void testPasswordlessTokenRejectedForAnOperationThatDoesNotAllowIt() throws Exception {
+ PolicyEnforcer policyEnforcer = newPolicyEnforcer("");
+ assertTrue(policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy());
+ policyEnforcer.registerSecurityEvent(passwordlessUsernameTokenEvent());
+
+ OperationSecurityEvent operationSecurityEvent = new OperationSecurityEvent();
+ operationSecurityEvent.setOperation(PASSWORD_OPERATION);
+
+ WSSecurityException ex = assertThrows(WSSecurityException.class,
+ () -> policyEnforcer.registerSecurityEvent(operationSecurityEvent));
+
+ assertEquals("The message uses a UsernameToken with no password, which the policy for "
+ + "operation " + PASSWORD_OPERATION + " does not allow",
+ ex.getCause().getMessage());
+ assertEquals(WSSecurityException.INVALID_SECURITY, ex.getFaultCode());
+ }
+
+ /**
+ * The same message is accepted for the operation whose policy does ask for
+ * sp:NoPassword.
+ */
+ @Test
+ public void testPasswordlessTokenAcceptedForTheOperationThatAllowsIt() throws Exception {
+ PolicyEnforcer policyEnforcer = newPolicyEnforcer("");
+ assertTrue(policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy());
+ policyEnforcer.registerSecurityEvent(passwordlessUsernameTokenEvent());
+
+ OperationSecurityEvent operationSecurityEvent = new OperationSecurityEvent();
+ operationSecurityEvent.setOperation(NO_PASSWORD_OPERATION);
+
+ policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+ }
+
+ /**
+ * A message that carries no password-less token is unaffected, even though the
+ * relaxation was granted across the endpoint.
+ */
+ @Test
+ public void testOperationWithoutAPasswordlessTokenIsUnaffected() throws Exception {
+ PolicyEnforcer policyEnforcer = newPolicyEnforcer("");
+ assertTrue(policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy());
+
+ OperationSecurityEvent operationSecurityEvent = new OperationSecurityEvent();
+ operationSecurityEvent.setOperation(PASSWORD_OPERATION);
+
+ policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+ }
+
+ private PolicyEnforcer newPolicyEnforcer(String soapAction) throws Exception {
+ PolicyEnforcerFactory policyEnforcerFactory = PolicyEnforcerFactory.newInstance(
+ this.getClass().getClassLoader().getResource("testdata/wsdl/mixedPasswordPolicies.wsdl"));
+ return policyEnforcerFactory.newPolicyEnforcer(soapAction, false, null, 0, false);
+ }
+
+ private UsernameTokenSecurityEvent passwordlessUsernameTokenEvent() throws XMLSecurityException {
+ UsernameTokenSecurityEvent usernameTokenSecurityEvent = new UsernameTokenSecurityEvent();
+ usernameTokenSecurityEvent.setUsernameTokenProfile(WSSConstants.NS_USERNAMETOKEN_PROFILE11);
+ ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
+ String created = DateUtil.getDateTimeFormatter(true).format(now);
+ UsernameSecurityTokenImpl usernameSecurityToken = new UsernameSecurityTokenImpl(
+ WSSConstants.UsernameTokenPasswordType.PASSWORD_NONE,
+ "username", null, created, null, new byte[10], 10L,
+ null, IDGenerator.generateID(null),
+ WSSecurityTokenConstants.KEYIDENTIFIER_SECURITY_TOKEN_DIRECT_REFERENCE);
+ usernameSecurityToken.addTokenUsage(WSSecurityTokenConstants.TOKENUSAGE_SUPPORTING_TOKENS);
+ usernameTokenSecurityEvent.setSecurityToken(usernameSecurityToken);
+ return usernameTokenSecurityEvent;
+ }
+}
diff --git a/ws-security-policy-stax/src/test/resources/testdata/wsdl/mixedPasswordPolicies.wsdl b/ws-security-policy-stax/src/test/resources/testdata/wsdl/mixedPasswordPolicies.wsdl
new file mode 100644
index 000000000..015ceb6ac
--- /dev/null
+++ b/ws-security-policy-stax/src/test/resources/testdata/wsdl/mixedPasswordPolicies.wsdl
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<wsdl:definitions
+ name="MixedPolicyService"
+ targetNamespace="http://www.example.net/MixedPolicyService"
+ xmlns:tns="http://www.example.net/MixedPolicyService"
+ xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
+ xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
+ xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ >
+
+ <!-- Two operations of one endpoint, only one of which asks for password-less
+ UsernameTokens. The other names no UsernameToken at all, so nothing in its own
+ policy would reject one that the engine has already accepted. -->
+
+ <wsdl:types>
+ <xs:schema targetNamespace="http://www.example.net/MixedPolicyService"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="noPasswordOperation" type="xs:string"/>
+ <xs:element name="passwordOperation" type="xs:string"/>
+ </xs:schema>
+ </wsdl:types>
+
+ <wsp:Policy wsu:Id="NoPasswordPolicy">
+ <wsp:ExactlyOne>
+ <wsp:All>
+ <sp:SupportingTokens>
+ <wsp:Policy>
+ <sp:UsernameToken>
+ <wsp:Policy>
+ <sp:NoPassword/>
+ </wsp:Policy>
+ </sp:UsernameToken>
+ </wsp:Policy>
+ </sp:SupportingTokens>
+ </wsp:All>
+ </wsp:ExactlyOne>
+ </wsp:Policy>
+
+ <wsdl:message name="noPasswordOperationRequest">
+ <wsdl:part name="parameters" element="tns:noPasswordOperation"/>
+ </wsdl:message>
+ <wsdl:message name="passwordOperationRequest">
+ <wsdl:part name="parameters" element="tns:passwordOperation"/>
+ </wsdl:message>
+
+ <wsdl:portType name="MixedPolicyPort">
+ <wsdl:operation name="noPasswordOperation">
+ <wsdl:input message="tns:noPasswordOperationRequest"/>
+ </wsdl:operation>
+ <wsdl:operation name="passwordOperation">
+ <wsdl:input message="tns:passwordOperationRequest"/>
+ </wsdl:operation>
+ </wsdl:portType>
+
+ <wsdl:binding name="MixedPolicySOAPBinding" type="tns:MixedPolicyPort">
+ <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
+ <wsdl:operation name="noPasswordOperation">
+ <wsp:PolicyReference URI="#NoPasswordPolicy"/>
+ <soap:operation soapAction="noPasswordOperationAction" style="document"/>
+ <wsdl:input>
+ <soap:body use="literal"/>
+ </wsdl:input>
+ </wsdl:operation>
+ <wsdl:operation name="passwordOperation">
+ <soap:operation soapAction="passwordOperationAction" style="document"/>
+ <wsdl:input>
+ <soap:body use="literal"/>
+ </wsdl:input>
+ </wsdl:operation>
+ </wsdl:binding>
+
+ <wsdl:service name="MixedPolicyService">
+ <wsdl:port name="MixedPolicy" binding="tns:MixedPolicySOAPBinding">
+ <soap:address location="http://localhost:8080/MixedPolicyService"/>
+ </wsdl:port>
+ </wsdl:service>
+</wsdl:definitions>