JBossWeb SVN: r1956 - branches/2.1.x/java/org/apache/jasper/compiler.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2012-02-13 05:20:17 -0500 (Mon, 13 Feb 2012)
New Revision: 1956
Modified:
branches/2.1.x/java/org/apache/jasper/compiler/AttributeParser.java
branches/2.1.x/java/org/apache/jasper/compiler/Parser.java
Log:
Fix regression of JBPAPP-6382.
[00591692] Port of "r1355" for JBPAPP-6382 bug introduced ?
See ASF https://issues.apache.org/bugzilla/show_bug.cgi?id=48627
Modified: branches/2.1.x/java/org/apache/jasper/compiler/AttributeParser.java
===================================================================
--- branches/2.1.x/java/org/apache/jasper/compiler/AttributeParser.java 2012-02-10 16:46:18 UTC (rev 1955)
+++ branches/2.1.x/java/org/apache/jasper/compiler/AttributeParser.java 2012-02-13 10:20:17 UTC (rev 1956)
@@ -43,13 +43,16 @@
* scripting expressions.
* @param isELIgnored Is expression language being ignored on the page
* where the JSP attribute is defined.
+ * @param isDeferredSyntaxAllowedAsLiteral
+ * Are deferred expressions treated as literals?
* @return An unquoted JSP attribute that, if it contains
* expression language can be safely passed to the EL
* processor without fear of ambiguity.
*/
public static String getUnquoted(String input, char quote,
- boolean isELIgnored) {
+ boolean isELIgnored, boolean isDeferredSyntaxAllowedAsLiteral) {
return (new AttributeParser(input, quote, isELIgnored,
+ isDeferredSyntaxAllowedAsLiteral,
STRICT_QUOTE_ESCAPING)).getUnquoted();
}
@@ -62,15 +65,18 @@
* scripting expressions.
* @param isELIgnored Is expression language being ignored on the page
* where the JSP attribute is defined.
+ * @param isDeferredSyntaxAllowedAsLiteral
+ * Are deferred expressions treated as literals?
* @param strict The value to use for STRICT_QUOTE_ESCAPING.
* @return An unquoted JSP attribute that, if it contains
* expression language can be safely passed to the EL
* processor without fear of ambiguity.
*/
protected static String getUnquoted(String input, char quote,
- boolean isELIgnored, boolean strict) {
+ boolean isELIgnored, boolean isDeferredSyntaxAllowedAsLiteral,
+ boolean strict) {
return (new AttributeParser(input, quote, isELIgnored,
- strict)).getUnquoted();
+ isDeferredSyntaxAllowedAsLiteral, strict)).getUnquoted();
}
/* The quoted input string. */
@@ -83,6 +89,9 @@
* treated as literals rather than quoted values. */
private final boolean isELIgnored;
+ /* Are deferred expression treated as literals */
+ private final boolean isDeferredSyntaxAllowedAsLiteral;
+
/* Overrides the STRICT_QUOTE_ESCAPING. Used for Unit tests only. */
private final boolean strict;
@@ -109,12 +118,15 @@
* @param strict
*/
private AttributeParser(String input, char quote,
- boolean isELIgnored, boolean strict) {
+ boolean isELIgnored, boolean isDeferredSyntaxAllowedAsLiteral,
+ boolean strict) {
this.input = input;
this.quote = quote;
// If quote is null this is a scriptign expressions and any EL syntax
// should be ignored
this.isELIgnored = isELIgnored || (quote == 0);
+ this.isDeferredSyntaxAllowedAsLiteral =
+ isDeferredSyntaxAllowedAsLiteral;
this.strict = strict;
this.type = getType(input);
this.size = input.length();
@@ -151,22 +163,27 @@
char ch = nextChar();
if (!isELIgnored && ch == '\\') {
if (type == 0) {
- type = '$';
+ result.append("\\");
+ } else {
+ result.append(type);
+ result.append("{'\\\\'}");
}
- result.append(type);
- result.append("{'\\\\'}");
} else if (!isELIgnored && ch == '$' && lastChEscaped){
if (type == 0) {
- type = '$';
+ result.append("\\$");
+ } else {
+ result.append(type);
+ result.append("{'$'}");
}
- result.append(type);
- result.append("{'$'}");
} else if (!isELIgnored && ch == '#' && lastChEscaped){
+ // Note if isDeferredSyntaxAllowedAsLiteral==true, \# will
+ // not be treated as an escape
if (type == 0) {
- type = '$';
+ result.append("\\#");
+ } else {
+ result.append(type);
+ result.append("{'#'}");
}
- result.append(type);
- result.append("{'#'}");
} else if (ch == type){
if (i < size) {
char next = input.charAt(i);
@@ -261,7 +278,10 @@
} else if (ch == '\\' && i + 1 < size) {
ch = input.charAt(i + 1);
if (ch == '\\' || ch == '\"' || ch == '\'' ||
- (!isELIgnored && (ch == '$' || ch == '#'))) {
+ (!isELIgnored &&
+ (ch == '$' ||
+ (!isDeferredSyntaxAllowedAsLiteral &&
+ ch == '#')))) {
i += 2;
lastChEscaped = true;
} else {
@@ -311,13 +331,13 @@
int j = 0;
int len = value.length();
char current;
-
+
while (j < len) {
current = value.charAt(j);
if (current == '\\') {
// Escape character - skip a character
j++;
- } else if (current == '#') {
+ } else if (current == '#' && !isDeferredSyntaxAllowedAsLiteral) {
if (j < (len -1) && value.charAt(j + 1) == '{') {
return '#';
}
Modified: branches/2.1.x/java/org/apache/jasper/compiler/Parser.java
===================================================================
--- branches/2.1.x/java/org/apache/jasper/compiler/Parser.java 2012-02-10 16:46:18 UTC (rev 1955)
+++ branches/2.1.x/java/org/apache/jasper/compiler/Parser.java 2012-02-13 10:20:17 UTC (rev 1956)
@@ -247,7 +247,8 @@
quote = watch.charAt(0);
}
ret = AttributeParser.getUnquoted(reader.getText(start, stop),
- quote, pageInfo.isELIgnored());
+ quote, pageInfo.isELIgnored(),
+ pageInfo.isDeferredSyntaxAllowedAsLiteral());
} catch (IllegalArgumentException iae) {
err.jspError(start, iae.getMessage());
}
12 years, 10 months
JBossWeb SVN: r1955 - in trunk: webapps/docs and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-02-10 11:46:18 -0500 (Fri, 10 Feb 2012)
New Revision: 1955
Modified:
trunk/java/org/apache/tomcat/util/net/AprEndpoint.java
trunk/webapps/docs/changelog.xml
Log:
AS7-3607: Fix bogus issue with lame hack.
Modified: trunk/java/org/apache/tomcat/util/net/AprEndpoint.java
===================================================================
--- trunk/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-08 17:33:26 UTC (rev 1954)
+++ trunk/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-10 16:46:18 UTC (rev 1955)
@@ -553,7 +553,13 @@
} else if (addressStr.indexOf(':') >= 0) {
family = Socket.APR_UNSPEC;
}
- }
+ if (!Boolean.getBoolean("java.net.preferIPv4Stack")) {
+ family = Socket.APR_INET6;
+ if (addressStr != null && addressStr.indexOf(':') < 0) {
+ addressStr = "::ffff:" + addressStr;
+ }
+ }
+ }
// Sendfile usage on systems which don't support it cause major problems
if (useSendfile && !Library.APR_HAS_SENDFILE) {
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2012-02-08 17:33:26 UTC (rev 1954)
+++ trunk/webapps/docs/changelog.xml 2012-02-10 16:46:18 UTC (rev 1955)
@@ -22,6 +22,10 @@
<fix>
<bug>52606</bug>: Body replay with AJP. (markt)
</fix>
+ <fix>
+ <jboss-jira>AS7-3607</jboss-jira>: Support ipv4 addresses as ipv6 when the
+ JVM is configured to prefer ipv6. (remm)
+ </fix>
</changelog>
</subsection>
</section>
12 years, 10 months
JBossWeb SVN: r1954 - in trunk: webapps/docs and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-02-08 12:33:26 -0500 (Wed, 08 Feb 2012)
New Revision: 1954
Modified:
trunk/java/org/apache/coyote/ajp/AjpAprProcessor.java
trunk/java/org/apache/coyote/ajp/AjpProcessor.java
trunk/webapps/docs/changelog.xml
Log:
Fix AJP body replay.
Modified: trunk/java/org/apache/coyote/ajp/AjpAprProcessor.java
===================================================================
--- trunk/java/org/apache/coyote/ajp/AjpAprProcessor.java 2012-02-04 00:46:32 UTC (rev 1953)
+++ trunk/java/org/apache/coyote/ajp/AjpAprProcessor.java 2012-02-08 17:33:26 UTC (rev 1954)
@@ -661,6 +661,7 @@
first = false;
empty = false;
replay = true;
+ endOfStream = false;
} else if (actionCode == ActionCode.ACTION_EVENT_BEGIN) {
event = true;
Modified: trunk/java/org/apache/coyote/ajp/AjpProcessor.java
===================================================================
--- trunk/java/org/apache/coyote/ajp/AjpProcessor.java 2012-02-04 00:46:32 UTC (rev 1953)
+++ trunk/java/org/apache/coyote/ajp/AjpProcessor.java 2012-02-08 17:33:26 UTC (rev 1954)
@@ -675,6 +675,7 @@
first = false;
empty = false;
replay = true;
+ endOfStream = false;
} else if (actionCode == ActionCode.ACTION_EVENT_BEGIN) {
event = true;
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2012-02-04 00:46:32 UTC (rev 1953)
+++ trunk/webapps/docs/changelog.xml 2012-02-08 17:33:26 UTC (rev 1954)
@@ -16,6 +16,16 @@
<body>
+<section name="JBoss Web 7.0.11.Final (remm)">
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ <bug>52606</bug>: Body replay with AJP. (markt)
+ </fix>
+ </changelog>
+ </subsection>
+</section>
+
<section name="JBoss Web 7.0.10.Final (remm)">
<subsection name="Catalina">
<changelog>
12 years, 10 months
JBossWeb SVN: r1953 - in branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049: java/org/apache/catalina/authenticator and 5 other directories.
by jbossweb-commits@lists.jboss.org
Author: bmaxwell
Date: 2012-02-03 19:46:32 -0500 (Fri, 03 Feb 2012)
New Revision: 1953
Modified:
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.properties.default
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.xml
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/AuthenticatorBase.java
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/DigestAuthenticator.java
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/LocalStrings.properties
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/mbeans-descriptors.xml
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/connector/OutputBuffer.java
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/realm/RealmBase.java
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/MimeHeaders.java
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/Parameters.java
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/net/AprEndpoint.java
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/webapps/docs/changelog.xml
Log:
[JBPAPP-8049] CVE Fixes: 2011-2204, 2011-2729, 2011-1184, 2011-2526, 2011-4858, 2011-4610
Property changes on: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049
___________________________________________________________________
Added: svn:mergeinfo
+ /branches/2.1.x:1871-1872,1898,1902
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.properties.default
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.properties.default 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.properties.default 2012-02-04 00:46:32 UTC (rev 1953)
@@ -12,9 +12,9 @@
# ----- Version Control Flags -----
version.major=2
version.minor=1
-version.build=4
+version.build=10
version.patch=0
-version.tag=SNAPSHOT
+version.tag=JBPAPP-8049
# ----- Default Base Path for Dependent Packages -----
# Please note this path must be absolute, not relative,
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.xml
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.xml 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/build.xml 2012-02-04 00:46:32 UTC (rev 1953)
@@ -16,9 +16,9 @@
<property name="year" value="2008" />
<property name="version.major" value="2" />
<property name="version.minor" value="1" />
- <property name="version.build" value="0" />
+ <property name="version.build" value="10" />
<property name="version.patch" value="0" />
- <property name="version.tag" value="SNAPSHOT" />
+ <property name="version.tag" value="JBPAPP-8049" />
<property name="version" value="${version.major}.${version.minor}.${version.build}.${version.tag}" />
<property name="version.number" value="${version.major}.${version.minor}.${version.build}.${version.patch}" />
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/AuthenticatorBase.java
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2012-02-04 00:46:32 UTC (rev 1953)
@@ -89,8 +89,17 @@
*/
protected static final String DEFAULT_ALGORITHM = "MD5";
+ /**
+ * Authentication header
+ */
+ protected static final String AUTH_HEADER_NAME = "WWW-Authenticate";
/**
+ * Default authentication realm name.
+ */
+ protected static final String REALM_NAME = "Authentication required";
+
+ /**
* The number of random bytes to include when generating a
* session identifier.
*/
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-02-04 00:46:32 UTC (rev 1953)
@@ -22,12 +22,18 @@
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
import java.security.Principal;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletResponse;
-
+import org.apache.catalina.Container;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
import org.apache.catalina.Realm;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
@@ -67,6 +73,11 @@
"org.apache.catalina.authenticator.DigestAuthenticator/1.0";
+ /**
+ * Tomcat's DIGEST implementation only supports auth quality of protection.
+ */
+ protected static final String QOP = "auth";
+
// ----------------------------------------------------------- Constructors
@@ -92,17 +103,49 @@
/**
+ * List of client nonce values currently being tracked
+ */
+ protected Map<String,NonceInfo> cnonces;
+
+
+ /**
+ * Maximum number of client nonces to keep in the cache. If not specified,
+ * the default value of 1000 is used.
+ */
+ protected int cnonceCacheSize = 1000;
+
+
+ /**
* Private key.
*/
- protected String key = "Catalina";
+ protected String key = null;
- // ------------------------------------------------------------- Properties
+ /**
+ * How long server nonces are valid for in milliseconds. Defaults to 5
+ * minutes.
+ */
+ protected long nonceValidity = 5 * 60 * 1000;
/**
+ * Opaque string.
+ */
+ protected String opaque;
+
+
+ /**
+ * Should the URI be validated as required by RFC2617? Can be disabled in
+ * reverse proxies where the proxy has modified the URI.
+ */
+ protected boolean validateUri = true;
+
+ // ------------------------------------------------------------- Properties
+
+ /**
* Return descriptive information about this Valve implementation.
*/
+ @Override
public String getInfo() {
return (info);
@@ -110,9 +153,58 @@
}
+ public int getCnonceCacheSize() {
+ return cnonceCacheSize;
+ }
+
+
+ public void setCnonceCacheSize(int cnonceCacheSize) {
+ this.cnonceCacheSize = cnonceCacheSize;
+ }
+
+
+ public String getKey() {
+ return key;
+ }
+
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+
+ public long getNonceValidity() {
+ return nonceValidity;
+ }
+
+
+ public void setNonceValidity(long nonceValidity) {
+ this.nonceValidity = nonceValidity;
+ }
+
+
+ public String getOpaque() {
+ return opaque;
+ }
+
+
+ public void setOpaque(String opaque) {
+ this.opaque = opaque;
+ }
+
+
+ public boolean isValidateUri() {
+ return validateUri;
+ }
+
+
+ public void setValidateUri(boolean validateUri) {
+ this.validateUri = validateUri;
+ }
+
+
// --------------------------------------------------------- Public Methods
-
/**
* Authenticate the user making this request, based on the specified
* login configuration. Return <code>true</code> if any specified
@@ -126,6 +218,7 @@
*
* @exception IOException if an input/output error occurs
*/
+ @Override
public boolean authenticate(Request request,
Response response,
LoginConfig config)
@@ -172,8 +265,13 @@
// Validate any credentials already included with this request
String authorization = request.getHeader("authorization");
+ DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
+ getKey(), cnonces, isValidateUri());
if (authorization != null) {
- principal = findPrincipal(request, authorization, context.getRealm());
+ if (digestInfo.validate(request, authorization, config)) {
+ principal = digestInfo.authenticate(context.getRealm());
+ }
+
if (principal != null) {
String username = parseUsername(authorization);
register(request, response, principal,
@@ -185,11 +283,12 @@
// Send an "unauthorized" response and an appropriate challenge
- // Next, generate a nOnce token (that is a token which is supposed
+ // Next, generate a nonce token (that is a token which is supposed
// to be unique).
- String nOnce = generateNOnce(request);
+ String nonce = generateNonce(request);
- setAuthenticateHeader(request, response, config, nOnce);
+ setAuthenticateHeader(request, response, config, nonce,
+ digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
// hres.flushBuffer();
return (false);
@@ -201,92 +300,6 @@
/**
- * Parse the specified authorization credentials, and return the
- * associated Principal that these credentials authenticate (if any)
- * from the specified Realm. If there is no such Principal, return
- * <code>null</code>.
- *
- * @param request HTTP servlet request
- * @param authorization Authorization credentials from this request
- * @param realm Realm used to authenticate Principals
- */
- protected static Principal findPrincipal(Request request,
- String authorization,
- Realm realm) {
-
- //System.out.println("Authorization token : " + authorization);
- // Validate the authorization credentials format
- if (authorization == null)
- return (null);
- if (!authorization.startsWith("Digest "))
- return (null);
- authorization = authorization.substring(7).trim();
-
- // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
- String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
-
- String userName = null;
- String realmName = null;
- String nOnce = null;
- String nc = null;
- String cnonce = null;
- String qop = null;
- String uri = null;
- String response = null;
- String method = request.getMethod();
-
- for (int i = 0; i < tokens.length; i++) {
- String currentToken = tokens[i];
- if (currentToken.length() == 0)
- continue;
-
- int equalSign = currentToken.indexOf('=');
- if (equalSign < 0)
- return null;
- String currentTokenName =
- currentToken.substring(0, equalSign).trim();
- String currentTokenValue =
- currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
- userName = removeQuotes(currentTokenValue);
- if ("realm".equals(currentTokenName))
- realmName = removeQuotes(currentTokenValue, true);
- if ("nonce".equals(currentTokenName))
- nOnce = removeQuotes(currentTokenValue);
- if ("nc".equals(currentTokenName))
- nc = removeQuotes(currentTokenValue);
- if ("cnonce".equals(currentTokenName))
- cnonce = removeQuotes(currentTokenValue);
- if ("qop".equals(currentTokenName))
- qop = removeQuotes(currentTokenValue);
- if ("uri".equals(currentTokenName))
- uri = removeQuotes(currentTokenValue);
- if ("response".equals(currentTokenName))
- response = removeQuotes(currentTokenValue);
- }
-
- if ( (userName == null) || (realmName == null) || (nOnce == null)
- || (uri == null) || (response == null) )
- return null;
-
- // Second MD5 digest used to calculate the digest :
- // MD5(Method + ":" + uri)
- String a2 = method + ":" + uri;
- //System.out.println("A2:" + a2);
-
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(a2.getBytes());
- }
- String md5a2 = md5Encoder.encode(buffer);
-
- return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop,
- realmName, md5a2));
-
- }
-
-
- /**
* Parse the username from the specified authorization string. If none
* can be identified, return <code>null</code>
*
@@ -294,7 +307,6 @@
*/
protected String parseUsername(String authorization) {
- //System.out.println("Authorization token : " + authorization);
// Validate the authorization credentials format
if (authorization == null)
return (null);
@@ -354,20 +366,20 @@
*
* @param request HTTP Servlet request
*/
- protected String generateNOnce(Request request) {
+ protected String generateNonce(Request request) {
long currentTime = System.currentTimeMillis();
- String nOnceValue = request.getRemoteAddr() + ":" +
- currentTime + ":" + key;
+
+ String ipTimeKey =
+ request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
- byte[] buffer = null;
+ byte[] buffer;
synchronized (md5Helper) {
- buffer = md5Helper.digest(nOnceValue.getBytes());
+ buffer = md5Helper.digest(ipTimeKey.getBytes());
}
- nOnceValue = md5Encoder.encode(buffer);
- return nOnceValue;
+ return currentTime + ":" + md5Encoder.encode(buffer);
}
@@ -379,7 +391,7 @@
* WWW-Authenticate = "WWW-Authenticate" ":" "Digest"
* digest-challenge
*
- * digest-challenge = 1#( realm | [ domain ] | nOnce |
+ * digest-challenge = 1#( realm | [ domain ] | nonce |
* [ digest-opaque ] |[ stale ] | [ algorithm ] )
*
* realm = "realm" "=" realm-value
@@ -396,29 +408,303 @@
* @param response HTTP Servlet response
* @param config Login configuration describing how authentication
* should be performed
- * @param nOnce nonce token
+ * @param nonce nonce token
*/
protected void setAuthenticateHeader(Request request,
Response response,
LoginConfig config,
- String nOnce) {
+ String nonce,
+ boolean isNonceStale) {
// Get the realm name
String realmName = config.getRealmName();
if (realmName == null)
- realmName = "Realm";
+ realmName = REALM_NAME;
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(nOnce.getBytes());
- }
+ String authenticateHeader;
+ if (isNonceStale) {
+ authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+ "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
+ getOpaque() + "\", stale=true";
+ } else {
+ authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+ "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
+ getOpaque() + "\"";
+ }
- String authenticateHeader = "Digest realm=\"" + realmName + "\", "
- + "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\""
- + md5Encoder.encode(buffer) + "\"";
response.setHeader("WWW-Authenticate", authenticateHeader);
}
+ // ------------------------------------------------------- Lifecycle Methods
+
+ @Override
+ public void start() throws LifecycleException {
+ super.start();
+
+ // Generate a random secret key
+ if (getKey() == null) {
+ setKey(generateSessionId());
+ }
+
+ // Generate the opaque string the same way
+ if (getOpaque() == null) {
+ setOpaque(generateSessionId());
+ }
+
+ cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
+ private static final long serialVersionUID = 1L;
+ private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
+
+ private long lastLog = 0;
+
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<String,NonceInfo> eldest) {
+ // This is called from a sync so keep it simple
+ long currentTime = System.currentTimeMillis();
+ if (size() > getCnonceCacheSize()) {
+ if (lastLog < currentTime &&
+ currentTime - eldest.getValue().getTimestamp() <
+ getNonceValidity()) {
+ // Replay attack is possible
+ log.warn(sm.getString(
+ "digestAuthenticator.cacheRemove"));
+ lastLog = currentTime + LOG_SUPPRESS_TIME;
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+
+ private static class DigestInfo {
+
+ private String opaque;
+ private long nonceValidity;
+ private String key;
+ private Map<String,NonceInfo> cnonces;
+ private boolean validateUri = true;
+
+ private String userName = null;
+ private String method = null;
+ private String uri = null;
+ private String response = null;
+ private String nonce = null;
+ private String nc = null;
+ private String cnonce = null;
+ private String realmName = null;
+ private String qop = null;
+
+ private boolean nonceStale = false;
+
+
+ public DigestInfo(String opaque, long nonceValidity, String key,
+ Map<String,NonceInfo> cnonces, boolean validateUri) {
+ this.opaque = opaque;
+ this.nonceValidity = nonceValidity;
+ this.key = key;
+ this.cnonces = cnonces;
+ this.validateUri = validateUri;
+ }
+
+ public boolean validate(Request request, String authorization,
+ LoginConfig config) {
+ // Validate the authorization credentials format
+ if (authorization == null) {
+ return false;
+ }
+ if (!authorization.startsWith("Digest ")) {
+ return false;
+ }
+ authorization = authorization.substring(7).trim();
+
+ // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
+ String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
+
+ method = request.getMethod();
+ String opaque = null;
+
+ for (int i = 0; i < tokens.length; i++) {
+ String currentToken = tokens[i];
+ if (currentToken.length() == 0)
+ continue;
+
+ int equalSign = currentToken.indexOf('=');
+ if (equalSign < 0) {
+ return false;
+ }
+ String currentTokenName =
+ currentToken.substring(0, equalSign).trim();
+ String currentTokenValue =
+ currentToken.substring(equalSign + 1).trim();
+ if ("username".equals(currentTokenName))
+ userName = removeQuotes(currentTokenValue);
+ if ("realm".equals(currentTokenName))
+ realmName = removeQuotes(currentTokenValue, true);
+ if ("nonce".equals(currentTokenName))
+ nonce = removeQuotes(currentTokenValue);
+ if ("nc".equals(currentTokenName))
+ nc = removeQuotes(currentTokenValue);
+ if ("cnonce".equals(currentTokenName))
+ cnonce = removeQuotes(currentTokenValue);
+ if ("qop".equals(currentTokenName))
+ qop = removeQuotes(currentTokenValue);
+ if ("uri".equals(currentTokenName))
+ uri = removeQuotes(currentTokenValue);
+ if ("response".equals(currentTokenName))
+ response = removeQuotes(currentTokenValue);
+ if ("opaque".equals(currentTokenName))
+ opaque = removeQuotes(currentTokenValue);
+ }
+
+ if ( (userName == null) || (realmName == null) || (nonce == null)
+ || (uri == null) || (response == null) ) {
+ return false;
+ }
+
+ // Validate the URI - should match the request line sent by client
+ if (validateUri) {
+ String uriQuery;
+ String query = request.getQueryString();
+ if (query == null) {
+ uriQuery = request.getRequestURI();
+ } else {
+ uriQuery = request.getRequestURI() + "?" + query;
+ }
+ if (!uri.equals(uriQuery)) {
+ return false;
+ }
+ }
+
+ // Validate the Realm name
+ String lcRealm = config.getRealmName();
+ if (lcRealm == null) {
+ lcRealm = REALM_NAME;
+ }
+ if (!lcRealm.equals(realmName)) {
+ return false;
+ }
+
+ // Validate the opaque string
+ if (!this.opaque.equals(opaque)) {
+ return false;
+ }
+
+ // Validate nonce
+ int i = nonce.indexOf(":");
+ if (i < 0 || (i + 1) == nonce.length()) {
+ return false;
+ }
+ long nonceTime;
+ try {
+ nonceTime = Long.parseLong(nonce.substring(0, i));
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ String md5clientIpTimeKey = nonce.substring(i + 1);
+ long currentTime = System.currentTimeMillis();
+ if ((currentTime - nonceTime) > nonceValidity) {
+ nonceStale = true;
+ return false;
+ }
+ String serverIpTimeKey =
+ request.getRemoteAddr() + ":" + nonceTime + ":" + key;
+ byte[] buffer = null;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(serverIpTimeKey.getBytes());
+ }
+ String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
+ return false;
+ }
+
+ // Validate qop
+ if (qop != null && !QOP.equals(qop)) {
+ return false;
+ }
+
+ // Validate cnonce and nc
+ // Check if presence of nc and nonce is consistent with presence of qop
+ if (qop == null) {
+ if (cnonce != null || nc != null) {
+ return false;
+ }
+ } else {
+ if (cnonce == null || nc == null) {
+ return false;
+ }
+ if (nc.length() != 8) {
+ return false;
+ }
+ long count;
+ try {
+ count = Long.parseLong(nc, 16);
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ NonceInfo info;
+ synchronized (cnonces) {
+ info = cnonces.get(cnonce);
+ }
+ if (info == null) {
+ info = new NonceInfo();
+ } else {
+ if (count <= info.getCount()) {
+ return false;
+ }
+ }
+ info.setCount(count);
+ info.setTimestamp(currentTime);
+ synchronized (cnonces) {
+ cnonces.put(cnonce, info);
+ }
+ }
+ return true;
+ }
+
+ public boolean isNonceStale() {
+ return nonceStale;
+ }
+
+ public Principal authenticate(Realm realm) {
+ // Second MD5 digest used to calculate the digest :
+ // MD5(Method + ":" + uri)
+ String a2 = method + ":" + uri;
+
+ byte[] buffer;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(a2.getBytes());
+ }
+ String md5a2 = md5Encoder.encode(buffer);
+
+ return realm.authenticate(userName, response, nonce, nc, cnonce,
+ qop, realmName, md5a2);
+ }
+
+ }
+
+ private static class NonceInfo {
+ private volatile long count;
+ private volatile long timestamp;
+
+ public void setCount(long l) {
+ count = l;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public void setTimestamp(long l) {
+ timestamp = l;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+ }
}
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/LocalStrings.properties
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/LocalStrings.properties 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/LocalStrings.properties 2012-02-04 00:46:32 UTC (rev 1953)
@@ -12,3 +12,5 @@
authenticator.sessionExpired=The time allowed for the login process has been exceeded. If you wish to continue you must either click back twice and re-click the link you requested or close and re-open your browser
authenticator.unauthorized=Cannot authenticate with the provided credentials
authenticator.userDataConstraint=This request violates a User Data constraint for this application
+
+DigestAuthenticator.cacheRemove=A valid entry has been removed from client nonce cache to make room for new entries. A replay attack is now possible. To prevent the possibility of replay attacks, reduce nonceValidity or increase cnonceCacheSize. Further warnings of this type will be suppressed for 5 minutes
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/mbeans-descriptors.xml
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/mbeans-descriptors.xml 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/authenticator/mbeans-descriptors.xml 2012-02-04 00:46:32 UTC (rev 1953)
@@ -44,10 +44,30 @@
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
-
+
+ <attribute name="cnonceCacheSize"
+ description="The size of the cnonce cache used to prevent replay attacks"
+ type="int"/>
+
<attribute name="entropy"
description="A String initialization parameter used to increase the entropy of the initialization of our random number generator"
type="java.lang.String"/>
+
+ <attribute name="key"
+ description="The secret key used by digest authentication"
+ type="java.lang.String"/>
+
+ <attribute name="nonceValidity"
+ description="The time, in milliseconds, for which a server issued nonce will be valid"
+ type="long"/>
+
+ <attribute name="opaque"
+ description="The opaque server string used by digest authentication"
+ type="java.lang.String"/>
+
+ <attribute name="validateUri"
+ description="Should the uri be validated as required by RFC2617?"
+ type="boolean"/>
</mbean>
<mbean name="FormAuthenticator"
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/connector/OutputBuffer.java
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/connector/OutputBuffer.java 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/connector/OutputBuffer.java 2012-02-04 00:46:32 UTC (rev 1953)
@@ -463,6 +463,10 @@
outputCharChunk.setChars(buf, off, len);
while (outputCharChunk.getLength() > 0) {
conv.convert(outputCharChunk, bb);
+ if (bb.getLength() == 0) {
+ // Break out of the loop if more chars are needed to produce any output
+ break;
+ }
if (outputCharChunk.getLength() > 0) {
bb.flushBuffer();
}
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/realm/RealmBase.java
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/realm/RealmBase.java 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/catalina/realm/RealmBase.java 2012-02-04 00:46:32 UTC (rev 1953)
@@ -367,8 +367,13 @@
String md5a1 = getDigest(username, realm);
if (md5a1 == null)
return null;
- String serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":"
- + cnonce + ":" + qop + ":" + md5a2;
+ String serverDigestValue;
+ if (qop == null) {
+ serverDigestValue = md5a1 + ":" + nOnce + ":" + md5a2;
+ } else {
+ serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":" +
+ cnonce + ":" + qop + ":" + md5a2;
+ }
byte[] valueBytes = null;
if(getDigestEncoding() == null) {
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/MimeHeaders.java
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/MimeHeaders.java 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/MimeHeaders.java 2012-02-04 00:46:32 UTC (rev 1953)
@@ -23,9 +23,6 @@
import org.apache.tomcat.util.buf.MessageBytes;
-/* XXX XXX XXX Need a major rewrite !!!!
- */
-
/**
* This class is used to contain standard internet message headers,
* used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for
@@ -77,12 +74,6 @@
* to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields,
* and reduce to 0 the memory overhead of tomcat.
*
- * TODO:
- * XXX one-buffer parsing - for http ( other protocols don't need that )
- * XXX remove unused methods
- * XXX External enumerations, with 0 GC.
- * XXX use HeaderName ID
- *
*
* @author dac(a)eng.sun.com
* @author James Todd [gonzo(a)eng.sun.com]
@@ -212,9 +203,10 @@
}
/** Initial size - should be == average number of headers per request
- * XXX make it configurable ( fine-tuning of web-apps )
*/
public static final int DEFAULT_HEADER_SIZE = 8;
+ protected static final int MAX_COUNT =
+ Integer.valueOf(System.getProperty("org.apache.tomcat.util.http.MimeHeaders.MAX_COUNT", "128")).intValue();
/**
* The header fields.
@@ -333,6 +325,9 @@
MimeHeaderField mh;
int len = headers.length;
if (count >= len) {
+ if (count >= MAX_COUNT) {
+ throw new IllegalStateException("Header count exceeded allowed maximum: " + MAX_COUNT);
+ }
// expand header list array
MimeHeaderField tmp[] = new MimeHeaderField[count * 2];
System.arraycopy(headers, 0, tmp, 0, len);
@@ -441,9 +436,7 @@
* @param name the name of the header field to be removed
*/
public void removeHeader(String name) {
- // XXX
// warning: rather sticky code; heavily tuned
-
for (int i = 0; i < count; i++) {
if (headers[i].getName().equalsIgnoreCase(name)) {
removeHeader(i--);
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/Parameters.java 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/http/Parameters.java 2012-02-04 00:46:32 UTC (rev 1953)
@@ -40,6 +40,8 @@
protected static final int LAST = -1;
public static final int INITIAL_SIZE = 8;
protected static final String[] ARRAY_TYPE = new String[0];
+ protected static final int MAX_COUNT =
+ Integer.valueOf(System.getProperty("org.apache.tomcat.util.http.Parameters.MAX_COUNT", "512")).intValue();
protected class Field {
MessageBytes name = MessageBytes.newInstance();
@@ -212,6 +214,9 @@
int len = fields.length;
int pos = count;
if (count >= len) {
+ if (count >= MAX_COUNT) {
+ throw new IllegalStateException("Parameter count exceeded allowed maximum: " + MAX_COUNT);
+ }
// expand header list array
Field tmp[] = new Field[pos * 2];
System.arraycopy(fields, 0, tmp, 0, len);
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/net/AprEndpoint.java
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-04 00:46:32 UTC (rev 1953)
@@ -301,7 +301,7 @@
/**
* Use sendfile for sending static files.
*/
- protected boolean useSendfile = Library.APR_HAS_SENDFILE;
+ protected boolean useSendfile = false; /* CVE-2011-2526 */
public void setUseSendfile(boolean useSendfile) { this.useSendfile = useSendfile; }
public boolean getUseSendfile() { return useSendfile; }
Modified: branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/webapps/docs/changelog.xml
===================================================================
--- branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/webapps/docs/changelog.xml 2012-02-04 00:30:30 UTC (rev 1952)
+++ branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/webapps/docs/changelog.xml 2012-02-04 00:46:32 UTC (rev 1953)
@@ -16,6 +16,24 @@
<body>
+<section name="JBoss Web 2.1.10.GA_JBPAPP-8049 (jfclere)">
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ Improve DIGEST authentication security. (remm)
+ </fix>
+ </changelog>
+ </subsection>
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ Add system properties which restrict parameter count (org.apache.tomcat.util.http.Parameters.MAX_COUNT
+ default to 512) and header count (org.apache.tomcat.util.http.MimeHeaders.MAX_COUNT to 128). (remm)
+ </fix>
+ </changelog>
+ </subsection>
+</section>
+
<section name="JBoss Web 2.1.10.GA (remm)">
<subsection name="Coyote">
<changelog>
12 years, 10 months
JBossWeb SVN: r1952 - branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050.
by jbossweb-commits@lists.jboss.org
Author: bmaxwell
Date: 2012-02-03 19:30:30 -0500 (Fri, 03 Feb 2012)
New Revision: 1952
Modified:
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.properties.default
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.xml
Log:
[JBPAPP-8050] CVE Fixes: 2011-2204, 2011-2729, 2011-1184, 2011-2526, 2011-4858, 2011-4610
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.properties.default
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.properties.default 2012-02-04 00:29:22 UTC (rev 1951)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.properties.default 2012-02-04 00:30:30 UTC (rev 1952)
@@ -12,9 +12,9 @@
# ----- Version Control Flags -----
version.major=2
version.minor=1
-version.build=4
+version.build=11
version.patch=0
-version.tag=SNAPSHOT
+version.tag=JBPAPP-8050
# ----- Default Base Path for Dependent Packages -----
# Please note this path must be absolute, not relative,
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.xml
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.xml 2012-02-04 00:29:22 UTC (rev 1951)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/build.xml 2012-02-04 00:30:30 UTC (rev 1952)
@@ -16,9 +16,9 @@
<property name="year" value="2008" />
<property name="version.major" value="2" />
<property name="version.minor" value="1" />
- <property name="version.build" value="0" />
+ <property name="version.build" value="11" />
<property name="version.patch" value="0" />
- <property name="version.tag" value="SNAPSHOT" />
+ <property name="version.tag" value="JBPAPP-8050" />
<property name="version" value="${version.major}.${version.minor}.${version.build}.${version.tag}" />
<property name="version.number" value="${version.major}.${version.minor}.${version.build}.${version.patch}" />
12 years, 10 months
JBossWeb SVN: r1951 - in branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050: java/org/apache/catalina/connector and 4 other directories.
by jbossweb-commits@lists.jboss.org
Author: bmaxwell
Date: 2012-02-03 19:29:22 -0500 (Fri, 03 Feb 2012)
New Revision: 1951
Modified:
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/AuthenticatorBase.java
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/DigestAuthenticator.java
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/LocalStrings.properties
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/mbeans-descriptors.xml
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/connector/OutputBuffer.java
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/realm/RealmBase.java
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/MimeHeaders.java
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/Parameters.java
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/net/AprEndpoint.java
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/webapps/docs/changelog.xml
Log:
[JBPAPP-8050] CVE Fixes: 2011-2204, 2011-2729, 2011-1184, 2011-2526, 2011-4858, 2011-4610
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/AuthenticatorBase.java
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2012-02-04 00:29:22 UTC (rev 1951)
@@ -89,8 +89,17 @@
*/
protected static final String DEFAULT_ALGORITHM = "MD5";
+ /**
+ * Authentication header
+ */
+ protected static final String AUTH_HEADER_NAME = "WWW-Authenticate";
/**
+ * Default authentication realm name.
+ */
+ protected static final String REALM_NAME = "Authentication required";
+
+ /**
* The number of random bytes to include when generating a
* session identifier.
*/
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-02-04 00:29:22 UTC (rev 1951)
@@ -22,12 +22,18 @@
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
import java.security.Principal;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletResponse;
-
+import org.apache.catalina.Container;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
import org.apache.catalina.Realm;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
@@ -67,6 +73,11 @@
"org.apache.catalina.authenticator.DigestAuthenticator/1.0";
+ /**
+ * Tomcat's DIGEST implementation only supports auth quality of protection.
+ */
+ protected static final String QOP = "auth";
+
// ----------------------------------------------------------- Constructors
@@ -92,17 +103,49 @@
/**
+ * List of client nonce values currently being tracked
+ */
+ protected Map<String,NonceInfo> cnonces;
+
+
+ /**
+ * Maximum number of client nonces to keep in the cache. If not specified,
+ * the default value of 1000 is used.
+ */
+ protected int cnonceCacheSize = 1000;
+
+
+ /**
* Private key.
*/
- protected String key = "Catalina";
+ protected String key = null;
- // ------------------------------------------------------------- Properties
+ /**
+ * How long server nonces are valid for in milliseconds. Defaults to 5
+ * minutes.
+ */
+ protected long nonceValidity = 5 * 60 * 1000;
/**
+ * Opaque string.
+ */
+ protected String opaque;
+
+
+ /**
+ * Should the URI be validated as required by RFC2617? Can be disabled in
+ * reverse proxies where the proxy has modified the URI.
+ */
+ protected boolean validateUri = true;
+
+ // ------------------------------------------------------------- Properties
+
+ /**
* Return descriptive information about this Valve implementation.
*/
+ @Override
public String getInfo() {
return (info);
@@ -110,9 +153,58 @@
}
+ public int getCnonceCacheSize() {
+ return cnonceCacheSize;
+ }
+
+
+ public void setCnonceCacheSize(int cnonceCacheSize) {
+ this.cnonceCacheSize = cnonceCacheSize;
+ }
+
+
+ public String getKey() {
+ return key;
+ }
+
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+
+ public long getNonceValidity() {
+ return nonceValidity;
+ }
+
+
+ public void setNonceValidity(long nonceValidity) {
+ this.nonceValidity = nonceValidity;
+ }
+
+
+ public String getOpaque() {
+ return opaque;
+ }
+
+
+ public void setOpaque(String opaque) {
+ this.opaque = opaque;
+ }
+
+
+ public boolean isValidateUri() {
+ return validateUri;
+ }
+
+
+ public void setValidateUri(boolean validateUri) {
+ this.validateUri = validateUri;
+ }
+
+
// --------------------------------------------------------- Public Methods
-
/**
* Authenticate the user making this request, based on the specified
* login configuration. Return <code>true</code> if any specified
@@ -126,6 +218,7 @@
*
* @exception IOException if an input/output error occurs
*/
+ @Override
public boolean authenticate(Request request,
Response response,
LoginConfig config)
@@ -172,8 +265,13 @@
// Validate any credentials already included with this request
String authorization = request.getHeader("authorization");
+ DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
+ getKey(), cnonces, isValidateUri());
if (authorization != null) {
- principal = findPrincipal(request, authorization, context.getRealm());
+ if (digestInfo.validate(request, authorization, config)) {
+ principal = digestInfo.authenticate(context.getRealm());
+ }
+
if (principal != null) {
String username = parseUsername(authorization);
register(request, response, principal,
@@ -185,11 +283,12 @@
// Send an "unauthorized" response and an appropriate challenge
- // Next, generate a nOnce token (that is a token which is supposed
+ // Next, generate a nonce token (that is a token which is supposed
// to be unique).
- String nOnce = generateNOnce(request);
+ String nonce = generateNonce(request);
- setAuthenticateHeader(request, response, config, nOnce);
+ setAuthenticateHeader(request, response, config, nonce,
+ digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
// hres.flushBuffer();
return (false);
@@ -201,92 +300,6 @@
/**
- * Parse the specified authorization credentials, and return the
- * associated Principal that these credentials authenticate (if any)
- * from the specified Realm. If there is no such Principal, return
- * <code>null</code>.
- *
- * @param request HTTP servlet request
- * @param authorization Authorization credentials from this request
- * @param realm Realm used to authenticate Principals
- */
- protected static Principal findPrincipal(Request request,
- String authorization,
- Realm realm) {
-
- //System.out.println("Authorization token : " + authorization);
- // Validate the authorization credentials format
- if (authorization == null)
- return (null);
- if (!authorization.startsWith("Digest "))
- return (null);
- authorization = authorization.substring(7).trim();
-
- // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
- String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
-
- String userName = null;
- String realmName = null;
- String nOnce = null;
- String nc = null;
- String cnonce = null;
- String qop = null;
- String uri = null;
- String response = null;
- String method = request.getMethod();
-
- for (int i = 0; i < tokens.length; i++) {
- String currentToken = tokens[i];
- if (currentToken.length() == 0)
- continue;
-
- int equalSign = currentToken.indexOf('=');
- if (equalSign < 0)
- return null;
- String currentTokenName =
- currentToken.substring(0, equalSign).trim();
- String currentTokenValue =
- currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
- userName = removeQuotes(currentTokenValue);
- if ("realm".equals(currentTokenName))
- realmName = removeQuotes(currentTokenValue, true);
- if ("nonce".equals(currentTokenName))
- nOnce = removeQuotes(currentTokenValue);
- if ("nc".equals(currentTokenName))
- nc = removeQuotes(currentTokenValue);
- if ("cnonce".equals(currentTokenName))
- cnonce = removeQuotes(currentTokenValue);
- if ("qop".equals(currentTokenName))
- qop = removeQuotes(currentTokenValue);
- if ("uri".equals(currentTokenName))
- uri = removeQuotes(currentTokenValue);
- if ("response".equals(currentTokenName))
- response = removeQuotes(currentTokenValue);
- }
-
- if ( (userName == null) || (realmName == null) || (nOnce == null)
- || (uri == null) || (response == null) )
- return null;
-
- // Second MD5 digest used to calculate the digest :
- // MD5(Method + ":" + uri)
- String a2 = method + ":" + uri;
- //System.out.println("A2:" + a2);
-
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(a2.getBytes());
- }
- String md5a2 = md5Encoder.encode(buffer);
-
- return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop,
- realmName, md5a2));
-
- }
-
-
- /**
* Parse the username from the specified authorization string. If none
* can be identified, return <code>null</code>
*
@@ -294,7 +307,6 @@
*/
protected String parseUsername(String authorization) {
- //System.out.println("Authorization token : " + authorization);
// Validate the authorization credentials format
if (authorization == null)
return (null);
@@ -354,20 +366,20 @@
*
* @param request HTTP Servlet request
*/
- protected String generateNOnce(Request request) {
+ protected String generateNonce(Request request) {
long currentTime = System.currentTimeMillis();
- String nOnceValue = request.getRemoteAddr() + ":" +
- currentTime + ":" + key;
+
+ String ipTimeKey =
+ request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
- byte[] buffer = null;
+ byte[] buffer;
synchronized (md5Helper) {
- buffer = md5Helper.digest(nOnceValue.getBytes());
+ buffer = md5Helper.digest(ipTimeKey.getBytes());
}
- nOnceValue = md5Encoder.encode(buffer);
- return nOnceValue;
+ return currentTime + ":" + md5Encoder.encode(buffer);
}
@@ -379,7 +391,7 @@
* WWW-Authenticate = "WWW-Authenticate" ":" "Digest"
* digest-challenge
*
- * digest-challenge = 1#( realm | [ domain ] | nOnce |
+ * digest-challenge = 1#( realm | [ domain ] | nonce |
* [ digest-opaque ] |[ stale ] | [ algorithm ] )
*
* realm = "realm" "=" realm-value
@@ -396,29 +408,303 @@
* @param response HTTP Servlet response
* @param config Login configuration describing how authentication
* should be performed
- * @param nOnce nonce token
+ * @param nonce nonce token
*/
protected void setAuthenticateHeader(Request request,
Response response,
LoginConfig config,
- String nOnce) {
+ String nonce,
+ boolean isNonceStale) {
// Get the realm name
String realmName = config.getRealmName();
if (realmName == null)
- realmName = "Realm";
+ realmName = REALM_NAME;
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(nOnce.getBytes());
- }
+ String authenticateHeader;
+ if (isNonceStale) {
+ authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+ "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
+ getOpaque() + "\", stale=true";
+ } else {
+ authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+ "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
+ getOpaque() + "\"";
+ }
- String authenticateHeader = "Digest realm=\"" + realmName + "\", "
- + "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\""
- + md5Encoder.encode(buffer) + "\"";
response.setHeader("WWW-Authenticate", authenticateHeader);
}
+ // ------------------------------------------------------- Lifecycle Methods
+
+ @Override
+ public void start() throws LifecycleException {
+ super.start();
+
+ // Generate a random secret key
+ if (getKey() == null) {
+ setKey(generateSessionId());
+ }
+
+ // Generate the opaque string the same way
+ if (getOpaque() == null) {
+ setOpaque(generateSessionId());
+ }
+
+ cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
+ private static final long serialVersionUID = 1L;
+ private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
+
+ private long lastLog = 0;
+
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<String,NonceInfo> eldest) {
+ // This is called from a sync so keep it simple
+ long currentTime = System.currentTimeMillis();
+ if (size() > getCnonceCacheSize()) {
+ if (lastLog < currentTime &&
+ currentTime - eldest.getValue().getTimestamp() <
+ getNonceValidity()) {
+ // Replay attack is possible
+ log.warn(sm.getString(
+ "digestAuthenticator.cacheRemove"));
+ lastLog = currentTime + LOG_SUPPRESS_TIME;
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+
+ private static class DigestInfo {
+
+ private String opaque;
+ private long nonceValidity;
+ private String key;
+ private Map<String,NonceInfo> cnonces;
+ private boolean validateUri = true;
+
+ private String userName = null;
+ private String method = null;
+ private String uri = null;
+ private String response = null;
+ private String nonce = null;
+ private String nc = null;
+ private String cnonce = null;
+ private String realmName = null;
+ private String qop = null;
+
+ private boolean nonceStale = false;
+
+
+ public DigestInfo(String opaque, long nonceValidity, String key,
+ Map<String,NonceInfo> cnonces, boolean validateUri) {
+ this.opaque = opaque;
+ this.nonceValidity = nonceValidity;
+ this.key = key;
+ this.cnonces = cnonces;
+ this.validateUri = validateUri;
+ }
+
+ public boolean validate(Request request, String authorization,
+ LoginConfig config) {
+ // Validate the authorization credentials format
+ if (authorization == null) {
+ return false;
+ }
+ if (!authorization.startsWith("Digest ")) {
+ return false;
+ }
+ authorization = authorization.substring(7).trim();
+
+ // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
+ String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
+
+ method = request.getMethod();
+ String opaque = null;
+
+ for (int i = 0; i < tokens.length; i++) {
+ String currentToken = tokens[i];
+ if (currentToken.length() == 0)
+ continue;
+
+ int equalSign = currentToken.indexOf('=');
+ if (equalSign < 0) {
+ return false;
+ }
+ String currentTokenName =
+ currentToken.substring(0, equalSign).trim();
+ String currentTokenValue =
+ currentToken.substring(equalSign + 1).trim();
+ if ("username".equals(currentTokenName))
+ userName = removeQuotes(currentTokenValue);
+ if ("realm".equals(currentTokenName))
+ realmName = removeQuotes(currentTokenValue, true);
+ if ("nonce".equals(currentTokenName))
+ nonce = removeQuotes(currentTokenValue);
+ if ("nc".equals(currentTokenName))
+ nc = removeQuotes(currentTokenValue);
+ if ("cnonce".equals(currentTokenName))
+ cnonce = removeQuotes(currentTokenValue);
+ if ("qop".equals(currentTokenName))
+ qop = removeQuotes(currentTokenValue);
+ if ("uri".equals(currentTokenName))
+ uri = removeQuotes(currentTokenValue);
+ if ("response".equals(currentTokenName))
+ response = removeQuotes(currentTokenValue);
+ if ("opaque".equals(currentTokenName))
+ opaque = removeQuotes(currentTokenValue);
+ }
+
+ if ( (userName == null) || (realmName == null) || (nonce == null)
+ || (uri == null) || (response == null) ) {
+ return false;
+ }
+
+ // Validate the URI - should match the request line sent by client
+ if (validateUri) {
+ String uriQuery;
+ String query = request.getQueryString();
+ if (query == null) {
+ uriQuery = request.getRequestURI();
+ } else {
+ uriQuery = request.getRequestURI() + "?" + query;
+ }
+ if (!uri.equals(uriQuery)) {
+ return false;
+ }
+ }
+
+ // Validate the Realm name
+ String lcRealm = config.getRealmName();
+ if (lcRealm == null) {
+ lcRealm = REALM_NAME;
+ }
+ if (!lcRealm.equals(realmName)) {
+ return false;
+ }
+
+ // Validate the opaque string
+ if (!this.opaque.equals(opaque)) {
+ return false;
+ }
+
+ // Validate nonce
+ int i = nonce.indexOf(":");
+ if (i < 0 || (i + 1) == nonce.length()) {
+ return false;
+ }
+ long nonceTime;
+ try {
+ nonceTime = Long.parseLong(nonce.substring(0, i));
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ String md5clientIpTimeKey = nonce.substring(i + 1);
+ long currentTime = System.currentTimeMillis();
+ if ((currentTime - nonceTime) > nonceValidity) {
+ nonceStale = true;
+ return false;
+ }
+ String serverIpTimeKey =
+ request.getRemoteAddr() + ":" + nonceTime + ":" + key;
+ byte[] buffer = null;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(serverIpTimeKey.getBytes());
+ }
+ String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
+ return false;
+ }
+
+ // Validate qop
+ if (qop != null && !QOP.equals(qop)) {
+ return false;
+ }
+
+ // Validate cnonce and nc
+ // Check if presence of nc and nonce is consistent with presence of qop
+ if (qop == null) {
+ if (cnonce != null || nc != null) {
+ return false;
+ }
+ } else {
+ if (cnonce == null || nc == null) {
+ return false;
+ }
+ if (nc.length() != 8) {
+ return false;
+ }
+ long count;
+ try {
+ count = Long.parseLong(nc, 16);
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ NonceInfo info;
+ synchronized (cnonces) {
+ info = cnonces.get(cnonce);
+ }
+ if (info == null) {
+ info = new NonceInfo();
+ } else {
+ if (count <= info.getCount()) {
+ return false;
+ }
+ }
+ info.setCount(count);
+ info.setTimestamp(currentTime);
+ synchronized (cnonces) {
+ cnonces.put(cnonce, info);
+ }
+ }
+ return true;
+ }
+
+ public boolean isNonceStale() {
+ return nonceStale;
+ }
+
+ public Principal authenticate(Realm realm) {
+ // Second MD5 digest used to calculate the digest :
+ // MD5(Method + ":" + uri)
+ String a2 = method + ":" + uri;
+
+ byte[] buffer;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(a2.getBytes());
+ }
+ String md5a2 = md5Encoder.encode(buffer);
+
+ return realm.authenticate(userName, response, nonce, nc, cnonce,
+ qop, realmName, md5a2);
+ }
+
+ }
+
+ private static class NonceInfo {
+ private volatile long count;
+ private volatile long timestamp;
+
+ public void setCount(long l) {
+ count = l;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public void setTimestamp(long l) {
+ timestamp = l;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+ }
}
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/LocalStrings.properties
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/LocalStrings.properties 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/LocalStrings.properties 2012-02-04 00:29:22 UTC (rev 1951)
@@ -12,3 +12,5 @@
authenticator.sessionExpired=The time allowed for the login process has been exceeded. If you wish to continue you must either click back twice and re-click the link you requested or close and re-open your browser
authenticator.unauthorized=Cannot authenticate with the provided credentials
authenticator.userDataConstraint=This request violates a User Data constraint for this application
+
+DigestAuthenticator.cacheRemove=A valid entry has been removed from client nonce cache to make room for new entries. A replay attack is now possible. To prevent the possibility of replay attacks, reduce nonceValidity or increase cnonceCacheSize. Further warnings of this type will be suppressed for 5 minutes
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/mbeans-descriptors.xml
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/mbeans-descriptors.xml 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/authenticator/mbeans-descriptors.xml 2012-02-04 00:29:22 UTC (rev 1951)
@@ -44,10 +44,30 @@
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
-
+
+ <attribute name="cnonceCacheSize"
+ description="The size of the cnonce cache used to prevent replay attacks"
+ type="int"/>
+
<attribute name="entropy"
description="A String initialization parameter used to increase the entropy of the initialization of our random number generator"
type="java.lang.String"/>
+
+ <attribute name="key"
+ description="The secret key used by digest authentication"
+ type="java.lang.String"/>
+
+ <attribute name="nonceValidity"
+ description="The time, in milliseconds, for which a server issued nonce will be valid"
+ type="long"/>
+
+ <attribute name="opaque"
+ description="The opaque server string used by digest authentication"
+ type="java.lang.String"/>
+
+ <attribute name="validateUri"
+ description="Should the uri be validated as required by RFC2617?"
+ type="boolean"/>
</mbean>
<mbean name="FormAuthenticator"
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/connector/OutputBuffer.java
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/connector/OutputBuffer.java 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/connector/OutputBuffer.java 2012-02-04 00:29:22 UTC (rev 1951)
@@ -463,6 +463,10 @@
outputCharChunk.setChars(buf, off, len);
while (outputCharChunk.getLength() > 0) {
conv.convert(outputCharChunk, bb);
+ if (bb.getLength() == 0) {
+ // Break out of the loop if more chars are needed to produce any output
+ break;
+ }
if (outputCharChunk.getLength() > 0) {
bb.flushBuffer();
}
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/realm/RealmBase.java
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/realm/RealmBase.java 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/catalina/realm/RealmBase.java 2012-02-04 00:29:22 UTC (rev 1951)
@@ -367,8 +367,13 @@
String md5a1 = getDigest(username, realm);
if (md5a1 == null)
return null;
- String serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":"
- + cnonce + ":" + qop + ":" + md5a2;
+ String serverDigestValue;
+ if (qop == null) {
+ serverDigestValue = md5a1 + ":" + nOnce + ":" + md5a2;
+ } else {
+ serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":" +
+ cnonce + ":" + qop + ":" + md5a2;
+ }
byte[] valueBytes = null;
if(getDigestEncoding() == null) {
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/MimeHeaders.java
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/MimeHeaders.java 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/MimeHeaders.java 2012-02-04 00:29:22 UTC (rev 1951)
@@ -23,9 +23,6 @@
import org.apache.tomcat.util.buf.MessageBytes;
-/* XXX XXX XXX Need a major rewrite !!!!
- */
-
/**
* This class is used to contain standard internet message headers,
* used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for
@@ -77,12 +74,6 @@
* to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields,
* and reduce to 0 the memory overhead of tomcat.
*
- * TODO:
- * XXX one-buffer parsing - for http ( other protocols don't need that )
- * XXX remove unused methods
- * XXX External enumerations, with 0 GC.
- * XXX use HeaderName ID
- *
*
* @author dac(a)eng.sun.com
* @author James Todd [gonzo(a)eng.sun.com]
@@ -212,9 +203,10 @@
}
/** Initial size - should be == average number of headers per request
- * XXX make it configurable ( fine-tuning of web-apps )
*/
public static final int DEFAULT_HEADER_SIZE = 8;
+ protected static final int MAX_COUNT =
+ Integer.valueOf(System.getProperty("org.apache.tomcat.util.http.MimeHeaders.MAX_COUNT", "128")).intValue();
/**
* The header fields.
@@ -333,6 +325,9 @@
MimeHeaderField mh;
int len = headers.length;
if (count >= len) {
+ if (count >= MAX_COUNT) {
+ throw new IllegalStateException("Header count exceeded allowed maximum: " + MAX_COUNT);
+ }
// expand header list array
MimeHeaderField tmp[] = new MimeHeaderField[count * 2];
System.arraycopy(headers, 0, tmp, 0, len);
@@ -441,9 +436,7 @@
* @param name the name of the header field to be removed
*/
public void removeHeader(String name) {
- // XXX
// warning: rather sticky code; heavily tuned
-
for (int i = 0; i < count; i++) {
if (headers[i].getName().equalsIgnoreCase(name)) {
removeHeader(i--);
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/Parameters.java 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/http/Parameters.java 2012-02-04 00:29:22 UTC (rev 1951)
@@ -40,6 +40,8 @@
protected static final int LAST = -1;
public static final int INITIAL_SIZE = 8;
protected static final String[] ARRAY_TYPE = new String[0];
+ protected static final int MAX_COUNT =
+ Integer.valueOf(System.getProperty("org.apache.tomcat.util.http.Parameters.MAX_COUNT", "512")).intValue();
protected class Field {
MessageBytes name = MessageBytes.newInstance();
@@ -212,6 +214,9 @@
int len = fields.length;
int pos = count;
if (count >= len) {
+ if (count >= MAX_COUNT) {
+ throw new IllegalStateException("Parameter count exceeded allowed maximum: " + MAX_COUNT);
+ }
// expand header list array
Field tmp[] = new Field[pos * 2];
System.arraycopy(fields, 0, tmp, 0, len);
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/net/AprEndpoint.java
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-04 00:29:22 UTC (rev 1951)
@@ -301,7 +301,7 @@
/**
* Use sendfile for sending static files.
*/
- protected boolean useSendfile = Library.APR_HAS_SENDFILE;
+ protected boolean useSendfile = false; /* CVE-2011-2526 */
public void setUseSendfile(boolean useSendfile) { this.useSendfile = useSendfile; }
public boolean getUseSendfile() { return useSendfile; }
Modified: branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/webapps/docs/changelog.xml
===================================================================
--- branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/webapps/docs/changelog.xml 2012-02-04 00:26:28 UTC (rev 1950)
+++ branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/webapps/docs/changelog.xml 2012-02-04 00:29:22 UTC (rev 1951)
@@ -28,6 +28,9 @@
<fix>
<jboss-jira>JBPAPP-5293</jboss-jira>: ConcurrentModificationException in HandshakeCompletedNotify-Thread. (remm)
</fix>
+ <fix>
+ <jboss-jira>JBPAPP-8050</jboss-jira>: Improve DIGEST authentication security. (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">
12 years, 10 months
JBossWeb SVN: r1950 - in branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051: java/org/apache/catalina/authenticator and 5 other directories.
by jbossweb-commits@lists.jboss.org
Author: bmaxwell
Date: 2012-02-03 19:26:28 -0500 (Fri, 03 Feb 2012)
New Revision: 1950
Modified:
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.properties.default
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.xml
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/AuthenticatorBase.java
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/DigestAuthenticator.java
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/LocalStrings.properties
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/mbeans-descriptors.xml
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/connector/OutputBuffer.java
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/realm/RealmBase.java
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/MimeHeaders.java
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/Parameters.java
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/net/AprEndpoint.java
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/webapps/docs/changelog.xml
Log:
[JBPAPP-8051] CVE Fixes: 2011-2204, 2011-2729, 2011-1184, 2011-2526, 2011-4858, 2011-4610
Property changes on: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051
___________________________________________________________________
Added: svn:mergeinfo
+ /branches/2.1.x:1871-1872,1898,1902
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.properties.default
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.properties.default 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.properties.default 2012-02-04 00:26:28 UTC (rev 1950)
@@ -12,9 +12,9 @@
# ----- Version Control Flags -----
version.major=2
version.minor=1
-version.build=4
+version.build=7
version.patch=0
-version.tag=SNAPSHOT
+version.tag=JBPAPP-8051
# ----- Default Base Path for Dependent Packages -----
# Please note this path must be absolute, not relative,
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.xml
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.xml 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/build.xml 2012-02-04 00:26:28 UTC (rev 1950)
@@ -16,9 +16,9 @@
<property name="year" value="2008" />
<property name="version.major" value="2" />
<property name="version.minor" value="1" />
- <property name="version.build" value="0" />
+ <property name="version.build" value="7" />
<property name="version.patch" value="0" />
- <property name="version.tag" value="SNAPSHOT" />
+ <property name="version.tag" value="JBPAPP-8051" />
<property name="version" value="${version.major}.${version.minor}.${version.build}.${version.tag}" />
<property name="version.number" value="${version.major}.${version.minor}.${version.build}.${version.patch}" />
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/AuthenticatorBase.java
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2012-02-04 00:26:28 UTC (rev 1950)
@@ -89,8 +89,17 @@
*/
protected static final String DEFAULT_ALGORITHM = "MD5";
+ /**
+ * Authentication header
+ */
+ protected static final String AUTH_HEADER_NAME = "WWW-Authenticate";
/**
+ * Default authentication realm name.
+ */
+ protected static final String REALM_NAME = "Authentication required";
+
+ /**
* The number of random bytes to include when generating a
* session identifier.
*/
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-02-04 00:26:28 UTC (rev 1950)
@@ -22,12 +22,18 @@
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
import java.security.Principal;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletResponse;
-
+import org.apache.catalina.Container;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
import org.apache.catalina.Realm;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
@@ -67,6 +73,11 @@
"org.apache.catalina.authenticator.DigestAuthenticator/1.0";
+ /**
+ * Tomcat's DIGEST implementation only supports auth quality of protection.
+ */
+ protected static final String QOP = "auth";
+
// ----------------------------------------------------------- Constructors
@@ -92,17 +103,49 @@
/**
+ * List of client nonce values currently being tracked
+ */
+ protected Map<String,NonceInfo> cnonces;
+
+
+ /**
+ * Maximum number of client nonces to keep in the cache. If not specified,
+ * the default value of 1000 is used.
+ */
+ protected int cnonceCacheSize = 1000;
+
+
+ /**
* Private key.
*/
- protected String key = "Catalina";
+ protected String key = null;
- // ------------------------------------------------------------- Properties
+ /**
+ * How long server nonces are valid for in milliseconds. Defaults to 5
+ * minutes.
+ */
+ protected long nonceValidity = 5 * 60 * 1000;
/**
+ * Opaque string.
+ */
+ protected String opaque;
+
+
+ /**
+ * Should the URI be validated as required by RFC2617? Can be disabled in
+ * reverse proxies where the proxy has modified the URI.
+ */
+ protected boolean validateUri = true;
+
+ // ------------------------------------------------------------- Properties
+
+ /**
* Return descriptive information about this Valve implementation.
*/
+ @Override
public String getInfo() {
return (info);
@@ -110,9 +153,58 @@
}
+ public int getCnonceCacheSize() {
+ return cnonceCacheSize;
+ }
+
+
+ public void setCnonceCacheSize(int cnonceCacheSize) {
+ this.cnonceCacheSize = cnonceCacheSize;
+ }
+
+
+ public String getKey() {
+ return key;
+ }
+
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+
+ public long getNonceValidity() {
+ return nonceValidity;
+ }
+
+
+ public void setNonceValidity(long nonceValidity) {
+ this.nonceValidity = nonceValidity;
+ }
+
+
+ public String getOpaque() {
+ return opaque;
+ }
+
+
+ public void setOpaque(String opaque) {
+ this.opaque = opaque;
+ }
+
+
+ public boolean isValidateUri() {
+ return validateUri;
+ }
+
+
+ public void setValidateUri(boolean validateUri) {
+ this.validateUri = validateUri;
+ }
+
+
// --------------------------------------------------------- Public Methods
-
/**
* Authenticate the user making this request, based on the specified
* login configuration. Return <code>true</code> if any specified
@@ -126,6 +218,7 @@
*
* @exception IOException if an input/output error occurs
*/
+ @Override
public boolean authenticate(Request request,
Response response,
LoginConfig config)
@@ -172,8 +265,13 @@
// Validate any credentials already included with this request
String authorization = request.getHeader("authorization");
+ DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
+ getKey(), cnonces, isValidateUri());
if (authorization != null) {
- principal = findPrincipal(request, authorization, context.getRealm());
+ if (digestInfo.validate(request, authorization, config)) {
+ principal = digestInfo.authenticate(context.getRealm());
+ }
+
if (principal != null) {
String username = parseUsername(authorization);
register(request, response, principal,
@@ -185,11 +283,12 @@
// Send an "unauthorized" response and an appropriate challenge
- // Next, generate a nOnce token (that is a token which is supposed
+ // Next, generate a nonce token (that is a token which is supposed
// to be unique).
- String nOnce = generateNOnce(request);
+ String nonce = generateNonce(request);
- setAuthenticateHeader(request, response, config, nOnce);
+ setAuthenticateHeader(request, response, config, nonce,
+ digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
// hres.flushBuffer();
return (false);
@@ -201,92 +300,6 @@
/**
- * Parse the specified authorization credentials, and return the
- * associated Principal that these credentials authenticate (if any)
- * from the specified Realm. If there is no such Principal, return
- * <code>null</code>.
- *
- * @param request HTTP servlet request
- * @param authorization Authorization credentials from this request
- * @param realm Realm used to authenticate Principals
- */
- protected static Principal findPrincipal(Request request,
- String authorization,
- Realm realm) {
-
- //System.out.println("Authorization token : " + authorization);
- // Validate the authorization credentials format
- if (authorization == null)
- return (null);
- if (!authorization.startsWith("Digest "))
- return (null);
- authorization = authorization.substring(7).trim();
-
- // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
- String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
-
- String userName = null;
- String realmName = null;
- String nOnce = null;
- String nc = null;
- String cnonce = null;
- String qop = null;
- String uri = null;
- String response = null;
- String method = request.getMethod();
-
- for (int i = 0; i < tokens.length; i++) {
- String currentToken = tokens[i];
- if (currentToken.length() == 0)
- continue;
-
- int equalSign = currentToken.indexOf('=');
- if (equalSign < 0)
- return null;
- String currentTokenName =
- currentToken.substring(0, equalSign).trim();
- String currentTokenValue =
- currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
- userName = removeQuotes(currentTokenValue);
- if ("realm".equals(currentTokenName))
- realmName = removeQuotes(currentTokenValue, true);
- if ("nonce".equals(currentTokenName))
- nOnce = removeQuotes(currentTokenValue);
- if ("nc".equals(currentTokenName))
- nc = removeQuotes(currentTokenValue);
- if ("cnonce".equals(currentTokenName))
- cnonce = removeQuotes(currentTokenValue);
- if ("qop".equals(currentTokenName))
- qop = removeQuotes(currentTokenValue);
- if ("uri".equals(currentTokenName))
- uri = removeQuotes(currentTokenValue);
- if ("response".equals(currentTokenName))
- response = removeQuotes(currentTokenValue);
- }
-
- if ( (userName == null) || (realmName == null) || (nOnce == null)
- || (uri == null) || (response == null) )
- return null;
-
- // Second MD5 digest used to calculate the digest :
- // MD5(Method + ":" + uri)
- String a2 = method + ":" + uri;
- //System.out.println("A2:" + a2);
-
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(a2.getBytes());
- }
- String md5a2 = md5Encoder.encode(buffer);
-
- return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop,
- realmName, md5a2));
-
- }
-
-
- /**
* Parse the username from the specified authorization string. If none
* can be identified, return <code>null</code>
*
@@ -294,7 +307,6 @@
*/
protected String parseUsername(String authorization) {
- //System.out.println("Authorization token : " + authorization);
// Validate the authorization credentials format
if (authorization == null)
return (null);
@@ -354,20 +366,20 @@
*
* @param request HTTP Servlet request
*/
- protected String generateNOnce(Request request) {
+ protected String generateNonce(Request request) {
long currentTime = System.currentTimeMillis();
- String nOnceValue = request.getRemoteAddr() + ":" +
- currentTime + ":" + key;
+
+ String ipTimeKey =
+ request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
- byte[] buffer = null;
+ byte[] buffer;
synchronized (md5Helper) {
- buffer = md5Helper.digest(nOnceValue.getBytes());
+ buffer = md5Helper.digest(ipTimeKey.getBytes());
}
- nOnceValue = md5Encoder.encode(buffer);
- return nOnceValue;
+ return currentTime + ":" + md5Encoder.encode(buffer);
}
@@ -379,7 +391,7 @@
* WWW-Authenticate = "WWW-Authenticate" ":" "Digest"
* digest-challenge
*
- * digest-challenge = 1#( realm | [ domain ] | nOnce |
+ * digest-challenge = 1#( realm | [ domain ] | nonce |
* [ digest-opaque ] |[ stale ] | [ algorithm ] )
*
* realm = "realm" "=" realm-value
@@ -396,30 +408,303 @@
* @param response HTTP Servlet response
* @param config Login configuration describing how authentication
* should be performed
- * @param nOnce nonce token
+ * @param nonce nonce token
*/
protected void setAuthenticateHeader(Request request,
Response response,
LoginConfig config,
- String nOnce) {
+ String nonce,
+ boolean isNonceStale) {
// Get the realm name
String realmName = config.getRealmName();
if (realmName == null)
- realmName = request.getServerName() + ":"
- + request.getServerPort();
+ realmName = REALM_NAME;
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(nOnce.getBytes());
- }
+ String authenticateHeader;
+ if (isNonceStale) {
+ authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+ "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
+ getOpaque() + "\", stale=true";
+ } else {
+ authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+ "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
+ getOpaque() + "\"";
+ }
- String authenticateHeader = "Digest realm=\"" + realmName + "\", "
- + "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\""
- + md5Encoder.encode(buffer) + "\"";
response.setHeader("WWW-Authenticate", authenticateHeader);
}
+ // ------------------------------------------------------- Lifecycle Methods
+
+ @Override
+ public void start() throws LifecycleException {
+ super.start();
+
+ // Generate a random secret key
+ if (getKey() == null) {
+ setKey(generateSessionId());
+ }
+
+ // Generate the opaque string the same way
+ if (getOpaque() == null) {
+ setOpaque(generateSessionId());
+ }
+
+ cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
+ private static final long serialVersionUID = 1L;
+ private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
+
+ private long lastLog = 0;
+
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<String,NonceInfo> eldest) {
+ // This is called from a sync so keep it simple
+ long currentTime = System.currentTimeMillis();
+ if (size() > getCnonceCacheSize()) {
+ if (lastLog < currentTime &&
+ currentTime - eldest.getValue().getTimestamp() <
+ getNonceValidity()) {
+ // Replay attack is possible
+ log.warn(sm.getString(
+ "digestAuthenticator.cacheRemove"));
+ lastLog = currentTime + LOG_SUPPRESS_TIME;
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+
+ private static class DigestInfo {
+
+ private String opaque;
+ private long nonceValidity;
+ private String key;
+ private Map<String,NonceInfo> cnonces;
+ private boolean validateUri = true;
+
+ private String userName = null;
+ private String method = null;
+ private String uri = null;
+ private String response = null;
+ private String nonce = null;
+ private String nc = null;
+ private String cnonce = null;
+ private String realmName = null;
+ private String qop = null;
+
+ private boolean nonceStale = false;
+
+
+ public DigestInfo(String opaque, long nonceValidity, String key,
+ Map<String,NonceInfo> cnonces, boolean validateUri) {
+ this.opaque = opaque;
+ this.nonceValidity = nonceValidity;
+ this.key = key;
+ this.cnonces = cnonces;
+ this.validateUri = validateUri;
+ }
+
+ public boolean validate(Request request, String authorization,
+ LoginConfig config) {
+ // Validate the authorization credentials format
+ if (authorization == null) {
+ return false;
+ }
+ if (!authorization.startsWith("Digest ")) {
+ return false;
+ }
+ authorization = authorization.substring(7).trim();
+
+ // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
+ String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
+
+ method = request.getMethod();
+ String opaque = null;
+
+ for (int i = 0; i < tokens.length; i++) {
+ String currentToken = tokens[i];
+ if (currentToken.length() == 0)
+ continue;
+
+ int equalSign = currentToken.indexOf('=');
+ if (equalSign < 0) {
+ return false;
+ }
+ String currentTokenName =
+ currentToken.substring(0, equalSign).trim();
+ String currentTokenValue =
+ currentToken.substring(equalSign + 1).trim();
+ if ("username".equals(currentTokenName))
+ userName = removeQuotes(currentTokenValue);
+ if ("realm".equals(currentTokenName))
+ realmName = removeQuotes(currentTokenValue, true);
+ if ("nonce".equals(currentTokenName))
+ nonce = removeQuotes(currentTokenValue);
+ if ("nc".equals(currentTokenName))
+ nc = removeQuotes(currentTokenValue);
+ if ("cnonce".equals(currentTokenName))
+ cnonce = removeQuotes(currentTokenValue);
+ if ("qop".equals(currentTokenName))
+ qop = removeQuotes(currentTokenValue);
+ if ("uri".equals(currentTokenName))
+ uri = removeQuotes(currentTokenValue);
+ if ("response".equals(currentTokenName))
+ response = removeQuotes(currentTokenValue);
+ if ("opaque".equals(currentTokenName))
+ opaque = removeQuotes(currentTokenValue);
+ }
+
+ if ( (userName == null) || (realmName == null) || (nonce == null)
+ || (uri == null) || (response == null) ) {
+ return false;
+ }
+
+ // Validate the URI - should match the request line sent by client
+ if (validateUri) {
+ String uriQuery;
+ String query = request.getQueryString();
+ if (query == null) {
+ uriQuery = request.getRequestURI();
+ } else {
+ uriQuery = request.getRequestURI() + "?" + query;
+ }
+ if (!uri.equals(uriQuery)) {
+ return false;
+ }
+ }
+
+ // Validate the Realm name
+ String lcRealm = config.getRealmName();
+ if (lcRealm == null) {
+ lcRealm = REALM_NAME;
+ }
+ if (!lcRealm.equals(realmName)) {
+ return false;
+ }
+
+ // Validate the opaque string
+ if (!this.opaque.equals(opaque)) {
+ return false;
+ }
+
+ // Validate nonce
+ int i = nonce.indexOf(":");
+ if (i < 0 || (i + 1) == nonce.length()) {
+ return false;
+ }
+ long nonceTime;
+ try {
+ nonceTime = Long.parseLong(nonce.substring(0, i));
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ String md5clientIpTimeKey = nonce.substring(i + 1);
+ long currentTime = System.currentTimeMillis();
+ if ((currentTime - nonceTime) > nonceValidity) {
+ nonceStale = true;
+ return false;
+ }
+ String serverIpTimeKey =
+ request.getRemoteAddr() + ":" + nonceTime + ":" + key;
+ byte[] buffer = null;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(serverIpTimeKey.getBytes());
+ }
+ String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
+ return false;
+ }
+
+ // Validate qop
+ if (qop != null && !QOP.equals(qop)) {
+ return false;
+ }
+
+ // Validate cnonce and nc
+ // Check if presence of nc and nonce is consistent with presence of qop
+ if (qop == null) {
+ if (cnonce != null || nc != null) {
+ return false;
+ }
+ } else {
+ if (cnonce == null || nc == null) {
+ return false;
+ }
+ if (nc.length() != 8) {
+ return false;
+ }
+ long count;
+ try {
+ count = Long.parseLong(nc, 16);
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ NonceInfo info;
+ synchronized (cnonces) {
+ info = cnonces.get(cnonce);
+ }
+ if (info == null) {
+ info = new NonceInfo();
+ } else {
+ if (count <= info.getCount()) {
+ return false;
+ }
+ }
+ info.setCount(count);
+ info.setTimestamp(currentTime);
+ synchronized (cnonces) {
+ cnonces.put(cnonce, info);
+ }
+ }
+ return true;
+ }
+
+ public boolean isNonceStale() {
+ return nonceStale;
+ }
+
+ public Principal authenticate(Realm realm) {
+ // Second MD5 digest used to calculate the digest :
+ // MD5(Method + ":" + uri)
+ String a2 = method + ":" + uri;
+
+ byte[] buffer;
+ synchronized (md5Helper) {
+ buffer = md5Helper.digest(a2.getBytes());
+ }
+ String md5a2 = md5Encoder.encode(buffer);
+
+ return realm.authenticate(userName, response, nonce, nc, cnonce,
+ qop, realmName, md5a2);
+ }
+
+ }
+
+ private static class NonceInfo {
+ private volatile long count;
+ private volatile long timestamp;
+
+ public void setCount(long l) {
+ count = l;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public void setTimestamp(long l) {
+ timestamp = l;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+ }
}
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/LocalStrings.properties
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/LocalStrings.properties 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/LocalStrings.properties 2012-02-04 00:26:28 UTC (rev 1950)
@@ -12,3 +12,5 @@
authenticator.sessionExpired=The time allowed for the login process has been exceeded. If you wish to continue you must either click back twice and re-click the link you requested or close and re-open your browser
authenticator.unauthorized=Cannot authenticate with the provided credentials
authenticator.userDataConstraint=This request violates a User Data constraint for this application
+
+DigestAuthenticator.cacheRemove=A valid entry has been removed from client nonce cache to make room for new entries. A replay attack is now possible. To prevent the possibility of replay attacks, reduce nonceValidity or increase cnonceCacheSize. Further warnings of this type will be suppressed for 5 minutes
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/mbeans-descriptors.xml
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/mbeans-descriptors.xml 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/authenticator/mbeans-descriptors.xml 2012-02-04 00:26:28 UTC (rev 1950)
@@ -44,10 +44,30 @@
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
-
+
+ <attribute name="cnonceCacheSize"
+ description="The size of the cnonce cache used to prevent replay attacks"
+ type="int"/>
+
<attribute name="entropy"
description="A String initialization parameter used to increase the entropy of the initialization of our random number generator"
type="java.lang.String"/>
+
+ <attribute name="key"
+ description="The secret key used by digest authentication"
+ type="java.lang.String"/>
+
+ <attribute name="nonceValidity"
+ description="The time, in milliseconds, for which a server issued nonce will be valid"
+ type="long"/>
+
+ <attribute name="opaque"
+ description="The opaque server string used by digest authentication"
+ type="java.lang.String"/>
+
+ <attribute name="validateUri"
+ description="Should the uri be validated as required by RFC2617?"
+ type="boolean"/>
</mbean>
<mbean name="FormAuthenticator"
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/connector/OutputBuffer.java
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/connector/OutputBuffer.java 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/connector/OutputBuffer.java 2012-02-04 00:26:28 UTC (rev 1950)
@@ -461,6 +461,10 @@
outputCharChunk.setChars(buf, off, len);
while (outputCharChunk.getLength() > 0) {
conv.convert(outputCharChunk, bb);
+ if (bb.getLength() == 0) {
+ // Break out of the loop if more chars are needed to produce any output
+ break;
+ }
if (outputCharChunk.getLength() > 0) {
bb.flushBuffer();
}
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/realm/RealmBase.java
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/realm/RealmBase.java 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/catalina/realm/RealmBase.java 2012-02-04 00:26:28 UTC (rev 1950)
@@ -367,8 +367,13 @@
String md5a1 = getDigest(username, realm);
if (md5a1 == null)
return null;
- String serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":"
- + cnonce + ":" + qop + ":" + md5a2;
+ String serverDigestValue;
+ if (qop == null) {
+ serverDigestValue = md5a1 + ":" + nOnce + ":" + md5a2;
+ } else {
+ serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":" +
+ cnonce + ":" + qop + ":" + md5a2;
+ }
byte[] valueBytes = null;
if(getDigestEncoding() == null) {
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/MimeHeaders.java
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/MimeHeaders.java 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/MimeHeaders.java 2012-02-04 00:26:28 UTC (rev 1950)
@@ -23,9 +23,6 @@
import org.apache.tomcat.util.buf.MessageBytes;
-/* XXX XXX XXX Need a major rewrite !!!!
- */
-
/**
* This class is used to contain standard internet message headers,
* used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for
@@ -77,12 +74,6 @@
* to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields,
* and reduce to 0 the memory overhead of tomcat.
*
- * TODO:
- * XXX one-buffer parsing - for http ( other protocols don't need that )
- * XXX remove unused methods
- * XXX External enumerations, with 0 GC.
- * XXX use HeaderName ID
- *
*
* @author dac(a)eng.sun.com
* @author James Todd [gonzo(a)eng.sun.com]
@@ -212,9 +203,10 @@
}
/** Initial size - should be == average number of headers per request
- * XXX make it configurable ( fine-tuning of web-apps )
*/
public static final int DEFAULT_HEADER_SIZE = 8;
+ protected static final int MAX_COUNT =
+ Integer.valueOf(System.getProperty("org.apache.tomcat.util.http.MimeHeaders.MAX_COUNT", "128")).intValue();
/**
* The header fields.
@@ -333,6 +325,9 @@
MimeHeaderField mh;
int len = headers.length;
if (count >= len) {
+ if (count >= MAX_COUNT) {
+ throw new IllegalStateException("Header count exceeded allowed maximum: " + MAX_COUNT);
+ }
// expand header list array
MimeHeaderField tmp[] = new MimeHeaderField[count * 2];
System.arraycopy(headers, 0, tmp, 0, len);
@@ -441,9 +436,7 @@
* @param name the name of the header field to be removed
*/
public void removeHeader(String name) {
- // XXX
// warning: rather sticky code; heavily tuned
-
for (int i = 0; i < count; i++) {
if (headers[i].getName().equalsIgnoreCase(name)) {
removeHeader(i--);
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/Parameters.java
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/Parameters.java 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/http/Parameters.java 2012-02-04 00:26:28 UTC (rev 1950)
@@ -40,6 +40,8 @@
protected static final int LAST = -1;
public static final int INITIAL_SIZE = 8;
protected static final String[] ARRAY_TYPE = new String[0];
+ protected static final int MAX_COUNT =
+ Integer.valueOf(System.getProperty("org.apache.tomcat.util.http.Parameters.MAX_COUNT", "512")).intValue();
protected class Field {
MessageBytes name = MessageBytes.newInstance();
@@ -212,6 +214,9 @@
int len = fields.length;
int pos = count;
if (count >= len) {
+ if (count >= MAX_COUNT) {
+ throw new IllegalStateException("Parameter count exceeded allowed maximum: " + MAX_COUNT);
+ }
// expand header list array
Field tmp[] = new Field[pos * 2];
System.arraycopy(fields, 0, tmp, 0, len);
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/net/AprEndpoint.java
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/java/org/apache/tomcat/util/net/AprEndpoint.java 2012-02-04 00:26:28 UTC (rev 1950)
@@ -301,7 +301,7 @@
/**
* Use sendfile for sending static files.
*/
- protected boolean useSendfile = Library.APR_HAS_SENDFILE;
+ protected boolean useSendfile = false; /* CVE-2011-2526 */
public void setUseSendfile(boolean useSendfile) { this.useSendfile = useSendfile; }
public boolean getUseSendfile() { return useSendfile; }
Modified: branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/webapps/docs/changelog.xml
===================================================================
--- branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/webapps/docs/changelog.xml 2012-02-03 22:53:50 UTC (rev 1949)
+++ branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/webapps/docs/changelog.xml 2012-02-04 00:26:28 UTC (rev 1950)
@@ -16,6 +16,24 @@
<body>
+<section name="JBoss Web 2.1.7.GA_JBPAPP-8051 (jfclere)">
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ Improve DIGEST authentication security. (remm)
+ </fix>
+ </changelog>
+ </subsection>
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ Add system properties which restrict parameter count (org.apache.tomcat.util.http.Parameters.MAX_COUNT
+ default to 512) and header count (org.apache.tomcat.util.http.MimeHeaders.MAX_COUNT to 128). (remm)
+ </fix>
+ </changelog>
+ </subsection>
+</section>
+
<section name="JBoss Web 2.1.7.GA (remm)">
<subsection name="Coyote">
<changelog>
12 years, 10 months
JBossWeb SVN: r1949 - branches.
by jbossweb-commits@lists.jboss.org
Author: bmaxwell
Date: 2012-02-03 17:53:50 -0500 (Fri, 03 Feb 2012)
New Revision: 1949
Added:
branches/JBOSSWEB_2_1_7_GA_JBPAPP-8051/
Log:
[JBPAPP-8051] create one off patch branch
12 years, 10 months
JBossWeb SVN: r1948 - branches.
by jbossweb-commits@lists.jboss.org
Author: bmaxwell
Date: 2012-02-03 17:20:09 -0500 (Fri, 03 Feb 2012)
New Revision: 1948
Added:
branches/JBOSSWEB_2_1_11_GA_JBPAPP-8050/
Log:
[JBPAPP-8050] create one off patch branch
12 years, 10 months
JBossWeb SVN: r1947 - branches.
by jbossweb-commits@lists.jboss.org
Author: bmaxwell
Date: 2012-02-03 13:03:48 -0500 (Fri, 03 Feb 2012)
New Revision: 1947
Added:
branches/JBOSSWEB_2_1_10_GA_JBPAPP-8049/
Log:
[JBPAPP-8049] one off patch branch
12 years, 10 months