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

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 7e9a3f5766b07a71abd0b5c2e7de820a11af0317
Author: opencode <[email protected]>
AuthorDate: Fri Oct 9 10:22:46 2026 +0200

    Do not store the runtime placeholder UNDEFINED Certificate element
    
    When an SSL host configuration contains no Certificate element, the
    endpoint registers a bare UNDEFINED-type placeholder certificate when it
    creates the SSL context. SSLHostConfigSF only removed UNDEFINED
    certificates when more than one was present, a case that can never occur
    since addCertificate() rejects mixing UNDEFINED with typed certificates,
    so the single placeholder was stored as a <Certificate
    type="UNDEFINED"/> element. Adding a typed certificate to the saved file
    then made the next start fail because only one certificate may use the
    undefined type.
    
    Expose the placeholder through SSLHostConfig.getDefaultCertificate() and
    skip that exact instance when storing. An explicitly configured,
    type-less certificate remains stored since it is a distinct instance that
    the administrator wrote.
---
 .../catalina/storeconfig/SSLHostConfigSF.java      |  9 ++---
 java/org/apache/tomcat/util/net/SSLHostConfig.java | 10 ++++++
 .../catalina/storeconfig/TestStoreConfig.java      | 39 ++++++++++++++++++++++
 3 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java 
b/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java
index f1269555b3..dc9d544667 100644
--- a/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java
+++ b/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java
@@ -23,7 +23,6 @@ import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.SSLHostConfig;
 import org.apache.tomcat.util.net.SSLHostConfigCertificate;
-import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
 import org.apache.tomcat.util.net.SSLHostConfigPreSharedKey;
 import org.apache.tomcat.util.net.openssl.OpenSSLConf;
 import org.apache.tomcat.util.net.openssl.OpenSSLConfCmd;
@@ -93,11 +92,13 @@ public class SSLHostConfigSF extends StoreFactoryBase {
                 hostConfigsCertificates =
                         sslHostConfig.getCertificates().toArray(new 
SSLHostConfigCertificate[0]);
             }
-            // Remove a possible default UNDEFINED certificate
-            if (hostConfigsCertificates.length > 1) {
+            // Remove the runtime placeholder certificate that was created when
+            // no certificate was configured
+            SSLHostConfigCertificate defaultCertificate = 
sslHostConfig.getDefaultCertificate();
+            if (defaultCertificate != null) {
                 ArrayList<SSLHostConfigCertificate> certificates = new 
ArrayList<>();
                 for (SSLHostConfigCertificate certificate : 
hostConfigsCertificates) {
-                    if (Type.UNDEFINED != certificate.getType()) {
+                    if (certificate != defaultCertificate) {
                         certificates.add(certificate);
                     }
                 }
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java 
b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index 0303376aaf..79d3b35b89 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -580,6 +580,16 @@ public class SSLHostConfig implements Serializable {
     }
 
 
+    /**
+     * Returns the placeholder certificate that was created at runtime because 
no certificate was configured, if any.
+     *
+     * @return the runtime placeholder certificate or {@code null} if none was 
created
+     */
+    public SSLHostConfigCertificate getDefaultCertificate() {
+        return defaultCertificate;
+    }
+
+
     /**
      * Adds a pre-shared key to this SSL host configuration.
      *
diff --git a/test/org/apache/catalina/storeconfig/TestStoreConfig.java 
b/test/org/apache/catalina/storeconfig/TestStoreConfig.java
index 9c4a32b86d..9b811bec3a 100644
--- a/test/org/apache/catalina/storeconfig/TestStoreConfig.java
+++ b/test/org/apache/catalina/storeconfig/TestStoreConfig.java
@@ -43,6 +43,7 @@ import org.apache.catalina.util.IOTools;
 import org.apache.catalina.util.SessionIdGeneratorBase;
 import org.apache.catalina.valves.AccessLogValve;
 import org.apache.tomcat.util.net.SSLHostConfig;
+import org.apache.tomcat.util.net.SSLHostConfigCertificate;
 import org.apache.tomcat.util.net.SSLHostConfigPreSharedKey;
 import org.apache.tomcat.util.net.openssl.OpenSSLConf;
 import org.apache.tomcat.util.net.openssl.OpenSSLConfCmd;
@@ -620,6 +621,44 @@ public class TestStoreConfig extends TomcatBaseTest {
         Assert.assertTrue(dump, dump.contains("name=\"groups\""));
     }
 
+    /**
+     * Verify that the placeholder certificate of type UNDEFINED that is 
created at runtime when no certificate is
+     * configured is not stored. The placeholder would fail the start of a 
server when a typed certificate is added to
+     * the stored configuration later. An explicitly configured certificate of 
type UNDEFINED must be kept since the
+     * filter matches the runtime registered instance.
+     *
+     * @throws Exception if the test experiences an unexpected error
+     */
+    @Test
+    public void testDefaultCertificatePlaceholderNotStored() throws Exception {
+        StoreLoader loader = new StoreLoader();
+        loader.load(null);
+        StoreRegistry registry = loader.getRegistry();
+        StoreDescription desc = registry.findDescription(SSLHostConfig.class);
+        Assert.assertNotNull(desc);
+
+        // The placeholder registered at runtime when the endpoint creates the 
SSL context
+        SSLHostConfig placeholder = new SSLHostConfig();
+        placeholder.getCertificates(true);
+        String dump = storeToString(desc, placeholder);
+        Assert.assertTrue(dump, dump.contains("<SSLHostConfig"));
+        Assert.assertFalse(dump, dump.contains("<Certificate"));
+
+        // An explicitly configured, type-less certificate must be kept
+        SSLHostConfig explicit = new SSLHostConfig();
+        explicit.addCertificate(new SSLHostConfigCertificate(explicit, 
SSLHostConfigCertificate.Type.UNDEFINED));
+        dump = storeToString(desc, explicit);
+        Assert.assertTrue(dump, dump.contains("<Certificate"));
+        Assert.assertTrue(dump, dump.contains("type=\"UNDEFINED\""));
+
+        // Typed certificates are unaffected
+        SSLHostConfig typed = new SSLHostConfig();
+        typed.addCertificate(new SSLHostConfigCertificate(typed, 
SSLHostConfigCertificate.Type.EC));
+        dump = storeToString(desc, typed);
+        Assert.assertTrue(dump, dump.contains("<Certificate"));
+        Assert.assertTrue(dump, dump.contains("type=\"EC\""));
+    }
+
     private static String storeToString(StoreDescription desc, Object element) 
throws Exception {
         StringWriter buffer = new StringWriter();
         desc.getStoreFactory().store(new PrintWriter(buffer), -2, element);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to