mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-15 01:18:40 +00:00
Co-authored-by: Mikhail Yankelevich <myankelevich@openjdk.org> Reviewed-by: djelinski, vyazici, dfuchs, myankelevich
380 lines
14 KiB
Java
380 lines
14 KiB
Java
/*
|
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @bug 8369950
|
|
* @summary Test that the HttpsURLConnection does not set IP address literals for
|
|
* SNI hostname during TLS handshake
|
|
* @library /test/lib
|
|
* @modules java.base/sun.net.util
|
|
* @comment Insert -Djavax.net.debug=all into the following lines to enable SSL debugging
|
|
* @run main/othervm SubjectAltNameIP 127.0.0.1
|
|
* @run main/othervm SubjectAltNameIP [::1]
|
|
*/
|
|
|
|
import javax.net.ssl.HandshakeCompletedListener;
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
import javax.net.ssl.SSLParameters;
|
|
import javax.net.ssl.SSLServerSocket;
|
|
import javax.net.ssl.SSLServerSocketFactory;
|
|
import javax.net.ssl.SSLSession;
|
|
import javax.net.ssl.SSLSocket;
|
|
import javax.net.ssl.SSLSocketFactory;
|
|
import java.io.BufferedWriter;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.OutputStreamWriter;
|
|
import java.net.InetAddress;
|
|
import java.net.Socket;
|
|
import java.net.SocketAddress;
|
|
import java.net.URI;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.function.Consumer;
|
|
|
|
import jdk.test.lib.Asserts;
|
|
import jdk.test.lib.net.IPSupport;
|
|
import jdk.test.lib.net.SimpleSSLContext;
|
|
import jtreg.SkippedException;
|
|
import sun.net.util.IPAddressUtil;
|
|
|
|
public class SubjectAltNameIP {
|
|
|
|
// Is the server ready to serve?
|
|
private final CountDownLatch serverReady = new CountDownLatch(1);
|
|
|
|
// Use any free port by default.
|
|
volatile int serverPort = 0;
|
|
|
|
// Stores an exception thrown by server in a separate thread.
|
|
volatile Exception serverException = null;
|
|
|
|
// SSLSocket object created by HttpsClient internally.
|
|
SSLSocket clientSSLSocket = null;
|
|
|
|
// The hostname the server socket is bound to.
|
|
String hostName;
|
|
|
|
static final byte[] requestEnd = new byte[] {'\r', '\n', '\r', '\n' };
|
|
|
|
// Read until the end of the request.
|
|
void readOneRequest(InputStream is) throws IOException {
|
|
int requestEndCount = 0, r;
|
|
while ((r = is.read()) != -1) {
|
|
if (r == requestEnd[requestEndCount]) {
|
|
requestEndCount++;
|
|
if (requestEndCount == 4) {
|
|
break;
|
|
}
|
|
} else {
|
|
requestEndCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Define the server side of the test.
|
|
*
|
|
* If the server prematurely exits, serverReady will be set to true
|
|
* to avoid infinite hangs.
|
|
*/
|
|
void doServerSide() throws Exception {
|
|
SSLServerSocketFactory sslssf =
|
|
new SimpleSSLContext().get().getServerSocketFactory();
|
|
SSLServerSocket sslServerSocket =
|
|
(SSLServerSocket) sslssf.createServerSocket(
|
|
serverPort, 0,
|
|
InetAddress.getByName(hostName));
|
|
sslServerSocket.setEnabledProtocols(new String[]{"TLSv1.3"});
|
|
serverPort = sslServerSocket.getLocalPort();
|
|
|
|
/*
|
|
* Signal the client, the server is ready to accept connection.
|
|
*/
|
|
serverReady.countDown();
|
|
|
|
SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
|
|
OutputStream sslOS = sslSocket.getOutputStream();
|
|
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sslOS));
|
|
bw.write("HTTP/1.1 200 OK\r\n\r\n");
|
|
bw.flush();
|
|
readOneRequest(sslSocket.getInputStream());
|
|
sslSocket.close();
|
|
}
|
|
|
|
/*
|
|
* Define the client side of the test.
|
|
*
|
|
* If the server prematurely exits, serverReady will be set to true
|
|
* to avoid infinite hangs.
|
|
*/
|
|
void doClientSide() throws Exception {
|
|
|
|
/*
|
|
* Wait for server to get started.
|
|
*/
|
|
serverReady.await();
|
|
if (serverException != null) {
|
|
throw new RuntimeException("Server failed to start.", serverException);
|
|
}
|
|
|
|
SSLSocketFactory sf = new SimpleSSLContext().get().getSocketFactory();
|
|
URI uri = new URI("https://" + hostName + ":" + serverPort + "/index.html");
|
|
HttpsURLConnection conn = (HttpsURLConnection)uri.toURL().openConnection();
|
|
|
|
/*
|
|
* Simulate an external JSSE implementation and store the client SSLSocket
|
|
* used internally.
|
|
*/
|
|
conn.setSSLSocketFactory(wrapSocketFactory(sf,
|
|
sslSocket -> {
|
|
Asserts.assertEquals(null, clientSSLSocket, "clientSSLSocket is");
|
|
clientSSLSocket = sslSocket;
|
|
}));
|
|
conn.getInputStream();
|
|
|
|
var sniSN = clientSSLSocket.getSSLParameters().getServerNames();
|
|
if (sniSN != null && !sniSN.isEmpty()) {
|
|
throw new RuntimeException("SNI server name '" +
|
|
sniSN.getFirst() + "' must not be set.");
|
|
}
|
|
|
|
if (conn.getResponseCode() == -1) {
|
|
throw new RuntimeException("getResponseCode() returns -1");
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
|
|
if (IPAddressUtil.isIPv6LiteralAddress(args[0]) && !IPSupport.hasIPv6()) {
|
|
throw new SkippedException("Skipping test - IPv6 is not supported");
|
|
}
|
|
/*
|
|
* Start the tests.
|
|
*/
|
|
new SubjectAltNameIP(args[0]);
|
|
}
|
|
|
|
Thread serverThread = null;
|
|
|
|
/*
|
|
* Primary constructor, used to drive remainder of the test.
|
|
*
|
|
* Fork off the other side, then do your work.
|
|
*/
|
|
SubjectAltNameIP(String host) throws Exception {
|
|
hostName = host;
|
|
startServer();
|
|
doClientSide();
|
|
|
|
/*
|
|
* Wait for other side to close down.
|
|
*/
|
|
serverThread.join();
|
|
|
|
if (serverException != null)
|
|
throw serverException;
|
|
}
|
|
|
|
void startServer() {
|
|
serverThread = new Thread(() -> {
|
|
try {
|
|
doServerSide();
|
|
} catch (Exception e) {
|
|
/*
|
|
* Our server thread just died.
|
|
*
|
|
* Store the exception and release the client.
|
|
*/
|
|
serverException = e;
|
|
serverReady.countDown();
|
|
}
|
|
});
|
|
serverThread.start();
|
|
}
|
|
|
|
/*
|
|
* Wraps SSLSocketImpl to simulate a different JSSE implementation
|
|
*/
|
|
private static SSLSocketFactory wrapSocketFactory(final SSLSocketFactory wrap, final Consumer<SSLSocket> store) {
|
|
return new SSLSocketFactory() {
|
|
@Override
|
|
public String[] getDefaultCipherSuites() {
|
|
return wrap.getDefaultCipherSuites();
|
|
}
|
|
@Override
|
|
public String[] getSupportedCipherSuites() {
|
|
return wrap.getSupportedCipherSuites();
|
|
}
|
|
@Override
|
|
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
|
|
throws IOException {
|
|
final SSLSocket so =
|
|
(SSLSocket) wrap.createSocket(s, host, port, autoClose);
|
|
|
|
// store the underlying SSLSocket for later use
|
|
store.accept(so);
|
|
|
|
return new SSLSocket() {
|
|
@Override
|
|
public void connect(SocketAddress endpoint,
|
|
int timeout) throws IOException {
|
|
so.connect(endpoint, timeout);
|
|
}
|
|
@Override
|
|
public String[] getSupportedCipherSuites() {
|
|
return so.getSupportedCipherSuites();
|
|
}
|
|
@Override
|
|
public String[] getEnabledCipherSuites() {
|
|
return so.getEnabledCipherSuites();
|
|
}
|
|
@Override
|
|
public void setEnabledCipherSuites(String[] suites) {
|
|
so.setEnabledCipherSuites(suites);
|
|
}
|
|
@Override
|
|
public String[] getSupportedProtocols() {
|
|
return so.getSupportedProtocols();
|
|
}
|
|
@Override
|
|
public String[] getEnabledProtocols() {
|
|
return so.getEnabledProtocols();
|
|
}
|
|
@Override
|
|
public void setEnabledProtocols(String[] protocols) {
|
|
so.setEnabledProtocols(protocols);
|
|
}
|
|
@Override
|
|
public SSLSession getSession() {
|
|
return so.getSession();
|
|
}
|
|
@Override
|
|
public SSLSession getHandshakeSession() {
|
|
return so.getHandshakeSession();
|
|
}
|
|
@Override
|
|
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
|
so.addHandshakeCompletedListener(listener);
|
|
}
|
|
@Override
|
|
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
|
so.removeHandshakeCompletedListener(listener);
|
|
}
|
|
@Override
|
|
public void startHandshake() throws IOException {
|
|
so.startHandshake();
|
|
}
|
|
@Override
|
|
public void setUseClientMode(boolean mode) {
|
|
so.setUseClientMode(mode);
|
|
}
|
|
@Override
|
|
public boolean getUseClientMode() {
|
|
return so.getUseClientMode();
|
|
}
|
|
@Override
|
|
public void setNeedClientAuth(boolean need) {
|
|
}
|
|
@Override
|
|
public boolean getNeedClientAuth() {
|
|
return false;
|
|
}
|
|
@Override
|
|
public void setWantClientAuth(boolean want) {
|
|
}
|
|
@Override
|
|
public boolean getWantClientAuth() {
|
|
return false;
|
|
}
|
|
@Override
|
|
public void setEnableSessionCreation(boolean flag) {
|
|
so.setEnableSessionCreation(flag);
|
|
}
|
|
@Override
|
|
public boolean getEnableSessionCreation() {
|
|
return so.getEnableSessionCreation();
|
|
}
|
|
@Override
|
|
public void close() throws IOException {
|
|
so.close();
|
|
}
|
|
@Override
|
|
public boolean isClosed() {
|
|
return so.isClosed();
|
|
}
|
|
@Override
|
|
public void shutdownInput() throws IOException {
|
|
so.shutdownInput();
|
|
}
|
|
@Override
|
|
public boolean isInputShutdown() {
|
|
return so.isInputShutdown();
|
|
}
|
|
@Override
|
|
public void shutdownOutput() throws IOException {
|
|
so.shutdownOutput();
|
|
}
|
|
@Override
|
|
public boolean isOutputShutdown() {
|
|
return so.isOutputShutdown();
|
|
}
|
|
@Override
|
|
public InputStream getInputStream() throws IOException {
|
|
return so.getInputStream();
|
|
}
|
|
@Override
|
|
public OutputStream getOutputStream() throws IOException {
|
|
return so.getOutputStream();
|
|
}
|
|
@Override
|
|
public SSLParameters getSSLParameters() {
|
|
return so.getSSLParameters();
|
|
}
|
|
@Override
|
|
public void setSSLParameters(SSLParameters params) {
|
|
so.setSSLParameters(params);
|
|
}
|
|
};
|
|
}
|
|
@Override
|
|
public Socket createSocket(String h, int p) {
|
|
return null;
|
|
}
|
|
@Override
|
|
public Socket createSocket(String h, int p, InetAddress ipa, int lp) {
|
|
return null;
|
|
}
|
|
@Override
|
|
public Socket createSocket(InetAddress h, int p) {
|
|
return null;
|
|
}
|
|
@Override
|
|
public Socket createSocket(InetAddress a, int p, InetAddress l, int lp) {
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
}
|