mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-21 07:45:11 +00:00
8182589: TLS SNI in new Java 9 client is not available
Reviewed-by: dfuchs
This commit is contained in:
parent
6e40f2e939
commit
d9cfff236d
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user