This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/8.5.x by this push:
new a38e8850c6 Support RFC 7616. Add support for multiple algorithms.
a38e8850c6 is described below
commit a38e8850c6b4051e860b1b846aebc0870fc12513
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Mar 3 17:58:05 2023 +0000
Support RFC 7616. Add support for multiple algorithms.
---
java/org/apache/catalina/Realm.java | 30 +++
.../authenticator/DigestAuthenticator.java | 218 ++++++++++++----
.../catalina/authenticator/LocalStrings.properties | 2 +
java/org/apache/catalina/realm/CombinedRealm.java | 4 +-
.../apache/catalina/realm/JAASCallbackHandler.java | 7 +-
.../catalina/realm/JAASMemoryLoginModule.java | 9 +-
java/org/apache/catalina/realm/JAASRealm.java | 6 +-
java/org/apache/catalina/realm/JNDIRealm.java | 4 +-
.../apache/catalina/realm/LocalStrings.properties | 1 +
java/org/apache/catalina/realm/LockOutRealm.java | 4 +-
java/org/apache/catalina/realm/RealmBase.java | 48 +++-
.../tomcat/websocket/DigestAuthenticator.java | 23 +-
.../TestDigestAuthenticatorAlgorithms.java | 284 +++++++++++++++++++++
test/org/apache/catalina/realm/TestJNDIRealm.java | 6 +-
webapps/docs/changelog.xml | 6 +
webapps/docs/config/valve.xml | 7 +
16 files changed, 581 insertions(+), 78 deletions(-)
diff --git a/java/org/apache/catalina/Realm.java
b/java/org/apache/catalina/Realm.java
index 48985283b7..6b24a57140 100644
--- a/java/org/apache/catalina/Realm.java
+++ b/java/org/apache/catalina/Realm.java
@@ -99,13 +99,43 @@ 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.
+ *
+ * @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.
+ */
+ Principal authenticate(String username, String digest,
+ String nonce, String nc, String cnonce,
+ String qop, String realm,
+ String digestA2, String algorithm);
+
+
/**
* 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 74ffdbee67..47b8835a5a 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 javax.servlet.http.HttpServletRequest;
@@ -33,12 +39,15 @@ 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.buf.StringUtils.Function;
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 +64,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 +138,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 +207,54 @@ public class DigestAuthenticator extends AuthenticatorBase
{
}
+ public String getAlgorithms() {
+ StringBuilder result = new StringBuilder();
+ StringUtils.join(algorithms, ',', new Function<AuthDigest>() {
+ @Override
+ public String apply(AuthDigest t) { return t.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 +288,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 +352,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 +373,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 +387,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 +399,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 +480,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 +510,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 +555,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 +626,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 +635,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 +683,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 +697,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 +740,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 352a06dac1..d5bdcca187 100644
--- a/java/org/apache/catalina/realm/CombinedRealm.java
+++ b/java/org/apache/catalina/realm/CombinedRealm.java
@@ -97,7 +97,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) {
@@ -105,7 +105,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 fcacb64537..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 (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 4e088951c6..a10956018e 100644
--- a/java/org/apache/catalina/realm/JAASMemoryLoginModule.java
+++ b/java/org/apache/catalina/realm/JAASMemoryLoginModule.java
@@ -247,7 +247,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;
@@ -258,6 +259,7 @@ public class JAASMemoryLoginModule extends MemoryRealm
implements LoginModule {
String qop = null;
String realmName = null;
String digestA2 = null;
+ String algorithm = null;
String authMethod = null;
try {
@@ -270,7 +272,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(e.toString());
}
@@ -280,7 +283,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 6314bb0c21..63aea5a289 100644
--- a/java/org/apache/catalina/realm/JAASRealm.java
+++ b/java/org/apache/catalina/realm/JAASRealm.java
@@ -326,9 +326,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));
}
@@ -488,7 +488,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 40dde8bc0a..1f9690ab78 100644
--- a/java/org/apache/catalina/realm/JNDIRealm.java
+++ b/java/org/apache/catalina/realm/JNDIRealm.java
@@ -1341,7 +1341,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 {
@@ -1350,7 +1350,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 2b4eb86aed..b13595272a 100644
--- a/java/org/apache/catalina/realm/LocalStrings.properties
+++ b/java/org/apache/catalina/realm/LocalStrings.properties
@@ -95,6 +95,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 c19b83c0c2..ea26e00be5 100644
--- a/java/org/apache/catalina/realm/LockOutRealm.java
+++ b/java/org/apache/catalina/realm/LockOutRealm.java
@@ -109,10 +109,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 0f2ac54275..40e45f6909 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 org.apache
}
+ @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 org.apache
throw new IllegalArgumentException(uee.getMessage());
}
- 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 +
@@ -1012,10 +1020,17 @@ public abstract class RealmBase extends
LifecycleMBeanBase implements org.apache
// ------------------------------------------------------ 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;
}
@@ -1024,13 +1039,30 @@ public abstract class RealmBase extends
LifecycleMBeanBase implements org.apache
/**
* 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);
}
@@ -1045,7 +1077,7 @@ public abstract class RealmBase extends
LifecycleMBeanBase implements org.apache
throw new IllegalArgumentException(uee.getMessage());
}
- return
HexUtils.toHexString(ConcurrentMessageDigest.digestMD5(valueBytes));
+ return HexUtils.toHexString(ConcurrentMessageDigest.digest(algorithm,
valueBytes));
}
diff --git a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
index ac9f2f7040..54d8aa579d 100644
--- a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
+++ b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
@@ -99,13 +99,19 @@ public class DigestAuthenticator extends Authenticator {
private String calculateRequestDigest(String requestUri, String userName,
String password, String realm,
String nonce, String qop, String algorithm) throws
NoSuchAlgorithmException {
+ boolean session = false;
+ if (algorithm.endsWith("-sess")) {
+ algorithm = algorithm.substring(0, algorithm.length() - 5);
+ session = true;
+ }
+
StringBuilder preDigest = new StringBuilder();
String A1;
- if (algorithm.equalsIgnoreCase("MD5")) {
- A1 = userName + ":" + realm + ":" + password;
+ if (session) {
+ A1 = encode(algorithm, userName + ":" + realm + ":" + password) +
":" + nonce + ":" + cNonce;
} else {
- A1 = encodeMD5(userName + ":" + realm + ":" + password) + ":" +
nonce + ":" + cNonce;
+ A1 = userName + ":" + realm + ":" + password;
}
/*
@@ -114,7 +120,7 @@ public class DigestAuthenticator extends Authenticator {
*/
String A2 = "GET:" + requestUri;
- preDigest.append(encodeMD5(A1));
+ preDigest.append(encode(algorithm, A1));
preDigest.append(':');
preDigest.append(nonce);
@@ -128,15 +134,14 @@ public class DigestAuthenticator extends Authenticator {
}
preDigest.append(':');
- preDigest.append(encodeMD5(A2));
-
- return encodeMD5(preDigest.toString());
+ preDigest.append(encode(algorithm, A2));
+ return encode(algorithm, preDigest.toString());
}
- private String encodeMD5(String value) throws NoSuchAlgorithmException {
+ private String encode(String algorithm, String value) throws
NoSuchAlgorithmException {
byte[] bytesOfMessage = value.getBytes(StandardCharsets.ISO_8859_1);
- MessageDigest md = MessageDigest.getInstance("MD5");
+ MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] thedigest = md.digest(bytesOfMessage);
return HexUtils.toHexString(thedigest);
diff --git
a/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
new file mode 100644
index 0000000000..66e51187b5
--- /dev/null
+++
b/test/org/apache/catalina/authenticator/TestDigestAuthenticatorAlgorithms.java
@@ -0,0 +1,284 @@
+/*
+ * 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.buf.StringUtils.Function;
+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, ',', new
Function<AuthDigest>() {
+ @Override
+ public String apply(AuthDigest t) { return
t.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 ff72bd2ff5..8c4e0bd1dc 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 185e180c4e..4cca125306 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -136,6 +136,12 @@
Reduce the default value of <code>maxParameterCount</code> from 10,000
to 1,000. (markt)
</update>
+ <add>
+ Update Digest authentication support to align with RFC 7616. This adds
a
+ new configuration attribute, <code>algorithms</code>, to the
+ <code>DigestAuthenticator</code> with a default of
+ <code>SHA-256,MD5</code>. (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Coyote">
diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml
index 6fdc190634..e09b167f0d 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -1556,6 +1556,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: [email protected]
For additional commands, e-mail: [email protected]