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);