Sophie

Sophie

distrib > Mageia > 3 > i586 > media > core-release-src > by-pkgid > 13381d75d67ffe2e5359fb12f7794ba5 > files > 31

tomcat5-5.5.31-14.mga3.src.rpm

Index: container/catalina/src/share/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/authenticator/DigestAuthenticator.java
+++ container/catalina/src/share/org/apache/catalina/authenticator/DigestAuthenticator.java	2011-12-08 16:45:06.012335133 +0100
@@ -23,6 +23,8 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.StringTokenizer;
 
 import javax.servlet.http.HttpServletResponse;
@@ -30,6 +32,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.catalina.LifecycleException;
 import org.apache.catalina.Realm;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
@@ -47,8 +50,8 @@
  * @version $Id: DigestAuthenticator.java 939523 2010-04-30 00:28:42Z kkolinko $
  */
 
-public class DigestAuthenticator
-    extends AuthenticatorBase {
+public class DigestAuthenticator extends AuthenticatorBase {
+
     private static Log log = LogFactory.getLog(DigestAuthenticator.class);
 
 
@@ -67,6 +70,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,15 +100,46 @@
 
 
     /**
+     * List of client nonce values currently being tracked
+     */
+    protected Map 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.
      */
     public String getInfo() {
@@ -110,9 +149,58 @@
     }
 
 
-    // --------------------------------------------------------- Public Methods
+    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
@@ -172,8 +260,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 +278,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 +295,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 +302,6 @@
      */
     protected String parseUsername(String authorization) {
 
-        //System.out.println("Authorization token : " + authorization);
         // Validate the authorization credentials format
         if (authorization == null)
             return (null);
@@ -354,20 +361,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 +386,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 +403,300 @@
      * @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_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
+    
+    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() {
+
+            private static final long serialVersionUID = 1L;
+            private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
+
+            private long lastLog = 0;
+
+            protected boolean removeEldestEntry(Map.Entry eldest) {
+                // This is called from a sync so keep it simple
+                long currentTime = System.currentTimeMillis();
+                if (size() > getCnonceCacheSize()) {
+                    if (lastLog < currentTime && (currentTime -
+                            ((NonceInfo) 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 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 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 = (NonceInfo) 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;
+        }
+    }
 }

--- container/catalina/src/share/org/apache/catalina/authenticator/LocalStrings.properties	2011/08/18 16:49:39	1159308
+++ container/catalina/src/share/org/apache/catalina/authenticator/LocalStrings.properties	2011/08/18 16:49:39	1159309
@@ -28,5 +28,7 @@
 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.
+ 
 formAuthenticator.forwardErrorFail=Unexpected error forwarding to error page
 formAuthenticator.forwardLoginFail=Unexpected error forwarding to login page

--- container/catalina/src/share/org/apache/catalina/authenticator/mbeans-descriptors.xml	2011/08/18 16:49:39	1159308
+++ container/catalina/src/share/org/apache/catalina/authenticator/mbeans-descriptors.xml	2011/08/18 16:49:39	1159309
@@ -60,10 +60,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"

--- container/catalina/src/share/org/apache/catalina/realm/RealmBase.java	2011/08/18 16:49:39	1159308
+++ container/catalina/src/share/org/apache/catalina/realm/RealmBase.java	2011/08/18 16:49:39	1159309
@@ -352,22 +352,27 @@
      *
      * @param username Username of the Principal to look up
      * @param clientDigest Digest which has been submitted by the client
-     * @param nOnce Unique (or supposedly unique) token which has been used
+     * @param nonce Unique (or supposedly unique) token which has been used
      * for this request
      * @param realm Realm name
      * @param md5a2 Second MD5 digest used to calculate the digest :
      * MD5(Method + ":" + uri)
      */
     public Principal authenticate(String username, String clientDigest,
-                                  String nOnce, String nc, String cnonce,
+                                  String nonce, String nc, String cnonce,
                                   String qop, String realm,
                                   String md5a2) {
 
         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) {
@@ -389,7 +394,7 @@
 
         if (log.isDebugEnabled()) {
             log.debug("Digest : " + clientDigest + " Username:" + username 
-                    + " ClientSigest:" + clientDigest + " nOnce:" + nOnce 
+                    + " ClientSigest:" + clientDigest + " nonce:" + nonce 
                     + " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop 
                     + " realm:" + realm + "md5a2:" + md5a2 
                     + " Server digest:" + serverDigest);

--- container/webapps/docs/config/valve.xml	2011/08/18 16:49:39	1159308
+++ container/webapps/docs/config/valve.xml	2011/08/18 16:49:39	1159309
@@ -504,6 +504,12 @@
         used.</p>
       </attribute>
 
+      <attribute name="cnonceCacheSize" required="false">
+        <p>To protect against replay attacks, the DIGEST authenticator tracks
+        client nonce and nonce count values. This attribute controls the size
+        of that cache. If not specified, the default value of 1000 is used.</p>
+      </attribute>
+
       <attribute name="disableProxyCaching" required="false">
         <p>Controls the caching of pages that are protected by security
         constraints. Setting this to <code>false</code> may help work around
@@ -514,6 +520,26 @@
         <code>true</code> will be used.</p>
       </attribute>
 
+      <attribute name="key" required="false">
+        <p>The secret key used by digest authentication. If not set, a secure
+        random value is generated. This should normally only be set when it is
+        necessary to keep key values constant either across server restarts
+        and/or across a cluster.</p>
+      </attribute>
+
+      <attribute name="nonceValidity" required="false">
+        <p>The time, in milliseconds, that a server generated nonce will be
+        considered valid for use in authentication. If not specified, the
+        default value of 300000 (5 minutes) will be used.</p>
+      </attribute>
+
+      <attribute name="opaque" required="false">
+        <p>The opaque server string used by digest authentication. If not set, a
+        random value is generated. This should normally only be set when it is
+        necessary to keep opaque values constant either across server restarts
+        and/or across a cluster.</p>
+      </attribute>
+
       <attribute name="securePagesWithPragma" required="false">
         <p>Controls the caching of pages that are protected by security
         constraints. Setting this to <code>false</code> may help work around
@@ -523,6 +549,14 @@
         If not set, the default value of <code>true</code> will be used.</p>
       </attribute>
 
+      <attribute name="validateUri" required="false">
+        <p>Should the URI be validated as required by RFC2617? If not specified,
+        the default value of <code>true</code> will be used. This should
+        normally only be set when Tomcat is located behind a reverse proxy and
+        the proxy is modifying the URI passed to Tomcat such that DIGEST
+        authentication always fails.</p>
+      </attribute>
+
     </attributes>
 
   </subsection>