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 19f4a48b3 Backwards compatibly sig-conf fix
19f4a48b3 is described below

commit 19f4a48b36f963f22ad5d627841d6fa353635d65
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Tue Sep 22 11:11:35 2026 +0100

    Backwards compatibly sig-conf fix
---
 .../org/apache/wss4j/dom/handler/WSHandler.java    | 52 +++++++++++++-----
 .../wss4j/dom/handler/WSHandlerConstants.java      |  4 +-
 .../dom/handler/SignatureConfirmationTest.java     | 64 +++++++++++++++++++++-
 3 files changed, 104 insertions(+), 16 deletions(-)

diff --git 
a/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java 
b/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java
index 66b5a34b8..8cc65b560 100644
--- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java
+++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java
@@ -249,9 +249,12 @@ public abstract class WSHandler {
          */
         if (reqData.isEnableSignatureConfirmation()
             && isRequest && !reqData.getSignatureValues().isEmpty()) {
+            // The set is typed as Set<Object> rather than Set<String> because 
it may have been
+            // created by a subclass that stores the legacy Integer hashes - 
see
+            // removeSavedSignatureValue.
             @SuppressWarnings("unchecked")
-            Set<String> savedSignatures =
-                (Set<String>)getProperty(reqData.getMsgContext(), 
WSHandlerConstants.SEND_SIGV);
+            Set<Object> savedSignatures =
+                (Set<Object>)getProperty(reqData.getMsgContext(), 
WSHandlerConstants.SEND_SIGV);
             if (savedSignatures == null) {
                 savedSignatures = new HashSet<>();
                 setProperty(
@@ -437,8 +440,8 @@ public abstract class WSHandler {
         //
         // First get all Signature values stored during sending the request
         //
-        Set<String> savedSignatures =
-            (Set<String>) getProperty(reqData.getMsgContext(), 
WSHandlerConstants.SEND_SIGV);
+        Set<Object> savedSignatures =
+            (Set<Object>) getProperty(reqData.getMsgContext(), 
WSHandlerConstants.SEND_SIGV);
         //
         // Now get all results that hold a SignatureConfirmation element from
         // the current run of receiver (we can have more than one run: if we
@@ -472,16 +475,11 @@ public abstract class WSHandler {
                                  + " signature values"}
                             );
                         }
-                    } else {
-                        String encodedValue = 
encodeSignatureValue(sc.getSignatureValue());
-                        if (savedSignatures.contains(encodedValue)) {
-                            savedSignatures.remove(encodedValue);
-                        } else {
-                            throw new 
WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
-                                new Object[] {"Received a 
SignatureConfirmation element, but there are no matching"
-                                + " stored signature values"}
-                            );
-                        }
+                    } else if (!removeSavedSignatureValue(savedSignatures, 
sc.getSignatureValue())) {
+                        throw new 
WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
+                            new Object[] {"Received a SignatureConfirmation 
element, but there are no matching"
+                            + " stored signature values"}
+                        );
                     }
                 }
             }
@@ -508,6 +506,32 @@ public abstract class WSHandler {
         return Base64.getEncoder().encodeToString(signatureValue);
     }
 
+    /**
+     * Remove from the saved signature values the entry matching the given 
received
+     * SignatureConfirmation value, returning whether one was found.
+     *
+     * Entries stored by this class are the Base64 encoding of the whole 
signature value. A
+     * subclass may populate {@link WSHandlerConstants#SEND_SIGV} itself, 
however, and before
+     * WSS4J 4.0.2 the representation was the {@code Arrays.hashCode} of the 
signature value, so
+     * Integer entries are matched against that hash as well. Matching on a 
32-bit hash is weak -
+     * a colliding value is trivial to construct, so the confirmation is not 
bound to the
+     * signature of the request - and such subclasses should be changed to 
store the encoded
+     * value instead.
+     */
+    private static boolean removeSavedSignatureValue(Set<Object> 
savedSignatures, byte[] signatureValue) {
+        if (savedSignatures.remove(encodeSignatureValue(signatureValue))) {
+            return true;
+        }
+        if 
(savedSignatures.remove(Integer.valueOf(Arrays.hashCode(signatureValue)))) {
+            LOG.debug("Matched a SignatureConfirmation against a legacy 
Arrays.hashCode entry in "
+                      + WSHandlerConstants.SEND_SIGV + ". This match is on 32 
bits only and is "
+                      + "trivially collidable: store the Base64 encoding of 
the signature value "
+                      + "instead.");
+            return true;
+        }
+        return false;
+    }
+
     protected void decodeUTParameter(RequestData reqData)
         throws WSSecurityException {
         Object mc = reqData.getMsgContext();
diff --git 
a/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandlerConstants.java
 
b/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandlerConstants.java
index 75b9058b7..b66710cd8 100644
--- 
a/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandlerConstants.java
+++ 
b/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandlerConstants.java
@@ -77,7 +77,9 @@ public final class WSHandlerConstants extends 
ConfigurationConstants {
      * each outbound Signature value, which an inbound SignatureConfirmation 
is matched against.
      * Before WSS4J 4.0.2 it held a {@code Set<Integer>} of {@code 
Arrays.hashCode} values; those
      * are 32 bits wide and trivially collidable, so the confirmation they 
backed was not a
-     * binding to the request's signature.
+     * binding to the request's signature. A handler that populates this 
property itself should
+     * store the Base64 encoding; Integer entries are still matched, against 
the old hash, so
+     * that such handlers keep working, but they get the weak check rather 
than the strong one.
      */
     public static final String SEND_SIGV = "_sendSignatureValues_";
 
diff --git 
a/ws-security-dom/src/test/java/org/apache/wss4j/dom/handler/SignatureConfirmationTest.java
 
b/ws-security-dom/src/test/java/org/apache/wss4j/dom/handler/SignatureConfirmationTest.java
index d0320a290..15a30a947 100644
--- 
a/ws-security-dom/src/test/java/org/apache/wss4j/dom/handler/SignatureConfirmationTest.java
+++ 
b/ws-security-dom/src/test/java/org/apache/wss4j/dom/handler/SignatureConfirmationTest.java
@@ -368,6 +368,68 @@ public class SignatureConfirmationTest {
     }
 
 
+    /**
+     * A handler that populates SEND_SIGV itself - rather than letting 
WSHandler.doSenderAction do
+     * it - may still store the pre-4.0.2 representation, the Arrays.hashCode 
of each signature
+     * value. Those entries must go on being matched, so that such a handler 
keeps working against
+     * an upgraded WSS4J. The match is the old weak one; the handler has to 
store the encoded
+     * value to get the strong one.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void
+    testSignatureConfirmationLegacyHashEntries() throws Exception {
+        final RequestData reqData = new RequestData();
+        java.util.Map<String, Object> msgContext = new java.util.TreeMap<>();
+        msgContext.put(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION, 
"true");
+        msgContext.put(WSHandlerConstants.SIG_PROP_FILE, "crypto.properties");
+        msgContext.put("password", "security");
+        reqData.setMsgContext(msgContext);
+        reqData.setUsername("16c73ab6-b892-458f-abf5-2f875f74882e");
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        CustomHandler handler = new CustomHandler();
+        HandlerAction action = new HandlerAction(WSConstants.SIGN);
+        handler.send(doc, reqData, Collections.singletonList(action), true);
+
+        //
+        // Replace the saved signature values with the representation used 
before WSS4J 4.0.2.
+        //
+        msgContext = (java.util.Map<String, Object>)reqData.getMsgContext();
+        Set<String> savedSignatures =
+            (Set<String>)msgContext.get(WSHandlerConstants.SEND_SIGV);
+        assertNotNull(savedSignatures);
+        assertFalse(savedSignatures.isEmpty());
+        Set<Integer> legacySignatures = new java.util.HashSet<>();
+        for (String savedSignature : savedSignatures) {
+            
legacySignatures.add(Arrays.hashCode(java.util.Base64.getDecoder().decode(savedSignature)));
+        }
+        msgContext.put(WSHandlerConstants.SEND_SIGV, legacySignatures);
+
+        //
+        // Verify the inbound request, and create a response with a Signature 
Confirmation
+        //
+        WSHandlerResult results = verify(doc);
+        doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        List<WSHandlerResult> receivedResults = new ArrayList<>();
+        receivedResults.add(results);
+        msgContext.put(WSHandlerConstants.RECV_RESULTS, receivedResults);
+        handler.send(doc, reqData, Collections.singletonList(action), false);
+
+        //
+        // Verify the SignatureConfirmation response
+        //
+        results = verify(doc);
+        WSSecurityEngineResult scResult =
+            results.getActionResults().get(WSConstants.SC).get(0);
+        assertNotNull(scResult);
+        
assertNotNull(scResult.get(WSSecurityEngineResult.TAG_SIGNATURE_CONFIRMATION));
+        handler.signatureConfirmation(reqData, results);
+
+        assertTrue(legacySignatures.isEmpty());
+    }
+
+
     /**
      * Test to see that a signature confirmation response that does not 
contain a wsu:Id fails
      * the BSP compliance is enabled.
@@ -547,4 +609,4 @@ public class SignatureConfirmationTest {
         return results;
     }
 
-}
\ No newline at end of file
+}

Reply via email to