This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 6edaf9b8f3 Review "Fix WebSocket client digest response calculation
for qop..."
6edaf9b8f3 is described below
commit 6edaf9b8f38631635a907dbbc28c71afe9f5cadd
Author: Mark Thomas <[email protected]>
AuthorDate: Wed Nov 4 18:14:14 2026 +0000
Review "Fix WebSocket client digest response calculation for qop..."
Stricter validation of qop (require auth or auth-int)
Better handling of multiple qop values
Fix various case sensitivity issues in Digest handling
---
.../tomcat/websocket/DigestAuthenticator.java | 49 ++++++++++++++++------
.../tomcat/websocket/LocalStrings.properties | 1 +
2 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
index 0cb58aeb7a..31878f96eb 100644
--- a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
+++ b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
@@ -20,10 +20,13 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.compat.JreCompat;
import org.apache.tomcat.util.res.StringManager;
/**
@@ -69,6 +72,28 @@ public class DigestAuthenticator extends Authenticator {
if (messageQop == null || messageQop.isEmpty()) {
throw new
AuthenticationException(sm.getString("digestAuthenticator.noQop"));
}
+ // Convert to lower case now to avoid handling case differences later
+ messageQop = messageQop.toLowerCase(Locale.ENGLISH);
+ Set<String> qopValues = new HashSet<>();
+ String qop;
+ if (messageQop.indexOf(',') > -1) {
+ // Multiple values
+ String[] qopValuesArray = messageQop.split(",");
+ for (String q : qopValuesArray) {
+ qopValues.add(JreCompat.getInstance().strip(q));
+ }
+ } else {
+ // Single value
+ qopValues.add(JreCompat.getInstance().strip(messageQop));
+ }
+ if (qopValues.contains("auth-int")) {
+ qop = "auth-int";
+ } else if (qopValues.contains("auth")) {
+ qop = "auth";
+ } else {
+ throw new
AuthenticationException(sm.getString("digestAuthenticator.unknownQop",
messageQop));
+ }
+
String algorithm = parameterMap.get("algorithm") == null ? "MD5" :
parameterMap.get("algorithm");
String opaque = parameterMap.get("opaque");
if (cnonceGenerator == null) {
@@ -91,8 +116,8 @@ public class DigestAuthenticator extends Authenticator {
try {
challenge.append("response=\"");
- challenge.append(calculateRequestDigest(method, requestUri,
userName, userPassword, realm, nonce,
- messageQop, algorithm));
+ challenge.append(calculateRequestDigest(method, requestUri,
userName, userPassword, realm, nonce, qop,
+ algorithm));
challenge.append("\",");
}
@@ -105,9 +130,9 @@ public class DigestAuthenticator extends Authenticator {
challenge.append("opaque=\"").append(opaque).append("\",");
}
- challenge.append("qop=\"").append(messageQop).append("\"");
+ challenge.append("qop=\"").append(qop).append("\"");
challenge.append(",cnonce=\"").append(cNonce).append("\",");
- challenge.append("nc=").append(String.format("%08X",
Integer.valueOf(nonceCount)));
+ challenge.append("nc=").append(String.format("%08x",
Integer.valueOf(nonceCount)));
return challenge.toString();
@@ -117,7 +142,7 @@ public class DigestAuthenticator extends Authenticator {
String realm, String nonce, String qop, String algorithm) throws
NoSuchAlgorithmException {
boolean session = false;
- if (algorithm.endsWith("-sess")) {
+ if (algorithm.toLowerCase(Locale.ENGLISH).endsWith("-sess")) {
algorithm = algorithm.substring(0, algorithm.length() - 5);
session = true;
}
@@ -146,14 +171,12 @@ public class DigestAuthenticator extends Authenticator {
preDigest.append(':');
preDigest.append(nonce);
- if (qop.toLowerCase(Locale.ENGLISH).contains("auth")) {
- preDigest.append(':');
- preDigest.append(String.format("%08X",
Integer.valueOf(nonceCount)));
- preDigest.append(':');
- preDigest.append(String.valueOf(cNonce));
- preDigest.append(':');
- preDigest.append(qop);
- }
+ preDigest.append(':');
+ preDigest.append(String.format("%08x", Integer.valueOf(nonceCount)));
+ preDigest.append(':');
+ preDigest.append(String.valueOf(cNonce));
+ preDigest.append(':');
+ preDigest.append(qop);
preDigest.append(':');
preDigest.append(encode(algorithm, A2));
diff --git a/java/org/apache/tomcat/websocket/LocalStrings.properties
b/java/org/apache/tomcat/websocket/LocalStrings.properties
index 91fea99600..3380d6b074 100644
--- a/java/org/apache/tomcat/websocket/LocalStrings.properties
+++ b/java/org/apache/tomcat/websocket/LocalStrings.properties
@@ -48,6 +48,7 @@ clientEndpointHolder.instanceRegistrationFailed=Failed to
register Endpoint inst
digestAuthenticator.algorithm=Unable to generate request digest [{0}]
digestAuthenticator.noQop=The server challenge did not include a qop value
digestAuthenticator.noRealm=The server challenge did not include a realm value
+digestAuthenticator.unknownQop=The server challenge included an unknown qop
value [{0}]
futureToSendHandler.timeout=Operation timed out after waiting [{0}] [{1}] to
complete
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]