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

markt 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 010aab9b70 Support RFC 7616. Add support for multiple algorithms.
010aab9b70 is described below

commit 010aab9b705147f61485b6327ff1e884ddd3c0c2
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Mar 3 17:58:05 2023 +0000

    Support RFC 7616. Add support for multiple algorithms.
---
 java/org/apache/catalina/Realm.java                |  40 +++
 .../authenticator/DigestAuthenticator.java         | 213 ++++++++++++----
 .../catalina/authenticator/LocalStrings.properties |   2 +
 java/org/apache/catalina/realm/CombinedRealm.java  |   4 +-
 .../apache/catalina/realm/JAASCallbackHandler.java |   7 +-
 .../catalina/realm/JAASMemoryLoginModule.java      |   9 +-
 java/org/apache/catalina/realm/JAASRealm.java      |   6 +-
 java/org/apache/catalina/realm/JNDIRealm.java      |   4 +-
 .../apache/catalina/realm/LocalStrings.properties  |   1 +
 java/org/apache/catalina/realm/LockOutRealm.java   |   4 +-
 java/org/apache/catalina/realm/RealmBase.java      |  48 +++-
 .../tomcat/websocket/DigestAuthenticator.java      |  23 +-
 .../TestDigestAuthenticatorAlgorithms.java         | 279 +++++++++++++++++++++
 test/org/apache/catalina/realm/TestJNDIRealm.java  |   6 +-
 webapps/docs/changelog.xml                         |   6 +
 webapps/docs/config/valve.xml                      |   7 +
 16 files changed, 581 insertions(+), 78 deletions(-)

diff --git a/java/org/apache/catalina/Realm.java 
b/java/org/apache/catalina/Realm.java
index cb27fdd487..e81802965e 100644
--- a/java/org/apache/catalina/Realm.java
+++ b/java/org/apache/catalina/Realm.java
@@ -101,13 +101,53 @@ public interface Realm extends Contained {
      * @param digestA2 Second digest calculated as digest(Method + ":" + uri)
      *
      * @return the associated principal, or {@code null} if there is none.
+     *
+     * @deprecated Unused. Use {@link #authenticate(String, String, String,
+     * String, String, String, String, String, String)}. Will be removed in
+     * Tomcat 11.
      */
+    @Deprecated
     Principal authenticate(String username, String digest,
                                   String nonce, String nc, String cnonce,
                                   String qop, String realm,
                                   String digestA2);
 
 
+    /**
+     * Try to authenticate with the specified username, which
+     * matches the digest calculated using the given parameters using the
+     * method described in RFC 7616.
+     * <p>
+     * The default implementation calls {@link #authenticate(String, String,
+     * String, String, String, String, String, String)} for backwards
+     * compatibility which effectively forces the use of MD5 regardless of the
+     * algorithm specified in the call to this method.
+     * <p>
+     * Implementations are expected to override the default implementation and
+     * take account of the algorithm parameter.
+     *
+     * @param username Username of the Principal to look up
+     * @param digest Digest which has been submitted by the client
+     * @param nonce Unique (or supposedly unique) token which has been used
+     * for this request
+     * @param nc the nonce counter
+     * @param cnonce the client chosen nonce
+     * @param qop the "quality of protection" ({@code nc} and {@code cnonce}
+     *        will only be used, if {@code qop} is not {@code null}).
+     * @param realm Realm name
+     * @param digestA2 Second digest calculated as digest(Method + ":" + uri)
+     * @param algorithm The message digest algorithm to use
+     *
+     * @return the associated principal, or {@code null} if there is none.
+     */
+    default Principal authenticate(String username, String digest,
+                                  String nonce, String nc, String cnonce,
+                                  String qop, String realm,
+                                  String digestA2, String algorithm) {
+        return authenticate(username, digest, nonce, nc, cnonce, qop, realm, 
digestA2);
+    }
+
+
     /**
      * Try to authenticate using a {@link GSSContext}.
      *
diff --git a/java/org/apache/catalina/authenticator/DigestAuthenticator.java 
b/java/org/apache/catalina/authenticator/DigestAuthenticator.java
index 0d5e681a3f..f80f2181e9 100644
--- a/java/org/apache/catalina/authenticator/DigestAuthenticator.java
+++ b/java/org/apache/catalina/authenticator/DigestAuthenticator.java
@@ -19,8 +19,14 @@ package org.apache.catalina.authenticator;
 import java.io.IOException;
 import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import jakarta.servlet.http.HttpServletRequest;
@@ -33,12 +39,14 @@ import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.http.parser.Authorization;
 import org.apache.tomcat.util.security.ConcurrentMessageDigest;
 
 
 /**
- * An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST 
Authentication (see RFC 2069).
+ * An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST 
Authentication, as outlined in RFC 7616: "HTTP
+ * Digest Authentication"
  *
  * @author Craig R. McClanahan
  * @author Remy Maucherat
@@ -55,6 +63,20 @@ public class DigestAuthenticator extends AuthenticatorBase {
      */
     protected static final String QOP = "auth";
 
+    private static final AuthDigest FALLBACK_DIGEST = AuthDigest.MD5;
+
+    private static final String NONCE_DIGEST = "SHA-256";
+
+    // List permitted algorithms and maps them to Java standard names
+    private static final Map<String, AuthDigest> PERMITTED_ALGORITHMS = new 
HashMap<>();
+    static {
+        // Allows the digester to be configured with either the Standard Java 
name or the name used the RFC.
+        for (AuthDigest authDigest : AuthDigest.values()) {
+            PERMITTED_ALGORITHMS.put(authDigest.getJavaName(), authDigest);
+            PERMITTED_ALGORITHMS.put(authDigest.getRfcName(), authDigest);
+        }
+    }
+
 
     // ----------------------------------------------------------- Constructors
 
@@ -115,6 +137,13 @@ public class DigestAuthenticator extends AuthenticatorBase 
{
      */
     protected boolean validateUri = true;
 
+
+    /**
+     * Algorithms to use for WWW-Authenticate challenges.
+     */
+    private List<AuthDigest> algorithms = Arrays.asList(AuthDigest.SHA_256, 
AuthDigest.MD5);
+
+
     // ------------------------------------------------------------- Properties
 
     public int getNonceCountWindowSize() {
@@ -177,6 +206,50 @@ public class DigestAuthenticator extends AuthenticatorBase 
{
     }
 
 
+    public String getAlgorithms() {
+        StringBuilder result = new StringBuilder();
+        StringUtils.join(algorithms, ',', (x) -> x.getRfcName(), result);
+        return result.toString();
+    }
+
+
+    public void setAlgorithms(String algorithmsString) {
+        String[] algorithmsArray = algorithmsString.split(",");
+        List<AuthDigest> algorithms = new ArrayList<>();
+
+        // Ignore the new setting if any of the algorithms are invalid
+        for (String algorithm : algorithmsArray) {
+            AuthDigest authDigest = PERMITTED_ALGORITHMS.get(algorithm);
+            if (authDigest == null) {
+                log.warn(sm.getString("digestAuthenticator.invalidAlgorithm", 
algorithmsString, algorithm));
+                return;
+            }
+            algorithms.add(authDigest);
+        }
+
+        initAlgorithms(algorithms);
+        this.algorithms = algorithms;
+    }
+
+
+    /*
+     *  Initialise algorithms, removing ones that the JRE does not support
+     */
+    private void initAlgorithms(List<AuthDigest> algorithms) {
+        Iterator<AuthDigest> algorithmIterator = algorithms.iterator();
+        while (algorithmIterator.hasNext()) {
+            AuthDigest algorithm = algorithmIterator.next();
+            try {
+                ConcurrentMessageDigest.init(algorithm.getJavaName());
+            } catch (NoSuchAlgorithmException e) {
+                // In theory, a JRE can choose not to implement SHA-512/256
+                
log.warn(sm.getString("digestAuthenticator.unsupportedAlgorithm", 
algorithm.getJavaName()), e);
+                algorithmIterator.remove();
+            }
+        }
+    }
+
+
     // --------------------------------------------------------- Public Methods
 
     /**
@@ -210,7 +283,7 @@ public class DigestAuthenticator extends AuthenticatorBase {
         DigestInfo digestInfo = new DigestInfo(getOpaque(), 
getNonceValidity(), getKey(), nonces, isValidateUri());
         if (authorization != null) {
             if (digestInfo.parse(request, authorization)) {
-                if (digestInfo.validate(request)) {
+                if (digestInfo.validate(request, algorithms)) {
                     principal = digestInfo.authenticate(context.getRealm());
                 }
 
@@ -274,8 +347,8 @@ public class DigestAuthenticator extends AuthenticatorBase {
     }
 
     /**
-     * Generate a unique token. The token is generated according to the 
following pattern. NOnceToken = Base64 ( MD5 (
-     * client-IP ":" time-stamp ":" private-key ) ).
+     * Generate a unique token. The token is generated according to the 
following pattern. NOnceToken = Base64 (
+     * NONCE_DIGEST ( client-IP ":" time-stamp ":" private-key ) ).
      *
      * @param request HTTP Servlet request
      *
@@ -295,7 +368,8 @@ public class DigestAuthenticator extends AuthenticatorBase {
 
         String ipTimeKey = request.getRemoteAddr() + ":" + currentTime + ":" + 
getKey();
 
-        byte[] buffer = 
ConcurrentMessageDigest.digestMD5(ipTimeKey.getBytes(StandardCharsets.ISO_8859_1));
+        // Note: The digest used to generate the nonce is independent of the 
the digest used for authentication.
+        byte[] buffer = ConcurrentMessageDigest.digest(NONCE_DIGEST, 
ipTimeKey.getBytes(StandardCharsets.ISO_8859_1));
         String nonce = currentTime + ":" + HexUtils.toHexString(buffer);
 
         NonceInfo info = new NonceInfo(currentTime, getNonceCountWindowSize());
@@ -308,26 +382,7 @@ public class DigestAuthenticator extends AuthenticatorBase 
{
 
 
     /**
-     * Generates the WWW-Authenticate header.
-     * <p>
-     * The header MUST follow this template :
-     *
-     * <pre>
-     *      WWW-Authenticate    = "WWW-Authenticate" ":" "Digest"
-     *                            digest-challenge
-     *
-     *      digest-challenge    = 1#( realm | [ domain ] | nonce |
-     *                  [ digest-opaque ] |[ stale ] | [ algorithm ] )
-     *
-     *      realm               = "realm" "=" realm-value
-     *      realm-value         = quoted-string
-     *      domain              = "domain" "=" &lt;"&gt; 1#URI &lt;"&gt;
-     *      nonce               = "nonce" "=" nonce-value
-     *      nonce-value         = quoted-string
-     *      opaque              = "opaque" "=" quoted-string
-     *      stale               = "stale" "=" ( "true" | "false" )
-     *      algorithm           = "algorithm" "=" ( "MD5" | token )
-     * </pre>
+     * Generates the WWW-Authenticate header(s) as per RFC 7616.
      *
      * @param request      HTTP Servlet request
      * @param response     HTTP Servlet response
@@ -339,17 +394,35 @@ public class DigestAuthenticator extends 
AuthenticatorBase {
 
         String realmName = getRealmName(context);
 
-        String authenticateHeader;
-        if (isNonceStale) {
-            authenticateHeader = "Digest realm=\"" + realmName + "\", " + 
"qop=\"" + QOP + "\", nonce=\"" + nonce +
-                    "\", " + "opaque=\"" + getOpaque() + "\", stale=true";
-        } else {
-            authenticateHeader = "Digest realm=\"" + realmName + "\", " + 
"qop=\"" + QOP + "\", nonce=\"" + nonce +
-                    "\", " + "opaque=\"" + getOpaque() + "\"";
-        }
-
-        response.setHeader(AUTH_HEADER_NAME, authenticateHeader);
+        boolean first = true;
+        for (AuthDigest algorithm : algorithms) {
+            StringBuilder authenticateHeader = new StringBuilder(200);
+            authenticateHeader.append("Digest realm=\"");
+            authenticateHeader.append(realmName);
+            authenticateHeader.append("\", qop=\"");
+            authenticateHeader.append(QOP);
+            authenticateHeader.append("\", nonce=\"");
+            authenticateHeader.append(nonce);
+            authenticateHeader.append("\", opaque=\"");
+            authenticateHeader.append(getOpaque());
+            authenticateHeader.append("\"");
+            if (isNonceStale) {
+                authenticateHeader.append(", stale=true");
+            }
+            authenticateHeader.append(", algorithm=");
+            authenticateHeader.append(algorithm.getRfcName());
 
+            if (first) {
+                response.setHeader(AUTH_HEADER_NAME, 
authenticateHeader.toString());
+                first = false;
+            } else {
+                response.addHeader(AUTH_HEADER_NAME, 
authenticateHeader.toString());
+            }
+            /*
+             * Note: userhash is not supported by this implementation so don't 
include it. The clients will use the
+             * default of false.
+             */
+        }
     }
 
 
@@ -402,8 +475,16 @@ public class DigestAuthenticator extends AuthenticatorBase 
{
                 return false;
             }
         };
+
+        initAlgorithms(algorithms);
+        try {
+            ConcurrentMessageDigest.init(NONCE_DIGEST);
+        } catch (NoSuchAlgorithmException e) {
+            // Not possible. NONCE_DIGEST uses an algorithm that JREs must 
support.
+        }
     }
 
+
     public static class DigestInfo {
 
         private final String opaque;
@@ -424,6 +505,7 @@ public class DigestAuthenticator extends AuthenticatorBase {
         private String opaqueReceived = null;
 
         private boolean nonceStale = false;
+        private AuthDigest algorithm = null;
 
 
         public DigestInfo(String opaque, long nonceValidity, String key, 
Map<String, NonceInfo> nonces,
@@ -468,11 +550,21 @@ public class DigestAuthenticator extends 
AuthenticatorBase {
             uri = directives.get("uri");
             response = directives.get("response");
             opaqueReceived = directives.get("opaque");
+            algorithm = PERMITTED_ALGORITHMS.get(directives.get("algorithm"));
+            if (algorithm == null) {
+                algorithm = FALLBACK_DIGEST;
+            }
 
             return true;
         }
 
+        @Deprecated
         public boolean validate(Request request) {
+            List<AuthDigest> fallbackList = Arrays.asList(FALLBACK_DIGEST);
+            return validate(request, fallbackList);
+        }
+
+        public boolean validate(Request request, List<AuthDigest> algorithms) {
             if ((userName == null) || (realmName == null) || (nonce == null) 
|| (uri == null) || (response == null)) {
                 return false;
             }
@@ -529,7 +621,7 @@ public class DigestAuthenticator extends AuthenticatorBase {
             } catch (NumberFormatException nfe) {
                 return false;
             }
-            String md5clientIpTimeKey = nonce.substring(i + 1);
+            String digestclientIpTimeKey = nonce.substring(i + 1);
             long currentTime = System.currentTimeMillis();
             if ((currentTime - nonceTime) > nonceValidity) {
                 nonceStale = true;
@@ -538,9 +630,11 @@ public class DigestAuthenticator extends AuthenticatorBase 
{
                 }
             }
             String serverIpTimeKey = request.getRemoteAddr() + ":" + nonceTime 
+ ":" + key;
-            byte[] buffer = 
ConcurrentMessageDigest.digestMD5(serverIpTimeKey.getBytes(StandardCharsets.ISO_8859_1));
-            String md5ServerIpTimeKey = HexUtils.toHexString(buffer);
-            if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
+            // Note: The digest used to generate the nonce is independent of 
the the digest used for authentication/
+            byte[] buffer = ConcurrentMessageDigest.digest(NONCE_DIGEST,
+                    serverIpTimeKey.getBytes(StandardCharsets.ISO_8859_1));
+            String digestServerIpTimeKey = HexUtils.toHexString(buffer);
+            if (!digestServerIpTimeKey.equals(digestclientIpTimeKey)) {
                 return false;
             }
 
@@ -584,6 +678,12 @@ public class DigestAuthenticator extends AuthenticatorBase 
{
                     }
                 }
             }
+
+            // Validate algorithm is one of the algorithms configured for the 
authenticator
+            if (!algorithms.contains(algorithm)) {
+                return false;
+            }
+
             return true;
         }
 
@@ -592,14 +692,14 @@ public class DigestAuthenticator extends 
AuthenticatorBase {
         }
 
         public Principal authenticate(Realm realm) {
-            // Second MD5 digest used to calculate the digest :
-            // MD5(Method + ":" + uri)
             String a2 = method + ":" + uri;
 
-            byte[] buffer = 
ConcurrentMessageDigest.digestMD5(a2.getBytes(StandardCharsets.ISO_8859_1));
+            byte[] buffer =
+                    ConcurrentMessageDigest.digest(algorithm.getJavaName(), 
a2.getBytes(StandardCharsets.ISO_8859_1));
             String digestA2 = HexUtils.toHexString(buffer);
 
-            return realm.authenticate(userName, response, nonce, nc, cnonce, 
qop, realmName, digestA2);
+            return realm.authenticate(
+                    userName, response, nonce, nc, cnonce, qop, realmName, 
digestA2, algorithm.getJavaName());
         }
 
     }
@@ -635,4 +735,31 @@ public class DigestAuthenticator extends AuthenticatorBase 
{
             return timestamp;
         }
     }
+
+
+    /**
+     * This enum exists because RFC 7616 and Java use different names for some 
digests.
+     */
+    public enum AuthDigest {
+
+        MD5("MD5", "MD5"),
+        SHA_256("SHA-256", "SHA-256"),
+        SHA_512_256("SHA-512/256", "SHA-512-256");
+
+        private final String javaName;
+        private final String rfcName;
+
+        AuthDigest(String javaName, String rfcName) {
+            this.javaName = javaName;
+            this.rfcName = rfcName;
+        }
+
+        public String getJavaName() {
+            return javaName;
+        }
+
+        public String getRfcName() {
+            return rfcName;
+        }
+    }
 }
diff --git a/java/org/apache/catalina/authenticator/LocalStrings.properties 
b/java/org/apache/catalina/authenticator/LocalStrings.properties
index c835736860..4be5aff94f 100644
--- a/java/org/apache/catalina/authenticator/LocalStrings.properties
+++ b/java/org/apache/catalina/authenticator/LocalStrings.properties
@@ -35,6 +35,8 @@ authenticator.unauthorized=Cannot authenticate with the 
provided credentials
 basicAuthenticator.invalidCharset=The only permitted values are null, the 
empty string or UTF-8
 
 digestAuthenticator.cacheRemove=A valid entry has been removed from client 
nonce cache to make room for new entries. A replay attack is now possible. To 
prevent the possibility of replay attacks, reduce nonceValidity or increase 
nonceCacheSize. Further warnings of this type will be suppressed for 5 minutes.
+digestAuthenticator.invalidAlgorithm=Unable to configure DIGEST authentication 
to use the algorithm [{0}] as it is not permitted by RFC 7616.
+digestAuthenticator.unsupportedAlgorithm=Unable to configure DIGEST 
authentication to use the algorithms [{0}] as [{1}] is not supported by the JRE.
 
 formAuthenticator.changeSessionIdLogin=Session ID changed before forwarding to 
login page during FORM authentication from [{0}] to [{1}]
 formAuthenticator.forwardErrorFail=Unexpected error forwarding to error page
diff --git a/java/org/apache/catalina/realm/CombinedRealm.java 
b/java/org/apache/catalina/realm/CombinedRealm.java
index 08804a29ac..69f50ab8ba 100644
--- a/java/org/apache/catalina/realm/CombinedRealm.java
+++ b/java/org/apache/catalina/realm/CombinedRealm.java
@@ -89,7 +89,7 @@ public class CombinedRealm extends RealmBase {
 
     @Override
     public Principal authenticate(String username, String clientDigest, String 
nonce, String nc, String cnonce,
-            String qop, String realmName, String digestA2) {
+            String qop, String realmName, String digestA2, String algorithm) {
         Principal authenticatedUser = null;
 
         for (Realm realm : realms) {
@@ -97,7 +97,7 @@ public class CombinedRealm extends RealmBase {
                 log.debug(sm.getString("combinedRealm.authStart", username, 
realm.getClass().getName()));
             }
 
-            authenticatedUser = realm.authenticate(username, clientDigest, 
nonce, nc, cnonce, qop, realmName, digestA2);
+            authenticatedUser = realm.authenticate(username, clientDigest, 
nonce, nc, cnonce, qop, realmName, digestA2, algorithm);
 
             if (authenticatedUser == null) {
                 if (log.isDebugEnabled()) {
diff --git a/java/org/apache/catalina/realm/JAASCallbackHandler.java 
b/java/org/apache/catalina/realm/JAASCallbackHandler.java
index a708befc0a..5d540b01d0 100644
--- a/java/org/apache/catalina/realm/JAASCallbackHandler.java
+++ b/java/org/apache/catalina/realm/JAASCallbackHandler.java
@@ -61,7 +61,7 @@ public class JAASCallbackHandler implements CallbackHandler {
      */
     public JAASCallbackHandler(JAASRealm realm, String username, String 
password) {
 
-        this(realm, username, password, null, null, null, null, null, null, 
null);
+        this(realm, username, password, null, null, null, null, null, null, 
null, null);
     }
 
 
@@ -77,14 +77,15 @@ public class JAASCallbackHandler implements CallbackHandler 
{
      * @param qop        Quality of protection applied to the message
      * @param realmName  Realm name
      * @param digestA2   Second digest calculated as digest(Method + ":" + uri)
+     * @param algorithm  The digest algorithm to use
      * @param authMethod The authentication method in use
      */
     public JAASCallbackHandler(JAASRealm realm, String username, String 
password, String nonce, String nc,
-            String cnonce, String qop, String realmName, String digestA2, 
String authMethod) {
+            String cnonce, String qop, String realmName, String digestA2, 
String algorithm, String authMethod) {
         this.realm = realm;
         this.username = username;
 
-        if (password != null && realm.hasMessageDigest()) {
+        if (password != null && realm.hasMessageDigest(algorithm)) {
             this.password = realm.getCredentialHandler().mutate(password);
         } else {
             this.password = password;
diff --git a/java/org/apache/catalina/realm/JAASMemoryLoginModule.java 
b/java/org/apache/catalina/realm/JAASMemoryLoginModule.java
index b5b99467d1..75f3677d99 100644
--- a/java/org/apache/catalina/realm/JAASMemoryLoginModule.java
+++ b/java/org/apache/catalina/realm/JAASMemoryLoginModule.java
@@ -248,7 +248,8 @@ public class JAASMemoryLoginModule extends MemoryRealm 
implements LoginModule {
         callbacks[5] = new TextInputCallback("qop");
         callbacks[6] = new TextInputCallback("realmName");
         callbacks[7] = new TextInputCallback("digestA2");
-        callbacks[8] = new TextInputCallback("authMethod");
+        callbacks[8] = new TextInputCallback("algorithm");
+        callbacks[9] = new TextInputCallback("authMethod");
 
         // Interact with the user to retrieve the username and password
         String username = null;
@@ -259,6 +260,7 @@ public class JAASMemoryLoginModule extends MemoryRealm 
implements LoginModule {
         String qop = null;
         String realmName = null;
         String digestA2 = null;
+        String algorithm = null;
         String authMethod = null;
 
         try {
@@ -271,7 +273,8 @@ public class JAASMemoryLoginModule extends MemoryRealm 
implements LoginModule {
             qop = ((TextInputCallback) callbacks[5]).getText();
             realmName = ((TextInputCallback) callbacks[6]).getText();
             digestA2 = ((TextInputCallback) callbacks[7]).getText();
-            authMethod = ((TextInputCallback) callbacks[8]).getText();
+            algorithm = ((TextInputCallback) callbacks[8]).getText();
+            authMethod = ((TextInputCallback) callbacks[9]).getText();
         } catch (IOException | UnsupportedCallbackException e) {
             throw new 
LoginException(sm.getString("jaasMemoryLoginModule.callbackHandlerError", 
e.toString()));
         }
@@ -281,7 +284,7 @@ public class JAASMemoryLoginModule extends MemoryRealm 
implements LoginModule {
             // BASIC or FORM
             principal = super.authenticate(username, password);
         } else if (authMethod.equals(HttpServletRequest.DIGEST_AUTH)) {
-            principal = super.authenticate(username, password, nonce, nc, 
cnonce, qop, realmName, digestA2);
+            principal = super.authenticate(username, password, nonce, nc, 
cnonce, qop, realmName, digestA2, algorithm);
         } else if (authMethod.equals(HttpServletRequest.CLIENT_CERT_AUTH)) {
             principal = super.getPrincipal(username);
         } else {
diff --git a/java/org/apache/catalina/realm/JAASRealm.java 
b/java/org/apache/catalina/realm/JAASRealm.java
index 4b14bc7fd9..9b80c44cbf 100644
--- a/java/org/apache/catalina/realm/JAASRealm.java
+++ b/java/org/apache/catalina/realm/JAASRealm.java
@@ -316,9 +316,9 @@ public class JAASRealm extends RealmBase {
 
     @Override
     public Principal authenticate(String username, String clientDigest, String 
nonce, String nc, String cnonce,
-            String qop, String realmName, String digestA2) {
+            String qop, String realmName, String digestA2, String algorithm) {
         return authenticate(username, new JAASCallbackHandler(this, username, 
clientDigest, nonce, nc, cnonce, qop,
-                realmName, digestA2, HttpServletRequest.DIGEST_AUTH));
+                realmName, digestA2, algorithm, 
HttpServletRequest.DIGEST_AUTH));
     }
 
 
@@ -471,7 +471,7 @@ public class JAASRealm extends RealmBase {
     protected Principal getPrincipal(String username) {
 
         return authenticate(username, new JAASCallbackHandler(this, username, 
null, null, null, null, null, null, null,
-                HttpServletRequest.CLIENT_CERT_AUTH));
+                null, HttpServletRequest.CLIENT_CERT_AUTH));
 
     }
 
diff --git a/java/org/apache/catalina/realm/JNDIRealm.java 
b/java/org/apache/catalina/realm/JNDIRealm.java
index 49c7488871..947eb1517f 100644
--- a/java/org/apache/catalina/realm/JNDIRealm.java
+++ b/java/org/apache/catalina/realm/JNDIRealm.java
@@ -1332,7 +1332,7 @@ public class JNDIRealm extends RealmBase {
      */
     @Override
     public Principal authenticate(String username, String clientDigest, String 
nonce, String nc, String cnonce,
-            String qop, String realm, String digestA2) {
+            String qop, String realm, String digestA2, String algorithm) {
         ClassLoader ocl = null;
         Thread currentThread = null;
         try {
@@ -1341,7 +1341,7 @@ public class JNDIRealm extends RealmBase {
                 ocl = currentThread.getContextClassLoader();
                 
currentThread.setContextClassLoader(this.getClass().getClassLoader());
             }
-            return super.authenticate(username, clientDigest, nonce, nc, 
cnonce, qop, realm, digestA2);
+            return super.authenticate(username, clientDigest, nonce, nc, 
cnonce, qop, realm, digestA2, algorithm);
         } finally {
             if (currentThread != null) {
                 currentThread.setContextClassLoader(ocl);
diff --git a/java/org/apache/catalina/realm/LocalStrings.properties 
b/java/org/apache/catalina/realm/LocalStrings.properties
index cd09ed4b50..bd993e00dd 100644
--- a/java/org/apache/catalina/realm/LocalStrings.properties
+++ b/java/org/apache/catalina/realm/LocalStrings.properties
@@ -99,6 +99,7 @@ realmBase.createUsernameRetriever.newInstance=Cannot create 
object of type [{0}]
 realmBase.credentialNotDelegated=Credential for user [{0}] has not been 
delegated though storing was requested
 realmBase.delegatedCredentialFail=Unable to obtain delegated credential for 
user [{0}]
 realmBase.digest=Error digesting user credentials
+realmBase.digestMismatch=Unable to authenticate user as DIGEST authentication 
used [{0}] but password was stored in Realm using [{1}]
 realmBase.forbidden=Access to the requested resource has been denied
 realmBase.gotX509Username=Got user name from X509 certificate: [{0}]
 realmBase.gssContextNotEstablished=Authenticator implementation error: the 
passed security context is not fully established
diff --git a/java/org/apache/catalina/realm/LockOutRealm.java 
b/java/org/apache/catalina/realm/LockOutRealm.java
index e229712f0f..45ce3736f0 100644
--- a/java/org/apache/catalina/realm/LockOutRealm.java
+++ b/java/org/apache/catalina/realm/LockOutRealm.java
@@ -104,10 +104,10 @@ public class LockOutRealm extends CombinedRealm {
 
     @Override
     public Principal authenticate(String username, String clientDigest, String 
nonce, String nc, String cnonce,
-            String qop, String realmName, String digestA2) {
+            String qop, String realmName, String digestA2, String algorithm) {
 
         Principal authenticatedUser = super.authenticate(username, 
clientDigest, nonce, nc, cnonce, qop, realmName,
-                digestA2);
+                digestA2, algorithm);
         return filterLockedAccounts(username, authenticatedUser);
     }
 
diff --git a/java/org/apache/catalina/realm/RealmBase.java 
b/java/org/apache/catalina/realm/RealmBase.java
index 26b706326a..c7bc3f9097 100644
--- a/java/org/apache/catalina/realm/RealmBase.java
+++ b/java/org/apache/catalina/realm/RealmBase.java
@@ -328,12 +328,20 @@ public abstract class RealmBase extends 
LifecycleMBeanBase implements Realm {
     }
 
 
+    @Deprecated
     @Override
     public Principal authenticate(String username, String clientDigest, String 
nonce, String nc, String cnonce,
             String qop, String realm, String digestA2) {
+        return authenticate(username, clientDigest, nonce, nc, cnonce, qop, 
realm, digestA2, "MD5");
+    }
+
+
+    @Override
+    public Principal authenticate(String username, String clientDigest, String 
nonce, String nc, String cnonce,
+            String qop, String realm, String digestA2, String algorithm) {
 
         // In digest auth, digests are always lower case
-        String digestA1 = getDigest(username, realm);
+        String digestA1 = getDigest(username, realm, algorithm);
         if (digestA1 == null) {
             return null;
         }
@@ -353,7 +361,7 @@ public abstract class RealmBase extends LifecycleMBeanBase 
implements Realm {
                     uee);
         }
 
-        String serverDigest = 
HexUtils.toHexString(ConcurrentMessageDigest.digestMD5(valueBytes));
+        String serverDigest = 
HexUtils.toHexString(ConcurrentMessageDigest.digest(algorithm, valueBytes));
 
         if (log.isDebugEnabled()) {
             log.debug("Digest : " + clientDigest + " Username:" + username + " 
ClientDigest:" + clientDigest +
@@ -1004,10 +1012,17 @@ public abstract class RealmBase extends 
LifecycleMBeanBase implements Realm {
 
     // ------------------------------------------------------ Protected Methods
 
-    protected boolean hasMessageDigest() {
+    protected boolean hasMessageDigest(String algorithm) {
         CredentialHandler ch = credentialHandler;
         if (ch instanceof MessageDigestCredentialHandler) {
-            return ((MessageDigestCredentialHandler) ch).getAlgorithm() != 
null;
+            String realmAlgorithm = ((MessageDigestCredentialHandler) 
ch).getAlgorithm();
+            if (realmAlgorithm != null) {
+                if (realmAlgorithm.equals(algorithm)) {
+                    return true;
+                } else {
+                    log.debug(sm.getString("relamBase.digestMismatch", 
algorithm, realmAlgorithm));
+                }
+            }
         }
         return false;
     }
@@ -1016,13 +1031,30 @@ public abstract class RealmBase extends 
LifecycleMBeanBase implements Realm {
     /**
      * Return the digest associated with given principal's user name.
      *
-     * @param username  the user name
-     * @param realmName the realm name
+     * @param username  The user name
+     * @param realmName The realm name
      *
      * @return the digest for the specified user
+     *
+     * @deprecated Unused. Use {@link #getDigest(String, String, String)}. 
Will be removed in Tomcat 11.
      */
+    @Deprecated
     protected String getDigest(String username, String realmName) {
-        if (hasMessageDigest()) {
+        return getDigest(username, realmName, "MD5");
+    }
+
+
+    /**
+     * Return the digest associated with given principal's user name.
+     *
+     * @param username  The user name
+     * @param realmName The realm name
+     * @param algorithm The name of the message digest algorithm to use
+     *
+     * @return the digest for the specified user
+     */
+    protected String getDigest(String username, String realmName, String 
algorithm) {
+        if (hasMessageDigest(algorithm)) {
             // Use pre-generated digest
             return getPassword(username);
         }
@@ -1037,7 +1069,7 @@ public abstract class RealmBase extends 
LifecycleMBeanBase implements Realm {
                     uee);
         }
 
-        return 
HexUtils.toHexString(ConcurrentMessageDigest.digestMD5(valueBytes));
+        return HexUtils.toHexString(ConcurrentMessageDigest.digest(algorithm, 
valueBytes));
     }
 
 
diff --git a/java/org/apache/tomcat/websocket/DigestAuthenticator.java 
b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
index ac9f2f7040..54d8aa579d 100644
--- a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
+++ b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
@@ -99,13 +99,19 @@ public class DigestAuthenticator extends Authenticator {
     private String calculateRequestDigest(String requestUri, String userName, 
String password, String realm,
             String nonce, String qop, String algorithm) throws 
NoSuchAlgorithmException {
 
+        boolean session = false;
+        if (algorithm.endsWith("-sess")) {
+            algorithm = algorithm.substring(0, algorithm.length() - 5);
+            session = true;
+        }
+
         StringBuilder preDigest = new StringBuilder();
         String A1;
 
-        if (algorithm.equalsIgnoreCase("MD5")) {
-            A1 = userName + ":" + realm + ":" + password;
+        if (session) {
+            A1 = encode(algorithm, userName + ":" + realm + ":" + password) + 
":" + nonce + ":" + cNonce;
         } else {
-            A1 = encodeMD5(userName + ":" + realm + ":" + password) + ":" + 
nonce + ":" + cNonce;
+            A1 = userName + ":" + realm + ":" + password;
         }
 
         /*
@@ -114,7 +120,7 @@ public class DigestAuthenticator extends Authenticator {
          */
         String A2 = "GET:" + requestUri;
 
-        preDigest.append(encodeMD5(A1));
+        preDigest.append(encode(algorithm, A1));
         preDigest.append(':');
         preDigest.append(nonce);
 
@@ -128,15 +134,14 @@ public class DigestAuthenticator extends Authenticator {
         }
 
         preDigest.append(':');
-        preDigest.append(encodeMD5(A2));
-
-        return encodeMD5(preDigest.toString());
+        preDigest.append(encode(algorithm, A2));
 
+        return encode(algorithm, preDigest.toString());
     }
 
-    private String encodeMD5(String value) throws NoSuchAlgorithmException {
+    private String encode(String algorithm, String value) throws 
NoSuchAlgorithmException {
         byte[] bytesOfMessage = value.getBytes(StandardCharsets.ISO_8859_1);
-        MessageDigest md = MessageDigest.getInstance("MD5");
+        MessageDigest md = MessageDigest.getInstance(algorithm);
         byte[] thedigest = md.digest(bytesOfMessage);
 
         return HexUtils.toHexString(thedigest);
diff --git 
a/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java 
b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
new file mode 100644
index 0000000000..5f7defbe18
--- /dev/null
+++ 
b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
@@ -0,0 +1,279 @@
+/*
+ * 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.catalina.authenticator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.authenticator.DigestAuthenticator.AuthDigest;
+import org.apache.catalina.realm.LockOutRealm;
+import org.apache.catalina.realm.MessageDigestCredentialHandler;
+import org.apache.catalina.startup.TesterMapRealm;
+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.buf.StringUtils;
+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 TestDigestAuthenticatorAlgorithms extends TomcatBaseTest {
+
+    private static final String USER = "user";
+    private static final String PASSWORD = "password";
+
+    private static final String URI = "/protected";
+
+    private static String REALM_NAME = "TestRealm";
+    private static String CNONCE = "cnonce";
+
+    private static final List<List<AuthDigest>> ALGORITHM_PERMUTATIONS = new 
ArrayList<>();
+    static {
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.MD5));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.MD5, 
AuthDigest.SHA_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.MD5, 
AuthDigest.SHA_256, AuthDigest.SHA_512_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.MD5, 
AuthDigest.SHA_512_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.MD5, 
AuthDigest.SHA_512_256, AuthDigest.SHA_256));
+
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_256, 
AuthDigest.MD5));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_256, 
AuthDigest.MD5, AuthDigest.SHA_512_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_256, 
AuthDigest.SHA_512_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_256, 
AuthDigest.SHA_512_256, AuthDigest.MD5));
+
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_512_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_512_256, 
AuthDigest.MD5));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_512_256, 
AuthDigest.MD5, AuthDigest.SHA_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_512_256, 
AuthDigest.SHA_256));
+        ALGORITHM_PERMUTATIONS.add(Arrays.asList(AuthDigest.SHA_512_256, 
AuthDigest.SHA_256, AuthDigest.MD5));
+    }
+
+    @Parameterized.Parameters(name = "{index}: Algorithms[{0}], 
Algorithm[{1}], PwdDigest[{2}], AuthExpected[{3}]")
+    public static Collection<Object[]> parameters() {
+        List<Object[]> parameterSets = new ArrayList<>();
+
+        for (List<AuthDigest> algorithmPermutation : ALGORITHM_PERMUTATIONS) {
+            StringBuilder algorithms = new StringBuilder();
+            StringUtils.join(algorithmPermutation, ',', (x) -> x.getRfcName(), 
algorithms);
+            for (AuthDigest algorithm : AuthDigest.values()) {
+                boolean authExpected = 
algorithmPermutation.contains(algorithm);
+                for (Boolean digestPassword : booleans) {
+                    String user;
+                    if (digestPassword.booleanValue()) {
+                        user = USER + "-" + algorithm;
+                    } else {
+                        user = USER;
+                    }
+                    parameterSets.add(new Object[] { algorithms.toString(), 
algorithm, digestPassword, user, Boolean.valueOf(authExpected) });
+                }
+            }
+        }
+
+        return parameterSets;
+    }
+
+    @Parameter(0)
+    public String serverAlgorithms;
+
+    @Parameter(1)
+    public AuthDigest clientAlgorithm;
+
+    @Parameter(2)
+    public boolean digestPassword;
+
+    @Parameter(3)
+    public String user;
+
+    @Parameter(4)
+    public boolean authExpected;
+
+
+    @Test
+    public void testDigestAuthentication() throws Exception {
+        // Make sure client algorithm is available for digests
+        ConcurrentMessageDigest.init(clientAlgorithm.getJavaName());
+
+        // Configure a context with digest authentication and a single 
protected resource
+        Tomcat tomcat = getTomcatInstance();
+
+        // No file system docBase required
+        Context ctxt = tomcat.addContext("", null);
+
+        // Add protected servlet
+        Tomcat.addServlet(ctxt, "TesterServlet", new TesterServlet());
+        ctxt.addServletMappingDecoded(URI, "TesterServlet");
+        SecurityCollection collection = new SecurityCollection();
+        collection.addPatternDecoded(URI);
+        SecurityConstraint sc = new SecurityConstraint();
+        sc.addAuthRole("role");
+        sc.addCollection(collection);
+        ctxt.addConstraint(sc);
+
+        // Configure the Realm
+        TesterMapRealm realm = new TesterMapRealm();
+        String password;
+        if (digestPassword) {
+            MessageDigestCredentialHandler mdch = new 
MessageDigestCredentialHandler();
+            mdch.setAlgorithm(clientAlgorithm.getJavaName());
+            mdch.setSaltLength(0);
+            realm.setCredentialHandler(mdch);
+            password = mdch.mutate(user + ":" + REALM_NAME + ":" + PASSWORD);
+        } else {
+            password = PASSWORD;
+        }
+        realm.addUser(user, password);
+        realm.addUserRole(user, "role");
+
+        LockOutRealm lockOutRealm = new LockOutRealm();
+        lockOutRealm.addRealm(realm);
+        ctxt.setRealm(lockOutRealm);
+
+        // Configure the authenticator
+        LoginConfig lc = new LoginConfig();
+        lc.setAuthMethod("DIGEST");
+        lc.setRealmName(REALM_NAME);
+        ctxt.setLoginConfig(lc);
+        DigestAuthenticator digestAuthenticator = new DigestAuthenticator();
+        digestAuthenticator.setAlgorithms(serverAlgorithms);
+        ctxt.getPipeline().addValve(digestAuthenticator);
+
+        tomcat.start();
+
+        // The first request will always fail - but we need the challenge
+        Map<String, List<String>> respHeaders = new HashMap<>();
+        ByteChunk bc = new ByteChunk();
+        int rc = getUrl("http://localhost:"; + getPort() + URI, bc, 
respHeaders);
+        Assert.assertEquals(401, rc);
+        Assert.assertTrue(bc.getLength() > 0);
+        bc.recycle();
+
+        // Second request will succeed depending on client and server 
algorithms
+        List<String> auth = new ArrayList<>();
+        auth.add(buildDigestResponse(user, PASSWORD, URI, REALM_NAME, 
clientAlgorithm,
+                respHeaders.get(AuthenticatorBase.AUTH_HEADER_NAME), 
"00000001", CNONCE, DigestAuthenticator.QOP));
+        Map<String, List<String>> reqHeaders = new HashMap<>();
+        reqHeaders.put("authorization", auth);
+        rc = getUrl("http://localhost:"; + getPort() + URI, bc, reqHeaders, 
null);
+
+        if (authExpected) {
+            Assert.assertEquals(200, rc);
+            Assert.assertEquals("OK", bc.toString());
+        } else {
+            Assert.assertEquals(401, rc);
+        }
+    }
+
+
+    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;
+        } 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("");
+        }
+        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/test/org/apache/catalina/realm/TestJNDIRealm.java 
b/test/org/apache/catalina/realm/TestJNDIRealm.java
index 472863300e..f1625ceae2 100644
--- a/test/org/apache/catalina/realm/TestJNDIRealm.java
+++ b/test/org/apache/catalina/realm/TestJNDIRealm.java
@@ -74,7 +74,7 @@ public class TestJNDIRealm {
         String expectedResponse =
                 HexUtils.toHexString(md5Helper.digest((digestA1() + ":" + 
NONCE + ":" + DIGEST_A2).getBytes()));
         Principal principal =
-                realm.authenticate(USER, expectedResponse, NONCE, null, null, 
null, REALM, DIGEST_A2);
+                realm.authenticate(USER, expectedResponse, NONCE, null, null, 
null, REALM, DIGEST_A2, ALGORITHM);
 
         // THEN
         Assert.assertNull(principal);
@@ -90,7 +90,7 @@ public class TestJNDIRealm {
         String expectedResponse =
                 HexUtils.toHexString(md5Helper.digest((digestA1() + ":" + 
NONCE + ":" + DIGEST_A2).getBytes()));
         Principal principal =
-                realm.authenticate(USER, expectedResponse, NONCE, null, null, 
null, REALM, DIGEST_A2);
+                realm.authenticate(USER, expectedResponse, NONCE, null, null, 
null, REALM, DIGEST_A2, ALGORITHM);
 
         // THEN
         assertThat(principal, instanceOf(GenericPrincipal.class));
@@ -108,7 +108,7 @@ public class TestJNDIRealm {
         String expectedResponse =
                 HexUtils.toHexString(md5Helper.digest((digestA1() + ":" + 
NONCE + ":" + DIGEST_A2).getBytes()));
         Principal principal =
-                realm.authenticate(USER, expectedResponse, NONCE, null, null, 
null, REALM, DIGEST_A2);
+                realm.authenticate(USER, expectedResponse, NONCE, null, null, 
null, REALM, DIGEST_A2, ALGORITHM);
 
         // THEN
         assertThat(principal, instanceOf(GenericPrincipal.class));
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b17f9aed13..4777094808 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -136,6 +136,12 @@
         Reduce the default value of <code>maxParameterCount</code> from 10,000
         to 1,000. (markt)
       </update>
+      <add>
+        Update Digest authentication support to align with RFC 7616. This adds 
a
+        new configuration attribute, <code>algorithms</code>, to the
+        <code>DigestAuthenticator</code> with a default of
+        <code>SHA-256,MD5</code>. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">
diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml
index dbec996c45..df79480994 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -1555,6 +1555,13 @@
 
     <attributes>
 
+      <attribute name="algoirthms" required="false">
+        <p>A comma-separated list of digest algorithms to be used for the
+        authentication process. Algorithms may be specified using the Java
+        Standard names or the names used by RFC 7616. If not specified, the
+        default value of <code>SHA-256,MD5</code> will be used.</p>
+      </attribute>
+
       <attribute name="allowCorsPreflight" required="false">
         <p>Are requests that appear to be CORS preflight requests allowed to
         bypass the authenticator as required by the CORS specification. The


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to