diff --git a/src/share/classes/sun/net/www/protocol/http/AuthenticationHeader.java b/src/share/classes/sun/net/www/protocol/http/AuthenticationHeader.java index 89b4d991ea88b517eacccee6321eb26305053069..9769a0040e9baf5e4568f33073a48298c96d08a1 100644 --- a/src/share/classes/sun/net/www/protocol/http/AuthenticationHeader.java +++ b/src/share/classes/sun/net/www/protocol/http/AuthenticationHeader.java @@ -26,8 +26,10 @@ package sun.net.www.protocol.http; import sun.net.www.*; +import java.util.Collections; import java.util.Iterator; import java.util.HashMap; +import java.util.Set; /** * This class is used to parse the information in WWW-Authenticate: and Proxy-Authenticate: @@ -66,8 +68,8 @@ import java.util.HashMap; * -Dhttp.auth.preference="scheme" * * which in this case, specifies that "scheme" should be used as the auth scheme when offered - * disregarding the default prioritisation. If scheme is not offered then the default priority - * is used. + * disregarding the default prioritisation. If scheme is not offered, or explicitly + * disabled, by {@code disabledSchemes}, then the default priority is used. * * Attention: when http.auth.preference is set as SPNEGO or Kerberos, it's actually "Negotiate * with SPNEGO" or "Negotiate with Kerberos", which means the user will prefer the Negotiate @@ -113,17 +115,32 @@ public class AuthenticationHeader { String hdrname; // Name of the header to look for /** - * parse a set of authentication headers and choose the preferred scheme - * that we support for a given host + * Parses a set of authentication headers and chooses the preferred scheme + * that is supported for a given host. */ public AuthenticationHeader (String hdrname, MessageHeader response, HttpCallerInfo hci, boolean dontUseNegotiate) { + this(hdrname, response, hci, dontUseNegotiate, Collections.emptySet()); + } + + /** + * Parses a set of authentication headers and chooses the preferred scheme + * that is supported for a given host. + * + *

The {@code disabledSchemes} parameter is a, possibly empty, set of + * authentication schemes that are disabled. + */ + public AuthenticationHeader(String hdrname, + MessageHeader response, + HttpCallerInfo hci, + boolean dontUseNegotiate, + Set disabledSchemes) { this.hci = hci; this.dontUseNegotiate = dontUseNegotiate; - rsp = response; + this.rsp = response; this.hdrname = hdrname; - schemes = new HashMap(); - parse(); + this.schemes = new HashMap<>(); + parse(disabledSchemes); } public HttpCallerInfo getHttpCallerInfo() { @@ -143,10 +160,11 @@ public class AuthenticationHeader { * then the last one will be used. The * preferred scheme that we support will be used. */ - private void parse () { + private void parse(Set disabledSchemes) { Iterator iter = rsp.multiValueIterator(hdrname); while (iter.hasNext()) { String raw = iter.next(); + // HeaderParser lower cases everything, so can be used case-insensitively HeaderParser hp = new HeaderParser(raw); Iterator keys = hp.keys(); int i, lastSchemeIndex; @@ -156,7 +174,8 @@ public class AuthenticationHeader { if (lastSchemeIndex != -1) { HeaderParser hpn = hp.subsequence (lastSchemeIndex, i); String scheme = hpn.findKey(0); - schemes.put (scheme, new SchemeMapValue (hpn, raw)); + if (!disabledSchemes.contains(scheme)) + schemes.put (scheme, new SchemeMapValue (hpn, raw)); } lastSchemeIndex = i; } @@ -164,7 +183,8 @@ public class AuthenticationHeader { if (i > lastSchemeIndex) { HeaderParser hpn = hp.subsequence (lastSchemeIndex, i); String scheme = hpn.findKey(0); - schemes.put(scheme, new SchemeMapValue (hpn, raw)); + if (!disabledSchemes.contains(scheme)) + schemes.put(scheme, new SchemeMapValue (hpn, raw)); } } diff --git a/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 25949db678566848ad503e61e2ccaa57ee0ac152..3a429e97072db9db2950fb1abb9a56fda4078988 100644 --- a/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -25,6 +25,7 @@ package sun.net.www.protocol.http; +import java.security.PrivilegedAction; import java.util.Arrays; import java.net.URL; import java.net.URLConnection; @@ -104,6 +105,14 @@ public class HttpURLConnection extends java.net.HttpURLConnection { static final boolean validateProxy; static final boolean validateServer; + /** A, possibly empty, set of authentication schemes that are disabled + * when proxying plain HTTP ( not HTTPS ). */ + static final Set disabledProxyingSchemes; + + /** A, possibly empty, set of authentication schemes that are disabled + * when setting up a tunnel for HTTPS ( HTTP CONNECT ). */ + static final Set disabledTunnelingSchemes; + private StreamingOutputStream strOutputStream; private final static String RETRY_MSG1 = "cannot retry due to proxy authentication, in streaming mode"; @@ -201,6 +210,22 @@ public class HttpURLConnection extends java.net.HttpURLConnection { "Via" }; + private static String getNetProperty(String name) { + PrivilegedAction pa = () -> NetProperties.get(name); + return AccessController.doPrivileged(pa); + } + + private static Set schemesListToSet(String list) { + if (list == null || list.isEmpty()) + return Collections.emptySet(); + + Set s = new HashSet<>(); + String[] parts = list.split("\\s*,\\s*"); + for (String part : parts) + s.add(part.toLowerCase(Locale.ROOT)); + return s; + } + static { maxRedirects = java.security.AccessController.doPrivileged( new sun.security.action.GetIntegerAction( @@ -215,6 +240,14 @@ public class HttpURLConnection extends java.net.HttpURLConnection { agent = agent + " Java/"+version; } userAgent = agent; + + // A set of net properties to control the use of authentication schemes + // when proxing/tunneling. + String p = getNetProperty("jdk.http.auth.tunneling.disabledSchemes"); + disabledTunnelingSchemes = schemesListToSet(p); + p = getNetProperty("jdk.http.auth.proxying.disabledSchemes"); + disabledProxyingSchemes = schemesListToSet(p); + validateProxy = java.security.AccessController.doPrivileged( new sun.security.action.GetBooleanAction( "http.auth.digest.validateProxy")).booleanValue(); @@ -1586,10 +1619,13 @@ public class HttpURLConnection extends java.net.HttpURLConnection { // altered in similar ways. AuthenticationHeader authhdr = new AuthenticationHeader ( - "Proxy-Authenticate", responses, - new HttpCallerInfo(url, http.getProxyHostUsed(), + "Proxy-Authenticate", + responses, + new HttpCallerInfo(url, + http.getProxyHostUsed(), http.getProxyPortUsed()), - dontUseNegotiate + dontUseNegotiate, + disabledProxyingSchemes ); if (!doingNTLMp2ndStage) { @@ -2036,10 +2072,13 @@ public class HttpURLConnection extends java.net.HttpURLConnection { } AuthenticationHeader authhdr = new AuthenticationHeader ( - "Proxy-Authenticate", responses, - new HttpCallerInfo(url, http.getProxyHostUsed(), + "Proxy-Authenticate", + responses, + new HttpCallerInfo(url, + http.getProxyHostUsed(), http.getProxyPortUsed()), - dontUseNegotiate + dontUseNegotiate, + disabledTunnelingSchemes ); if (!doingNTLMp2ndStage) { proxyAuthentication = diff --git a/src/share/lib/net.properties b/src/share/lib/net.properties index da78a84d511d3a137736d6f0438c3c1892515b38..b490e174a3f4915c54610f59f4826d58f2d2a050 100644 --- a/src/share/lib/net.properties +++ b/src/share/lib/net.properties @@ -72,3 +72,30 @@ ftp.nonProxyHosts=localhost|127.*|[::1] # value is 10). # http.KeepAlive.remainingData=512 # http.KeepAlive.queuedConnections=10 + +# Authentication Scheme restrictions for HTTP and HTTPS. +# +# In some environments certain authentication schemes may be undesirable +# when proxying HTTP or HTTPS. For example, "Basic" results in effectively the +# cleartext transmission of the user's password over the physical network. +# This section describes the mechanism for disabling authentication schemes +# based on the scheme name. Disabled schemes will be treated as if they are not +# supported by the implementation. +# +# The 'jdk.http.auth.tunneling.disabledSchemes' property lists the authentication +# schemes that will be disabled when tunneling HTTPS over a proxy, HTTP CONNECT. +# The 'jdk.http.auth.proxying.disabledSchemes' property lists the authentication +# schemes that will be disabled when proxying HTTP. +# +# In both cases the property is a comma-separated list of, case-insensitive, +# authentication scheme names, as defined by their relevant RFCs. An +# implementation may, but is not required to, support common schemes whose names +# include: 'Basic', 'Digest', 'NTLM', 'Kerberos', 'Negotiate'. A scheme that +# is not known, or not supported, by the implementation is ignored. +# +# Note: This property is currently used by the JDK Reference implementation. It +# is not guaranteed to be examined and used by other implementations. +# +#jdk.http.auth.proxying.disabledSchemes= +jdk.http.auth.tunneling.disabledSchemes=Basic + diff --git a/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/OriginServer.java b/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/OriginServer.java index b24aed6fd73fc019ef9e638528e5d5ef3a2315bd..dc2070fdf4ff7ef6e725e16b313c76116fe8c107 100644 --- a/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/OriginServer.java +++ b/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/OriginServer.java @@ -36,10 +36,12 @@ import javax.net.*; * Http get request in both clear and secure channel */ -public abstract class OriginServer implements Runnable { +public abstract class OriginServer implements Runnable, Closeable { private ServerSocket server = null; Exception serverException = null; + private volatile boolean closed; + /** * Constructs a OriginServer based on ss and * obtains a response data's bytecodes using the method @@ -53,6 +55,14 @@ public abstract class OriginServer implements Runnable { throw serverException; } + @Override + public void close() throws IOException { + if (closed) + return; + closed = true; + server.close(); + } + /** * Returns an array of bytes containing the bytes for * the data sent in the response. diff --git a/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyAuthTest.java b/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyAuthTest.java index a4ef930ff6450e5a4c1ac5a0c0725b3ae2b1c27b..50e3d06b4cd17ba6e348f008a769ee02061d23df 100644 --- a/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyAuthTest.java +++ b/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyAuthTest.java @@ -23,21 +23,30 @@ /* * @test - * @bug 4323990 4413069 + * @bug 4323990 4413069 8160838 * @summary HttpsURLConnection doesn't send Proxy-Authorization on CONNECT * Incorrect checking of proxy server response - * @run main/othervm ProxyAuthTest - * - * No way to reserve and restore java.lang.Authenticator, need to run this - * test in othervm mode. + * @run main/othervm ProxyAuthTest fail + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes=Basic ProxyAuthTest fail + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes=Basic, ProxyAuthTest fail + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes=BAsIc ProxyAuthTest fail + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes=Basic,Digest ProxyAuthTest fail + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes=Unknown,bAsIc ProxyAuthTest fail + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes= ProxyAuthTest succeed + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes=Digest,NTLM,Negotiate ProxyAuthTest succeed + * @run main/othervm -Djdk.http.auth.tunneling.disabledSchemes=UNKNOWN,notKnown ProxyAuthTest succeed */ +// No way to reserve and restore java.lang.Authenticator, as well as read-once +// system properties, so this tests needs to run in othervm mode. + import java.io.*; import java.net.*; import java.security.KeyStore; import javax.net.*; import javax.net.ssl.*; import java.security.cert.*; +import static java.nio.charset.StandardCharsets.US_ASCII; /* * ProxyAuthTest.java -- includes a simple server that can serve @@ -74,7 +83,7 @@ public class ProxyAuthTest { */ public byte[] getBytes() { return "Proxy authentication for tunneling succeeded ..". - getBytes(); + getBytes(US_ASCII); } } @@ -82,6 +91,13 @@ public class ProxyAuthTest { * Main method to create the server and the client */ public static void main(String args[]) throws Exception { + boolean expectSuccess; + if (args[0].equals("succeed")) { + expectSuccess = true; + } else { + expectSuccess = false; + } + String keyFilename = System.getProperty("test.src", "./") + "/" + pathToStores + "/" + keyStoreFile; @@ -98,12 +114,13 @@ public class ProxyAuthTest { /* * setup the server */ + Closeable server; try { ServerSocketFactory ssf = ProxyAuthTest.getServerSocketFactory(useSSL); ServerSocket ss = ssf.createServerSocket(serverPort); serverPort = ss.getLocalPort(); - new TestServer(ss); + server = new TestServer(ss); } catch (Exception e) { System.out.println("Server side failed:" + e.getMessage()); @@ -112,10 +129,28 @@ public class ProxyAuthTest { // trigger the client try { doClientSide(); - } catch (Exception e) { + if (!expectSuccess) { + throw new RuntimeException( + "Expected exception/failure to connect, but succeeded."); + } + } catch (IOException e) { + if (expectSuccess) { System.out.println("Client side failed: " + e.getMessage()); throw e; } + + if (! (e.getMessage().contains("Unable to tunnel through proxy") && + e.getMessage().contains("407")) ) { + throw new RuntimeException( + "Expected exception about cannot tunnel, 407, etc, but got", e); + } else { + // Informative + System.out.println("Caught expected exception: " + e.getMessage()); + } + } finally { + if (server != null) + server.close(); + } } private static ServerSocketFactory getServerSocketFactory @@ -144,11 +179,11 @@ public class ProxyAuthTest { } } - static void doClientSide() throws Exception { + static void doClientSide() throws IOException { /* * setup up a proxy with authentication information */ - setupProxy(); + ProxyTunnelServer ps = setupProxy(); /* * we want to avoid URLspoofCheck failures in cases where the cert @@ -156,18 +191,28 @@ public class ProxyAuthTest { */ HttpsURLConnection.setDefaultHostnameVerifier( new NameVerifier()); + + InetSocketAddress paddr = new InetSocketAddress("localhost", ps.getPort()); + Proxy proxy = new Proxy(Proxy.Type.HTTP, paddr); + URL url = new URL("https://" + "localhost:" + serverPort + "/index.html"); BufferedReader in = null; + HttpsURLConnection uc = (HttpsURLConnection) url.openConnection(proxy); try { - in = new BufferedReader(new InputStreamReader( - url.openStream())); + in = new BufferedReader(new InputStreamReader(uc.getInputStream())); String inputLine; System.out.print("Client recieved from the server: "); while ((inputLine = in.readLine()) != null) System.out.println(inputLine); in.close(); - } catch (SSLException e) { + } catch (IOException e) { + // Assert that the error stream is not accessible from the failed + // tunnel setup. + if (uc.getErrorStream() != null) { + throw new RuntimeException("Unexpected error stream."); + } + if (in != null) in.close(); throw e; @@ -180,7 +225,7 @@ public class ProxyAuthTest { } } - static void setupProxy() throws IOException { + static ProxyTunnelServer setupProxy() throws IOException { ProxyTunnelServer pserver = new ProxyTunnelServer(); /* * register a system wide authenticator and setup the proxy for @@ -193,9 +238,7 @@ public class ProxyAuthTest { pserver.setUserAuth("Test", "test123"); pserver.start(); - System.setProperty("https.proxyHost", "localhost"); - System.setProperty("https.proxyPort", String.valueOf( - pserver.getPort())); + return pserver; } public static class TestAuthenticator extends Authenticator { @@ -206,3 +249,4 @@ public class ProxyAuthTest { } } } + diff --git a/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyTunnelServer.java b/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyTunnelServer.java index 145cb49c54191ad94739d07c6122058cc3be2e9e..90315e8cc8411b8e8fcca40a4833e7128e398991 100644 --- a/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyTunnelServer.java +++ b/test/sun/security/ssl/com/sun/net/ssl/internal/www/protocol/https/HttpsClient/ProxyTunnelServer.java @@ -65,6 +65,7 @@ public class ProxyTunnelServer extends Thread { ss = (ServerSocket) ServerSocketFactory.getDefault(). createServerSocket(0); } + setDaemon(true); } public void needUserAuth(boolean auth) { @@ -211,6 +212,7 @@ public class ProxyTunnelServer extends Thread { this.sockOut = sockOut; input = sockIn.getInputStream(); output = sockOut.getOutputStream(); + setDaemon(true); } public void run() { diff --git a/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/PostThruProxyWithAuth.sh b/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/PostThruProxyWithAuth.sh index fd24c76a475c000371ab7426955d28923d947c85..a89827c9db81223969da85319f253f72f9839670 100644 --- a/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/PostThruProxyWithAuth.sh +++ b/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/PostThruProxyWithAuth.sh @@ -53,5 +53,7 @@ esac ${COMPILEJAVA}${FS}bin${FS}javac ${TESTJAVACOPTS} ${TESTTOOLVMOPTS} -d . ${TESTSRC}${FS}OriginServer.java \ ${TESTSRC}${FS}ProxyTunnelServer.java \ ${TESTSRC}${FS}PostThruProxyWithAuth.java -${TESTJAVA}${FS}bin${FS}java ${TESTVMOPTS} PostThruProxyWithAuth ${HOSTNAME} ${TESTSRC} +${TESTJAVA}${FS}bin${FS}java ${TESTVMOPTS} \ + -Djdk.http.auth.tunneling.disabledSchemes= \ + PostThruProxyWithAuth ${HOSTNAME} ${TESTSRC} exit