You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2022/03/17 13:41:24 UTC
[tomcat] branch main updated: Harden CredentialHandler implementations - constant-time comparisons
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 594ae43 Harden CredentialHandler implementations - constant-time comparisons
594ae43 is described below
commit 594ae43b1ec6d9caf7dbafe64e665711c6be8673
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Mar 17 13:36:20 2022 +0000
Harden CredentialHandler implementations - constant-time comparisons
Based on a patch proposed by schultz
---
.../realm/DigestCredentialHandlerBase.java | 75 +++++++++++++++++++++-
.../realm/MessageDigestCredentialHandler.java | 5 +-
webapps/docs/changelog.xml | 4 ++
3 files changed, 81 insertions(+), 3 deletions(-)
diff --git a/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java b/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java
index b6a33d5..122adef 100644
--- a/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java
+++ b/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java
@@ -16,6 +16,7 @@
*/
package org.apache.catalina.realm;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
@@ -200,7 +201,7 @@ public abstract class DigestCredentialHandlerBase implements CredentialHandler {
return false;
}
- return storedHexEncoded.equalsIgnoreCase(inputHexEncoded);
+ return DigestCredentialHandlerBase.equals(storedHexEncoded, inputHexEncoded, true);
}
@@ -292,4 +293,76 @@ public abstract class DigestCredentialHandlerBase implements CredentialHandler {
* @return the logger for the CredentialHandler instance.
*/
protected abstract Log getLog();
+
+ /**
+ * Implements String equality which always compares all characters in the
+ * string, without stopping early if any characters do not match.
+ *
+ * @implNote
+ * This implementation was adapted from {@link MessageDigest#isEqual}
+ * which we assume is as optimizer-defeating as possible.
+ *
+ * @param s1 The first string to compare.
+ * @param s2 The second string to compare.
+ * @param ignoreCase <code>true</code> if the strings should be compared
+ * without regard to case. Note that "true" here is only guaranteed
+ * to work with plain ASCII characters.
+ *
+ * @return <code>true</code> if the strings are equal to each other,
+ * <code>false</code> otherwise.
+ */
+ public static boolean equals(final String s1, final String s2, final boolean ignoreCase) {
+ if (s1 == s2) {
+ return true;
+ }
+ if (s1 == null || s2 == null) {
+ return false;
+ }
+
+ final int len1 = s1.length();
+ final int len2 = s2.length();
+
+ if (len2 == 0) {
+ return len1 == 0;
+ }
+
+ int result = 0;
+ result |= len1 - len2;
+
+ // time-constant comparison
+ for (int i = 0; i < len1; i++) {
+ // If i >= len2, index2 is 0; otherwise, i.
+ final int index2 = ((i - len2) >>> 31) * i;
+ char c1 = s1.charAt(i);
+ char c2 = s2.charAt(index2);
+ if(ignoreCase) {
+ c1 = Character.toLowerCase(c1);
+ c2 = Character.toLowerCase(c2);
+ }
+ result |= c1 ^ c2;
+ }
+ return result == 0;
+ }
+
+ /**
+ * Implements byte-array equality which always compares all bytes in the
+ * array, without stopping early if any bytes do not match.
+ *
+ * @implNote
+ * Implementation note: this method delegates to {@link MessageDigest#isEqual}
+ * under the assumption that it provides a constant-time comparison of the
+ * bytes in the arrays. Java 7+ has such an implementation, but neither the
+ * Javadoc nor any specification requires it. Therefore, Tomcat should
+ * continue to use <i>this</i> method internally in case the JDK
+ * implementation changes so this method can be re-implemented properly.
+ *
+ * @param b1 The first array to compare.
+ * @param b2 The second array to compare.
+ *
+ * @return <code>true</code> if the arrays are equal to each other,
+ * <code>false</code> otherwise.
+ */
+ public static boolean equals(final byte[] b1, final byte[] b2) {
+ return MessageDigest.isEqual(b1, b2);
+ }
}
diff --git a/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java b/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java
index 616e0e5..d43465c 100644
--- a/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java
+++ b/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java
@@ -100,7 +100,7 @@ public class MessageDigestCredentialHandler extends DigestCredentialHandlerBase
if (getAlgorithm() == null) {
// No digests, compare directly
- return storedCredentials.equals(inputCredentials);
+ return DigestCredentialHandlerBase.equals(inputCredentials, storedCredentials, false);
} else {
// Some directories and databases prefix the password with the hash
// type. The string is in a format compatible with Base64.encode not
@@ -112,7 +112,8 @@ public class MessageDigestCredentialHandler extends DigestCredentialHandlerBase
byte[] userDigest = ConcurrentMessageDigest.digest(
getAlgorithm(), inputCredentials.getBytes(StandardCharsets.ISO_8859_1));
String base64UserDigest = Base64.encodeBase64String(userDigest);
- return base64UserDigest.equals(base64ServerDigest);
+
+ return DigestCredentialHandlerBase.equals(base64UserDigest, base64ServerDigest, false);
} else if (storedCredentials.startsWith("{SSHA}")) {
// "{SSHA}<sha-1 digest:20><salt:n>"
// Need to convert the salt to bytes to apply it to the user's
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 46b2f43..64ff59b 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -112,6 +112,10 @@
renamed for Jakarta EE 10) including the implementation of the new
methods on <code>AuthConfigFactory</code>. (markt)
</scode>
+ <scode>
+ Harden the CredentialHandler implementations by switching to a
+ constant-time implementation for credential comparisons. (schultz/markt)
+ </scode>
</changelog>
</subsection>
<subsection name="Web applications">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org