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]

Reply via email to