This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 3d4d3fae07 Fix the handling of invalid users with DIGEST authentication
3d4d3fae07 is described below

commit 3d4d3fae07a6cd9c2eb193c5491001740ec64448
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Apr 23 12:54:42 2026 +0100

    Fix the handling of invalid users with DIGEST authentication
---
 java/org/apache/catalina/realm/RealmBase.java      |  11 ++-
 .../TestDigestAuthenticatorAlgorithms.java         |   6 +-
 .../authenticator/TestDigestAuthenticatorB.java    | 106 +++------------------
 webapps/docs/changelog.xml                         |   3 +
 4 files changed, 29 insertions(+), 97 deletions(-)

diff --git a/java/org/apache/catalina/realm/RealmBase.java 
b/java/org/apache/catalina/realm/RealmBase.java
index 08f6d62e82..b24786024a 100644
--- a/java/org/apache/catalina/realm/RealmBase.java
+++ b/java/org/apache/catalina/realm/RealmBase.java
@@ -1146,12 +1146,19 @@ public abstract class RealmBase extends 
LifecycleMBeanBase implements Realm {
      * @return the digest for the specified user
      */
     protected String getDigest(String username, String realmName, String 
algorithm) {
+        String password = getPassword(username);
+
+        // Short-cut null password case
+        if (password == null) {
+            return null;
+        }
+
         if (hasMessageDigest(algorithm)) {
             // Use pre-generated digest
-            return getPassword(username);
+            return password;
         }
 
-        String digestValue = username + ":" + realmName + ":" + 
getPassword(username);
+        String digestValue = username + ":" + realmName + ":" + password;
 
         byte[] valueBytes;
         try {
diff --git 
a/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java 
b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
index 279c68490e..d5027c4b4c 100644
--- 
a/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
+++ 
b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
@@ -194,21 +194,21 @@ public class TestDigestAuthenticatorAlgorithms extends 
TomcatBaseTest {
     }
 
 
-    protected static String getNonce(String authHeader) {
+    private static String getNonce(String authHeader) {
         int start = authHeader.indexOf("nonce=\"") + 7;
         int end = authHeader.indexOf('\"', start);
         return authHeader.substring(start, end);
     }
 
 
-    protected static String getOpaque(String authHeader) {
+    private static String getOpaque(String authHeader) {
         int start = authHeader.indexOf("opaque=\"") + 8;
         int end = authHeader.indexOf('\"', start);
         return authHeader.substring(start, end);
     }
 
 
-    private static String buildDigestResponse(String user, String pwd, String 
uri, String realm, AuthDigest algorithm,
+    static String buildDigestResponse(String user, String pwd, String uri, 
String realm, AuthDigest algorithm,
             List<String> authHeaders, String nc, String cnonce, String qop) {
 
         // Find auth header with correct algorithm
diff --git 
a/test/org/apache/catalina/authenticator/TestDigestAuthenticatorB.java 
b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorB.java
index 9210b1780b..ae64f34a83 100644
--- a/test/org/apache/catalina/authenticator/TestDigestAuthenticatorB.java
+++ b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorB.java
@@ -35,11 +35,9 @@ import org.apache.catalina.startup.TesterServlet;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.tomcat.util.buf.ByteChunk;
-import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.descriptor.web.LoginConfig;
 import org.apache.tomcat.util.descriptor.web.SecurityCollection;
 import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
-import org.apache.tomcat.util.security.ConcurrentMessageDigest;
 
 @RunWith(Parameterized.class)
 public class TestDigestAuthenticatorB extends TomcatBaseTest {
@@ -55,8 +53,10 @@ public class TestDigestAuthenticatorB extends TomcatBaseTest 
{
     @Parameterized.Parameters(name = "{index}")
     public static Collection<Object[]> parameters() {
         List<Object[]> parameterSets = new ArrayList<>();
-        parameterSets.add(new Object[] { validRole, validUser, validPassword 
});
-        parameterSets.add(new Object[] { "**", validUser, validPassword });
+        parameterSets.add(new Object[] { validRole, validUser, validPassword, 
Boolean.TRUE });
+        parameterSets.add(new Object[] { "**", validUser, validPassword, 
Boolean.TRUE });
+        parameterSets.add(new Object[] { "**", validUser, "null", 
Boolean.FALSE });
+        parameterSets.add(new Object[] { "**", "invalid", "null", 
Boolean.FALSE });
         return parameterSets;
     }
 
@@ -69,6 +69,9 @@ public class TestDigestAuthenticatorB extends TomcatBaseTest {
     @Parameter(2)
     public String clientPassword;
 
+    @Parameter(3)
+    public boolean validCredentials;
+
 
     @Test
     public void testDigestAuthentication() throws Exception {
@@ -115,99 +118,18 @@ public class TestDigestAuthenticatorB extends 
TomcatBaseTest {
 
         // Second request should
         List<String> auth = new ArrayList<>();
-        auth.add(buildDigestResponse(clientUserName, clientPassword, 
targetURI, realmName, AuthDigest.SHA_256,
-                respHeaders.get(AuthenticatorBase.AUTH_HEADER_NAME), 
"00000001", clientNonce, DigestAuthenticator.QOP));
+        
auth.add(TestDigestAuthenticatorAlgorithms.buildDigestResponse(clientUserName, 
clientPassword, targetURI,
+                realmName, AuthDigest.SHA_256, 
respHeaders.get(AuthenticatorBase.AUTH_HEADER_NAME), "00000001",
+                clientNonce, DigestAuthenticator.QOP));
         Map<String,List<String>> reqHeaders = new HashMap<>();
         reqHeaders.put("authorization", auth);
         rc = getUrl("http://localhost:"; + getPort() + targetURI, bc, 
reqHeaders, null);
 
-        Assert.assertEquals(200, rc);
-        Assert.assertEquals("OK", bc.toString());
-    }
-
-
-    protected static String getNonce(String authHeader) {
-        int start = authHeader.indexOf("nonce=\"") + 7;
-        int end = authHeader.indexOf('\"', start);
-        return authHeader.substring(start, end);
-    }
-
-
-    protected static String getOpaque(String authHeader) {
-        int start = authHeader.indexOf("opaque=\"") + 8;
-        int end = authHeader.indexOf('\"', start);
-        return authHeader.substring(start, end);
-    }
-
-
-    private static String buildDigestResponse(String user, String pwd, String 
uri, String realm, AuthDigest algorithm,
-            List<String> authHeaders, String nc, String cnonce, String qop) {
-
-        // Find auth header with correct algorithm
-        String nonce = null;
-        String opaque = null;
-        for (String authHeader : authHeaders) {
-            nonce = getNonce(authHeader);
-            opaque = getOpaque(authHeader);
-            if (authHeader.contains("algorithm=" + algorithm.getRfcName())) {
-                break;
-            }
-        }
-        if (nonce == null || opaque == null) {
-            Assert.fail();
-        }
-
-        String a1 = user + ":" + realm + ":" + pwd;
-        String a2 = "GET:" + uri;
-
-        String digestA1 = digest(algorithm.getJavaName(), a1);
-        String digestA2 = digest(algorithm.getJavaName(), a2);
-
-        String response;
-        if (qop == null) {
-            response = digestA1 + ":" + nonce + ":" + digestA2;
+        if (validCredentials) {
+            Assert.assertEquals(200, rc);
+            Assert.assertEquals("OK", bc.toString());
         } else {
-            response = digestA1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" 
+ qop + ":" + digestA2;
-        }
-
-        String digestResponse = digest(algorithm.getJavaName(), response);
-
-        StringBuilder auth = new StringBuilder();
-        auth.append("Digest username=\"");
-        auth.append(user);
-        auth.append("\", realm=\"");
-        auth.append(realm);
-        auth.append("\", algorithm=");
-        auth.append(algorithm.getRfcName());
-        auth.append(", nonce=\"");
-        auth.append(nonce);
-        auth.append("\", uri=\"");
-        auth.append(uri);
-        auth.append("\", opaque=\"");
-        auth.append(opaque);
-        auth.append("\", response=\"");
-        auth.append(digestResponse);
-        auth.append("\"");
-        if (qop != null) {
-            auth.append(", qop=");
-            auth.append(qop);
-            auth.append("");
+            Assert.assertEquals(401, rc);
         }
-        if (nc != null) {
-            auth.append(", nc=");
-            auth.append(nc);
-        }
-        if (cnonce != null) {
-            auth.append(", cnonce=\"");
-            auth.append(cnonce);
-            auth.append("\"");
-        }
-
-        return auth.toString();
-    }
-
-
-    private static String digest(String algorithm, String input) {
-        return HexUtils.toHexString(ConcurrentMessageDigest.digest(algorithm, 
input.getBytes()));
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index a1490b2a13..1dbc3d673e 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -169,6 +169,9 @@
         <code>false</code>, meaning user names are treated in a case 
insensitive
         manner. (markt)
       </add>
+      <fix>
+        Correct the handling of invalid users with DIGEST authentication. 
(markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to