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 9126de9274 Switch strong ETag generation from SHA-1 to SHA-256 9126de9274 is described below commit 9126de92742cb5eeaef5fa6b902eaec4291cd7de Author: Mark Thomas <[email protected]> AuthorDate: Tue Sep 2 17:00:43 2025 +0100 Switch strong ETag generation from SHA-1 to SHA-256 --- java/org/apache/catalina/webresources/AbstractResource.java | 4 ++-- java/org/apache/catalina/webresources/CachedResource.java | 2 +- .../apache/tomcat/util/security/ConcurrentMessageDigest.java | 10 ++++++++++ .../catalina/servlets/TestDefaultServletIfMatchRequests.java | 4 ++-- .../catalina/servlets/TestDefaultServletRangeRequests.java | 4 ++-- test/org/apache/catalina/servlets/TestWebdavServlet.java | 8 ++++++-- webapps/docs/changelog.xml | 6 ++++++ 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/java/org/apache/catalina/webresources/AbstractResource.java b/java/org/apache/catalina/webresources/AbstractResource.java index 83109274ad..bfc543a52e 100644 --- a/java/org/apache/catalina/webresources/AbstractResource.java +++ b/java/org/apache/catalina/webresources/AbstractResource.java @@ -90,7 +90,7 @@ public abstract class AbstractResource implements WebResource { if (contentLength <= 16 * 1024) { byte[] buf = getContent(); if (buf != null) { - buf = ConcurrentMessageDigest.digest("SHA-1", buf); + buf = ConcurrentMessageDigest.digestSHA256(buf); strongETag = "\"" + HexUtils.toHexString(buf) + "\""; } else { strongETag = getETag(); @@ -98,7 +98,7 @@ public abstract class AbstractResource implements WebResource { } else { byte[] buf = new byte[4096]; try (InputStream is = getInputStream()) { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); + MessageDigest digest = MessageDigest.getInstance("SHA-256"); while (true) { int n = is.read(buf); if (n <= 0) { diff --git a/java/org/apache/catalina/webresources/CachedResource.java b/java/org/apache/catalina/webresources/CachedResource.java index 7dc704db49..6bdd117002 100644 --- a/java/org/apache/catalina/webresources/CachedResource.java +++ b/java/org/apache/catalina/webresources/CachedResource.java @@ -296,7 +296,7 @@ public class CachedResource implements WebResource { if (cachedStrongETag == null) { byte[] buf = getContent(); if (buf != null) { - buf = ConcurrentMessageDigest.digest("SHA-1", buf); + buf = ConcurrentMessageDigest.digestSHA256(buf); cachedStrongETag = "\"" + HexUtils.toHexString(buf) + "\""; } else { cachedStrongETag = webResource.getStrongETag(); diff --git a/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java b/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java index 9404f55044..b4d9cf3f67 100644 --- a/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java +++ b/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java @@ -38,6 +38,7 @@ public class ConcurrentMessageDigest { private static final String MD5 = "MD5"; private static final String SHA1 = "SHA-1"; + private static final String SHA256 = "SHA-256"; private static final Map<String,Queue<MessageDigest>> queues = new ConcurrentHashMap<>(); @@ -58,6 +59,11 @@ public class ConcurrentMessageDigest { } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException(sm.getString("concurrentMessageDigest.noDigest"), e); } + try { + init(SHA256); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(sm.getString("concurrentMessageDigest.noDigest"), e); + } } public static byte[] digestMD5(byte[]... input) { @@ -68,6 +74,10 @@ public class ConcurrentMessageDigest { return digest(SHA1, input); } + public static byte[] digestSHA256(byte[]... input) { + return digest(SHA256, input); + } + public static byte[] digest(String algorithm, byte[]... input) { return digest(algorithm, 1, input); } diff --git a/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java b/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java index 1df310e6b0..bcfa359388 100644 --- a/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java +++ b/test/org/apache/catalina/servlets/TestDefaultServletIfMatchRequests.java @@ -19,7 +19,6 @@ package org.apache.catalina.servlets; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -40,6 +39,7 @@ import org.apache.catalina.startup.TomcatBaseTest; import org.apache.catalina.util.IOTools; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.HexUtils; +import org.apache.tomcat.util.security.ConcurrentMessageDigest; @RunWith(Parameterized.class) public class TestDefaultServletIfMatchRequests extends TomcatBaseTest { @@ -62,7 +62,7 @@ public class TestDefaultServletIfMatchRequests extends TomcatBaseTest { try (FileInputStream is = new FileInputStream(index)) { ByteArrayOutputStream os = new ByteArrayOutputStream(); IOTools.flow(is, os); - resourceETagStrong = "\"" + HexUtils.toHexString(MessageDigest.getInstance("SHA-1").digest(os.toByteArray())) + "\""; + resourceETagStrong = "\"" + HexUtils.toHexString(ConcurrentMessageDigest.digestSHA256(os.toByteArray())) + "\""; } catch (Exception e) { } resourceETagWeak = "W/" + "\"" + index.length() + "-" + index.lastModified() + "\""; diff --git a/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java b/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java index e59534b5f9..60b1aec9bf 100644 --- a/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java +++ b/test/org/apache/catalina/servlets/TestDefaultServletRangeRequests.java @@ -19,7 +19,6 @@ package org.apache.catalina.servlets; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -40,6 +39,7 @@ import org.apache.catalina.util.IOTools; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.HexUtils; import org.apache.tomcat.util.http.FastHttpDateFormat; +import org.apache.tomcat.util.security.ConcurrentMessageDigest; @RunWith(Parameterized.class) public class TestDefaultServletRangeRequests extends TomcatBaseTest { @@ -58,7 +58,7 @@ public class TestDefaultServletRangeRequests extends TomcatBaseTest { try (FileInputStream is = new FileInputStream(index)) { ByteArrayOutputStream os = new ByteArrayOutputStream(); IOTools.flow(is, os); - strongETag = "\"" + HexUtils.toHexString(MessageDigest.getInstance("SHA-1").digest(os.toByteArray())) + "\""; + strongETag = "\"" + HexUtils.toHexString(ConcurrentMessageDigest.digestSHA256(os.toByteArray())) + "\""; } catch (Exception e) { } diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java b/test/org/apache/catalina/servlets/TestWebdavServlet.java index 2bd5b2ca6f..96016f345e 100644 --- a/test/org/apache/catalina/servlets/TestWebdavServlet.java +++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java @@ -852,8 +852,12 @@ public class TestWebdavServlet extends TomcatBaseTest { Assert.assertFalse(client.getResponseBody().contains("/myfolder/file4.txt")); Assert.assertTrue(client.getResponseBody().contains("/file7.txt")); Assert.assertTrue(client.getResponseBody().contains("Second-")); - Assert.assertTrue(client.getResponseBody().contains("d1dc021f456864e84f9a37b7a6f51c51301128a0")); - Assert.assertTrue(client.getResponseBody().contains("f3390fe2e5546dac3d1968970df1a222a3a39c00")); + // SHA-256 hash for "FOOBAR...FOOBAR" (repeats 3000 times) + Assert.assertTrue(client.getResponseBody().contains( + "bb94e8d310800b24310036b168aa5a946e27f9572b3d99f956f3a3ed2e7d3045")); + // SHA-256 hash for "FOOBAR" + Assert.assertTrue(client.getResponseBody().contains( + "24c422e681f1c1bd08286c7aaf5d23a5f088dcdb0b219806b3a9e579244f00c5")); String timeoutValue = client.getResponseBody().substring(client.getResponseBody().indexOf("Second-")); timeoutValue = timeoutValue.substring("Second-".length(), timeoutValue.indexOf('<')); Assert.assertTrue(Integer.valueOf(timeoutValue).intValue() <= 20); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 1f4908207c..1832d06049 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -172,6 +172,12 @@ performance. (markt) </update> <!-- Entries for backport and removal before 12.0.0-M1 below this line --> + <update> + Change the digest used to calculate strong ETags (if enabled) for the + default Servlet from SHA-1 to SHA-256 to align with the recommendation + in RFC 9110 that hash functions used to generate strong ETags should be + collision resistant. (markt) + </update> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]