[SCM] tomcat7: Servlet and JSP engine branch, master, updated. debian/7.0.28-3-1-ge52ed04
tony mancill
tmancill at debian.org
Mon Nov 26 04:38:22 UTC 2012
The following commit has been merged in the master branch:
commit e52ed042eea3face36f6524f2ba4db6ffde291f8
Author: Michael Gilbert <mgilbert at debian.org>
Date: Sun Nov 18 01:40:30 2012 +0000
Imported Debian patch 7.0.28-3+nmu1
diff --git a/debian/changelog b/debian/changelog
index f2f7c6e..56461ca 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+tomcat7 (7.0.28-3+nmu1) unstable; urgency=high
+
+ * Non-maintainer upload.
+ * Fix cve-2012-3439: multiple replay attack issues in digest authentication.
+ (closes: #692440)
+
+ -- Michael Gilbert <mgilbert at debian.org> Sun, 18 Nov 2012 01:40:30 +0000
+
tomcat7 (7.0.28-3) unstable; urgency=low
[ Miguel Landaeta ]
diff --git a/debian/patches/cve-2012-3439-tests.patch b/debian/patches/cve-2012-3439-tests.patch
new file mode 100644
index 0000000..0713675
--- /dev/null
+++ b/debian/patches/cve-2012-3439-tests.patch
@@ -0,0 +1,123 @@
+Index: tomcat7-7.0.28/test/org/apache/catalina/authenticator/TesterDigestAuthenticatorPerformance.java
+===================================================================
+--- tomcat7-7.0.28.orig/test/org/apache/catalina/authenticator/TesterDigestAuthenticatorPerformance.java 2011-11-06 16:04:49.000000000 -0500
++++ tomcat7-7.0.28/test/org/apache/catalina/authenticator/TesterDigestAuthenticatorPerformance.java 2012-11-17 20:39:49.077628722 -0500
+@@ -17,8 +17,7 @@
+ package org.apache.catalina.authenticator;
+
+ import java.io.IOException;
+-import java.security.MessageDigest;
+-import java.security.NoSuchAlgorithmException;
++import java.util.concurrent.atomic.AtomicInteger;
+
+ import javax.servlet.http.HttpServletResponse;
+
+@@ -34,6 +33,7 @@
+ import org.apache.catalina.filters.TesterResponse;
+ import org.apache.catalina.startup.TestTomcat.MapRealm;
+ import org.apache.catalina.util.MD5Encoder;
++import org.apache.catalina.util.ConcurrentMessageDigest;
+
+ public class TesterDigestAuthenticatorPerformance {
+
+@@ -47,6 +47,8 @@
+ private static String REALM = "TestRealm";
+ private static String QOP = "auth";
+
++ private static final AtomicInteger nonceCount = new AtomicInteger(0);
++
+ private DigestAuthenticator authenticator = new DigestAuthenticator();
+
+
+@@ -60,9 +62,11 @@
+ TesterRunnable runnables[] = new TesterRunnable[threadCount];
+ Thread threads[] = new Thread[threadCount];
+
++ String nonce = authenticator.generateNonce(new TesterDigestRequest());
++
+ // Create the runnables & threads
+ for (int i = 0; i < threadCount; i++) {
+- runnables[i] = new TesterRunnable(requestCount);
++ runnables[i] = new TesterRunnable(nonce, requestCount);
+ threads[i] = new Thread(runnables[i]);
+ }
+
+@@ -113,15 +117,14 @@
+ // Make the Context and Realm visible to the Authenticator
+ authenticator.setContainer(context);
+
+- // Prevent caching of cnonces so we can the same one for all requests
+- authenticator.setCnonceCacheSize(0);
+ authenticator.start();
+ }
+
+
+ private class TesterRunnable implements Runnable {
+
+- // Number of valid requests required
++
++ private String nonce;
+ private int requestCount;
+
+ private int success = 0;
+@@ -132,12 +135,11 @@
+ private LoginConfig config;
+
+ // All init code should be in here. run() needs to be quick
+- public TesterRunnable(int requestCount) throws Exception {
++ public TesterRunnable(String nonce, int requestCount) throws Exception {
++ this.nonce = nonce;
+ this.requestCount = requestCount;
+
+ request = new TesterDigestRequest();
+- String nonce = authenticator.generateNonce(request);
+- request.setAuthHeader(buildDigestResponse(nonce));
+
+ response = new TesterResponse();
+
+@@ -150,7 +152,8 @@
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < requestCount; i++) {
+ try {
+- if (authenticator.authenticate(request, response, config)) {
++ request.setAuthHeader(buildDigestResponse(nonce));
++ if (authenticator.authenticate(request, response)) {
+ success++;
+ }
+ } catch (IOException ioe) {
+@@ -168,26 +171,27 @@
+ return time;
+ }
+
+- private String buildDigestResponse(String nonce)
+- throws NoSuchAlgorithmException {
++ private String buildDigestResponse(String nonce) {
+
+- String ncString = "00000001";
++ String ncString = String.format("%1$08x",
++ Integer.valueOf(nonceCount.incrementAndGet()));
+ String cnonce = "cnonce";
+
+ String a1 = USER + ":" + REALM + ":" + PWD;
+ String a2 = METHOD + ":" + CONTEXT_PATH + URI;
+
+- MessageDigest digester = MessageDigest.getInstance("MD5");
+ MD5Encoder encoder = new MD5Encoder();
+
+- String md5a1 = encoder.encode(digester.digest(a1.getBytes()));
+- String md5a2 = encoder.encode(digester.digest(a2.getBytes()));
++ String md5a1 = encoder.encode(
++ ConcurrentMessageDigest.digest("MD5", a1.getBytes()));
++ String md5a2 = encoder.encode(
++ ConcurrentMessageDigest.digest("MD5", a2.getBytes()));
+
+ String response = md5a1 + ":" + nonce + ":" + ncString + ":" +
+ cnonce + ":" + QOP + ":" + md5a2;
+
+- String md5response =
+- encoder.encode(digester.digest(response.getBytes()));
++ String md5response = encoder.encode(
++ ConcurrentMessageDigest.digest("MD5", response.getBytes()));
+
+ StringBuilder auth = new StringBuilder();
+ auth.append("Digest username=\"");
diff --git a/debian/patches/cve-2012-3439.patch b/debian/patches/cve-2012-3439.patch
new file mode 100644
index 0000000..a47b1bd
--- /dev/null
+++ b/debian/patches/cve-2012-3439.patch
@@ -0,0 +1,427 @@
+Index: tomcat7-7.0.28/java/org/apache/catalina/authenticator/DigestAuthenticator.java
+===================================================================
+--- tomcat7-7.0.28.orig/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-06-12 09:26:10.000000000 -0400
++++ tomcat7-7.0.28/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-11-17 20:12:37.877615037 -0500
+@@ -20,7 +20,6 @@
+
+
+ import java.io.IOException;
+-import java.nio.charset.Charset;
+ import java.security.MessageDigest;
+ import java.security.NoSuchAlgorithmException;
+ import java.security.Principal;
+@@ -38,6 +37,7 @@
+ import org.apache.catalina.util.MD5Encoder;
+ import org.apache.juli.logging.Log;
+ import org.apache.juli.logging.LogFactory;
++import org.apache.tomcat.util.buf.B2CConverter;
+
+
+
+@@ -80,6 +80,7 @@
+
+ public DigestAuthenticator() {
+ super();
++ setCache(false);
+ try {
+ if (md5Helper == null)
+ md5Helper = MessageDigest.getInstance("MD5");
+@@ -100,16 +101,16 @@
+
+
+ /**
+- * List of client nonce values currently being tracked
++ * List of server nonce values currently being tracked
+ */
+- protected Map<String,NonceInfo> cnonces;
++ protected Map<String,NonceInfo> nonces;
+
+
+ /**
+- * Maximum number of client nonces to keep in the cache. If not specified,
++ * Maximum number of server nonces to keep in the cache. If not specified,
+ * the default value of 1000 is used.
+ */
+- protected int cnonceCacheSize = 1000;
++ protected int nonceCacheSize = 1000;
+
+
+ /**
+@@ -150,13 +151,13 @@
+ }
+
+
+- public int getCnonceCacheSize() {
+- return cnonceCacheSize;
++ public int getNonceCacheSize() {
++ return nonceCacheSize;
+ }
+
+
+- public void setCnonceCacheSize(int cnonceCacheSize) {
+- this.cnonceCacheSize = cnonceCacheSize;
++ public void setNonceCacheSize(int nonceCacheSize) {
++ this.nonceCacheSize = nonceCacheSize;
+ }
+
+
+@@ -263,18 +264,20 @@
+ // Validate any credentials already included with this request
+ String authorization = request.getHeader("authorization");
+ DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
+- getKey(), cnonces, isValidateUri());
++ getKey(), nonces, isValidateUri());
+ if (authorization != null) {
+- if (digestInfo.validate(request, authorization, config)) {
+- principal = digestInfo.authenticate(context.getRealm());
+- }
++ if (digestInfo.parse(request, authorization)) {
++ if (digestInfo.validate(request, config)) {
++ principal = digestInfo.authenticate(context.getRealm());
++ }
+
+- if (principal != null) {
+- String username = parseUsername(authorization);
+- register(request, response, principal,
+- HttpServletRequest.DIGEST_AUTH,
+- username, null);
+- return (true);
++ if (principal != null && !digestInfo.isNonceStale()) {
++ String username = parseUsername(authorization);
++ register(request, response, principal,
++ HttpServletRequest.DIGEST_AUTH,
++ username, null);
++ return true;
++ }
+ }
+ }
+
+@@ -285,11 +288,9 @@
+ String nonce = generateNonce(request);
+
+ setAuthenticateHeader(request, response, config, nonce,
+- digestInfo.isNonceStale());
++ principal != null && digestInfo.isNonceStale());
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+- // hres.flushBuffer();
+- return (false);
+-
++ return false;
+ }
+
+
+@@ -380,10 +381,17 @@
+ byte[] buffer;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(
+- ipTimeKey.getBytes(Charset.defaultCharset()));
++ ipTimeKey.getBytes(B2CConverter.ISO_8859_1));
++ }
++
++ String nonce = currentTime + ":" + md5Encoder.encode(buffer);
++
++ NonceInfo info = new NonceInfo(currentTime, 100);
++ synchronized (nonces) {
++ nonces.put(nonce, info);
+ }
+
+- return currentTime + ":" + md5Encoder.encode(buffer);
++ return nonce;
+ }
+
+
+@@ -457,7 +465,7 @@
+ setOpaque(sessionIdGenerator.generateSessionId());
+ }
+
+- cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
++ nonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
+ private static final long serialVersionUID = 1L;
+ private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
+@@ -469,7 +477,7 @@
+ Map.Entry<String,NonceInfo> eldest) {
+ // This is called from a sync so keep it simple
+ long currentTime = System.currentTimeMillis();
+- if (size() > getCnonceCacheSize()) {
++ if (size() > getNonceCacheSize()) {
+ if (lastLog < currentTime &&
+ currentTime - eldest.getValue().getTimestamp() <
+ getNonceValidity()) {
+@@ -487,10 +495,10 @@
+
+ private static class DigestInfo {
+
+- private String opaque;
+- private long nonceValidity;
+- private String key;
+- private Map<String,NonceInfo> cnonces;
++ private final String opaque;
++ private final long nonceValidity;
++ private final String key;
++ private final Map<String,NonceInfo> nonces;
+ private boolean validateUri = true;
+
+ private String userName = null;
+@@ -502,21 +510,22 @@
+ private String cnonce = null;
+ private String realmName = null;
+ private String qop = null;
++ private String opaqueReceived = null;
+
+ private boolean nonceStale = false;
+
+
+ public DigestInfo(String opaque, long nonceValidity, String key,
+- Map<String,NonceInfo> cnonces, boolean validateUri) {
++ Map<String,NonceInfo> nonces, boolean validateUri) {
+ this.opaque = opaque;
+ this.nonceValidity = nonceValidity;
+ this.key = key;
+- this.cnonces = cnonces;
++ this.nonces = nonces;
+ this.validateUri = validateUri;
+ }
+
+- public boolean validate(Request request, String authorization,
+- LoginConfig config) {
++
++ public boolean parse(Request request, String authorization) {
+ // Validate the authorization credentials format
+ if (authorization == null) {
+ return false;
+@@ -530,7 +539,6 @@
+ String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
+
+ method = request.getMethod();
+- String opaque = null;
+
+ for (int i = 0; i < tokens.length; i++) {
+ String currentToken = tokens[i];
+@@ -562,9 +570,13 @@
+ if ("response".equals(currentTokenName))
+ response = removeQuotes(currentTokenValue);
+ if ("opaque".equals(currentTokenName))
+- opaque = removeQuotes(currentTokenValue);
++ opaqueReceived = removeQuotes(currentTokenValue);
+ }
+
++ return true;
++ }
++
++ public boolean validate(Request request, LoginConfig config) {
+ if ( (userName == null) || (realmName == null) || (nonce == null)
+ || (uri == null) || (response == null) ) {
+ return false;
+@@ -610,7 +622,7 @@
+ }
+
+ // Validate the opaque string
+- if (!this.opaque.equals(opaque)) {
++ if (!opaque.equals(opaqueReceived)) {
+ return false;
+ }
+
+@@ -629,14 +641,16 @@
+ long currentTime = System.currentTimeMillis();
+ if ((currentTime - nonceTime) > nonceValidity) {
+ nonceStale = true;
+- return false;
++ synchronized (nonces) {
++ nonces.remove(nonce);
++ }
+ }
+ String serverIpTimeKey =
+ request.getRemoteAddr() + ":" + nonceTime + ":" + key;
+ byte[] buffer = null;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(
+- serverIpTimeKey.getBytes(Charset.defaultCharset()));
++ serverIpTimeKey.getBytes(B2CConverter.ISO_8859_1));
+ }
+ String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
+@@ -649,7 +663,7 @@
+ }
+
+ // Validate cnonce and nc
+- // Check if presence of nc and nonce is consistent with presence of qop
++ // Check if presence of nc and Cnonce is consistent with presence of qop
+ if (qop == null) {
+ if (cnonce != null || nc != null) {
+ return false;
+@@ -670,21 +684,18 @@
+ return false;
+ }
+ NonceInfo info;
+- synchronized (cnonces) {
+- info = cnonces.get(cnonce);
++ synchronized (nonces) {
++ info = nonces.get(nonce);
+ }
+ if (info == null) {
+- info = new NonceInfo();
++ // Nonce is valid but not in cache. It must have dropped out
++ // of the cache - force a re-authentication
++ nonceStale = true;
+ } else {
+- if (count <= info.getCount()) {
++ if (!info.nonceCountValid(count)) {
+ return false;
+ }
+ }
+- info.setCount(count);
+- info.setTimestamp(currentTime);
+- synchronized (cnonces) {
+- cnonces.put(cnonce, info);
+- }
+ }
+ return true;
+ }
+@@ -700,7 +711,7 @@
+
+ byte[] buffer;
+ synchronized (md5Helper) {
+- buffer = md5Helper.digest(a2.getBytes(Charset.defaultCharset()));
++ buffer = md5Helper.digest(a2.getBytes(B2CConverter.ISO_8859_1));
+ }
+ String md5a2 = md5Encoder.encode(buffer);
+
+@@ -711,19 +722,31 @@
+ }
+
+ private static class NonceInfo {
+- private volatile long count;
+ private volatile long timestamp;
+-
+- public void setCount(long l) {
+- count = l;
+- }
+-
+- public long getCount() {
+- return count;
++ private volatile boolean seen[];
++ private volatile int offset;
++ private volatile int count = 0;
++
++ public NonceInfo(long currentTime, int seenWindowSize) {
++ this.timestamp = currentTime;
++ seen = new boolean[seenWindowSize];
++ offset = seenWindowSize / 2;
+ }
+
+- public void setTimestamp(long l) {
+- timestamp = l;
++ public synchronized boolean nonceCountValid(long nonceCount) {
++ if ((count - offset) >= nonceCount ||
++ (nonceCount > count - offset + seen.length)) {
++ return false;
++ }
++ int checkIndex = (int) ((nonceCount + offset) % seen.length);
++ if (seen[checkIndex]) {
++ return false;
++ } else {
++ seen[checkIndex] = true;
++ seen[count % seen.length] = false;
++ count++;
++ return true;
++ }
+ }
+
+ public long getTimestamp() {
+Index: tomcat7-7.0.28/java/org/apache/catalina/util/ConcurrentMessageDigest.java
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ tomcat7-7.0.28/java/org/apache/catalina/util/ConcurrentMessageDigest.java 2012-11-17 19:50:35.013603940 -0500
+@@ -0,0 +1,91 @@
++/*
++ * 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.util;
++
++import java.security.MessageDigest;
++import java.security.NoSuchAlgorithmException;
++import java.util.HashMap;
++import java.util.Map;
++import java.util.Queue;
++import java.util.concurrent.ConcurrentLinkedQueue;
++
++/**
++ * A thread safe wrapper around {@link MessageDigest} that does not make use
++ * of ThreadLocal and - broadly - only creates enough MessageDigest objects
++ * to satisfy the concurrency requirements.
++ */
++public class ConcurrentMessageDigest {
++
++ private static final Map<String,Queue<MessageDigest>> queues =
++ new HashMap<String,Queue<MessageDigest>>();
++
++
++ private ConcurrentMessageDigest() {
++ // Hide default constructor for this utility class
++ }
++
++
++ public static byte[] digest(String algorithm, byte[] input) {
++
++ Queue<MessageDigest> queue = queues.get(algorithm);
++ if (queue == null) {
++ throw new IllegalStateException("Must call init() first");
++ }
++
++ MessageDigest md = queue.poll();
++ if (md == null) {
++ try {
++ md = MessageDigest.getInstance(algorithm);
++ } catch (NoSuchAlgorithmException e) {
++ // Ignore. Impossible if init() has been successfully called
++ // first.
++ throw new IllegalStateException("Must call init() first");
++ }
++ }
++
++ byte[] result = md.digest(input);
++
++ queue.add(md);
++
++ return result;
++ }
++
++
++ /**
++ * Ensures that {@link #digest(String, byte[])} and
++ * {@link #digestAsHex(String, byte[])} will support the specified
++ * algorithm. This method <b>must</b> be called and return successfully
++ * before using {@link #digest(String, byte[])} or
++ * {@link #digestAsHex(String, byte[])}.
++ *
++ * @param algorithm The message digest algorithm to be supported
++ *
++ * @throws NoSuchAlgorithmException If the algorithm is not supported by the
++ * JVM
++ */
++ public static void init(String algorithm) throws NoSuchAlgorithmException {
++ synchronized (queues) {
++ if (!queues.containsKey(algorithm)) {
++ MessageDigest md = MessageDigest.getInstance(algorithm);
++ Queue<MessageDigest> queue =
++ new ConcurrentLinkedQueue<MessageDigest>();
++ queue.add(md);
++ queues.put(algorithm, queue);
++ }
++ }
++ }
++}
diff --git a/debian/patches/series b/debian/patches/series
index 95942d9..4f78259 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -9,3 +9,5 @@
0011-fix-classpath-lintian-warnings.patch
0012-java7-compat.patch
0013-dont-look-for-build-properties-in-user-home.patch
+cve-2012-3439.patch
+cve-2012-3439-tests.patch
--
tomcat7: Servlet and JSP engine
More information about the pkg-java-commits
mailing list