From bc31ccc95be9523cc6c64932f6d39f81c2e82bdd Mon Sep 17 00:00:00 2001 From: Michael McMahon Date: Thu, 9 Dec 2021 17:38:49 +0000 Subject: [PATCH] 8278312: Update SimpleSSLContext keystore to use SANs for localhost IP addresses Reviewed-by: dfuchs --- test/jdk/com/sun/net/httpserver/SANTest.java | 202 ++++++++++++++++++ .../http2/server/Http2TestServer.java | 24 ++- test/lib/jdk/test/lib/net/testkeys | Bin 4217 -> 4410 bytes 3 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 test/jdk/com/sun/net/httpserver/SANTest.java diff --git a/test/jdk/com/sun/net/httpserver/SANTest.java b/test/jdk/com/sun/net/httpserver/SANTest.java new file mode 100644 index 00000000000..db0fc0542d6 --- /dev/null +++ b/test/jdk/com/sun/net/httpserver/SANTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021, 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 8278312 + * @library /test/lib /test/jdk/java/net/httpclient /test/jdk/java/net/httpclient/http2/server + * @build jdk.test.lib.net.SimpleSSLContext HttpServerAdapters Http2Handler + * jdk.test.lib.net.IPSupport + * Http2TestExchange + * + * @modules java.net.http/jdk.internal.net.http.common + * java.net.http/jdk.internal.net.http.frame + * java.net.http/jdk.internal.net.http.hpack + * java.logging + * java.base/sun.net.www.http + * java.base/sun.net.www + * java.base/sun.net + * + * @run main/othervm SANTest + * @summary Update SimpleSSLContext keystore to use SANs for localhost IP addresses + */ + +import com.sun.net.httpserver.*; + +import java.util.concurrent.*; +import java.io.*; +import java.net.*; +import java.net.http.*; +import java.nio.charset.StandardCharsets; +import javax.net.ssl.*; +import jdk.test.lib.net.SimpleSSLContext; +import jdk.test.lib.net.URIBuilder; +import jdk.test.lib.net.IPSupport; + +/* + * Will fail if the testkeys file belonging to SimpleSSLContext + * does not have SAN entries for 127.0.0.1 or ::1 + */ +public class SANTest implements HttpServerAdapters { + + static SSLContext ctx; + + static HttpServer getHttpsServer(InetSocketAddress addr, Executor exec, SSLContext ctx) throws Exception { + HttpsServer server = HttpsServer.create(addr, 0); + server.setExecutor(exec); + server.setHttpsConfigurator(new HttpsConfigurator (ctx)); + return server; + } + + static final boolean hasIPv4 = IPSupport.hasIPv4(); + static final boolean hasIPv6 = IPSupport.hasIPv6(); + + static HttpTestServer initServer(boolean h2, InetAddress addr, SSLContext ctx, + String sni, ExecutorService e) throws Exception { + HttpTestServer s = null; + InetSocketAddress ia = new InetSocketAddress (addr, 0); + if ((addr instanceof Inet4Address) && !hasIPv4) + return null; + if ((addr instanceof Inet6Address) && !hasIPv6) + return null; + + if (!h2) { + s = HttpTestServer.of(getHttpsServer(ia, e, ctx)); + HttpTestHandler h = new HttpTestEchoHandler(); + s.addHandler(h, "/test1"); + s.start(); + return s; + } else { + s = HttpTestServer.of(new Http2TestServer(addr, sni, true, 0, e, + 10, null, ctx, false)); + HttpTestHandler h = new HttpTestEchoHandler(); + s.addHandler(h, "/test1"); + s.start(); + return s; + } + } + + public static void main (String[] args) throws Exception { + // Http/1.1 servers + HttpTestServer h1s1 = null; + HttpTestServer h1s2 = null; + + // Http/2 servers + HttpTestServer h2s1 = null; + HttpTestServer h2s2 = null; + + ExecutorService executor=null; + try { + System.out.print ("SANTest: "); + ctx = new SimpleSSLContext().get(); + executor = Executors.newCachedThreadPool(); + + InetAddress l1 = InetAddress.getByName("::1"); + InetAddress l2 = InetAddress.getByName("127.0.0.1"); + + h1s1 = initServer(false, l1, ctx, "::1", executor); + h1s2 = initServer(false, l2, ctx, "127.0.0.1", executor); + + h2s1 = initServer(true, l1, ctx, "::1", executor); + h2s2 = initServer(true, l2, ctx, "127.0.0.1", executor); + + test("127.0.0.1", h1s2); + test("::1", h1s1); + testNew("127.0.0.1", h2s2, executor); + testNew("::1", h2s1, executor); + System.out.println ("OK"); + } finally { + if (h1s1 != null) + h1s1.stop(); + if (h1s2 != null) + h1s2.stop(); + if (h2s1 != null) + h2s1.stop(); + if (h2s2 != null) + h2s2.stop(); + if (executor != null) + executor.shutdown (); + } + } + + static void test (String host, HttpTestServer server) throws Exception { + if (server == null) + return; + int port = server.getAddress().getPort(); + String body = "Yellow world"; + URL url = URIBuilder.newBuilder() + .scheme("https") + .host(host) + .port(port) + .path("/test1/foo.txt") + .toURL(); + System.out.println("URL = " + url); + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); + System.out.println("urlc = " + urlc); + if (urlc instanceof HttpsURLConnection) { + HttpsURLConnection urlcs = (HttpsURLConnection) urlc; + urlcs.setSSLSocketFactory (ctx.getSocketFactory()); + } + + urlc.setRequestMethod("POST"); + urlc.setDoOutput(true); + + OutputStream os = urlc.getOutputStream(); + os.write(body.getBytes(StandardCharsets.ISO_8859_1)); + os.close(); + InputStream is = urlc.getInputStream(); + byte[] vv = is.readAllBytes(); + String ff = new String(vv, StandardCharsets.ISO_8859_1); + System.out.println("resp = " + ff); + if (!ff.equals(body)) + throw new RuntimeException(); + is.close(); + } + + static void testNew (String host, HttpTestServer server, Executor exec) throws Exception { + if (server == null) + return; + int port = server.getAddress().getPort(); + String body = "Red and Yellow world"; + URI uri = URIBuilder.newBuilder() + .scheme("https") + .host(host) + .port(port) + .path("/test1/foo.txt") + .build(); + + HttpClient client = HttpClient.newBuilder() + .sslContext(ctx) + .executor(exec) + .build(); + HttpRequest req = HttpRequest.newBuilder(uri) + .version(HttpClient.Version.HTTP_2) + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); + System.out.println("resp = " + resp.body()); + if (!resp.body().equals(body)) + throw new RuntimeException(); + } +} diff --git a/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java b/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java index 33e5ade4f83..f8592272588 100644 --- a/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java +++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java @@ -124,6 +124,20 @@ public class Http2TestServer implements AutoCloseable { this(serverName, secure, port, exec, backlog, properties, context, false); } + public Http2TestServer(String serverName, + boolean secure, + int port, + ExecutorService exec, + int backlog, + Properties properties, + SSLContext context, + boolean supportsHTTP11) + throws Exception + { + this(InetAddress.getLoopbackAddress(), serverName, secure, port, exec, + backlog, properties, context, supportsHTTP11); + } + /** * Create a Http2Server listening on the given port. Currently needs * to know in advance whether incoming connections are plain TCP "h2c" @@ -134,6 +148,7 @@ public class Http2TestServer implements AutoCloseable { * "X-Magic", "HTTP/1.1 request received by HTTP/2 server", * "X-Received-Body", ); * + * @param localAddr local address to bind to * @param serverName SNI servername * @param secure https or http * @param port listen port @@ -146,7 +161,8 @@ public class Http2TestServer implements AutoCloseable { * connection without the h2 ALPN. Otherwise, false to operate in * HTTP/2 mode exclusively. */ - public Http2TestServer(String serverName, + public Http2TestServer(InetAddress localAddr, + String serverName, boolean secure, int port, ExecutorService exec, @@ -163,7 +179,7 @@ public class Http2TestServer implements AutoCloseable { this.sslContext = context; else this.sslContext = SSLContext.getDefault(); - server = initSecure(port, backlog); + server = initSecure(localAddr, port, backlog); } else { this.sslContext = context; server = initPlaintext(port, backlog); @@ -236,14 +252,14 @@ public class Http2TestServer implements AutoCloseable { } - final ServerSocket initSecure(int port, int backlog) throws Exception { + final ServerSocket initSecure(InetAddress localAddr, int port, int backlog) throws Exception { ServerSocketFactory fac; SSLParameters sslp = null; fac = sslContext.getServerSocketFactory(); sslp = sslContext.getSupportedSSLParameters(); SSLServerSocket se = (SSLServerSocket) fac.createServerSocket(); se.setReuseAddress(false); - se.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), backlog); + se.bind(new InetSocketAddress(localAddr, 0), backlog); if (supportsHTTP11) { sslp.setApplicationProtocols(new String[]{"h2", "http/1.1"}); } else { diff --git a/test/lib/jdk/test/lib/net/testkeys b/test/lib/jdk/test/lib/net/testkeys index 4673898e0ae15936ac8b83357b802dc425f0c64c..d2818ec9b6c6dd5386a2cabe53b923bf6046e577 100644 GIT binary patch literal 4410 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