This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
The following commit(s) were added to refs/heads/master by this push:
new 347cead36 Backwards compatibly sig-conf fix
347cead36 is described below
commit 347cead366e516710281d1c2a7b58a1513c28ec5
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 | 62 ++++++++++++++++++++++
3 files changed, 103 insertions(+), 15 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 116bc2cf3..49adcd2c9 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
@@ -250,9 +250,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(
@@ -430,8 +433,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
@@ -465,16 +468,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"}
+ );
}
}
}
@@ -501,6 +499,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 aa3f61166..6e21e4093 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
@@ -363,6 +363,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 HashSet<>();
+ for (String savedSignature : savedSignatures) {
+
legacySignatures.add(Arrays.hashCode(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.