Hi,

I need to get a code review for,
CR 6687282 : "URLConnection for HTTPS connection through Proxy w/ Digest Authentication gives 400 Bad Request".

Digest authentication uses the request-URI as part of its algorithm when generating the response hash. The request-URI is usually the abs_path of the uri, but not always. When tunneling the target servers 'host:port' is used as the request-URI, e.g.
   "CONNECT verisign.com:443 HTTP/1.1"

The implementation in sun.net.www.protocol.http.DigestAuthentication only uses the abs_path of the uri. This is incorrect and the target servers 'host:port' should be used when tunneling. Also, the request method ( GET/POST/CONNECT ) is used when generating the response hash. This needs to be "CONNECT" when tunneling.

(diffs below)

Thanks,
-Chris.
--- old/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java      
Wed Apr 16 11:24:47 2008
+++ new/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java      
Wed Apr 16 11:24:47 2008
@@ -75,6 +75,8 @@
 
     private static Logger logger = 
Logger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
 
+    static String HTTP_CONNECT = "CONNECT";
+
     static final String version;
     public static final String userAgent;
 
@@ -265,7 +267,21 @@
 
     /* If we decide we want to reuse a client, we put it here */
     private HttpClient reuseClient = null;
+    
+    /* Tunnel states */
+    enum TunnelState {
+        /* No tunnel */
+        NONE,
 
+        /* Setting up a tunnel */
+        SETUP,
+
+        /* Tunnel has been successfully setup */
+        TUNNELING
+    }
+
+    private TunnelState tunnelState = TunnelState.NONE;
+
     /* Redefine timeouts from java.net.URLConnection as we nee -1 to mean
      * not set. This is to ensure backward compatibility.
      */
@@ -338,7 +354,7 @@
          * others that have been set
          */
         // send any pre-emptive authentication
-        if (http.usingProxy) {
+        if (http.usingProxy && tunnelState() != TunnelState.TUNNELING) {
             setPreemptiveProxyAuthentication(requests);
         }
         if (!setRequests) {
@@ -1404,11 +1420,17 @@
             String raw = auth.raw();
             if (proxyAuthentication.isAuthorizationStale (raw)) {
                 /* we can retry with the current credentials */
-                requests.set (proxyAuthentication.getHeaderName(),
-                              proxyAuthentication.getHeaderValue(
-                                                     url, method));
+                String value;
+                if (tunnelState() == TunnelState.SETUP &&
+                      proxyAuthentication instanceof DigestAuthentication) {
+                    value = ((DigestAuthentication)proxyAuthentication)
+                            .getHeaderValue(connectRequestURI(url), 
HTTP_CONNECT);
+                } else {
+                    value = proxyAuthentication.getHeaderValue(url, method);
+                }
+                requests.set(proxyAuthentication.getHeaderName(), value);
                 currentProxyCredentials = proxyAuthentication;
-                return  proxyAuthentication;
+                return proxyAuthentication;
             } else {
                 proxyAuthentication.removeFromCache();
             }
@@ -1417,6 +1439,24 @@
         currentProxyCredentials = proxyAuthentication;
         return  proxyAuthentication;
     }
+    
+    /**
+     * Returns the tunnel state.
+     * 
+     * @return  the state
+     */
+    TunnelState tunnelState() {
+        return tunnelState;
+    }
+    
+    /**
+     * Set the tunneling status.
+     * 
+     * @param  the state
+     */
+    void setTunnelState(TunnelState tunnelState) {
+        this.tunnelState = tunnelState;
+    }
 
     /**
      * establish a tunnel through proxy server
@@ -1437,6 +1477,9 @@
         boolean inNegotiateProxy = false;
 
         try {
+            /* Actively setting up a tunnel */
+            setTunnelState(TunnelState.SETUP);
+            
             do {
                 if (!checkReuseConnection()) {
                     proxiedConnect(url, proxyHost, proxyPort, false);
@@ -1512,11 +1555,13 @@
                 }
 
                 if (respCode == HTTP_OK) {
+                    setTunnelState(TunnelState.TUNNELING);
                     break;
                 }
                 // we don't know how to deal with other response code
                 // so disconnect and report error
                 disconnectInternal();
+                setTunnelState(TunnelState.NONE);
                 break;
             } while (retryTunnel < maxRedirects);
 
@@ -1538,6 +1583,14 @@
         responses.reset();
     }
 
+    static String connectRequestURI(URL url) {
+        String host = url.getHost();
+        int port = url.getPort();
+        port = port != -1 ? port : url.getDefaultPort();
+
+        return host + ":" + port;
+    }
+
     /**
      * send a CONNECT request for establishing a tunnel to proxy server
      */
@@ -1551,8 +1604,7 @@
         // otherwise, there may have 2 http methods in headers
         if (setRequests) requests.set(0, null, null);
 
-        requests.prepend("CONNECT " + url.getHost() + ":"
-                         + (port != -1 ? port : url.getDefaultPort())
+        requests.prepend(HTTP_CONNECT + " " + connectRequestURI(url)
                          + " " + httpVersion, null);
         requests.setIfNotSet("User-Agent", userAgent);
 
@@ -1583,9 +1635,17 @@
             = AuthenticationInfo.getProxyAuth(http.getProxyHostUsed(),
                                               http.getProxyPortUsed());
         if (pauth != null && pauth.supportsPreemptiveAuthorization()) {
+            String value;
+            if (tunnelState() == TunnelState.SETUP && 
+                    pauth instanceof DigestAuthentication) {
+                value = ((DigestAuthentication)pauth)
+                        .getHeaderValue(connectRequestURI(url), HTTP_CONNECT);
+            } else {
+                value = pauth.getHeaderValue(url, method);
+            }
+
             // Sets "Proxy-authorization"
-            requests.set(pauth.getHeaderName(),
-                                 pauth.getHeaderValue(url,method));
+            requests.set(pauth.getHeaderName(), value);
             currentProxyCredentials = pauth;
         }
     }
--- old/src/share/classes/sun/net/www/protocol/http/DigestAuthentication.java   
Wed Apr 16 11:24:50 2008
+++ new/src/share/classes/sun/net/www/protocol/http/DigestAuthentication.java   
Wed Apr 16 11:24:49 2008
@@ -36,6 +36,7 @@
 import sun.net.www.HeaderParser;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import static sun.net.www.protocol.http.HttpURLConnection.HTTP_CONNECT;
 
 
 /**
@@ -210,13 +211,42 @@
 
     /**
      * Reclaculates the request-digest and returns it.
+     *
+     * <P> Used in the common case where the requestURI is simply the
+     * abs_path. 
+     *
+     * @param  url
+     *         the URL
+     *
+     * @param  method
+     *         the HTTP method
+     *
      * @return the value of the HTTP header this authentication wants set
      */
     String getHeaderValue(URL url, String method) {
-        return getHeaderValueImpl (url.getFile(), method);
+        return getHeaderValueImpl(url.getFile(), method);
     }
 
     /**
+     * Reclaculates the request-digest and returns it.
+     *
+     * <P> Used when the requestURI is not the abs_path. The exact
+     * requestURI can be passed as a String.
+     *
+     * @param  requestURI
+     *         the Request-URI from the HTTP request line
+     *
+     * @param  method
+     *         the HTTP method
+     *
+     * @return the value of the HTTP header this authentication wants set
+     */
+    String getHeaderValue(String requestURI, String method) {
+        return getHeaderValueImpl(requestURI, method);
+    }
+
+
+    /**
      * Check if the header indicates that the current auth. parameters are 
stale.
      * If so, then replace the relevant field with the new value
      * and return true. Otherwise return false.
@@ -249,7 +279,16 @@
         params.setOpaque (p.findValue("opaque"));
         params.setQop (p.findValue("qop"));
 
-        String uri = conn.getURL().getFile();
+        String uri;
+        String method;
+        if (type == PROXY_AUTHENTICATION &&
+                conn.tunnelState() == HttpURLConnection.TunnelState.SETUP) {
+            uri = HttpURLConnection.connectRequestURI(conn.getURL());
+            method = HTTP_CONNECT;
+        } else {
+            uri = conn.getURL().getFile();
+            method = conn.getMethod();
+        }
 
         if (params.nonce == null || authMethod == null || pw == null || realm 
== null) {
             return false;
@@ -275,7 +314,7 @@
             params.setNewCnonce();
         }
 
-        String value = getHeaderValueImpl (uri, conn.getMethod());
+        String value = getHeaderValueImpl (uri, method);
         if (value != null) {
             conn.setAuthenticationProperty(getHeaderName(), value);
             return true;

Reply via email to