[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