8212261: Add SSLSession accessors to HttpsURLConnection and SecureCacheResponse
authorxuelei
Fri, 09 Nov 2018 08:24:38 -0800
changeset 52474 13266dac5fdb
parent 52473 a8389aeabf90
child 52475 9887f04b0e0f
8212261: Add SSLSession accessors to HttpsURLConnection and SecureCacheResponse Reviewed-by: mullan, chegar
src/java.base/share/classes/java/net/SecureCacheResponse.java
src/java.base/share/classes/javax/net/ssl/HttpsURLConnection.java
src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java
src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java
src/java.base/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java
test/jdk/javax/net/ssl/HttpsURLConnection/DefaultCacheResponse.java
test/jdk/javax/net/ssl/HttpsURLConnection/DummyCacheResponse.java
test/jdk/javax/net/ssl/HttpsURLConnection/HttpsSession.java
--- a/src/java.base/share/classes/java/net/SecureCacheResponse.java	Fri Nov 09 16:08:14 2018 +0100
+++ b/src/java.base/share/classes/java/net/SecureCacheResponse.java	Fri Nov 09 08:24:38 2018 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2018, 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
@@ -26,9 +26,11 @@
 package java.net;
 
 import java.security.cert.Certificate;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import java.security.Principal;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Represents a cache response originally retrieved through secure
@@ -105,4 +107,27 @@
       * @see #getPeerPrincipal()
       */
      public abstract Principal getLocalPrincipal();
+
+    /**
+     * Returns an {@link Optional} containing the {@code SSLSession} in
+     * use on the original connection that retrieved the network resource.
+     * Returns an empty {@code Optional} if the underlying implementation
+     * does not support this method.
+     *
+     * @implSpec For compatibility, the default implementation of this
+     *           method returns an empty {@code Optional}.  Subclasses
+     *           should override this method with an appropriate
+     *           implementation since an application may need to access
+     *           additional parameters associated with the SSL session.
+     *
+     * @return   an {@link Optional} containing the {@code SSLSession} in
+     *           use on the original connection
+     *
+     * @see SSLSession
+     *
+     * @since 12
+     */
+    public Optional<SSLSession> getSSLSession() {
+        return Optional.empty();
+    }
 }
--- a/src/java.base/share/classes/javax/net/ssl/HttpsURLConnection.java	Fri Nov 09 16:08:14 2018 +0100
+++ b/src/java.base/share/classes/javax/net/ssl/HttpsURLConnection.java	Fri Nov 09 08:24:38 2018 -0800
@@ -29,6 +29,7 @@
 import java.net.HttpURLConnection;
 import java.security.Principal;
 import java.security.cert.X509Certificate;
+import java.util.Optional;
 
 /**
  * <code>HttpsURLConnection</code> extends <code>HttpURLConnection</code>
@@ -52,9 +53,7 @@
  *
  * @since 1.4
  */
-public abstract
-class HttpsURLConnection extends HttpURLConnection
-{
+public abstract class HttpsURLConnection extends HttpURLConnection {
     /**
      * Creates an <code>HttpsURLConnection</code> using the
      * URL specified.
@@ -378,4 +377,29 @@
     public SSLSocketFactory getSSLSocketFactory() {
         return sslSocketFactory;
     }
+
+    /**
+     * Returns an {@link Optional} containing the {@code SSLSession} in
+     * use on this connection.  Returns an empty {@code Optional} if the
+     * underlying implementation does not support this method.
+     *
+     * @implSpec For compatibility, the default implementation of this
+     *           method returns an empty {@code Optional}.  Subclasses
+     *           should override this method with an appropriate
+     *           implementation since an application may need to access
+     *           additional parameters associated with the SSL session.
+     *
+     * @return   an {@link Optional} containing the {@code SSLSession} in
+     *           use on this connection.
+     *
+     * @throws   IllegalStateException if this method is called before
+     *           the connection has been established
+     *
+     * @see SSLSession
+     *
+     * @since 12
+     */
+    public Optional<SSLSession> getSSLSession() {
+        return Optional.empty();
+    }
 }
--- a/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java	Fri Nov 09 16:08:14 2018 +0100
+++ b/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java	Fri Nov 09 08:24:38 2018 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2018, 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
@@ -31,6 +31,8 @@
 import java.security.Principal;
 import java.io.IOException;
 import java.util.List;
+import java.util.Optional;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import sun.net.www.http.*;
 import sun.net.www.protocol.http.HttpURLConnection;
@@ -296,4 +298,19 @@
         }
     }
 
+    SSLSession getSSLSession() {
+        if (cachedResponse != null) {
+            Optional<SSLSession> option =
+                    ((SecureCacheResponse)cachedResponse).getSSLSession();
+            if (option.isPresent()) {
+                return option.orElseThrow();
+            }
+        }
+
+        if (http == null) {
+            throw new IllegalStateException("connection not yet open");
+        }
+
+        return ((HttpsClient)http).getSSLSession();
+    }
 }
--- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java	Fri Nov 09 16:08:14 2018 +0100
+++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java	Fri Nov 09 08:24:38 2018 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2018, 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
@@ -739,6 +739,13 @@
     }
 
     /**
+     * Returns the {@code SSLSession} in use on this connection.
+     */
+    SSLSession getSSLSession() {
+        return session;
+    }
+
+    /**
      * This method implements the SSL HandshakeCompleted callback,
      * remembering the resulting session so that it may be queried
      * for the current cipher suite and peer certificates.  Servers
--- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java	Fri Nov 09 16:08:14 2018 +0100
+++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java	Fri Nov 09 08:24:38 2018 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2018, 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
@@ -46,6 +46,7 @@
 import java.security.Principal;
 import java.util.Map;
 import java.util.List;
+import java.util.Optional;
 import sun.net.www.http.HttpClient;
 
 /**
@@ -533,4 +534,9 @@
     public void setAuthenticator(Authenticator auth) {
         delegate.setAuthenticator(auth);
     }
+
+    @Override
+    public Optional<SSLSession> getSSLSession() {
+        return Optional.ofNullable(delegate.getSSLSession());
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/javax/net/ssl/HttpsURLConnection/DefaultCacheResponse.java	Fri Nov 09 08:24:38 2018 -0800
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2018, 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 8212261
+ * @summary Add SSLSession accessors to HttpsURLConnection and
+ *          SecureCacheResponse
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SecureCacheResponse;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+
+public class DefaultCacheResponse extends SecureCacheResponse {
+
+    public static void main(String[] args) throws Exception {
+        DefaultCacheResponse defaultImpl = new DefaultCacheResponse();
+
+        Optional<SSLSession> sslSession = defaultImpl.getSSLSession();
+        if (sslSession.isPresent()) {
+            throw new Exception(
+                "The default SecureCacheResponse.getSSLSession " +
+                "implementation should return an empty Optional");
+        }
+    }
+
+    @Override
+    public String getCipherSuite() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public List<Certificate> getLocalCertificateChain() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public List<Certificate> getServerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Principal getLocalPrincipal() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaders() throws IOException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public InputStream getBody() throws IOException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/javax/net/ssl/HttpsURLConnection/DummyCacheResponse.java	Fri Nov 09 08:24:38 2018 -0800
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2018, 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 8212261
+ * @summary Add SSLSession accessors to HttpsURLConnection and
+ *          SecureCacheResponse
+ * @library /test/lib
+ * @modules jdk.httpserver
+ * @build jdk.test.lib.net.SimpleSSLContext
+ * @run main/othervm DummyCacheResponse
+ */
+
+import java.io.*;
+import java.net.*;
+import javax.net.ssl.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import jdk.test.lib.net.SimpleSSLContext;
+import com.sun.net.httpserver.*;
+
+public class DummyCacheResponse extends SecureCacheResponse {
+    static SSLContext sslContext;
+    private final SSLSession cachedSession;
+    private final Map<String, List<String>> rqstHeaders;
+
+    public static void main(String[] args) throws Exception {
+        ResponseCache reservedResponseCache = ResponseCache.getDefault();
+        HttpsServer httpsServer = null;
+        ExecutorService executor = null;
+        try {
+            ResponseCache.setDefault(new DummyResponseCache());
+
+            httpsServer = HttpsServer.create(new InetSocketAddress(0), 0);
+            HttpContext c2 =
+                    httpsServer.createContext("/test", new HttpsHandler());
+
+            executor = Executors.newCachedThreadPool();
+            httpsServer.setExecutor(executor);
+
+            sslContext = new SimpleSSLContext().get();
+            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+            httpsServer.start();
+
+            int httpsPort = httpsServer.getAddress().getPort();
+            System.out.println(
+                    "Server address: " + httpsServer.getAddress());
+
+            // the 1st connection
+            runTest(httpsPort, false);
+
+            // the 2nd connection that use the cache
+            runTest(httpsPort, true);
+        } finally {
+            if (httpsServer != null) {
+                httpsServer.stop(2);
+            }
+            if (executor != null) {
+                executor.shutdown();
+            }
+
+            ResponseCache.setDefault(reservedResponseCache);
+        }
+    }
+
+    private static class HttpsHandler implements HttpHandler {
+        public void handle(HttpExchange httpExchange) throws IOException {
+            InputStream is = httpExchange.getRequestBody();
+
+            while (is.read() != -1) {
+                // read to EOF
+            }
+            is.close();
+
+            httpExchange.sendResponseHeaders(200, 0);
+            httpExchange.close();
+        }
+    }
+
+    static void runTest(int port, boolean useCache) throws Exception {
+        URL url = new URL(
+                String.format("https://localhost:%s/test/", port));
+        HttpsURLConnection urlc =
+                (HttpsURLConnection)url.openConnection();
+
+        urlc.setSSLSocketFactory(sslContext.getSocketFactory());
+        urlc.setHostnameVerifier(new HostnameVerifier() {
+            public boolean verify(String s, SSLSession s1) {
+                return true;
+            }
+        });
+
+        try (InputStream is = urlc.getInputStream()) {
+            while (is.read() != -1) {
+                // read to EOF
+            }
+
+            SSLSession session = urlc.getSSLSession().orElseThrow();
+            if (!Objects.equals(urlc.getCipherSuite(),
+                    session.getCipherSuite())) {
+                throw new Exception(
+                    "Incorrect SSLSession for HTTPsURLConnection: " +
+                    urlc.getCipherSuite() + "/" + session.getCipherSuite());
+            }
+
+            // Make sure the cache implementation is used.
+            try {
+                urlc.getServerCertificates();
+                if (useCache) {
+                    throw new Exception(
+                        "The SecureCacheResponse impl should be used");
+                }
+            } catch (UnsupportedOperationException uoe) {
+                if (!useCache) {
+                    throw new Exception(
+                        "The SecureCacheResponse impl should not be used");
+                }
+            }
+        }
+    }
+
+    DummyCacheResponse(SSLSession sslSession,
+            Map<String, List<String>> rqstHeaders) {
+        this.rqstHeaders = rqstHeaders;
+        this.cachedSession = sslSession;
+    }
+
+    @Override
+    public String getCipherSuite() {
+        return cachedSession.getCipherSuite();
+    }
+
+    @Override
+    public List<Certificate> getLocalCertificateChain() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public List<Certificate> getServerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Principal getLocalPrincipal() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaders() throws IOException {
+        return rqstHeaders;
+    }
+
+    @Override
+    public InputStream getBody() throws IOException {
+        return new ByteArrayInputStream(new byte[0]);
+    }
+
+    @Override
+    public Optional<SSLSession> getSSLSession() {
+        return Optional.of(cachedSession);
+    }
+
+    private static class DummyResponseCache extends ResponseCache {
+        Map<URI, SSLSession> httpsConnections = new HashMap<>();
+
+        @Override
+        public CacheResponse get(URI uri, String rqstMethod,
+                Map<String, List<String>> rqstHeaders) throws IOException {
+            if (httpsConnections.containsKey(uri)) {
+                return new DummyCacheResponse(
+                        httpsConnections.get(uri), rqstHeaders);
+            }
+
+            return null;
+        }
+
+        @Override
+        public CacheRequest put(URI uri,
+                URLConnection conn) throws IOException {
+            if (conn instanceof HttpsURLConnection) {
+                HttpsURLConnection httpsConn = (HttpsURLConnection)conn;
+                httpsConnections.putIfAbsent(
+                        uri, httpsConn.getSSLSession().orElseThrow());
+            }
+
+            return null;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/javax/net/ssl/HttpsURLConnection/HttpsSession.java	Fri Nov 09 08:24:38 2018 -0800
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2018, 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 8212261
+ * @summary Add SSLSession accessors to HttpsURLConnection and
+ *          SecureCacheResponse
+ * @library /test/lib
+ * @modules jdk.httpserver
+ * @build jdk.test.lib.net.SimpleSSLContext
+ * @run main/othervm HttpsSession
+ */
+import com.sun.net.httpserver.*;
+import java.net.*;
+import java.io.*;
+import javax.net.ssl.*;
+import java.util.concurrent.*;
+import java.util.Objects;
+import jdk.test.lib.net.SimpleSSLContext;
+
+public class HttpsSession {
+
+    static SSLContext sslContext;
+
+    public static void main(String[] args) throws Exception {
+        HttpsServer httpsServer = null;
+        ExecutorService executor = null;
+        try {
+            httpsServer = HttpsServer.create(new InetSocketAddress(0), 0);
+            HttpContext c2 =
+                    httpsServer.createContext("/test", new HttpsHandler());
+
+            executor = Executors.newCachedThreadPool();
+            httpsServer.setExecutor(executor);
+
+            sslContext = new SimpleSSLContext().get();
+            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+            httpsServer.start();
+
+            int httpsPort = httpsServer.getAddress().getPort();
+            System.out.println(
+                    "Server address: " + httpsServer.getAddress());
+
+            runTest(httpsPort);
+        } finally {
+            if (httpsServer != null) {
+                httpsServer.stop(2);
+            }
+            if (executor != null) {
+                executor.shutdown();
+            }
+        }
+    }
+
+    private static class HttpsHandler implements HttpHandler {
+        public void handle(HttpExchange httpExchange) throws IOException {
+            InputStream is = httpExchange.getRequestBody();
+
+            while (is.read() != -1) {
+                // read to EOF
+            }
+            is.close();
+
+            httpExchange.sendResponseHeaders(200, 0);
+            httpExchange.close();
+        }
+    }
+
+    static void runTest(int port) throws Exception {
+        URL url = new URL(
+                String.format("https://localhost:%s/test/", port));
+        HttpsURLConnection urlc =
+                (HttpsURLConnection)url.openConnection();
+
+        urlc.setSSLSocketFactory(sslContext.getSocketFactory());
+        urlc.setHostnameVerifier(new HostnameVerifier() {
+            public boolean verify(String s, SSLSession s1) {
+                return true;
+            }
+        });
+
+        try {
+            urlc.getSSLSession();
+            throw new Exception(
+                "HttpsURLConnection.getSSLSession() should throw " +
+                "IllegalStateException before the connection established");
+        } catch (IllegalStateException ise) {
+            // That's the expected behavior, continue.
+        }
+
+        try (InputStream is = urlc.getInputStream()) {
+            while (is.read() != -1) {
+                // read to EOF
+            }
+
+            SSLSession session = urlc.getSSLSession().orElseThrow();
+            if (!Objects.equals(urlc.getCipherSuite(),
+                    session.getCipherSuite())) {
+                throw new Exception(
+                    "Incorrect SSLSession for HTTPsURLConnection: " +
+                    urlc.getCipherSuite() + "/" + session.getCipherSuite());
+            }
+        }
+    }
+}