Author: gawor Date: Mon Nov 12 16:22:02 2012 New Revision: 1408342 URL: http://svn.apache.org/viewvc?rev=1408342&view=rev Log: GERONIMO-6404: Applied patch for CVE-2012-3439 Added: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java (with props) Modified: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/authenticator/DigestAuthenticator.java geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/MD5Encoder.java Modified: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/authenticator/DigestAuthenticator.java URL: http://svn.apache.org/viewvc/geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/authenticator/DigestAuthenticator.java?rev=1408342&r1=1408341&r2=1408342&view=diff ============================================================================== --- geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/authenticator/DigestAuthenticator.java (original) +++ geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/authenticator/DigestAuthenticator.java Mon Nov 12 16:22:02 2012 @@ -20,7 +20,6 @@ package org.apache.catalina.authenticato 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.deploy.LoginC 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 class DigestAuthenticator extends public DigestAuthenticator() { super(); + setCache(false); try { if (md5Helper == null) md5Helper = MessageDigest.getInstance("MD5"); @@ -100,16 +101,16 @@ public class DigestAuthenticator extends /** - * List of client nonce values currently being tracked + * List of server nonce values currently being tracked */ - protected Map cnonces; + protected Map 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 class DigestAuthenticator extends } - 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 @@ public class DigestAuthenticator extends // 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 (principal != null) { - String username = parseUsername(authorization); - register(request, response, principal, - HttpServletRequest.DIGEST_AUTH, - username, null); - return (true); + 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); + } } } @@ -285,11 +288,9 @@ public class DigestAuthenticator extends 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 @@ public class DigestAuthenticator extends 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 @@ public class DigestAuthenticator extends setOpaque(sessionIdGenerator.generateSessionId()); } - cnonces = new LinkedHashMap() { + nonces = new LinkedHashMap() { private static final long serialVersionUID = 1L; private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000; @@ -469,7 +477,7 @@ public class DigestAuthenticator extends Map.Entry 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 @@ public class DigestAuthenticator extends private static class DigestInfo { - private String opaque; - private long nonceValidity; - private String key; - private Map cnonces; + private final String opaque; + private final long nonceValidity; + private final String key; + private final Map nonces; private boolean validateUri = true; private String userName = null; @@ -502,21 +510,22 @@ public class DigestAuthenticator extends 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 cnonces, boolean validateUri) { + Map 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 @@ public class DigestAuthenticator extends 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 @@ public class DigestAuthenticator extends 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; @@ -594,7 +606,7 @@ public class DigestAuthenticator extends } // Validate the opaque string - if (!this.opaque.equals(opaque)) { + if (!opaque.equals(opaqueReceived)) { return false; } @@ -613,14 +625,16 @@ public class DigestAuthenticator extends 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)) { @@ -633,7 +647,7 @@ public class DigestAuthenticator extends } // 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; @@ -652,21 +666,18 @@ public class DigestAuthenticator extends 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; } @@ -682,7 +693,7 @@ public class DigestAuthenticator extends 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); @@ -693,19 +704,31 @@ public class DigestAuthenticator extends } private static class NonceInfo { - private volatile long count; private volatile long timestamp; - - public void setCount(long l) { - count = l; + 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 long getCount() { - return count; - } - - 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() { Added: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java URL: http://svn.apache.org/viewvc/geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java?rev=1408342&view=auto ============================================================================== --- geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java (added) +++ geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java Mon Nov 12 16:22:02 2012 @@ -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> queues = + new HashMap>(); + + + private ConcurrentMessageDigest() { + // Hide default constructor for this utility class + } + + + public static byte[] digest(String algorithm, byte[] input) { + + Queue 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 must 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 queue = + new ConcurrentLinkedQueue(); + queue.add(md); + queues.put(algorithm, queue); + } + } + } +} Propchange: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java ------------------------------------------------------------------------------ svn:keywords = Date Revision Propchange: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/ConcurrentMessageDigest.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/MD5Encoder.java URL: http://svn.apache.org/viewvc/geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/MD5Encoder.java?rev=1408342&r1=1408341&r2=1408342&view=diff ============================================================================== --- geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/MD5Encoder.java (original) +++ geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina/util/MD5Encoder.java Mon Nov 12 16:22:02 2012 @@ -50,7 +50,7 @@ public final class MD5Encoder { * @param binaryData Array containing the digest * @return Encoded MD5, or null if encoding failed */ - public String encode( byte[] binaryData ) { + public static String encode( byte[] binaryData ) { if (binaryData.length != 16) return null;