This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 87c76e552e8210187a8b7ac26836684225e5c6df 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 9676ec4d7f..3bc936c3e7 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.openssl.OpenSSLConf; import org.apache.tomcat.util.net.openssl.OpenSSLConfCmd; @@ -87,11 +86,13 @@ public class SSLHostConfigSF extends StoreFactoryBase { // Store nested <SSLHostConfigCertificate> elements SSLHostConfigCertificate[] 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 16dc62e783..0db535b8bf 100644 --- a/java/org/apache/tomcat/util/net/SSLHostConfig.java +++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java @@ -577,6 +577,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; + } + + // ----------------------------------------- Common configuration properties /** diff --git a/test/org/apache/catalina/storeconfig/TestStoreConfig.java b/test/org/apache/catalina/storeconfig/TestStoreConfig.java index faba0f337d..cfb0de62ee 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.openssl.OpenSSLConf; import org.apache.tomcat.util.net.openssl.OpenSSLConfCmd; import org.xml.sax.InputSource; @@ -605,6 +606,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]
