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/10 09:18:27 UTC

(ws-wss4j) branch 2_4_x-fixes updated: Reject duplicate C14N settings + Detect unknown top-level policy assertions (#684)

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 2_4_x-fixes
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git


The following commit(s) were added to refs/heads/2_4_x-fixes by this push:
     new bf5186e59 Reject duplicate C14N settings + Detect unknown top-level policy assertions (#684)
bf5186e59 is described below

commit bf5186e59eefbf0c6251a7abc58cc851e91822a3
Author: Colm O hEigeartaigh <co...@users.noreply.github.com>
AuthorDate: Thu Sep 10 10:16:01 2026 +0100

    Reject duplicate C14N settings + Detect unknown top-level policy assertions (#684)
---
 .../apache/wss4j/policy/model/AlgorithmSuite.java  |   7 +
 .../wss4j/policy/stax/enforcer/PolicyEnforcer.java |  65 +++++-
 .../policy/stax/test/UnknownAssertionsTest.java    | 247 +++++++++++++++++++++
 3 files changed, 308 insertions(+), 11 deletions(-)

diff --git a/policy/src/main/java/org/apache/wss4j/policy/model/AlgorithmSuite.java b/policy/src/main/java/org/apache/wss4j/policy/model/AlgorithmSuite.java
index 25b029e5e..3ee0cd3e3 100644
--- a/policy/src/main/java/org/apache/wss4j/policy/model/AlgorithmSuite.java
+++ b/policy/src/main/java/org/apache/wss4j/policy/model/AlgorithmSuite.java
@@ -704,6 +704,7 @@ public class AlgorithmSuite extends AbstractSecurityAssertion implements PolicyC
         //this means that if we have a compact policy only the first alternative is visible
         //in contrary to a normalized policy where just one alternative exists
         if (alternatives.hasNext()) {
+            boolean c14nSeen = false;
             List<Assertion> assertions = alternatives.next();
             for (Assertion assertion : assertions) {
                 String assertionName = assertion.getName().getLocalPart();
@@ -728,6 +729,12 @@ public class AlgorithmSuite extends AbstractSecurityAssertion implements PolicyC
                 }
                 C14NType c14NType = C14NType.lookUp(assertionName);
                 if (c14NType != null) {
+                    // Reject duplicate C14N assertions instead of letting the last one win,
+                    // consistent with the sibling SOAPNorm/STR/XPath branches below
+                    if (c14nSeen) {
+                        throw new IllegalArgumentException(SPConstants.ERR_INVALID_POLICY);
+                    }
+                    c14nSeen = true;
                     algorithmSuite.setC14n(c14NType);
                     continue;
                 }
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 49f2d4c98..c95fde087 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
@@ -45,6 +45,7 @@ import org.apache.wss4j.policy.model.AbstractSecurityAssertion;
 import org.apache.wss4j.policy.model.AbstractSymmetricAsymmetricBinding;
 import org.apache.wss4j.policy.model.AbstractToken;
 import org.apache.wss4j.policy.model.AlgorithmSuite;
+import org.apache.wss4j.policy.model.BootstrapPolicy;
 import org.apache.wss4j.policy.model.ContentEncryptedElements;
 import org.apache.wss4j.policy.model.EncryptedElements;
 import org.apache.wss4j.policy.model.EncryptedParts;
@@ -128,6 +129,16 @@ public class PolicyEnforcer implements SecurityEventListener {
     private static final transient org.slf4j.Logger LOG =
         org.slf4j.LoggerFactory.getLogger(PolicyEnforcer.class);
 
+    /**
+     * System property controlling how policy assertions which this enforcer cannot enforce
+     * are handled. The default ("false") logs a warning and skips the assertion (the
+     * historical behaviour). When set to "true", building the assertion state map fails
+     * with a WSSPolicyException instead, so that the server cannot silently enforce less
+     * than the policy it advertises.
+     */
+    public static final String FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY =
+        "org.apache.wss4j.policy.failOnUnsupportedAssertions";
+
     private static final QName SOAP11_FAULT = new QName(WSSConstants.NS_SOAP11, "Fault");
     private static final QName SOAP12_FAULT = new QName(WSSConstants.NS_SOAP12, "Fault");
 
@@ -231,7 +242,7 @@ public class PolicyEnforcer implements SecurityEventListener {
                 if (policyOperator instanceof ExactlyOne) {
                     assertionStateMap.add(new HashMap<SecurityEventConstants.Event,
                                           Map<Assertion, List<Assertable>>>());
-                    buildAssertionStateMap(curPolicyComponent, assertionStateMap, alternative++);
+                    buildAssertionStateMap(curPolicyComponent, assertionStateMap, alternative++, false);
                 } else {
                     buildAssertionStateMap(curPolicyComponent, assertionStateMap);
                 }
@@ -246,7 +257,8 @@ public class PolicyEnforcer implements SecurityEventListener {
             PolicyComponent policyComponent,
             List<Map<SecurityEventConstants.Event,
             Map<Assertion, List<Assertable>>>> assertionStateMap,
-            int alternative
+            int alternative,
+            boolean nestedPolicy
     ) throws WSSPolicyException {
         if (policyComponent instanceof PolicyOperator) {
             PolicyOperator policyOperator = (PolicyOperator) policyComponent;
@@ -254,7 +266,7 @@ public class PolicyEnforcer implements SecurityEventListener {
             Iterator<PolicyComponent> policyComponentIterator = policyComponents.iterator();
             while (policyComponentIterator.hasNext()) {
                 PolicyComponent curPolicyComponent = policyComponentIterator.next();
-                buildAssertionStateMap(curPolicyComponent, assertionStateMap, alternative);
+                buildAssertionStateMap(curPolicyComponent, assertionStateMap, alternative, nestedPolicy);
             }
         } else if (policyComponent instanceof AbstractSecurityAssertion) {
             AbstractSecurityAssertion abstractSecurityAssertion = (AbstractSecurityAssertion) policyComponent;
@@ -278,14 +290,46 @@ public class PolicyEnforcer implements SecurityEventListener {
             }
             if (abstractSecurityAssertion instanceof PolicyContainingAssertion) {
                 buildAssertionStateMap(((PolicyContainingAssertion) abstractSecurityAssertion).getPolicy(),
-                                       assertionStateMap, alternative);
-            }
-        } else if (!(policyComponent instanceof PrimitiveAssertion)) {
+                                       assertionStateMap, alternative, true);
+            }
+        } else if (policyComponent instanceof PrimitiveAssertion) {
+            if (!nestedPolicy) {
+                // A top-level PrimitiveAssertion is an assertion which no registered builder
+                // converted into a security policy model object - a namespace typo, an SP
+                // version mismatch, or a custom/unknown assertion. It cannot be enforced by
+                // this layer: the server would silently enforce less than the policy it
+                // advertises. Warn by default; fail if configured to do so.
+                handleUnknownAssertion(((PrimitiveAssertion) policyComponent).getName().toString());
+            }
+            // Inside a PolicyContainingAssertion's nested policy the remaining
+            // PrimitiveAssertions are the standard leaves (e.g. sp:Basic256, sp:Strict,
+            // sp:IncludeTimestamp, sp:WssX509V3Token10) which the parent model's
+            // parseNestedPolicy already consumed by name and which are enforced through the
+            // parent's assertion state. They are not unknown, so they are deliberately
+            // skipped here (the historical behaviour).
+        } else {
             throw new WSSPolicyException("Unsupported PolicyComponent: " + policyComponent
                                          + " type: " + policyComponent.getType());
         }
     }
 
+    /**
+     * Handle a policy assertion which this enforcer cannot enforce. By default a warning is
+     * logged and the assertion is skipped (the historical behaviour). If the
+     * {@link #FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY} system property is set to "true", a
+     * WSSPolicyException is thrown instead, so that a policy advertising more than the
+     * enforcer can verify is rejected at build time rather than silently under-enforced.
+     */
+    private void handleUnknownAssertion(String assertionName) throws WSSPolicyException {
+        String msg = "Policy assertion " + assertionName
+            + " is unknown to the PolicyEnforcer and will NOT be enforced";
+        if (Boolean.parseBoolean(System.getProperty(FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY, "false"))) {
+            throw new WSSPolicyException(msg);
+        }
+        LOG.warn("{}. Set the {} system property to \"true\" to reject unsupported assertions instead.",
+                 msg, FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY);
+    }
+
     private void addAssertionState(Map<Assertion, List<Assertable>> assertables,
                                    Assertion keyAssertion,
                                    Assertable assertable) {
@@ -502,15 +546,14 @@ public class PolicyEnforcer implements SecurityEventListener {
                     policyAsserter.assertPolicy(new QName(namespace, SPConstants.SCOPE_POLICY_15));
                 }
             }
+        } else if (abstractSecurityAssertion instanceof PolicyContainingAssertion
+            || abstractSecurityAssertion instanceof BootstrapPolicy) {
+            policyAsserter.assertPolicy(abstractSecurityAssertion);
         } else {
+            handleUnknownAssertion(abstractSecurityAssertion.getName().toString());
             policyAsserter.assertPolicy(abstractSecurityAssertion);
         }
 
-        /*else if (abstractSecurityAssertion instanceof AsymmetricBinding) {
-        } else if (abstractSecurityAssertion instanceof SymmetricBinding) {
-        } else if (abstractSecurityAssertion instanceof TransportBinding) {
-        } */
-
         return assertableList;
     }
 
diff --git a/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/UnknownAssertionsTest.java b/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/UnknownAssertionsTest.java
new file mode 100644
index 000000000..f1404ee4d
--- /dev/null
+++ b/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/UnknownAssertionsTest.java
@@ -0,0 +1,247 @@
+/**
+ * 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.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.apache.wss4j.common.WSSPolicyException;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.policy.stax.enforcer.PolicyEnforcer;
+import org.apache.wss4j.stax.ext.WSSConstants;
+import org.apache.wss4j.stax.impl.securityToken.HttpsSecurityTokenImpl;
+import org.apache.wss4j.stax.impl.securityToken.X509SecurityTokenImpl;
+import org.apache.wss4j.stax.securityEvent.HttpsTokenSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.OperationSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.RequiredElementSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.SignedPartSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.TimestampSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.X509TokenSecurityEvent;
+import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
+import org.apache.xml.security.stax.ext.XMLSecurityConstants;
+import org.apache.xml.security.stax.securityEvent.ContentEncryptedElementSecurityEvent;
+import org.apache.xml.security.stax.securityEvent.EncryptedElementSecurityEvent;
+import org.apache.xml.security.stax.securityToken.InboundSecurityToken;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Tests for the handling of policy assertions which the PolicyEnforcer cannot enforce:
+ * unknown assertions must be warned about (or rejected, when the
+ * {@link PolicyEnforcer#FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY} system property is set),
+ * while stock policies - whose nested leaves and container assertions are enforced via
+ * the parent model's assertion states - must never be flagged.
+ */
+public class UnknownAssertionsTest extends AbstractPolicyTestBase {
+
+    private static final String UNKNOWN_ASSERTION_POLICY =
+            "<foo:UnknownAssertion xmlns:foo=\"http://www.example.org/custom-assertions\"/>";
+
+    /**
+     * A stock transport binding (nested AlgorithmSuite/Basic256 and IncludeTimestamp
+     * leaves) must build and verify cleanly even with failOnUnsupportedAssertions
+     * enabled - the nested leaves are consumed by the parent model's parser and are
+     * not unknown.
+     */
+    @Test
+    public void testStockTransportBindingPolicyWithFailOnUnsupportedAssertionsEnabled() throws Exception {
+        String policyString =
+                "<sp:TransportBinding xmlns:sp=\"http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702\" xmlns:sp3=\"http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200802\">\n" +
+                        "<wsp:Policy xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\">\n" +
+                        "   <sp:AlgorithmSuite>\n" +
+                        "       <wsp:Policy>\n" +
+                        "           <sp:Basic256/>\n" +
+                        "       </wsp:Policy>\n" +
+                        "   </sp:AlgorithmSuite>\n" +
+                        "<sp:IncludeTimestamp/>\n" +
+                        "</wsp:Policy>\n" +
+                        "</sp:TransportBinding>";
+        System.setProperty(PolicyEnforcer.FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY, "true");
+        try {
+            PolicyEnforcer policyEnforcer = buildAndStartPolicyEngine(policyString);
+            TimestampSecurityEvent timestampSecurityEvent = new TimestampSecurityEvent();
+            policyEnforcer.registerSecurityEvent(timestampSecurityEvent);
+
+            RequiredElementSecurityEvent requiredElementSecurityEvent = new RequiredElementSecurityEvent();
+            List<QName> headerPath = new ArrayList<>();
+            headerPath.addAll(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH);
+            headerPath.add(WSSConstants.TAG_WSU_TIMESTAMP);
+            requiredElementSecurityEvent.setElementPath(headerPath);
+            policyEnforcer.registerSecurityEvent(requiredElementSecurityEvent);
+
+            HttpsTokenSecurityEvent httpsTokenSecurityEvent = new HttpsTokenSecurityEvent();
+            HttpsSecurityTokenImpl httpsSecurityToken = new HttpsSecurityTokenImpl(true, "username");
+            httpsSecurityToken.addTokenUsage(WSSecurityTokenConstants.TOKENUSAGE_MAIN_SIGNATURE);
+            httpsTokenSecurityEvent.setSecurityToken(httpsSecurityToken);
+            policyEnforcer.registerSecurityEvent(httpsTokenSecurityEvent);
+
+            OperationSecurityEvent operationSecurityEvent = new OperationSecurityEvent();
+            operationSecurityEvent.setOperation(WSDL_DEFINITIONS);
+            policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+
+            List<XMLSecurityConstants.ContentType> protectionOrder = new LinkedList<>();
+            protectionOrder.add(XMLSecurityConstants.ContentType.SIGNATURE);
+            protectionOrder.add(XMLSecurityConstants.ContentType.ENCRYPTION);
+            EncryptedElementSecurityEvent encryptedElementSecurityEvent =
+                    new EncryptedElementSecurityEvent(null, true, protectionOrder);
+            headerPath = new ArrayList<>();
+            headerPath.addAll(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH);
+            headerPath.add(WSSConstants.TAG_dsig_Signature);
+            encryptedElementSecurityEvent.setElementPath(headerPath);
+            policyEnforcer.registerSecurityEvent(encryptedElementSecurityEvent);
+
+            encryptedElementSecurityEvent = new EncryptedElementSecurityEvent(null, true, protectionOrder);
+            headerPath = new ArrayList<>();
+            headerPath.addAll(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH);
+            headerPath.add(WSSConstants.TAG_WSSE11_SIG_CONF);
+            encryptedElementSecurityEvent.setElementPath(headerPath);
+            policyEnforcer.registerSecurityEvent(encryptedElementSecurityEvent);
+
+            SignedPartSecurityEvent signedPartSecurityEvent = new SignedPartSecurityEvent(null, true, protectionOrder);
+            signedPartSecurityEvent.setElementPath(WSSConstants.SOAP_11_BODY_PATH);
+            policyEnforcer.registerSecurityEvent(signedPartSecurityEvent);
+
+            policyEnforcer.doFinal();
+        } finally {
+            System.clearProperty(PolicyEnforcer.FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY);
+        }
+    }
+
+    /**
+     * A stock asymmetric binding whose tokens are carried in InitiatorToken/RecipientToken
+     * wrappers (with nested X509Token leaves such as sp:WssX509V3Token11) must build and
+     * verify cleanly even with failOnUnsupportedAssertions enabled - the token wrappers
+     * are recognized containers whose nested policies are enforced via the
+     * PolicyContainingAssertion recursion.
+     */
+    @Test
+    public void testAsymmetricBindingWithTokenWrappersAndFailOnUnsupportedAssertionsEnabled() throws Exception {
+        String policyString =
+                "<sp:AsymmetricBinding xmlns:sp=\"http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702\" xmlns:sp3=\"http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200802\">\n" +
+                        "<wsp:Policy xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\">\n" +
+                        "<sp:InitiatorToken>\n" +
+                        "   <wsp:Policy>\n" +
+                        "       <sp:X509Token>\n" +
+                        "           <sp:IssuerName>CN=transmitter,OU=swssf,C=CH</sp:IssuerName>\n" +
+                        "           <wsp:Policy xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\">\n" +
+                        "               <sp:RequireThumbprintReference/>\n" +
+                        "               <sp:WssX509V3Token11/>\n" +
+                        "           </wsp:Policy>\n" +
+                        "       </sp:X509Token>\n" +
+                        "   </wsp:Policy>\n" +
+                        "</sp:InitiatorToken>\n" +
+                        "<sp:RecipientToken>\n" +
+                        "   <wsp:Policy>\n" +
+                        "       <sp:X509Token>\n" +
+                        "           <sp:IssuerName>CN=transmitter,OU=swssf,C=CH</sp:IssuerName>\n" +
+                        "           <wsp:Policy xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\">\n" +
+                        "               <sp:RequireThumbprintReference/>\n" +
+                        "               <sp:WssX509V3Token11/>\n" +
+                        "           </wsp:Policy>\n" +
+                        "       </sp:X509Token>\n" +
+                        "   </wsp:Policy>\n" +
+                        "</sp:RecipientToken>\n" +
+                        "   <sp:AlgorithmSuite>\n" +
+                        "       <wsp:Policy>\n" +
+                        "           <sp:Basic256/>\n" +
+                        "       </wsp:Policy>\n" +
+                        "   </sp:AlgorithmSuite>\n" +
+                        "</wsp:Policy>\n" +
+                        "</sp:AsymmetricBinding>";
+        System.setProperty(PolicyEnforcer.FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY, "true");
+        try {
+            PolicyEnforcer policyEnforcer = buildAndStartPolicyEngine(policyString);
+            X509TokenSecurityEvent initiatorX509TokenSecurityEvent = new X509TokenSecurityEvent();
+            X509SecurityTokenImpl securityToken = getX509Token(WSSecurityTokenConstants.X509V3Token);
+            securityToken.addTokenUsage(WSSecurityTokenConstants.TOKENUSAGE_MAIN_SIGNATURE);
+            initiatorX509TokenSecurityEvent.setSecurityToken(securityToken);
+            policyEnforcer.registerSecurityEvent(initiatorX509TokenSecurityEvent);
+
+            X509TokenSecurityEvent recipientX509TokenSecurityEvent = new X509TokenSecurityEvent();
+            securityToken = getX509Token(WSSecurityTokenConstants.X509V3Token);
+            securityToken.addTokenUsage(WSSecurityTokenConstants.TOKENUSAGE_MAIN_ENCRYPTION);
+            recipientX509TokenSecurityEvent.setSecurityToken(securityToken);
+            policyEnforcer.registerSecurityEvent(recipientX509TokenSecurityEvent);
+
+            List<XMLSecurityConstants.ContentType> protectionOrder = new LinkedList<>();
+            protectionOrder.add(XMLSecurityConstants.ContentType.SIGNATURE);
+            protectionOrder.add(XMLSecurityConstants.ContentType.ENCRYPTION);
+            SignedPartSecurityEvent signedPartSecurityEvent =
+                    new SignedPartSecurityEvent(
+                            (InboundSecurityToken)recipientX509TokenSecurityEvent.getSecurityToken(), true, protectionOrder);
+            signedPartSecurityEvent.setElementPath(WSSConstants.SOAP_11_BODY_PATH);
+            policyEnforcer.registerSecurityEvent(signedPartSecurityEvent);
+
+            ContentEncryptedElementSecurityEvent contentEncryptedElementSecurityEvent =
+                    new ContentEncryptedElementSecurityEvent(
+                            (InboundSecurityToken)recipientX509TokenSecurityEvent.getSecurityToken(), true, protectionOrder);
+            contentEncryptedElementSecurityEvent.setElementPath(WSSConstants.SOAP_11_BODY_PATH);
+            policyEnforcer.registerSecurityEvent(contentEncryptedElementSecurityEvent);
+
+            OperationSecurityEvent operationSecurityEvent = new OperationSecurityEvent();
+            operationSecurityEvent.setOperation(WSDL_DEFINITIONS);
+            policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+
+            policyEnforcer.doFinal();
+        } finally {
+            System.clearProperty(PolicyEnforcer.FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY);
+        }
+    }
+
+    /**
+     * A genuinely unknown top-level assertion (no registered builder) is skipped with a
+     * warning by default - the historical behaviour - so the policy still builds.
+     */
+    @Test
+    public void testUnknownPrimitiveAssertionIsSkippedByDefault() throws Exception {
+        PolicyEnforcer policyEnforcer = buildAndStartPolicyEngine(UNKNOWN_ASSERTION_POLICY);
+        OperationSecurityEvent operationSecurityEvent = new OperationSecurityEvent();
+        operationSecurityEvent.setOperation(WSDL_DEFINITIONS);
+        policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+        policyEnforcer.doFinal();
+    }
+
+    /**
+     * The same unknown top-level assertion must fail the policy build when
+     * failOnUnsupportedAssertions is enabled.
+     */
+    @Test
+    public void testUnknownPrimitiveAssertionFailsWhenConfigured() throws Exception {
+        System.setProperty(PolicyEnforcer.FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY, "true");
+        try {
+            PolicyEnforcer policyEnforcer = buildAndStartPolicyEngine(UNKNOWN_ASSERTION_POLICY);
+            OperationSecurityEvent operationSecurityEvent = new OperationSecurityEvent();
+            operationSecurityEvent.setOperation(WSDL_DEFINITIONS);
+            try {
+                policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+                fail("Exception expected");
+            } catch (WSSecurityException e) {
+                assertTrue(e.getCause() instanceof WSSPolicyException);
+                assertTrue(e.getCause().getMessage().contains("UnknownAssertion"));
+            }
+        } finally {
+            System.clearProperty(PolicyEnforcer.FAIL_ON_UNSUPPORTED_ASSERTIONS_PROPERTY);
+        }
+    }
+}