You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2020/12/04 09:10:43 UTC

[tomcat] branch 8.5.x updated: Fix BZ 64110 - request attr for requested ciphers and protocols

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/8.5.x by this push:
     new 2448e04  Fix BZ 64110 - request attr for requested ciphers and protocols
2448e04 is described below

commit 2448e046c314bbf356535ac6c5a6f93f2cde5e6f
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Nov 25 18:44:11 2020 +0000

    Fix BZ 64110 - request attr for requested ciphers and protocols
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=64110
---
 java/org/apache/catalina/connector/Request.java    |  8 ++
 java/org/apache/catalina/util/TLSUtil.java         | 19 +++--
 java/org/apache/coyote/AbstractProcessor.java      |  8 ++
 java/org/apache/tomcat/util/buf/HexUtils.java      | 14 ++++
 java/org/apache/tomcat/util/net/AprSSLSupport.java | 13 ++++
 .../apache/tomcat/util/net/LocalStrings.properties |  1 +
 java/org/apache/tomcat/util/net/Nio2Endpoint.java  |  7 +-
 java/org/apache/tomcat/util/net/NioEndpoint.java   |  7 +-
 .../apache/tomcat/util/net/SSLImplementation.java  | 27 +++++++
 java/org/apache/tomcat/util/net/SSLSupport.java    | 33 +++++++-
 .../apache/tomcat/util/net/SecureNio2Channel.java  | 22 +++++-
 .../apache/tomcat/util/net/SecureNioChannel.java   | 20 +++++
 .../tomcat/util/net/TLSClientHelloExtractor.java   | 89 ++++++++++++++++++++--
 .../tomcat/util/net/jsse/JSSEImplementation.java   | 11 ++-
 .../apache/tomcat/util/net/jsse/JSSESupport.java   | 34 ++++++++-
 .../util/net/openssl/OpenSSLImplementation.java    |  9 +++
 webapps/docs/changelog.xml                         |  9 +++
 webapps/docs/config/http.xml                       |  6 ++
 18 files changed, 306 insertions(+), 31 deletions(-)

diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
index 094bdec..dcf6c7f 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -944,6 +944,14 @@ public class Request implements HttpServletRequest {
             if (attr != null) {
                 attributes.put(SSLSupport.PROTOCOL_VERSION_KEY, attr);
             }
+            attr = coyoteRequest.getAttribute(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY);
+            if (attr != null) {
+                attributes.put(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY, attr);
+            }
+            attr = coyoteRequest.getAttribute(SSLSupport.REQUESTED_CIPHERS_KEY);
+            if (attr != null) {
+                attributes.put(SSLSupport.REQUESTED_CIPHERS_KEY, attr);
+            }
             attr = attributes.get(name);
             sslAttributesParsed = true;
         }
diff --git a/java/org/apache/catalina/util/TLSUtil.java b/java/org/apache/catalina/util/TLSUtil.java
index a739021..7f895dd 100644
--- a/java/org/apache/catalina/util/TLSUtil.java
+++ b/java/org/apache/catalina/util/TLSUtil.java
@@ -33,11 +33,18 @@ public class TLSUtil {
      *         information, otherwise {@code false}
      */
     public static boolean isTLSRequestAttribute(String name) {
-        return Globals.CERTIFICATES_ATTR.equals(name) ||
-                Globals.CIPHER_SUITE_ATTR.equals(name) ||
-                Globals.KEY_SIZE_ATTR.equals(name)  ||
-                Globals.SSL_SESSION_ID_ATTR.equals(name) ||
-                Globals.SSL_SESSION_MGR_ATTR.equals(name) ||
-                SSLSupport.PROTOCOL_VERSION_KEY.equals(name);
+        switch (name) {
+            case Globals.CERTIFICATES_ATTR:
+            case Globals.CIPHER_SUITE_ATTR:
+            case Globals.KEY_SIZE_ATTR:
+            case Globals.SSL_SESSION_ID_ATTR:
+            case Globals.SSL_SESSION_MGR_ATTR:
+            case SSLSupport.PROTOCOL_VERSION_KEY:
+            case SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY:
+            case SSLSupport.REQUESTED_CIPHERS_KEY:
+                return true;
+            default:
+                return false;
+        }
     }
 }
diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java
index 6480ddf..6140839 100644
--- a/java/org/apache/coyote/AbstractProcessor.java
+++ b/java/org/apache/coyote/AbstractProcessor.java
@@ -798,6 +798,14 @@ public abstract class AbstractProcessor extends AbstractProcessorLight implement
                 if (sslO != null) {
                     request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, sslO);
                 }
+                sslO = sslSupport.getRequestedProtocols();
+                if (sslO != null) {
+                    request.setAttribute(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY, sslO);
+                }
+                sslO = sslSupport.getRequestedCiphers();
+                if (sslO != null) {
+                    request.setAttribute(SSLSupport.REQUESTED_CIPHERS_KEY, sslO);
+                }
                 request.setAttribute(SSLSupport.SESSION_MGR, sslSupport);
             }
         } catch (Exception e) {
diff --git a/java/org/apache/tomcat/util/buf/HexUtils.java b/java/org/apache/tomcat/util/buf/HexUtils.java
index e80179b..fffa5f9 100644
--- a/java/org/apache/tomcat/util/buf/HexUtils.java
+++ b/java/org/apache/tomcat/util/buf/HexUtils.java
@@ -75,6 +75,20 @@ public final class HexUtils {
     }
 
 
+    public static String toHexString(char c) {
+        // 2 bytes / 4 hex digits
+        StringBuilder sb = new StringBuilder(4);
+
+        sb.append(hex[(c & 0xf000) >> 4]);
+        sb.append(hex[(c & 0x0f00)]);
+
+        sb.append(hex[(c & 0xf0) >> 4]);
+        sb.append(hex[(c & 0x0f)]);
+
+        return sb.toString();
+    }
+
+
     public static String toHexString(byte[] bytes) {
         if (null == bytes) {
             return null;
diff --git a/java/org/apache/tomcat/util/net/AprSSLSupport.java b/java/org/apache/tomcat/util/net/AprSSLSupport.java
index 8f9be75..f36eaf4 100644
--- a/java/org/apache/tomcat/util/net/AprSSLSupport.java
+++ b/java/org/apache/tomcat/util/net/AprSSLSupport.java
@@ -104,6 +104,7 @@ public class AprSSLSupport implements SSLSupport {
         }
     }
 
+
     @Override
     public String getProtocol() throws IOException {
         try {
@@ -112,4 +113,16 @@ public class AprSSLSupport implements SSLSupport {
             throw new IOException(e);
         }
    }
+
+
+    @Override
+    public String getRequestedProtocols() throws IOException {
+        return null;
+    }
+
+
+    @Override
+    public String getRequestedCiphers() throws IOException {
+        return null;
+    }
 }
diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/LocalStrings.properties
index 17c3309..94913e8 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings.properties
@@ -124,6 +124,7 @@ endpoint.warn.unlockAcceptorFailed=Acceptor thread [{0}] failed to unlock. Forci
 
 sniExtractor.clientHelloInvalid=The ClientHello message was not correctly formatted
 sniExtractor.clientHelloTooBig=The ClientHello was not presented in a single TLS record so no SNI information could be extracted
+sniExtractor.tooEarly=It is illegal to call this method before the client hello has been parsed
 
 socket.apr.clientAbort=The client aborted the connection.
 socket.apr.closed=The socket [{0}] associated with this connection has been closed.
diff --git a/java/org/apache/tomcat/util/net/Nio2Endpoint.java b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
index b28941c..5e0dfe1 100644
--- a/java/org/apache/tomcat/util/net/Nio2Endpoint.java
+++ b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
@@ -39,7 +39,6 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
 import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLSession;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -1520,11 +1519,7 @@ public class Nio2Endpoint extends AbstractJsseEndpoint<Nio2Channel> {
         public SSLSupport getSslSupport(String clientCertProvider) {
             if (getSocket() instanceof SecureNio2Channel) {
                 SecureNio2Channel ch = (SecureNio2Channel) getSocket();
-                SSLEngine sslEngine = ch.getSslEngine();
-                if (sslEngine != null) {
-                    SSLSession session = sslEngine.getSession();
-                    return ((Nio2Endpoint) getEndpoint()).getSslImplementation().getSSLSupport(session);
-                }
+                return ch.getSSLSupport();
             }
             return null;
         }
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
index 281c914..b32b856 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -44,7 +44,6 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
 import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLSession;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -1431,11 +1430,7 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
         public SSLSupport getSslSupport(String clientCertProvider) {
             if (getSocket() instanceof SecureNioChannel) {
                 SecureNioChannel ch = (SecureNioChannel) getSocket();
-                SSLEngine sslEngine = ch.getSslEngine();
-                if (sslEngine != null) {
-                    SSLSession session = sslEngine.getSession();
-                    return ((NioEndpoint) getEndpoint()).getSslImplementation().getSSLSupport(session);
-                }
+                return ch.getSSLSupport();
             }
             return null;
         }
diff --git a/java/org/apache/tomcat/util/net/SSLImplementation.java b/java/org/apache/tomcat/util/net/SSLImplementation.java
index 43ccbe5..4bafd25 100644
--- a/java/org/apache/tomcat/util/net/SSLImplementation.java
+++ b/java/org/apache/tomcat/util/net/SSLImplementation.java
@@ -17,6 +17,9 @@
 
 package org.apache.tomcat.util.net;
 
+import java.util.List;
+import java.util.Map;
+
 import javax.net.ssl.SSLSession;
 
 import org.apache.juli.logging.Log;
@@ -63,7 +66,31 @@ public abstract class SSLImplementation {
         }
     }
 
+    /**
+     * Obtain an instance of SSLSupport.
+     *
+     * @param session   The SSL session
+     * @param additionalAttributes  Additional SSL attributes that are not
+     *                              available from the session.
+     *
+     * @return An instance of SSLSupport based on the given session and the
+     *         provided additional attributes
+     */
+    public SSLSupport getSSLSupport(SSLSession session, Map<String,List<String>> additionalAttributes) {
+        return getSSLSupport(session);
+    }
 
+    /**
+     * Obtain an instance of SSLSupport.
+     *
+     * @param session   The TLS session
+     *
+     * @return An instance of SSLSupport based on the given session.
+     *
+     * @deprecated This will be removed in Tomcat 10.1.x onwards.
+     *             Use {@link #getSSLSupport(SSLSession, Map)}.
+     */
+    @Deprecated
     public abstract SSLSupport getSSLSupport(SSLSession session);
 
     public abstract SSLUtil getSSLUtil(SSLHostConfigCertificate certificate);
diff --git a/java/org/apache/tomcat/util/net/SSLSupport.java b/java/org/apache/tomcat/util/net/SSLSupport.java
index 75740f9..a085b87 100644
--- a/java/org/apache/tomcat/util/net/SSLSupport.java
+++ b/java/org/apache/tomcat/util/net/SSLSupport.java
@@ -63,6 +63,20 @@ public interface SSLSupport {
             "org.apache.tomcat.util.net.secure_protocol_version";
 
     /**
+     * The request attribute key under which the String indicating the ciphers
+     * requested by the client are recorded.
+     */
+    public static final String REQUESTED_CIPHERS_KEY =
+            "org.apache.tomcat.util.net.secure_requested_ciphers";
+
+    /**
+     * The request attribute key under which the String indicating the protocols
+     * requested by the client are recorded.
+     */
+    public static final String REQUESTED_PROTOCOL_VERSIONS_KEY =
+            "org.apache.tomcat.util.net.secure_requested_protocol_versions";
+
+    /**
      * The cipher suite being used on this connection.
      *
      * @return The name of the cipher suite as returned by the SSL/TLS
@@ -121,5 +135,22 @@ public interface SSLSupport {
      *   information from the socket
      */
     public String getProtocol() throws IOException;
-}
 
+    /**
+     *
+     * @return the list of SSL/TLS protocol versions requested by the client
+     *
+     * @throws IOException If an error occurs trying to obtain the client
+     *   requested protocol information from the socket
+     */
+    public String getRequestedProtocols() throws IOException;
+
+    /**
+    *
+    * @return the list of SSL/TLS ciphers requested by the client
+    *
+     * @throws IOException If an error occurs trying to obtain the client
+     *   request cipher information from the socket
+    */
+   public String getRequestedCiphers() throws IOException;
+}
diff --git a/java/org/apache/tomcat/util/net/SecureNio2Channel.java b/java/org/apache/tomcat/util/net/SecureNio2Channel.java
index 8899da4..92fe2f0 100644
--- a/java/org/apache/tomcat/util/net/SecureNio2Channel.java
+++ b/java/org/apache/tomcat/util/net/SecureNio2Channel.java
@@ -23,7 +23,9 @@ import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.CompletionHandler;
 import java.nio.channels.WritePendingException;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -34,6 +36,7 @@ import javax.net.ssl.SSLEngineResult;
 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
 import javax.net.ssl.SSLEngineResult.Status;
 import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -61,7 +64,7 @@ public class SecureNio2Channel extends Nio2Channel  {
     protected SSLEngine sslEngine;
     protected final Nio2Endpoint endpoint;
 
-    protected boolean sniComplete = false;
+    protected volatile boolean sniComplete = false;
 
     private volatile boolean handshakeComplete;
     private volatile HandshakeStatus handshakeStatus; //gets set by handshake
@@ -71,6 +74,8 @@ public class SecureNio2Channel extends Nio2Channel  {
     protected boolean closed;
     protected boolean closing;
 
+    private final Map<String,List<String>> additionalTlsAttributes = new HashMap<>();
+
     private final CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>> handshakeReadCompletionHandler;
     private final CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>> handshakeWriteCompletionHandler;
 
@@ -432,6 +437,13 @@ public class SecureNio2Channel extends Nio2Channel  {
         sslEngine = endpoint.createSSLEngine(hostName, clientRequestedCiphers,
                 clientRequestedApplicationProtocols);
 
+        // Populate additional TLS attributes obtained from the handshake that
+        // aren't available from the session
+        additionalTlsAttributes.put(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY,
+                extractor.getClientRequestedProtocols());
+        additionalTlsAttributes.put(SSLSupport.REQUESTED_CIPHERS_KEY,
+                extractor.getClientRequestedCipherNames());
+
         // Ensure the application buffers (which have to be created earlier) are
         // big enough.
         getBufHandler().expand(sslEngine.getSession().getApplicationBufferSize());
@@ -563,6 +575,14 @@ public class SecureNio2Channel extends Nio2Channel  {
         return result;
     }
 
+    public SSLSupport getSSLSupport() {
+        if (sslEngine != null) {
+            SSLSession session = sslEngine.getSession();
+            return endpoint.getSslImplementation().getSSLSupport(session, additionalTlsAttributes);
+        }
+        return null;
+    }
+
     /**
      * Sends an SSL close message, will not physically close the connection here.<br>
      * To close the connection, you could do something like
diff --git a/java/org/apache/tomcat/util/net/SecureNioChannel.java b/java/org/apache/tomcat/util/net/SecureNioChannel.java
index 5f59899..f235ade 100644
--- a/java/org/apache/tomcat/util/net/SecureNioChannel.java
+++ b/java/org/apache/tomcat/util/net/SecureNioChannel.java
@@ -24,13 +24,16 @@ import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
 import java.nio.channels.SocketChannel;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
 import javax.net.ssl.SSLEngineResult.Status;
 import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -65,6 +68,8 @@ public class SecureNioChannel extends NioChannel  {
     protected boolean closed = false;
     protected boolean closing = false;
 
+    private final Map<String,List<String>> additionalTlsAttributes = new HashMap<>();
+
     protected NioSelectorPool pool;
     private final NioEndpoint endpoint;
 
@@ -328,6 +333,13 @@ public class SecureNioChannel extends NioChannel  {
         sslEngine = endpoint.createSSLEngine(hostName, clientRequestedCiphers,
                 clientRequestedApplicationProtocols);
 
+        // Populate additional TLS attributes obtained from the handshake that
+        // aren't available from the session
+        additionalTlsAttributes.put(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY,
+                extractor.getClientRequestedProtocols());
+        additionalTlsAttributes.put(SSLSupport.REQUESTED_CIPHERS_KEY,
+                extractor.getClientRequestedCipherNames());
+
         // Ensure the application buffers (which have to be created earlier) are
         // big enough.
         getBufHandler().expand(sslEngine.getSession().getApplicationBufferSize());
@@ -502,6 +514,14 @@ public class SecureNioChannel extends NioChannel  {
         return result;
     }
 
+    public SSLSupport getSSLSupport() {
+        if (sslEngine != null) {
+            SSLSession session = sslEngine.getSession();
+            return endpoint.getSslImplementation().getSSLSupport(session, additionalTlsAttributes);
+        }
+        return null;
+    }
+
     /**
      * Sends an SSL close message, will not physically close the connection here.
      * <br>To close the connection, you could do something like
diff --git a/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java b/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java
index 6ce27cb..21a5924 100644
--- a/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java
+++ b/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java
@@ -25,6 +25,7 @@ import java.util.List;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.http.parser.HttpParser;
 import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
 import org.apache.tomcat.util.res.StringManager;
@@ -40,13 +41,16 @@ public class TLSClientHelloExtractor {
 
     private final ExtractorResult result;
     private final List<Cipher> clientRequestedCiphers;
+    private final List<String> clientRequestedCipherNames;
     private final String sniValue;
     private final List<String> clientRequestedApplicationProtocols;
+    private final List<String> clientRequestedProtocols;
 
     private static final int TLS_RECORD_HEADER_LEN = 5;
 
     private static final int TLS_EXTENSION_SERVER_NAME = 0;
     private static final int TLS_EXTENSION_ALPN = 16;
+    private static final int TLS_EXTENSION_SUPPORTED_VERSION = 43;
 
     public static byte[] USE_TLS_RESPONSE = ("HTTP/1.1 400 \r\n" +
             "Content-Type: text/plain;charset=UTF-8\r\n" +
@@ -72,7 +76,9 @@ public class TLSClientHelloExtractor {
         int limit = netInBuffer.limit();
         ExtractorResult result = ExtractorResult.NOT_PRESENT;
         List<Cipher> clientRequestedCiphers = new ArrayList<>();
+        List<String> clientRequestedCipherNames = new ArrayList<>();
         List<String> clientRequestedApplicationProtocols = new ArrayList<>();
+        List<String> clientRequestedProtocols = new ArrayList<>();
         String sniValue = null;
         try {
             // Switch to read mode.
@@ -110,7 +116,7 @@ public class TLSClientHelloExtractor {
             }
 
             // Protocol Version
-            skipBytes(netInBuffer, 2);
+            String legacyVersion = readProtocol(netInBuffer);
             // Random
             skipBytes(netInBuffer, 32);
             // Session ID (single byte for length)
@@ -120,8 +126,15 @@ public class TLSClientHelloExtractor {
             // (2 bytes for length, each cipher ID is 2 bytes)
             int cipherCount = netInBuffer.getChar() / 2;
             for (int i = 0; i < cipherCount; i++) {
-                int cipherId = netInBuffer.getChar();
-                clientRequestedCiphers.add(Cipher.valueOf(cipherId));
+                char cipherId = netInBuffer.getChar();
+                Cipher c = Cipher.valueOf(cipherId);
+                // Some clients transmit grease values (see RFC 8701)
+                if (c == null) {
+                    clientRequestedCipherNames.add("Unknown(0x" + HexUtils.toHexString(cipherId) + ")");
+                } else {
+                    clientRequestedCiphers.add(c);
+                    clientRequestedCipherNames.add(c.name());
+                }
             }
 
             // Compression methods (single byte for length)
@@ -136,8 +149,8 @@ public class TLSClientHelloExtractor {
             skipBytes(netInBuffer, 2);
             // Read the extensions until we run out of data or find the data
             // we need
-            while (netInBuffer.hasRemaining() &&
-                    (sniValue == null || clientRequestedApplicationProtocols.size() == 0)) {
+            while (netInBuffer.hasRemaining() && (sniValue == null ||
+                    clientRequestedApplicationProtocols.isEmpty()) || clientRequestedProtocols.isEmpty()) {
                 // Extension type is two byte
                 char extensionType = netInBuffer.getChar();
                 // Extension size is another two bytes
@@ -150,19 +163,27 @@ public class TLSClientHelloExtractor {
                 case TLS_EXTENSION_ALPN:
                     readAlpnExtension(netInBuffer, clientRequestedApplicationProtocols);
                     break;
+                case TLS_EXTENSION_SUPPORTED_VERSION:
+                    readSupportedVersions(netInBuffer, clientRequestedProtocols);
+                    break;
                 default: {
                     skipBytes(netInBuffer, extensionDataSize);
                 }
                 }
             }
+            if (clientRequestedProtocols.isEmpty()) {
+                clientRequestedProtocols.add(legacyVersion);
+            }
             result = ExtractorResult.COMPLETE;
         } catch (BufferUnderflowException | IllegalArgumentException e) {
             throw new IOException(sm.getString("sniExtractor.clientHelloInvalid"), e);
         } finally {
             this.result = result;
             this.clientRequestedCiphers = clientRequestedCiphers;
+            this.clientRequestedCipherNames = clientRequestedCipherNames;
             this.clientRequestedApplicationProtocols = clientRequestedApplicationProtocols;
             this.sniValue = sniValue;
+            this.clientRequestedProtocols = clientRequestedProtocols;
             // Whatever happens, return the buffer to its original state
             netInBuffer.limit(limit);
             netInBuffer.position(pos);
@@ -179,7 +200,7 @@ public class TLSClientHelloExtractor {
         if (result == ExtractorResult.COMPLETE) {
             return sniValue;
         } else {
-            throw new IllegalStateException();
+            throw new IllegalStateException(sm.getString("sniExtractor.tooEarly"));
         }
     }
 
@@ -188,7 +209,16 @@ public class TLSClientHelloExtractor {
         if (result == ExtractorResult.COMPLETE || result == ExtractorResult.NOT_PRESENT) {
             return clientRequestedCiphers;
         } else {
-            throw new IllegalStateException();
+            throw new IllegalStateException(sm.getString("sniExtractor.tooEarly"));
+        }
+    }
+
+
+    public List<String> getClientRequestedCipherNames() {
+        if (result == ExtractorResult.COMPLETE || result == ExtractorResult.NOT_PRESENT) {
+            return clientRequestedCipherNames;
+        } else {
+            throw new IllegalStateException(sm.getString("sniExtractor.tooEarly"));
         }
     }
 
@@ -197,7 +227,16 @@ public class TLSClientHelloExtractor {
         if (result == ExtractorResult.COMPLETE || result == ExtractorResult.NOT_PRESENT) {
             return clientRequestedApplicationProtocols;
         } else {
-            throw new IllegalStateException();
+            throw new IllegalStateException(sm.getString("sniExtractor.tooEarly"));
+        }
+    }
+
+
+    public List<String> getClientRequestedProtocols() {
+        if (result == ExtractorResult.COMPLETE || result == ExtractorResult.NOT_PRESENT) {
+            return clientRequestedProtocols;
+        } else {
+            throw new IllegalStateException(sm.getString("sniExtractor.tooEarly"));
         }
     }
 
@@ -328,6 +367,30 @@ public class TLSClientHelloExtractor {
     }
 
 
+    private static String readProtocol(ByteBuffer bb) {
+        char protocol = bb.getChar();
+        switch (protocol) {
+            case 0x0300: {
+                return Constants.SSL_PROTO_SSLv3;
+            }
+            case 0x0301: {
+                return Constants.SSL_PROTO_TLSv1_0;
+            }
+            case 0x0302: {
+                return Constants.SSL_PROTO_TLSv1_1;
+            }
+            case 0x0303: {
+                return Constants.SSL_PROTO_TLSv1_2;
+            }
+            case 0x0304: {
+                return Constants.SSL_PROTO_TLSv1_3;
+            }
+            default:
+                return "Unknown(0x" + HexUtils.toHexString(protocol) + ")";
+        }
+    }
+
+
     private static String readSniExtension(ByteBuffer bb) {
         // First 2 bytes are size of server name list (only expecting one)
         // Next byte is type (0 for hostname)
@@ -356,6 +419,16 @@ public class TLSClientHelloExtractor {
     }
 
 
+    private static void readSupportedVersions(ByteBuffer bb, List<String> protocolNames) {
+        // First byte is the size of the list in bytes
+        int count = (bb.get() & 0xFF) / 2;
+        // Then the list of protocols
+        for (int i = 0; i < count; i++) {
+            protocolNames.add(readProtocol(bb));
+        }
+    }
+
+
     public enum ExtractorResult {
         COMPLETE,
         NOT_PRESENT,
diff --git a/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java b/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
index 1c1eae8..2c90513 100644
--- a/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
+++ b/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
@@ -16,6 +16,9 @@
  */
 package org.apache.tomcat.util.net.jsse;
 
+import java.util.List;
+import java.util.Map;
+
 import javax.net.ssl.SSLSession;
 
 import org.apache.tomcat.util.compat.JreCompat;
@@ -40,9 +43,15 @@ public class JSSEImplementation extends SSLImplementation {
         JSSESupport.init();
     }
 
+    @Deprecated
     @Override
     public SSLSupport getSSLSupport(SSLSession session) {
-        return new JSSESupport(session);
+        return getSSLSupport(session, null);
+    }
+
+    @Override
+    public SSLSupport getSSLSupport(SSLSession session, Map<String, List<String>> additionalAttributes) {
+        return new JSSESupport(session, additionalAttributes);
     }
 
     @Override
diff --git a/java/org/apache/tomcat/util/net/jsse/JSSESupport.java b/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
index 98c6eb9..5d15e2c 100644
--- a/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
+++ b/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
@@ -22,12 +22,14 @@ import java.io.IOException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.net.ssl.SSLSession;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.net.SSLSessionManager;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
@@ -72,10 +74,22 @@ public class JSSESupport implements SSLSupport, SSLSessionManager {
     }
 
     private SSLSession session;
+    private Map<String,List<String>> additionalAttributes;
 
-
+    /**
+     * @param session SSLSession from which information is to be extracted
+     *
+     * @deprecated This will be removed in Tomcat 10.1.x onwards
+     *             Use {@link JSSESupport#JSSESupport(SSLSession, Map)}
+     */
+    @Deprecated
     public JSSESupport(SSLSession session) {
+        this(session, null);
+    }
+
+    public JSSESupport(SSLSession session, Map<String,List<String>> additionalAttributes) {
         this.session = session;
+        this.additionalAttributes = additionalAttributes;
     }
 
     @Override
@@ -186,6 +200,22 @@ public class JSSESupport implements SSLSupport, SSLSessionManager {
            return null;
         }
        return session.getProtocol();
-   }
+    }
+
+    @Override
+    public String getRequestedProtocols() throws IOException {
+        if (additionalAttributes == null) {
+            return null;
+        }
+        return StringUtils.join(additionalAttributes.get(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY));
+    }
+
+    @Override
+    public String getRequestedCiphers() throws IOException {
+        if (additionalAttributes == null) {
+            return null;
+        }
+        return StringUtils.join(additionalAttributes.get(SSLSupport.REQUESTED_CIPHERS_KEY));
+    }
 }
 
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java b/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java
index 94b4bf2..da36f08 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java
@@ -16,6 +16,9 @@
  */
 package org.apache.tomcat.util.net.openssl;
 
+import java.util.List;
+import java.util.Map;
+
 import javax.net.ssl.SSLSession;
 
 import org.apache.tomcat.util.net.SSLHostConfigCertificate;
@@ -26,12 +29,18 @@ import org.apache.tomcat.util.net.jsse.JSSESupport;
 
 public class OpenSSLImplementation extends SSLImplementation {
 
+    @Deprecated
     @Override
     public SSLSupport getSSLSupport(SSLSession session) {
         return new JSSESupport(session);
     }
 
     @Override
+    public SSLSupport getSSLSupport(SSLSession session, Map<String, List<String>> additionalAttributes) {
+        return new JSSESupport(session, additionalAttributes);
+    }
+
+    @Override
     public SSLUtil getSSLUtil(SSLHostConfigCertificate certificate) {
         return new OpenSSLUtil(certificate);
     }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 33db707..b9e4544 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -104,6 +104,15 @@
   issues do not "pop up" wrt. others).
 -->
 <section name="Tomcat 8.5.62 (markt)" rtext="in development">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        <bug>64110</bug>: Add support for additional TLS related request
+        attributes that provide details of the protocols and ciphers requested
+        by a client in the initial TLS handshake. (markt)
+      </add>
+    </changelog>
+  </subsection>
 </section>
 <section name="Tomcat 8.5.61 (markt)" rtext="release in progress">
   <subsection name="Catalina">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 27fb3f2..9d66d5d 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -1219,6 +1219,12 @@
   error. It is expected that Tomcat 10 will drop support for the SSL
   configuration attributes in the <strong>Connector</strong>.</p>
 
+  <p>In addition to the standard TLS related request attributes defined in
+  section 3.10 of the Servlet specification, Tomcat supports a number of
+  additional TLS related attributes. The full list may be found in the <a
+  href="http://tomcat.apache.org/tomcat-10.0-doc/api/index.html">SSLSupport
+  Javadoc</a>.</p>
+
   <p>For more information, see the
   <a href="../ssl-howto.html">SSL Configuration How-To</a>.</p>
 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org