> On Mar 15, 2023, at 01:45, ma...@apache.org wrote:
> 
> This is an automated email from the ASF dual-hosted git repository.
> 
> markt pushed a commit to branch main
> in repository https://gitbox.apache.org/repos/asf/tomcat.git
> 
> 
> The following commit(s) were added to refs/heads/main by this push:
>     new 55ad7dbe19 Support RFC 7616. Add support for multiple algorithms.
> 55ad7dbe19 is described below
> 
> commit 55ad7dbe196ba35dd16952701890d318e7926c02
> 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      |  22 +-
> .../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, 580 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..e75810eba2 100644
> --- a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
> +++ b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
> @@ -99,13 +99,18 @@ public class DigestAuthenticator extends Authenticator {
>     private String calculateRequestDigest(String requestUri, String userName, 
> String password, String realm,
>             String nonce, String qop, String algorithm) throws 
> NoSuchAlgorithmException {
<snip/>

> 
> +        boolean session = false;
> +        if (algorithm.endsWith("-sess")) {
> +            algorithm = algorithm.substring(0, algorithm.length() - 5);
> +        }

I guess here maybe a bug that ‘session’ not assigned correct value, right?

Han

> +        if (session) {
> +            A1 = encode(algorithm, userName + ":" + realm + ":" + password) 
> + ":" + nonce + ":" + cNonce;
>         } else {
> -            A1 = encodeMD5(userName + ":" + realm + ":" + password) + ":" + 
> nonce + ":" + cNonce;
> +            A1 = userName + ":" + realm + ":" + password;
>         }
> 
>         /*
> @@ -114,7 +119,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 +133,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 fdaab5447c..88905a5380 100644
> --- a/webapps/docs/changelog.xml
> +++ b/webapps/docs/changelog.xml
> @@ -137,6 +137,12 @@
>         Implement the new Servlet API methods that provide additional control
>         when sending a redirect to the client. (markt)
>       </add>
> +      <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 e3986215cb..1cf349253f 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
> 


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

Reply via email to