changeset 14477:d727e88aa12a jdk8u302-b05

8254631: Better support ALPN byte wire values in SunJSSE Reviewed-by: xuelei, dfuchs, sgehwolf
author wetmore
date Wed, 02 Dec 2020 04:14:28 +0000
parents 219c9107171b
children 8f0c77f98b2a
files src/share/classes/sun/security/ssl/AlpnExtension.java src/share/lib/security/java.security-aix src/share/lib/security/java.security-linux src/share/lib/security/java.security-macosx src/share/lib/security/java.security-solaris src/share/lib/security/java.security-windows test/sun/security/ssl/ALPN/AlpnGreaseTest.java
diffstat 7 files changed, 392 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/ssl/AlpnExtension.java	Thu May 27 19:17:04 2021 +0300
+++ b/src/share/classes/sun/security/ssl/AlpnExtension.java	Wed Dec 02 04:14:28 2020 +0000
@@ -27,7 +27,10 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.Security;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -59,6 +62,20 @@
 
     static final SSLStringizer alpnStringizer = new AlpnStringizer();
 
+    // Encoding Charset to convert between String and byte[]
+    static final Charset alpnCharset;
+
+    static {
+        String alpnCharsetString = AccessController.doPrivileged(
+                (PrivilegedAction<String>) ()
+                        -> Security.getProperty("jdk.tls.alpnCharset"));
+        if ((alpnCharsetString == null)
+                || (alpnCharsetString.length() == 0)) {
+            alpnCharsetString = "ISO_8859_1";
+        }
+        alpnCharset = Charset.forName(alpnCharsetString);
+    }
+
     /**
      * The "application_layer_protocol_negotiation" extension.
      *
@@ -97,7 +114,7 @@
                         "extension: empty application protocol name");
                 }
 
-                String appProtocol = new String(bytes, StandardCharsets.UTF_8);
+                String appProtocol = new String(bytes, alpnCharset);
                 protocolNames.add(appProtocol);
             }
 
@@ -164,10 +181,10 @@
                 return null;
             }
 
-            // Produce the extension.
+            // Produce the extension:  first find the overall length
             int listLength = 0;     // ProtocolNameList length
             for (String ap : laps) {
-                int length = ap.getBytes(StandardCharsets.UTF_8).length;
+                int length = ap.getBytes(alpnCharset).length;
                 if (length == 0) {
                     // log the configuration problem
                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
@@ -219,8 +236,10 @@
             byte[] extData = new byte[listLength + 2];
             ByteBuffer m = ByteBuffer.wrap(extData);
             Record.putInt16(m, listLength);
+
+            // opaque ProtocolName<1..2^8-1>;
             for (String ap : laps) {
-                Record.putBytes8(m, ap.getBytes(StandardCharsets.UTF_8));
+                Record.putBytes8(m, ap.getBytes(alpnCharset));
             }
 
             // Update the context.
@@ -415,14 +434,14 @@
             }
 
             // opaque ProtocolName<1..2^8-1>, RFC 7301.
-            int listLen = shc.applicationProtocol.length() + 1;
-                                                        // 1: length byte
+            byte[] bytes = shc.applicationProtocol.getBytes(alpnCharset);
+            int listLen = bytes.length + 1;             // 1: length byte
+
             // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301.
             byte[] extData = new byte[listLen + 2];     // 2: list length
             ByteBuffer m = ByteBuffer.wrap(extData);
             Record.putInt16(m, listLen);
-            Record.putBytes8(m,
-                    shc.applicationProtocol.getBytes(StandardCharsets.UTF_8));
+            Record.putBytes8(m, bytes);
 
             // Update the context.
             shc.conContext.applicationProtocol = shc.applicationProtocol;
--- a/src/share/lib/security/java.security-aix	Thu May 27 19:17:04 2021 +0300
+++ b/src/share/lib/security/java.security-aix	Wed Dec 02 04:14:28 2020 +0000
@@ -1193,6 +1193,16 @@
 #jdk.security.allowNonCaAnchor=true
 
 #
+# The default Character set name (java.nio.charset.Charset.forName())
+# for converting TLS ALPN values between byte arrays and Strings.
+# Prior versions of the JDK may use UTF-8 as the default charset. If
+# you experience interoperability issues, setting this property to UTF-8
+# may help.
+#
+# jdk.tls.alpnCharset=UTF-8
+jdk.tls.alpnCharset=ISO_8859_1
+
+#
 # JNDI Object Factories Filter
 #
 # This filter is used by the JNDI runtime to control the set of object factory classes
--- a/src/share/lib/security/java.security-linux	Thu May 27 19:17:04 2021 +0300
+++ b/src/share/lib/security/java.security-linux	Wed Dec 02 04:14:28 2020 +0000
@@ -1199,6 +1199,16 @@
 #jdk.security.allowNonCaAnchor=true
 
 #
+# The default Character set name (java.nio.charset.Charset.forName())
+# for converting TLS ALPN values between byte arrays and Strings.
+# Prior versions of the JDK may use UTF-8 as the default charset. If
+# you experience interoperability issues, setting this property to UTF-8
+# may help.
+#
+# jdk.tls.alpnCharset=UTF-8
+jdk.tls.alpnCharset=ISO_8859_1
+
+#
 # JNDI Object Factories Filter
 #
 # This filter is used by the JNDI runtime to control the set of object factory classes
--- a/src/share/lib/security/java.security-macosx	Thu May 27 19:17:04 2021 +0300
+++ b/src/share/lib/security/java.security-macosx	Wed Dec 02 04:14:28 2020 +0000
@@ -1197,6 +1197,16 @@
 #jdk.security.allowNonCaAnchor=true
 
 #
+# The default Character set name (java.nio.charset.Charset.forName())
+# for converting TLS ALPN values between byte arrays and Strings.
+# Prior versions of the JDK may use UTF-8 as the default charset. If
+# you experience interoperability issues, setting this property to UTF-8
+# may help.
+#
+# jdk.tls.alpnCharset=UTF-8
+jdk.tls.alpnCharset=ISO_8859_1
+
+#
 # JNDI Object Factories Filter
 #
 # This filter is used by the JNDI runtime to control the set of object factory classes
--- a/src/share/lib/security/java.security-solaris	Thu May 27 19:17:04 2021 +0300
+++ b/src/share/lib/security/java.security-solaris	Wed Dec 02 04:14:28 2020 +0000
@@ -1195,6 +1195,16 @@
 #jdk.security.allowNonCaAnchor=true
 
 #
+# The default Character set name (java.nio.charset.Charset.forName())
+# for converting TLS ALPN values between byte arrays and Strings.
+# Prior versions of the JDK may use UTF-8 as the default charset. If
+# you experience interoperability issues, setting this property to UTF-8
+# may help.
+#
+# jdk.tls.alpnCharset=UTF-8
+jdk.tls.alpnCharset=ISO_8859_1
+
+#
 # JNDI Object Factories Filter
 #
 # This filter is used by the JNDI runtime to control the set of object factory classes
--- a/src/share/lib/security/java.security-windows	Thu May 27 19:17:04 2021 +0300
+++ b/src/share/lib/security/java.security-windows	Wed Dec 02 04:14:28 2020 +0000
@@ -1197,6 +1197,16 @@
 #jdk.security.allowNonCaAnchor=true
 
 #
+# The default Character set name (java.nio.charset.Charset.forName())
+# for converting TLS ALPN values between byte arrays and Strings.
+# Prior versions of the JDK may use UTF-8 as the default charset. If
+# you experience interoperability issues, setting this property to UTF-8
+# may help.
+#
+# jdk.tls.alpnCharset=UTF-8
+jdk.tls.alpnCharset=ISO_8859_1
+
+#
 # JNDI Object Factories Filter
 #
 # This filter is used by the JNDI runtime to control the set of object factory classes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/ssl/ALPN/AlpnGreaseTest.java	Wed Dec 02 04:14:28 2020 +0000
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8254631
+ * @summary Better support ALPN byte wire values in SunJSSE
+ * @library /javax/net/ssl/templates
+ * @run main/othervm AlpnGreaseTest
+ */
+import javax.net.ssl.*;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * A SSLEngine usage example which simplifies the presentation
+ * by removing the I/O and multi-threading concerns.
+ *
+ * The test creates two SSLEngines, simulating a client and server.
+ * The "transport" layer consists two byte buffers:  think of them
+ * as directly connected pipes.
+ *
+ * Note, this is a *very* simple example: real code will be much more
+ * involved.  For example, different threading and I/O models could be
+ * used, transport mechanisms could close unexpectedly, and so on.
+ *
+ * When this application runs, notice that several messages
+ * (wrap/unwrap) pass before any application data is consumed or
+ * produced.
+ */
+public class AlpnGreaseTest implements SSLContextTemplate {
+
+    private final SSLEngine clientEngine;     // client Engine
+    private final ByteBuffer clientOut;       // write side of clientEngine
+    private final ByteBuffer clientIn;        // read side of clientEngine
+
+    private final SSLEngine serverEngine;     // server Engine
+    private final ByteBuffer serverOut;       // write side of serverEngine
+    private final ByteBuffer serverIn;        // read side of serverEngine
+
+    // For data transport, this example uses local ByteBuffers.  This
+    // isn't really useful, but the purpose of this example is to show
+    // SSLEngine concepts, not how to do network transport.
+    private final ByteBuffer cTOs;      // "reliable" transport client->server
+    private final ByteBuffer sTOc;      // "reliable" transport server->client
+
+    // These are the various 8-bit char values that could be sent as GREASE
+    // values.  We'll just make one big String here to make it easy to check
+    // that the right values are being output.
+    private static final byte[] greaseBytes = new byte[] {
+        (byte) 0x0A, (byte) 0x1A, (byte) 0x2A, (byte) 0x3A,
+        (byte) 0x4A, (byte) 0x5A, (byte) 0x6A, (byte) 0x7A,
+        (byte) 0x8A, (byte) 0x9A, (byte) 0xAA, (byte) 0xBA,
+        (byte) 0xCA, (byte) 0xDA, (byte) 0xEA, (byte) 0xFA
+    };
+
+    private static final String greaseString =
+            new String(greaseBytes, StandardCharsets.ISO_8859_1);
+
+    private static void findGreaseInClientHello(byte[] bytes) throws Exception {
+        for (int i = 0; i < bytes.length - greaseBytes.length; i++) {
+            if (findGreaseInArray(bytes, i)) {
+                System.out.println("Found greaseBytes in ClientHello at: " + i);
+                return;
+            }
+        }
+        throw new Exception("Couldn't find greaseBytes");
+    }
+
+    private static boolean findGreaseInArray(byte[] bytes, int startOff) {
+        for (int i = 0; i < greaseBytes.length; i++) {
+            if (bytes[startOff + i] != greaseBytes[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private AlpnGreaseTest() throws Exception {
+        serverEngine = configureServerEngine(
+                createServerSSLContext().createSSLEngine());
+
+        clientEngine = configureClientEngine(
+                createClientSSLContext().createSSLEngine());
+
+        // We'll assume the buffer sizes are the same
+        // between client and server.
+        SSLSession session = clientEngine.getSession();
+        int appBufferMax = session.getApplicationBufferSize();
+        int netBufferMax = session.getPacketBufferSize();
+
+        // We'll make the input buffers a bit bigger than the max needed
+        // size, so that unwrap()s following a successful data transfer
+        // won't generate BUFFER_OVERFLOWS.
+        //
+        // We'll use a mix of direct and indirect ByteBuffers for
+        // tutorial purposes only.  In reality, only use direct
+        // ByteBuffers when they give a clear performance enhancement.
+        clientIn = ByteBuffer.allocate(appBufferMax + 50);
+        serverIn = ByteBuffer.allocate(appBufferMax + 50);
+
+        cTOs = ByteBuffer.allocateDirect(netBufferMax);
+        sTOc = ByteBuffer.allocateDirect(netBufferMax);
+
+        clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
+        serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes());
+    }
+
+    //
+    // Protected methods could be used to customize the test case.
+    //
+
+    /*
+     * Configure the client side engine.
+     */
+    protected SSLEngine configureClientEngine(SSLEngine clientEngine) {
+        clientEngine.setUseClientMode(true);
+
+        // Get/set parameters if needed
+        SSLParameters paramsClient = clientEngine.getSSLParameters();
+        paramsClient.setApplicationProtocols(new String[] { greaseString });
+
+        clientEngine.setSSLParameters(paramsClient);
+
+        return clientEngine;
+    }
+
+    /*
+     * Configure the server side engine.
+     */
+    protected SSLEngine configureServerEngine(SSLEngine serverEngine) {
+        serverEngine.setUseClientMode(false);
+        serverEngine.setNeedClientAuth(true);
+
+        // Get/set parameters if needed
+        //
+        SSLParameters paramsServer = serverEngine.getSSLParameters();
+        paramsServer.setApplicationProtocols(new String[] { greaseString });
+        serverEngine.setSSLParameters(paramsServer);
+
+        return serverEngine;
+    }
+
+    public static void main(String[] args) throws Exception {
+        new AlpnGreaseTest().runTest();
+    }
+
+    //
+    // Private methods that used to build the common part of the test.
+    //
+
+    private void runTest() throws Exception {
+        SSLEngineResult clientResult;
+        SSLEngineResult serverResult;
+
+        boolean dataDone = false;
+        boolean firstClientWrap = true;
+        while (isOpen(clientEngine) || isOpen(serverEngine)) {
+            log("=================");
+
+            // client wrap
+            log("---Client Wrap---");
+            clientResult = clientEngine.wrap(clientOut, cTOs);
+            logEngineStatus(clientEngine, clientResult);
+            runDelegatedTasks(clientEngine);
+
+            if (firstClientWrap) {
+                firstClientWrap = false;
+                byte[] bytes = new byte[cTOs.position()];
+                ((ByteBuffer)(cTOs.duplicate().flip())).get(bytes);
+                findGreaseInClientHello(bytes);
+            }
+
+            // server wrap
+            log("---Server Wrap---");
+            serverResult = serverEngine.wrap(serverOut, sTOc);
+            logEngineStatus(serverEngine, serverResult);
+            runDelegatedTasks(serverEngine);
+
+            cTOs.flip();
+            sTOc.flip();
+
+            // client unwrap
+            log("---Client Unwrap---");
+            clientResult = clientEngine.unwrap(sTOc, clientIn);
+            logEngineStatus(clientEngine, clientResult);
+            runDelegatedTasks(clientEngine);
+
+            // server unwrap
+            log("---Server Unwrap---");
+            serverResult = serverEngine.unwrap(cTOs, serverIn);
+            logEngineStatus(serverEngine, serverResult);
+            runDelegatedTasks(serverEngine);
+
+            cTOs.compact();
+            sTOc.compact();
+
+            // After we've transferred all application data between the client
+            // and server, we close the clientEngine's outbound stream.
+            // This generates a close_notify handshake message, which the
+            // server engine receives and responds by closing itself.
+            if (!dataDone && (clientOut.limit() == serverIn.position()) &&
+                    (serverOut.limit() == clientIn.position())) {
+
+                // Check ALPN Value
+                String alpnServerValue = serverEngine.getApplicationProtocol();
+                String alpnClientValue = clientEngine.getApplicationProtocol();
+
+                if (!alpnServerValue.equals(greaseString)
+                        || !alpnClientValue.equals(greaseString)) {
+                    throw new Exception("greaseString didn't match");
+                }
+
+                // A sanity check to ensure we got what was sent.
+                checkTransfer(serverOut, clientIn);
+                checkTransfer(clientOut, serverIn);
+
+                log("\tClosing clientEngine's *OUTBOUND*...");
+                clientEngine.closeOutbound();
+                logEngineStatus(clientEngine);
+
+                dataDone = true;
+                log("\tClosing serverEngine's *OUTBOUND*...");
+                serverEngine.closeOutbound();
+                logEngineStatus(serverEngine);
+            }
+        }
+    }
+
+    private static boolean isOpen(SSLEngine engine) {
+        return (!engine.isOutboundDone() || !engine.isInboundDone());
+    }
+
+    private static void logEngineStatus(SSLEngine engine) {
+        log("\tCurrent HS State: " + engine.getHandshakeStatus());
+        log("\tisInboundDone() : " + engine.isInboundDone());
+        log("\tisOutboundDone(): " + engine.isOutboundDone());
+    }
+
+    private static void logEngineStatus(
+            SSLEngine engine, SSLEngineResult result) {
+        log("\tResult Status    : " + result.getStatus());
+        log("\tResult HS Status : " + result.getHandshakeStatus());
+        log("\tEngine HS Status : " + engine.getHandshakeStatus());
+        log("\tisInboundDone()  : " + engine.isInboundDone());
+        log("\tisOutboundDone() : " + engine.isOutboundDone());
+        log("\tMore Result      : " + result);
+    }
+
+    private static void log(String message) {
+        System.err.println(message);
+    }
+
+    // If the result indicates that we have outstanding tasks to do,
+    // go ahead and run them in this thread.
+    private static void runDelegatedTasks(SSLEngine engine) throws Exception {
+        if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+            Runnable runnable;
+            while ((runnable = engine.getDelegatedTask()) != null) {
+                log("    running delegated task...");
+                runnable.run();
+            }
+            HandshakeStatus hsStatus = engine.getHandshakeStatus();
+            if (hsStatus == HandshakeStatus.NEED_TASK) {
+                throw new Exception(
+                        "handshake shouldn't need additional tasks");
+            }
+            logEngineStatus(engine);
+        }
+    }
+
+    // Simple check to make sure everything came across as expected.
+    private static void checkTransfer(ByteBuffer a, ByteBuffer b)
+            throws Exception {
+        a.flip();
+        b.flip();
+
+        if (!a.equals(b)) {
+            throw new Exception("Data didn't transfer cleanly");
+        } else {
+            log("\tData transferred cleanly");
+        }
+
+        a.position(a.limit());
+        b.position(b.limit());
+        a.limit(a.capacity());
+        b.limit(b.capacity());
+    }
+}