# HG changeset patch # User michaelm # Date 1498731030 -3600 # Node ID ee3f2cbfe23ada5f601cb970e9135b90a61347dc # Parent 4f662a7e43a7632c0564d8b8c17974ca17eb62a6 8182589: TLS SNI in new Java 9 client is not available Reviewed-by: dfuchs diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/java.base/share/classes/module-info.java --- a/jdk/src/java.base/share/classes/module-info.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/java.base/share/classes/module-info.java Thu Jun 29 11:10:30 2017 +0100 @@ -222,7 +222,8 @@ jdk.naming.dns; exports sun.net.util to java.desktop, - jdk.jconsole; + jdk.jconsole, + jdk.incubator.httpclient; exports sun.net.www to java.desktop, jdk.incubator.httpclient, diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java Thu Jun 29 11:10:30 2017 +0100 @@ -46,11 +46,13 @@ final AsyncSSLDelegate sslDelegate; final PlainHttpConnection plainConnection; + final String serverName; AsyncSSLConnection(InetSocketAddress addr, HttpClientImpl client, String[] ap) { super(addr, client); plainConnection = new PlainHttpConnection(addr, client); - sslDelegate = new AsyncSSLDelegate(plainConnection, client, ap); + serverName = Utils.getServerName(addr); + sslDelegate = new AsyncSSLDelegate(plainConnection, client, ap, serverName); } @Override diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLDelegate.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLDelegate.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLDelegate.java Thu Jun 29 11:10:30 2017 +0100 @@ -108,6 +108,7 @@ final SSLParameters sslParameters; final HttpConnection lowerOutput; final HttpClientImpl client; + final String serverName; // should be volatile to provide proper synchronization(visibility) action volatile Consumer asyncReceiver; volatile Consumer errorHandler; @@ -121,9 +122,10 @@ // alpn[] may be null. upcall is callback which receives incoming decoded bytes off socket - AsyncSSLDelegate(HttpConnection lowerOutput, HttpClientImpl client, String[] alpn) + AsyncSSLDelegate(HttpConnection lowerOutput, HttpClientImpl client, String[] alpn, String sname) { SSLContext context = client.sslContext(); + this.serverName = sname; engine = context.createSSLEngine(); engine.setUseClientMode(true); SSLParameters sslp = client.sslParameters() @@ -135,6 +137,10 @@ } else { Log.logSSL("AsyncSSLDelegate: no applications set!"); } + if (serverName != null) { + SNIHostName sn = new SNIHostName(serverName); + sslParameters.setServerNames(List.of(sn)); + } logParams(sslParameters); engine.setSSLParameters(sslParameters); this.lowerOutput = lowerOutput; diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Thu Jun 29 11:10:30 2017 +0100 @@ -58,7 +58,8 @@ * Creates an HttpRequestImpl from the given builder. */ public HttpRequestImpl(HttpRequestBuilderImpl builder) { - this.method = builder.method(); + String method = builder.method(); + this.method = method == null? "GET" : method; this.userHeaders = ImmutableHeaders.of(builder.headers().map(), ALLOWED_HEADERS); this.systemHeaders = new HttpHeadersImpl(); this.uri = builder.uri(); @@ -77,7 +78,8 @@ * Creates an HttpRequestImpl from the given request. */ public HttpRequestImpl(HttpRequest request) { - this.method = request.method(); + String method = request.method(); + this.method = method == null? "GET" : method; this.userHeaders = request.headers(); if (request instanceof HttpRequestImpl) { this.systemHeaders = ((HttpRequestImpl) request).systemHeaders; diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseHeaders.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseHeaders.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseHeaders.java Thu Jun 29 11:10:30 2017 +0100 @@ -154,14 +154,12 @@ String k = key.toLowerCase(Locale.US); cookedHeaders.merge(k, newValues, (v1, v2) -> { - if (v1 == null) { - ArrayList newV = new ArrayList<>(); - newV.addAll(v2); - return newV; - } else { - v1.addAll(v2); - return v1; + ArrayList newV = new ArrayList<>(); + if (v1 != null) { + newV.addAll(v1); } + newV.addAll(v2); + return newV; }); } return cookedHeaders; diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java Thu Jun 29 11:10:30 2017 +0100 @@ -46,13 +46,14 @@ PlainHttpConnection delegate; SSLDelegate sslDelegate; final String[] alpn; + final String serverName; @Override public CompletableFuture connectAsync() { return delegate.connectAsync() .thenCompose((Void v) -> MinimalFuture.supply( () -> { - this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn); + this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn, serverName); return null; })); } @@ -60,12 +61,13 @@ @Override public void connect() throws IOException { delegate.connect(); - this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn); + this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn, serverName); } SSLConnection(InetSocketAddress addr, HttpClientImpl client, String[] ap) { super(addr, client); this.alpn = ap; + this.serverName = Utils.getServerName(addr); delegate = new PlainHttpConnection(addr, client); } @@ -77,8 +79,9 @@ super(c.address, c.client); this.delegate = c.plainConnection; AsyncSSLDelegate adel = c.sslDelegate; - this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client); + this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client, adel.serverName); this.alpn = adel.alpn; + this.serverName = adel.serverName; } @Override diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java Thu Jun 29 11:10:30 2017 +0100 @@ -29,6 +29,7 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Arrays; +import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.net.ssl.SSLEngineResult.HandshakeStatus; @@ -50,26 +51,33 @@ final SSLParameters sslParameters; final SocketChannel chan; final HttpClientImpl client; + final String serverName; - SSLDelegate(SSLEngine eng, SocketChannel chan, HttpClientImpl client) + SSLDelegate(SSLEngine eng, SocketChannel chan, HttpClientImpl client, String sn) { this.engine = eng; this.chan = chan; this.client = client; this.wrapper = new EngineWrapper(chan, engine); this.sslParameters = engine.getSSLParameters(); + this.serverName = sn; } // alpn[] may be null - SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn) + SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn, String sn) throws IOException { + serverName = sn; SSLContext context = client.sslContext(); engine = context.createSSLEngine(); engine.setUseClientMode(true); SSLParameters sslp = client.sslParameters() .orElseGet(context::getSupportedSSLParameters); sslParameters = Utils.copySSLParameters(sslp); + if (sn != null) { + SNIHostName sni = new SNIHostName(sn); + sslParameters.setServerNames(List.of(sni)); + } if (alpn != null) { sslParameters.setApplicationProtocols(alpn); Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn)); diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLTunnelConnection.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLTunnelConnection.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLTunnelConnection.java Thu Jun 29 11:10:30 2017 +0100 @@ -46,11 +46,12 @@ final PlainTunnelingConnection delegate; protected SSLDelegate sslDelegate; private volatile boolean connected; + final String serverName; @Override public void connect() throws IOException, InterruptedException { delegate.connect(); - this.sslDelegate = new SSLDelegate(delegate.channel(), client, null); + this.sslDelegate = new SSLDelegate(delegate.channel(), client, null, serverName); connected = true; } @@ -67,7 +68,7 @@ // can this block? this.sslDelegate = new SSLDelegate(delegate.channel(), client, - null); + null, serverName); connected = true; } catch (IOException e) { throw new UncheckedIOException(e); @@ -80,6 +81,7 @@ InetSocketAddress proxy) { super(addr, client); + this.serverName = Utils.getServerName(addr); delegate = new PlainTunnelingConnection(addr, proxy, client); } diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java --- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java Thu Jun 29 11:10:30 2017 +0100 @@ -27,6 +27,7 @@ import jdk.internal.misc.InnocuousThread; import sun.net.NetProperties; +import sun.net.util.IPAddressUtil; import javax.net.ssl.SSLParameters; import java.io.ByteArrayOutputStream; @@ -35,6 +36,7 @@ import java.io.UncheckedIOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; import java.net.NetPermission; import java.net.URI; import java.net.URLPermission; @@ -164,6 +166,22 @@ return !token.isEmpty(); } + /** + * If the address was created with a domain name, then return + * the domain name string. If created with a literal IP address + * then return null. We do this to avoid doing a reverse lookup + * Used to populate the TLS SNI parameter. So, SNI is only set + * when a domain name was supplied. + */ + public static String getServerName(InetSocketAddress addr) { + String host = addr.getHostString(); + if (IPAddressUtil.textToNumericFormatV4(host) != null) + return null; + if (IPAddressUtil.textToNumericFormatV6(host) != null) + return null; + return host; + } + /* * Validates a RFC 7230 field-value. * diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/test/java/net/httpclient/http2/TLSConnection.java --- a/jdk/test/java/net/httpclient/http2/TLSConnection.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/test/java/net/httpclient/http2/TLSConnection.java Thu Jun 29 11:10:30 2017 +0100 @@ -65,7 +65,7 @@ Handler handler = new Handler(); - try (Http2TestServer server = new Http2TestServer(true, 0)) { + try (Http2TestServer server = new Http2TestServer("127.0.0.1", true, 0)) { server.addHandler(handler, "/"); server.start(); diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/test/java/net/httpclient/http2/server/Http2TestServer.java --- a/jdk/test/java/net/httpclient/http2/server/Http2TestServer.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/test/java/net/httpclient/http2/server/Http2TestServer.java Thu Jun 29 11:10:30 2017 +0100 @@ -33,6 +33,7 @@ import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SNIServerName; /** * Waits for incoming TCP connections from a client and establishes @@ -48,6 +49,7 @@ volatile boolean stopping = false; final Map handlers; final SSLContext sslContext; + final String serverName; final HashMap connections; private static ThreadFactory defaultThreadFac = @@ -62,8 +64,12 @@ return Executors.newCachedThreadPool(defaultThreadFac); } + public Http2TestServer(String serverName, boolean secure, int port) throws Exception { + this(serverName, secure, port, getDefaultExecutor(), null); + } + public Http2TestServer(boolean secure, int port) throws Exception { - this(secure, port, getDefaultExecutor(), null); + this(null, secure, port, getDefaultExecutor(), null); } public InetSocketAddress getAddress() { @@ -72,7 +78,19 @@ public Http2TestServer(boolean secure, SSLContext context) throws Exception { - this(secure, 0, null, context); + this(null, secure, 0, null, context); + } + + public Http2TestServer(String serverName, boolean secure, + SSLContext context) throws Exception { + this(serverName, secure, 0, null, context); + } + + public Http2TestServer(boolean secure, + int port, + ExecutorService exec, + SSLContext context) throws Exception { + this(null, secure, port, exec, context); } /** @@ -80,17 +98,20 @@ * to know in advance whether incoming connections are plain TCP "h2c" * or TLS "h2"/ * + * @param serverName SNI servername * @param secure https or http * @param port listen port * @param exec executor service (cached thread pool is used if null) * @param context the SSLContext used when secure is true */ - public Http2TestServer(boolean secure, + public Http2TestServer(String serverName, + boolean secure, int port, ExecutorService exec, SSLContext context) throws Exception { + this.serverName = serverName; if (secure) { server = initSecure(port); } else { @@ -165,6 +186,10 @@ return se; } + public String serverName() { + return serverName; + } + /** * Starts a thread which waits for incoming connections. */ diff -r 4f662a7e43a7 -r ee3f2cbfe23a jdk/test/java/net/httpclient/http2/server/Http2TestServerConnection.java --- a/jdk/test/java/net/httpclient/http2/server/Http2TestServerConnection.java Wed Jun 28 17:57:20 2017 -0700 +++ b/jdk/test/java/net/httpclient/http2/server/Http2TestServerConnection.java Thu Jun 29 11:10:30 2017 +0100 @@ -25,12 +25,13 @@ import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.IOException; +import java.io.UncheckedIOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.URI; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; +import java.net.InetAddress; +import javax.net.ssl.*; import java.net.URISyntaxException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -79,6 +80,9 @@ final static byte[] clientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(); Http2TestServerConnection(Http2TestServer server, Socket socket) throws IOException { + if (socket instanceof SSLSocket) { + handshake(server.serverName(), (SSLSocket)socket); + } System.err.println("TestServer: New connection from " + socket); this.server = server; this.streams = Collections.synchronizedMap(new HashMap<>()); @@ -92,6 +96,42 @@ os = new BufferedOutputStream(socket.getOutputStream()); } + private static boolean compareIPAddrs(InetAddress addr1, String host) { + try { + InetAddress addr2 = InetAddress.getByName(host); + return addr1.equals(addr2); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void handshake(String name, SSLSocket sock) throws IOException { + if (name == null) { + // no name set. No need to check + return; + } else if (name.equals("127.0.0.1")) { + name = "localhost"; + } + final String fname = name; + final InetAddress addr1 = InetAddress.getByName(name); + SSLParameters params = sock.getSSLParameters(); + SNIMatcher matcher = new SNIMatcher(StandardConstants.SNI_HOST_NAME) { + public boolean matches (SNIServerName n) { + String host = ((SNIHostName)n).getAsciiName(); + if (host.equals("127.0.0.1")) + host = "localhost"; + boolean cmp = host.equalsIgnoreCase(fname); + if (cmp) + return true; + return compareIPAddrs(addr1, host); + } + }; + List list = List.of(matcher); + params.setSNIMatchers(list); + sock.setSSLParameters(params); + sock.getSession(); // blocks until handshake done + } + void close() { streams.forEach((i, q) -> { q.close();