You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by ga...@apache.org on 2012/11/12 17:22:03 UTC
svn commit: r1408342 - in
/geronimo/external/trunk/tomcat-parent-7.0.27/catalina/src/main/java/org/apache/catalina:
authenticator/DigestAuthenticator.java util/ConcurrentMessageDigest.java
util/MD5Encoder.java
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<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 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<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 @@ public class DigestAuthenticator extends
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 @@ public class DigestAuthenticator extends
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 @@ 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<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 @@ 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<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);
+ }
+ }
+ }
+}
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;