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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org