Add support for jakarta.servlet.request.secure_protocol

This request attribute is new in Servlet 6.1. The Tomcat specific
request attribute org.apache.tomcat.util.net.secure_protocol_version has
been deprecated and will be removed in Tomcat 12.
diff --git a/java/org/apache/catalina/Globals.java b/java/org/apache/catalina/Globals.java
index 0e52e18..a3c127e 100644
--- a/java/org/apache/catalina/Globals.java
+++ b/java/org/apache/catalina/Globals.java
@@ -130,6 +130,13 @@
 
 
     /**
+     * The request attribute under which we store the name of the security protocol (e.g. TLSv1.3) being used on a
+     * secured connection (as an object of type {@link String}).
+     */
+    public static final String SECURE_PROTOCOL_ATTR = "jakarta.servlet.request.secure_protocol";
+
+
+    /**
      * The request attribute under which we store the name of the cipher suite
      * being used on an SSL connection (as an object of type {@link String}).
      */
diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
index 7883212..508b279 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -836,6 +836,7 @@
      *
      * @param name Name of the request attribute to return
      */
+    @SuppressWarnings("deprecation")
     @Override
     public Object getAttribute(String name) {
         // Special attributes
@@ -860,6 +861,11 @@
             if (attr != null) {
                 attributes.put(Globals.CERTIFICATES_ATTR, attr);
             }
+            attr = coyoteRequest.getAttribute(Globals.SECURE_PROTOCOL_ATTR);
+            if (attr != null) {
+                attributes.put(Globals.SECURE_PROTOCOL_ATTR, attr);
+                attributes.put(SSLSupport.PROTOCOL_VERSION_KEY, attr);
+            }
             attr = coyoteRequest.getAttribute(Globals.CIPHER_SUITE_ATTR);
             if (attr != null) {
                 attributes.put(Globals.CIPHER_SUITE_ATTR, attr);
@@ -876,10 +882,6 @@
             if (attr != null) {
                 attributes.put(Globals.SSL_SESSION_MGR_ATTR, attr);
             }
-            attr = coyoteRequest.getAttribute(SSLSupport.PROTOCOL_VERSION_KEY);
-            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);
@@ -911,6 +913,7 @@
      * <li>{@link Globals#DISPATCHER_REQUEST_PATH_ATTR}</li>
      * <li>{@link Globals#ASYNC_SUPPORTED_ATTR}</li>
      * <li>{@link Globals#CERTIFICATES_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals#SECURE_PROTOCOL_ATTR} (SSL connections only)</li>
      * <li>{@link Globals#CIPHER_SUITE_ATTR} (SSL connections only)</li>
      * <li>{@link Globals#KEY_SIZE_ATTR} (SSL connections only)</li>
      * <li>{@link Globals#SSL_SESSION_ID_ATTR} (SSL connections only)</li>
diff --git a/java/org/apache/catalina/util/TLSUtil.java b/java/org/apache/catalina/util/TLSUtil.java
index 7f895dd..43644d9 100644
--- a/java/org/apache/catalina/util/TLSUtil.java
+++ b/java/org/apache/catalina/util/TLSUtil.java
@@ -32,9 +32,11 @@
      * @return {@code true} if the attribute is used to pass TLS configuration
      *         information, otherwise {@code false}
      */
+    @SuppressWarnings("deprecation")
     public static boolean isTLSRequestAttribute(String name) {
         switch (name) {
             case Globals.CERTIFICATES_ATTR:
+            case Globals.SECURE_PROTOCOL_ATTR:
             case Globals.CIPHER_SUITE_ATTR:
             case Globals.KEY_SIZE_ATTR:
             case Globals.SSL_SESSION_ID_ATTR:
diff --git a/java/org/apache/catalina/valves/SSLValve.java b/java/org/apache/catalina/valves/SSLValve.java
index 2daeb5e..e5a8878 100644
--- a/java/org/apache/catalina/valves/SSLValve.java
+++ b/java/org/apache/catalina/valves/SSLValve.java
@@ -46,6 +46,7 @@
  * <pre>
  * &lt;IfModule ssl_module&gt;
  *   RequestHeader set SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s"
+ *   RequestHeader set SSL_SECURE_PROTOCOL "%{SSL_PROTOCOL}s"
  *   RequestHeader set SSL_CIPHER "%{SSL_CIPHER}s"
  *   RequestHeader set SSL_SESSION_ID "%{SSL_SESSION_ID}s"
  *   RequestHeader set SSL_CIPHER_USEKEYSIZE "%{SSL_CIPHER_USEKEYSIZE}s"
@@ -67,6 +68,7 @@
 
     private String sslClientCertHeader = "ssl_client_cert";
     private String sslClientEscapedCertHeader = "ssl_client_escaped_cert";
+    private String sslSecureProtocolHeader = "ssl_secure_protocol";
     private String sslCipherHeader = "ssl_cipher";
     private String sslSessionIdHeader = "ssl_session_id";
     private String sslCipherUserKeySizeHeader = "ssl_cipher_usekeysize";
@@ -93,6 +95,14 @@
         this.sslClientEscapedCertHeader = sslClientEscapedCertHeader;
     }
 
+    public String getSslSecureProtocolHeader() {
+        return sslSecureProtocolHeader;
+    }
+
+    public void setSslSecureProtocolHeader(String sslSecureProtocolHeader) {
+        this.sslSecureProtocolHeader = sslSecureProtocolHeader;
+    }
+
     public String getSslCipherHeader() {
         return sslCipherHeader;
     }
@@ -178,6 +188,10 @@
                 request.setAttribute(Globals.CERTIFICATES_ATTR, jsseCerts);
             }
         }
+        headerValue = mygetHeader(request, sslSecureProtocolHeader);
+        if (headerValue != null) {
+            request.setAttribute(Globals.SECURE_PROTOCOL_ATTR, headerValue);
+        }
         headerValue = mygetHeader(request, sslCipherHeader);
         if (headerValue != null) {
             request.setAttribute(Globals.CIPHER_SUITE_ATTR, headerValue);
diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java
index 4e26dca..6cb85ff 100644
--- a/java/org/apache/coyote/AbstractProcessor.java
+++ b/java/org/apache/coyote/AbstractProcessor.java
@@ -764,10 +764,16 @@
      * Populate the TLS related request attributes from the {@link SSLSupport} instance associated with this processor.
      * Protocols that populate TLS attributes from a different source (e.g. AJP) should override this method.
      */
+    @SuppressWarnings("deprecation")
     protected void populateSslRequestAttributes() {
         try {
             if (sslSupport != null) {
-                Object sslO = sslSupport.getCipherSuite();
+                Object sslO = sslSupport.getProtocol();
+                if (sslO != null) {
+                    request.setAttribute(SSLSupport.SECURE_PROTOCOL_KEY, sslO);
+                    request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, sslO);
+                }
+                sslO = sslSupport.getCipherSuite();
                 if (sslO != null) {
                     request.setAttribute(SSLSupport.CIPHER_SUITE_KEY, sslO);
                 }
@@ -783,10 +789,6 @@
                 if (sslO != null) {
                     request.setAttribute(SSLSupport.SESSION_ID_KEY, sslO);
                 }
-                sslO = sslSupport.getProtocol();
-                if (sslO != null) {
-                    request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, sslO);
-                }
                 sslO = sslSupport.getRequestedProtocols();
                 if (sslO != null) {
                     request.setAttribute(SSLSupport.REQUESTED_PROTOCOL_VERSIONS_KEY, sslO);
diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java b/java/org/apache/coyote/ajp/AjpProcessor.java
index ef22da9..c8a070b 100644
--- a/java/org/apache/coyote/ajp/AjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpProcessor.java
@@ -132,6 +132,7 @@
 
         // Build Map of Java Servlet to Jakarta Servlet attribute names
         Map<String, String> m = new HashMap<>();
+        m.put("jakarta.servlet.request.secure_protocol", "jakarta.servlet.request.secure_protocol");
         m.put("jakarta.servlet.request.cipher_suite", "jakarta.servlet.request.cipher_suite");
         m.put("jakarta.servlet.request.key_size", "jakarta.servlet.request.key_size");
         m.put("jakarta.servlet.request.ssl_session", "jakarta.servlet.request.ssl_session");
@@ -643,6 +644,7 @@
     /**
      * After reading the request headers, we have to setup the request filters.
      */
+    @SuppressWarnings("deprecation")
     private void prepareRequest() {
 
         // Translate the HTTP method code to a String.
@@ -751,6 +753,7 @@
                             // Ignore invalid value
                         }
                     } else if (n.equals(Constants.SC_A_SSL_PROTOCOL)) {
+                        request.setAttribute(SSLSupport.SECURE_PROTOCOL_KEY, v);
                         request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v);
                     } else if (n.equals("JK_LB_ACTIVATION")) {
                         request.setAttribute(n, v);
diff --git a/java/org/apache/tomcat/util/net/SSLSupport.java b/java/org/apache/tomcat/util/net/SSLSupport.java
index d98e205..528df94 100644
--- a/java/org/apache/tomcat/util/net/SSLSupport.java
+++ b/java/org/apache/tomcat/util/net/SSLSupport.java
@@ -26,6 +26,13 @@
     /**
      * The Request attribute key for the cipher suite.
      */
+    String SECURE_PROTOCOL_KEY =
+            "jakarta.servlet.request.secure_protocol";
+
+
+    /**
+     * The Request attribute key for the cipher suite.
+     */
     String CIPHER_SUITE_KEY =
             "jakarta.servlet.request.cipher_suite";
 
@@ -57,7 +64,10 @@
     /**
      * The request attribute key under which the String indicating the protocol
      * that created the SSL socket is recorded - e.g. TLSv1 or TLSv1.2 etc.
+     *
+     * @deprecated Replaced by {@link #SECURE_PROTOCOL_KEY}. This constant will be removed in Tomcat 12.
      */
+    @Deprecated
     String PROTOCOL_VERSION_KEY =
             "org.apache.tomcat.util.net.secure_protocol_version";
 
diff --git a/test/org/apache/catalina/valves/TestSSLValve.java b/test/org/apache/catalina/valves/TestSSLValve.java
index 5c7753e..702606e 100644
--- a/test/org/apache/catalina/valves/TestSSLValve.java
+++ b/test/org/apache/catalina/valves/TestSSLValve.java
@@ -269,6 +269,17 @@
 
 
     @Test
+    public void testSslSecureProtocolHeaderPresent() throws Exception {
+        String protocol = "secured-with";
+        mockRequest.setHeader(valve.getSslSecureProtocolHeader(), protocol);
+
+        valve.invoke(mockRequest, null);
+
+        Assert.assertEquals(protocol, mockRequest.getAttribute(Globals.SECURE_PROTOCOL_ATTR));
+    }
+
+
+    @Test
     public void testSslCipherHeaderPresent() throws Exception {
         String cipher = "ciphered-with";
         mockRequest.setHeader(valve.getSslCipherHeader(), cipher);
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 9ae0584..9a7e836 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -111,6 +111,12 @@
         Background processes should not be run concurrently with lifecycle
         oprations of a container. (remm)
       </fix>
+      <add>
+        Add support for the <code>jakarta.servlet.request.secure_protocol</code>
+        request attribute that has been added in Jakarta Servlet 6.1. This
+        replaces the now deprecated Tomcat specific request attribute
+        <code>org.apache.tomcat.util.net.secure_protocol_version</code>. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">