--- a/jdk/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationHeader.java Fri Jul 15 20:57:41 2016 +0100
+++ b/jdk/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationHeader.java Mon Jul 18 08:28:48 2016 +0100
@@ -25,8 +25,11 @@
package sun.net.www.protocol.http;
+import java.util.Collections;
import java.util.Iterator;
import java.util.HashMap;
+import java.util.Set;
+
import sun.net.www.*;
import sun.security.action.GetPropertyAction;
@@ -67,8 +70,8 @@
* -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 +116,32 @@
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.
+ *
+ * <p> 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<String> 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 +161,11 @@
* then the last one will be used. The
* preferred scheme that we support will be used.
*/
- private void parse () {
+ private void parse(Set<String> disabledSchemes) {
Iterator<String> 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<String> keys = hp.keys();
int i, lastSchemeIndex;
@@ -156,7 +175,8 @@
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 +184,8 @@
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));
}
}
--- a/jdk/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Fri Jul 15 20:57:41 2016 +0100
+++ b/jdk/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Mon Jul 18 08:28:48 2016 +0100
@@ -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;
@@ -109,6 +110,14 @@
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<String> disabledProxyingSchemes;
+
+ /** A, possibly empty, set of authentication schemes that are disabled
+ * when setting up a tunnel for HTTPS ( HTTP CONNECT ). */
+ static final Set<String> disabledTunnelingSchemes;
+
private StreamingOutputStream strOutputStream;
private static final String RETRY_MSG1 =
"cannot retry due to proxy authentication, in streaming mode";
@@ -206,6 +215,22 @@
"Via"
};
+ private static String getNetProperty(String name) {
+ PrivilegedAction<String> pa = () -> NetProperties.get(name);
+ return AccessController.doPrivileged(pa);
+ }
+
+ private static Set<String> schemesListToSet(String list) {
+ if (list == null || list.isEmpty())
+ return Collections.emptySet();
+
+ Set<String> s = new HashSet<>();
+ String[] parts = list.split("\\s*,\\s*");
+ for (String part : parts)
+ s.add(part.toLowerCase(Locale.ROOT));
+ return s;
+ }
+
static {
Properties props = GetPropertyAction.privilegedGetProperties();
maxRedirects = GetIntegerAction.privilegedGetProperty(
@@ -218,6 +243,14 @@
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 = Boolean.parseBoolean(
props.getProperty("http.auth.digest.validateProxy"));
validateServer = Boolean.parseBoolean(
@@ -1575,10 +1608,13 @@
// altered in similar ways.
AuthenticationHeader authhdr = new AuthenticationHeader (
- "Proxy-Authenticate", responses,
- new HttpCallerInfo(url, http.getProxyHostUsed(),
- http.getProxyPortUsed()),
- dontUseNegotiate
+ "Proxy-Authenticate",
+ responses,
+ new HttpCallerInfo(url,
+ http.getProxyHostUsed(),
+ http.getProxyPortUsed()),
+ dontUseNegotiate,
+ disabledProxyingSchemes
);
if (!doingNTLMp2ndStage) {
@@ -2024,11 +2060,14 @@
}
}
- AuthenticationHeader authhdr = new AuthenticationHeader (
- "Proxy-Authenticate", responses,
- new HttpCallerInfo(url, http.getProxyHostUsed(),
- http.getProxyPortUsed()),
- dontUseNegotiate
+ AuthenticationHeader authhdr = new AuthenticationHeader(
+ "Proxy-Authenticate",
+ responses,
+ new HttpCallerInfo(url,
+ http.getProxyHostUsed(),
+ http.getProxyPortUsed()),
+ dontUseNegotiate,
+ disabledTunnelingSchemes
);
if (!doingNTLMp2ndStage) {
proxyAuthentication =
--- a/jdk/src/java.base/share/conf/net.properties Fri Jul 15 20:57:41 2016 +0100
+++ b/jdk/src/java.base/share/conf/net.properties Mon Jul 18 08:28:48 2016 +0100
@@ -72,3 +72,30 @@
# 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
+
--- a/jdk/test/sun/net/www/protocol/https/HttpsClient/OriginServer.java Fri Jul 15 20:57:41 2016 +0100
+++ b/jdk/test/sun/net/www/protocol/https/HttpsClient/OriginServer.java Mon Jul 18 08:28:48 2016 +0100
@@ -36,10 +36,12 @@
* 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 @@
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.
@@ -73,8 +83,10 @@
try {
socket = server.accept();
} catch (IOException e) {
- System.out.println("Class Server died: " + e.getMessage());
- serverException = e;
+ if (!closed) {
+ System.out.println("Class Server died: " + e.getMessage());
+ serverException = e;
+ }
return;
}
try {
--- a/jdk/test/sun/net/www/protocol/https/HttpsClient/ProxyAuthTest.java Fri Jul 15 20:57:41 2016 +0100
+++ b/jdk/test/sun/net/www/protocol/https/HttpsClient/ProxyAuthTest.java Mon Jul 18 08:28:48 2016 +0100
@@ -23,22 +23,31 @@
/*
* @test
- * @bug 4323990 4413069
+ * @bug 4323990 4413069 8160838
* @summary HttpsURLConnection doesn't send Proxy-Authorization on CONNECT
* Incorrect checking of proxy server response
* @modules java.base/sun.net.www
- * @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
@@ -75,7 +84,7 @@
*/
public byte[] getBytes() {
return "Proxy authentication for tunneling succeeded ..".
- getBytes();
+ getBytes(US_ASCII);
}
}
@@ -83,6 +92,13 @@
* 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;
@@ -99,12 +115,13 @@
/*
* 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());
@@ -113,9 +130,27 @@
// trigger the client
try {
doClientSide();
- } catch (Exception e) {
- System.out.println("Client side failed: " + e.getMessage());
- throw 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();
}
}
@@ -145,11 +180,11 @@
}
}
- 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
@@ -157,18 +192,28 @@
*/
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;
@@ -181,7 +226,7 @@
}
}
- static void setupProxy() throws IOException {
+ static ProxyTunnelServer setupProxy() throws IOException {
ProxyTunnelServer pserver = new ProxyTunnelServer();
/*
* register a system wide authenticator and setup the proxy for
@@ -194,9 +239,7 @@
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 {
--- a/jdk/test/sun/net/www/protocol/https/HttpsClient/ProxyTunnelServer.java Fri Jul 15 20:57:41 2016 +0100
+++ b/jdk/test/sun/net/www/protocol/https/HttpsClient/ProxyTunnelServer.java Mon Jul 18 08:28:48 2016 +0100
@@ -65,6 +65,7 @@
ss = (ServerSocket) ServerSocketFactory.getDefault().
createServerSocket(0);
}
+ setDaemon(true);
}
public void needUserAuth(boolean auth) {
@@ -211,6 +212,7 @@
this.sockOut = sockOut;
input = sockIn.getInputStream();
output = sockOut.getOutputStream();
+ setDaemon(true);
}
public void run() {
--- a/jdk/test/sun/net/www/protocol/https/HttpsURLConnection/PostThruProxyWithAuth.sh Fri Jul 15 20:57:41 2016 +0100
+++ b/jdk/test/sun/net/www/protocol/https/HttpsURLConnection/PostThruProxyWithAuth.sh Mon Jul 18 08:28:48 2016 +0100
@@ -57,5 +57,6 @@
${TESTSRC}${FS}ProxyTunnelServer.java \
${TESTSRC}${FS}PostThruProxyWithAuth.java
${TESTJAVA}${FS}bin${FS}java ${TESTVMOPTS} ${EXTRAOPTS} \
+ -Djdk.http.auth.tunneling.disabledSchemes= \
PostThruProxyWithAuth ${HOSTNAME} ${TESTSRC}
exit