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 77d665e53 Redact passwords from UsernameToken toString (#711)
77d665e53 is described below
commit 77d665e53e4779d8ba44b6d5414009c21a716626
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Sep 18 17:41:56 2026 +0100
Redact passwords from UsernameToken toString (#711)
---
.../wss4j/dom/message/token/UsernameToken.java | 13 ++-
.../message/token/UsernameTokenToStringTest.java | 118 +++++++++++++++++++++
2 files changed, 129 insertions(+), 2 deletions(-)
diff --git
a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/token/UsernameToken.java
b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/token/UsernameToken.java
index 71c2c7698..db47b5335 100644
---
a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/token/UsernameToken.java
+++
b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/token/UsernameToken.java
@@ -544,12 +544,21 @@ public class UsernameToken {
}
/**
- * Returns the string representation of the token.
+ * Returns the string representation of the token, with the content of any
wsse:Password
+ * redacted.
*
* @return a XML string representation
*/
public String toString() {
- return DOM2Writer.nodeToString(element);
+ Element redacted = (Element)element.cloneNode(true);
+ Element passwordElement =
+ XMLUtils.getDirectChildElement(
+ redacted, WSConstants.PASSWORD_LN, WSConstants.WSSE_NS
+ );
+ if (passwordElement != null) {
+ passwordElement.setTextContent("***");
+ }
+ return DOM2Writer.nodeToString(redacted);
}
/**
diff --git
a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/token/UsernameTokenToStringTest.java
b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/token/UsernameTokenToStringTest.java
new file mode 100644
index 000000000..51f063af3
--- /dev/null
+++
b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/token/UsernameTokenToStringTest.java
@@ -0,0 +1,118 @@
+/**
+ * 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.dom.message.token;
+
+import org.apache.wss4j.common.util.SOAPUtil;
+import org.apache.wss4j.dom.WSConstants;
+import org.apache.wss4j.dom.engine.WSSConfig;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A UsernameToken object reaches a log line or an exception message far more
readily than a
+ * password does by any other route, so toString() must not hand the password
over with it.
+ */
+public class UsernameTokenToStringTest {
+
+ private static final String PASSWORD = "SuperSecretPassword123";
+
+ public UsernameTokenToStringTest() {
+ WSSConfig.init();
+ }
+
+ @Test
+ public void testPlaintextPasswordIsRedacted() throws Exception {
+ Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+ UsernameToken usernameToken = new UsernameToken(true, doc,
WSConstants.PASSWORD_TEXT);
+ usernameToken.setName("bob");
+ usernameToken.setPassword(PASSWORD);
+
+ String serialized = usernameToken.toString();
+
+ assertFalse(serialized.contains(PASSWORD),
+ "toString() must not disclose the password: " + serialized);
+ assertTrue(serialized.contains("bob"),
+ "toString() should still identify the token: " + serialized);
+ assertTrue(serialized.contains("***"),
+ "the password element should still be there, redacted: " +
serialized);
+ }
+
+ /**
+ * A password digest is not the password, but it is the value an offline
attack runs against,
+ * so it is withheld in the same way.
+ */
+ @Test
+ public void testDigestPasswordIsRedacted() throws Exception {
+ Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+ // the PASSWORD_DIGEST constructor adds the Nonce and Created itself
+ UsernameToken usernameToken = new UsernameToken(true, doc,
WSConstants.PASSWORD_DIGEST);
+ usernameToken.setName("bob");
+ usernameToken.setPassword(PASSWORD);
+
+ String digest = usernameToken.getPassword();
+ String serialized = usernameToken.toString();
+
+ assertFalse(serialized.contains(PASSWORD), "toString() disclosed the
password");
+ assertFalse(serialized.contains(digest),
+ "toString() must not disclose the password digest: " + serialized);
+ assertTrue(serialized.contains("***"), serialized);
+ }
+
+ /**
+ * Redaction happens on a copy: the token itself must be untouched, or the
password would be
+ * destroyed by the act of logging it.
+ */
+ @Test
+ public void testRedactionDoesNotAlterTheToken() throws Exception {
+ Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+ UsernameToken usernameToken = new UsernameToken(true, doc,
WSConstants.PASSWORD_TEXT);
+ usernameToken.setName("bob");
+ usernameToken.setPassword(PASSWORD);
+
+ usernameToken.toString();
+
+ assertEquals(PASSWORD, usernameToken.getPassword(),
+ "toString() must not modify the token it was called on");
+ assertEquals(PASSWORD,
+ usernameToken.getElement().getElementsByTagNameNS(
+ WSConstants.WSSE_NS,
WSConstants.PASSWORD_LN).item(0).getTextContent(),
+ "toString() must not modify the underlying element");
+ }
+
+ /**
+ * A UsernameToken carrying no password at all - the key derivation case -
still serialises.
+ */
+ @Test
+ public void testTokenWithoutPasswordIsUnaffected() throws Exception {
+ Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+ UsernameToken usernameToken = new UsernameToken(true, doc, null);
+ usernameToken.setName("bob");
+
+ String serialized = usernameToken.toString();
+
+ assertTrue(serialized.contains("bob"), serialized);
+ assertFalse(serialized.contains("***"),
+ "nothing to redact, so nothing should be redacted: " + serialized);
+ }
+}