8182589: TLS SNI in new Java 9 client is not available

Reviewed-by: dfuchs
This commit is contained in:
Michael McMahon 2017-06-29 11:10:30 +01:00
parent 6e40f2e939
commit d9cfff236d
12 changed files with 130 additions and 25 deletions

View File

@ -222,7 +222,8 @@ module java.base {
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,

View File

@ -46,11 +46,13 @@ class AsyncSSLConnection extends HttpConnection
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

View File

@ -108,6 +108,7 @@ class AsyncSSLDelegate implements ExceptionallyCloseable, AsyncConnection {
final SSLParameters sslParameters;
final HttpConnection lowerOutput;
final HttpClientImpl client;
final String serverName;
// should be volatile to provide proper synchronization(visibility) action
volatile Consumer<ByteBufferReference> asyncReceiver;
volatile Consumer<Throwable> errorHandler;
@ -121,9 +122,10 @@ class AsyncSSLDelegate implements ExceptionallyCloseable, AsyncConnection {
// 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 @@ class AsyncSSLDelegate implements ExceptionallyCloseable, AsyncConnection {
} 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;

View File

@ -58,7 +58,8 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
* 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 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
* 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;

View File

@ -154,14 +154,12 @@ final class ResponseHeaders implements HttpHeaders {
String k = key.toLowerCase(Locale.US);
cookedHeaders.merge(k, newValues,
(v1, v2) -> {
if (v1 == null) {
ArrayList<String> newV = new ArrayList<>();
newV.addAll(v2);
return newV;
} else {
v1.addAll(v2);
return v1;
ArrayList<String> newV = new ArrayList<>();
if (v1 != null) {
newV.addAll(v1);
}
newV.addAll(v2);
return newV;
});
}
return cookedHeaders;

View File

@ -46,13 +46,14 @@ class SSLConnection extends HttpConnection {
PlainHttpConnection delegate;
SSLDelegate sslDelegate;
final String[] alpn;
final String serverName;
@Override
public CompletableFuture<Void> 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 @@ class SSLConnection extends HttpConnection {
@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 @@ class SSLConnection extends HttpConnection {
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

View File

@ -29,6 +29,7 @@ import java.io.IOException;
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 @@ class SSLDelegate {
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));

View File

@ -46,11 +46,12 @@ class SSLTunnelConnection extends HttpConnection {
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 @@ class SSLTunnelConnection extends HttpConnection {
// 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 @@ class SSLTunnelConnection extends HttpConnection {
InetSocketAddress proxy)
{
super(addr, client);
this.serverName = Utils.getServerName(addr);
delegate = new PlainTunnelingConnection(addr, proxy, client);
}

View File

@ -27,6 +27,7 @@ package jdk.incubator.http.internal.common;
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.IOException;
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 @@ public final class Utils {
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.
*

View File

@ -65,7 +65,7 @@ public class TLSConnection {
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();

View File

@ -33,6 +33,7 @@ import javax.net.ssl.SSLContext;
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 @@ public class Http2TestServer implements AutoCloseable {
volatile boolean stopping = false;
final Map<String,Http2Handler> handlers;
final SSLContext sslContext;
final String serverName;
final HashMap<InetSocketAddress,Http2TestServerConnection> connections;
private static ThreadFactory defaultThreadFac =
@ -62,8 +64,12 @@ public class Http2TestServer implements AutoCloseable {
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 class Http2TestServer implements AutoCloseable {
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 @@ public class Http2TestServer implements AutoCloseable {
* 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 @@ public class Http2TestServer implements AutoCloseable {
return se;
}
public String serverName() {
return serverName;
}
/**
* Starts a thread which waits for incoming connections.
*/

View File

@ -25,12 +25,13 @@ import java.io.BufferedInputStream;
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 @@ public class Http2TestServerConnection {
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 @@ public class Http2TestServerConnection {
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<SNIMatcher> list = List.of(matcher);
params.setSNIMatchers(list);
sock.setSSLParameters(params);
sock.getSession(); // blocks until handshake done
}
void close() {
streams.forEach((i, q) -> {
q.close();