This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch bz-64110 in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit a0e8389070b51eedb7d13b4d885b7c9f1e4b635c 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 | 4 +- 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 ++++ 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 | 30 ++++++++ .../apache/tomcat/util/net/SecureNio2Channel.java | 20 ++++++ .../apache/tomcat/util/net/SecureNioChannel.java | 20 ++++++ .../tomcat/util/net/TLSClientHelloExtractor.java | 83 ++++++++++++++++++++-- .../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 | 5 ++ webapps/docs/config/http.xml | 6 ++ 17 files changed, 285 insertions(+), 21 deletions(-) diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java index e2dff97..37ed6d2 100644 --- a/java/org/apache/catalina/connector/Request.java +++ b/java/org/apache/catalina/connector/Request.java @@ -885,6 +885,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..37ae78c 100644 --- a/java/org/apache/catalina/util/TLSUtil.java +++ b/java/org/apache/catalina/util/TLSUtil.java @@ -38,6 +38,8 @@ public class TLSUtil { 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); + SSLSupport.PROTOCOL_VERSION_KEY.equals(name) || + SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY.equals(name) || + SSLSupport.REQUESTED_CIPHERS_KEY.equals(name); } } diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java index f5787aa..7947a59 100644 --- a/java/org/apache/coyote/AbstractProcessor.java +++ b/java/org/apache/coyote/AbstractProcessor.java @@ -795,6 +795,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 977205e..c7bada8 100644 --- a/java/org/apache/tomcat/util/buf/HexUtils.java +++ b/java/org/apache/tomcat/util/buf/HexUtils.java @@ -74,6 +74,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 0e261b1..3262df91 100644 --- a/java/org/apache/tomcat/util/net/AprSSLSupport.java +++ b/java/org/apache/tomcat/util/net/AprSSLSupport.java @@ -108,6 +108,7 @@ public class AprSSLSupport implements SSLSupport { } } + @Override public String getProtocol() throws IOException { try { @@ -116,4 +117,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/Nio2Endpoint.java b/java/org/apache/tomcat/util/net/Nio2Endpoint.java index 7280d2f..8c3f561 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; @@ -1540,11 +1539,7 @@ public class Nio2Endpoint extends AbstractJsseEndpoint<Nio2Channel,AsynchronousS 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 0f71519..328a53a 100644 --- a/java/org/apache/tomcat/util/net/NioEndpoint.java +++ b/java/org/apache/tomcat/util/net/NioEndpoint.java @@ -43,7 +43,6 @@ import java.util.concurrent.TimeUnit; 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; @@ -1357,11 +1356,7 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> 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 6c0166b..2a69b8c 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 @@ -132,5 +146,21 @@ 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 + */ + public String getRequestedProtocols() throws IOException; + + /** + * + * @return the list of SSL/TLS ciphers requested by the client + * + * @throws IOException + */ + 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 394837c..cbe3f8b 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; @@ -70,6 +73,8 @@ public class SecureNio2Channel extends Nio2Channel { protected boolean closed; protected boolean closing; + private Map<String,List<String>> additionalTlsAttributes = new HashMap<>(); + private volatile boolean unwrapBeforeRead; 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 a176675..6e1fe14 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; @@ -68,6 +71,8 @@ public class SecureNioChannel extends NioChannel { protected boolean closed = false; protected boolean closing = false; + private Map<String,List<String>> additionalTlsAttributes = new HashMap<>(); + public SecureNioChannel(SocketBufferHandler bufHandler, NioEndpoint endpoint) { super(bufHandler); @@ -303,6 +308,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()); @@ -487,6 +499,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..cb8436e 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); @@ -193,6 +214,15 @@ public class TLSClientHelloExtractor { } + public List<String> getClientRequestedCipherNames() { + if (result == ExtractorResult.COMPLETE || result == ExtractorResult.NOT_PRESENT) { + return clientRequestedCipherNames; + } else { + throw new IllegalStateException(); + } + } + + public List<String> getClientRequestedApplicationProtocols() { if (result == ExtractorResult.COMPLETE || result == ExtractorResult.NOT_PRESENT) { return clientRequestedApplicationProtocols; @@ -202,6 +232,15 @@ public class TLSClientHelloExtractor { } + public List<String> getClientRequestedProtocols() { + if (result == ExtractorResult.COMPLETE || result == ExtractorResult.NOT_PRESENT) { + return clientRequestedProtocols; + } else { + throw new IllegalStateException(); + } + } + + private static ExtractorResult handleIncompleteRead(ByteBuffer bb) { if (bb.limit() == bb.capacity()) { // Buffer not big enough @@ -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 0ca7ba7..fdce0c2 100644 --- a/java/org/apache/tomcat/util/net/jsse/JSSESupport.java +++ b/java/org/apache/tomcat/util/net/jsse/JSSESupport.java @@ -23,12 +23,14 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; 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; @@ -73,10 +75,22 @@ public class JSSESupport implements SSLSupport, SSLSessionManager { } private SSLSession session; + private Map<String,List<String>> additionalAttributes; - + /** + * @param session + * + * @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 @@ -201,6 +215,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 1c768d4..6af0349 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -127,6 +127,11 @@ specified to wait for client connections to complete and close before the Container hierarchy is stopped. (markt) </add> + <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> <fix> <bug>64921</bug>: Ensure that the <code>LoadBalancerDrainingValve</code> uses the correct setting for the secure attribute for any session diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml index ae5fd8a..1616aa8 100644 --- a/webapps/docs/config/http.xml +++ b/webapps/docs/config/http.xml @@ -1175,6 +1175,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