> 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" "=" <"> 1#URI <">
> - * 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