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 3ae19b522 Adding some derived key tests
3ae19b522 is described below

commit 3ae19b522de40c6ee053173df0cebc40ca47a46e
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Sep 18 14:47:22 2026 +0100

    Adding some derived key tests
---
 .../common/crypto/AlgorithmSuiteValidatorTest.java |  72 ++++++++++++
 .../org/apache/wss4j/dom/message/WSSecDKSign.java  |  14 +++
 .../apache/wss4j/dom/message/DerivedKeyTest.java   | 121 +++++++++++++++++++++
 3 files changed, 207 insertions(+)

diff --git 
a/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidatorTest.java
 
b/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidatorTest.java
index c7eac46d3..94122942e 100644
--- 
a/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidatorTest.java
+++ 
b/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidatorTest.java
@@ -78,4 +78,76 @@ class AlgorithmSuiteValidatorTest {
                     () -> 
validator.checkAsymmetricKeyLength(keyPair.getPublic()));
         }
     }
+
+    /**
+     * The derived key length checks take a length in bytes - the denomination 
of the wsc:Length
+     * element of a DerivedKeyToken - and compare it against an AlgorithmSuite 
requirement stated
+     * in bits. Pinning the units here matters: the checks used to divide the 
byte value by 8
+     * instead of multiplying, so they never matched any real length, and they 
only logged a
+     * warning instead of failing, which hid it.
+     */
+    @ParameterizedTest
+    @CsvSource({
+        // requirement (bits), wsc:Length (bytes), should be rejected
+        "192, 24, false",   // Basic256 / Basic192 / TripleDes signature key 
derivation
+        "128, 16, false",   // Basic128 signature key derivation
+        "192, 20, true",    // what WSSecDKSign emits by default for HMAC-SHA1 
(160 bits)
+        "128, 20, true",
+        "192, 32, true",    // the DerivedKeyToken default when wsc:Length is 
absent (256 bits)
+        "128, 32, true",
+        "192, 1, true",     // a deliberately short key
+        "192, 192, true",   // the requirement misread as bytes
+    })
+    void checkSignatureDerivedKeyLength(int requiredBits, int suppliedBytes, 
boolean fail) {
+        AlgorithmSuite algorithmSuite = new AlgorithmSuite();
+        algorithmSuite.setSignatureDerivedKeyLength(requiredBits);
+        AlgorithmSuiteValidator validator = new 
AlgorithmSuiteValidator(algorithmSuite);
+
+        if (fail) {
+            WSSecurityException result = 
Assertions.assertThrows(WSSecurityException.class,
+                    () -> 
validator.checkSignatureDerivedKeyLength(suppliedBytes));
+            assertEquals(WSSecurityException.ErrorCode.INVALID_SECURITY, 
result.getErrorCode());
+        } else {
+            Assertions.assertDoesNotThrow(
+                    () -> 
validator.checkSignatureDerivedKeyLength(suppliedBytes));
+        }
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+        // requirement (bits), wsc:Length (bytes), should be rejected
+        "256, 32, false",   // Basic256 encryption key derivation
+        "192, 24, false",   // Basic192 / TripleDes encryption key derivation
+        "128, 16, false",   // Basic128 encryption key derivation
+        "256, 16, true",
+        "128, 32, true",
+        "128, 128, true",   // the requirement misread as bytes
+    })
+    void checkEncryptionDerivedKeyLength(int requiredBits, int suppliedBytes, 
boolean fail) {
+        AlgorithmSuite algorithmSuite = new AlgorithmSuite();
+        algorithmSuite.setEncryptionDerivedKeyLength(requiredBits);
+        AlgorithmSuiteValidator validator = new 
AlgorithmSuiteValidator(algorithmSuite);
+
+        if (fail) {
+            WSSecurityException result = 
Assertions.assertThrows(WSSecurityException.class,
+                    () -> 
validator.checkEncryptionDerivedKeyLength(suppliedBytes));
+            assertEquals(WSSecurityException.ErrorCode.INVALID_SECURITY, 
result.getErrorCode());
+        } else {
+            Assertions.assertDoesNotThrow(
+                    () -> 
validator.checkEncryptionDerivedKeyLength(suppliedBytes));
+        }
+    }
+
+    /**
+     * An AlgorithmSuite that states no derived key length requirement must 
not impose one. This is
+     * the WSHandler path: decodeAlgorithmSuite never populates these two 
fields, so a handler-driven
+     * deployment has no derived key length requirement at all.
+     */
+    @Test
+    void derivedKeyLengthUnsetImposesNoRequirement() {
+        AlgorithmSuiteValidator validator = new AlgorithmSuiteValidator(new 
AlgorithmSuite());
+
+        Assertions.assertDoesNotThrow(() -> 
validator.checkSignatureDerivedKeyLength(20));
+        Assertions.assertDoesNotThrow(() -> 
validator.checkEncryptionDerivedKeyLength(20));
+    }
 }
diff --git 
a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecDKSign.java 
b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecDKSign.java
index ff6ccef0e..e46f1bcb1 100644
--- 
a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecDKSign.java
+++ 
b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecDKSign.java
@@ -300,6 +300,20 @@ public class WSSecDKSign extends WSSecDerivedKeyBase {
         return derivedKeyLength > 0 ? derivedKeyLength : 
KeyUtils.getKeyLength(sigAlgo);
     }
 
+    /**
+     * Set the length of the derived key, in bytes, which is written out as 
the wsc:Length of the
+     * DerivedKeyToken. When left unset the length defaults to the key length 
of the signature
+     * algorithm, which is 20 bytes (160 bits) for HMAC-SHA1.
+     * <p/>
+     * Under a WS-SecurityPolicy AlgorithmSuite the length is not a free 
choice: the suite states
+     * the signature key derivation length, a receiver enforcing the suite 
requires the wsc:Length
+     * to match it exactly, and no standard suite derives a 160 bit signature 
key (Basic128
+     * requires 128 bits; Basic192, Basic256 and TripleDes require 192). A 
sender operating under
+     * such a policy must therefore set the length from the suite in use 
rather than rely on the
+     * default.
+     *
+     * @param keyLength the length of the derived key in bytes
+     */
     public void setDerivedKeyLength(int keyLength) {
         derivedKeyLength = keyLength;
     }
diff --git 
a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/DerivedKeyTest.java
 
b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/DerivedKeyTest.java
index 683a1804e..0495585f4 100644
--- 
a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/DerivedKeyTest.java
+++ 
b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/DerivedKeyTest.java
@@ -26,12 +26,15 @@ import org.apache.wss4j.dom.common.KeystoreCallbackHandler;
 import org.apache.wss4j.dom.engine.WSSConfig;
 import org.apache.wss4j.dom.engine.WSSecurityEngine;
 import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
+import org.apache.wss4j.dom.handler.RequestData;
 import org.apache.wss4j.dom.handler.WSHandlerResult;
 
 import org.junit.jupiter.api.Test;
+import org.apache.wss4j.common.crypto.AlgorithmSuite;
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.crypto.CryptoFactory;
 import org.apache.wss4j.common.crypto.CryptoType;
+import org.apache.wss4j.common.ext.WSSecurityException;
 import org.apache.wss4j.common.token.SecurityTokenReference;
 import org.apache.wss4j.common.util.KeyUtils;
 import org.apache.wss4j.common.util.XMLUtils;
@@ -43,9 +46,11 @@ import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 import javax.security.auth.callback.CallbackHandler;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 /**
  * A set of tests for using a derived key for encryption/signature.
@@ -407,6 +412,122 @@ public class DerivedKeyTest {
      * @param envelope
      * @throws Exception Thrown when there is a problem in verification
      */
+    /**
+     * A derived key signature verified against an AlgorithmSuite that states 
a signature key
+     * derivation length. The requirement is denominated in bits and the 
wsc:Length of the
+     * DerivedKeyToken in bytes, and the two must agree exactly: 24 bytes 
satisfies the 192 bit
+     * requirement of the Basic256, Basic192 and TripleDes suites.
+     */
+    @Test
+    public void testSignatureAlgorithmSuiteDerivedKeyLength() throws Exception 
{
+        Document doc = signWithDerivedKey(24);
+
+        AlgorithmSuite algorithmSuite = createAlgorithmSuite();
+        algorithmSuite.setSignatureDerivedKeyLength(192);
+
+        verify(doc, algorithmSuite);
+    }
+
+    @Test
+    public void testSignatureAlgorithmSuiteDerivedKeyLengthMismatch() throws 
Exception {
+        Document doc = signWithDerivedKey(24);
+
+        AlgorithmSuite algorithmSuite = createAlgorithmSuite();
+        algorithmSuite.setSignatureDerivedKeyLength(256);
+
+        try {
+            verify(doc, algorithmSuite);
+            fail("Expected failure as the derived key length does not match 
the AlgorithmSuite");
+        } catch (WSSecurityException ex) {
+            assertEquals(WSSecurityException.ErrorCode.INVALID_SECURITY, 
ex.getErrorCode());
+        }
+    }
+
+    /**
+     * Unless it is told otherwise, WSSecDKSign derives a key of 
KeyUtils.getKeyLength(sigAlgo)
+     * bytes, which for HMAC-SHA1 is 20 bytes - 160 bits. No standard 
WS-SecurityPolicy algorithm
+     * suite derives a 160 bit signature key: Basic128 requires 128 bits, and 
Basic192, Basic256
+     * and TripleDes all require 192. A message built with the default length 
is therefore
+     * rejected by a receiver enforcing any of them, and a sender under such a 
policy has to set
+     * the length from the suite in use (as Apache CXF does). This test pins 
that trap down rather
+     * than endorsing it - the derived key length is only checked at all once 
an AlgorithmSuite
+     * states a requirement, which WSHandler.decodeAlgorithmSuite never does.
+     */
+    @Test
+    public void testSignatureAlgorithmSuiteDefaultDerivedKeyLengthIsRejected() 
throws Exception {
+        for (int requiredKeyLength : new int[] {128, 192, 256}) {
+            Document doc = signWithDerivedKey(0);
+
+            AlgorithmSuite algorithmSuite = createAlgorithmSuite();
+            algorithmSuite.setSignatureDerivedKeyLength(requiredKeyLength);
+
+            try {
+                verify(doc, algorithmSuite);
+                fail("Expected the default 160 bit derived key to be rejected 
by a "
+                     + requiredKeyLength + " bit requirement");
+            } catch (WSSecurityException ex) {
+                assertEquals(WSSecurityException.ErrorCode.INVALID_SECURITY, 
ex.getErrorCode());
+            }
+        }
+    }
+
+    /**
+     * Sign the SOAP Body with a key derived from an EncryptedKey.
+     *
+     * @param derivedKeyLength the wsc:Length to request, in bytes, or 0 to 
leave the builder's
+     *                         own default in place
+     */
+    private Document signWithDerivedKey(int derivedKeyLength) throws Exception 
{
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader(doc);
+        secHeader.insertSecurityHeader();
+
+        WSSecEncryptedKey encrKeyBuilder = new WSSecEncryptedKey(secHeader);
+        encrKeyBuilder.setUserInfo("wss40");
+        encrKeyBuilder.setKeyIdentifierType(WSConstants.THUMBPRINT_IDENTIFIER);
+
+        KeyGenerator keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_128);
+        SecretKey symmetricKey = keyGen.generateKey();
+        encrKeyBuilder.prepare(crypto, symmetricKey);
+
+        WSSecDKSign sigBuilder = new WSSecDKSign(secHeader);
+        sigBuilder.setTokenIdentifier(encrKeyBuilder.getId());
+        sigBuilder.setSignatureAlgorithm(WSConstants.HMAC_SHA1);
+        if (derivedKeyLength > 0) {
+            sigBuilder.setDerivedKeyLength(derivedKeyLength);
+        }
+        sigBuilder.build(symmetricKey.getEncoded());
+
+        encrKeyBuilder.prependToHeader();
+        encrKeyBuilder.prependBSTElementToHeader();
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug(XMLUtils.prettyDocumentToString(doc));
+        }
+
+        return doc;
+    }
+
+    /**
+     * An AlgorithmSuite that constrains nothing but what each test sets on 
it. Empty algorithm
+     * sets are permissive, and the asymmetric bound is widened for the test 
key.
+     */
+    private AlgorithmSuite createAlgorithmSuite() {
+        AlgorithmSuite algorithmSuite = new AlgorithmSuite();
+        algorithmSuite.setMinimumAsymmetricKeyLength(512);
+        return algorithmSuite;
+    }
+
+    private WSHandlerResult verify(Document doc, AlgorithmSuite 
algorithmSuite) throws Exception {
+        RequestData data = new RequestData();
+        data.setSigVerCrypto(crypto);
+        data.setDecCrypto(crypto);
+        data.setCallbackHandler(callbackHandler);
+        data.setAlgorithmSuite(algorithmSuite);
+
+        return secEngine.processSecurityHeader(doc, data);
+    }
+
     private WSHandlerResult verify(Document doc) throws Exception {
         WSHandlerResult results =
             secEngine.processSecurityHeader(doc, null, callbackHandler, 
crypto);

Reply via email to