From c333b35603ed5a0946cacf5ebfc835ee4d86f620 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 23 Mar 2026 14:07:14 +0100 Subject: [PATCH 1/3] CAMEL-22497: Make HTTPS easier for camel.server - Move SSL configuration before HTTP server configuration so global SSL context is available when the server is created - Auto-enable useGlobalSslContextParameters on HTTP server and management server when camel.ssl.enabled=true - Generate self-signed certificate when SSL is enabled but no keystore is configured, for easy development use Co-Authored-By: Claude Opus 4.6 --- .../apache/camel/main/BaseMainSupport.java | 74 ++++-- .../main/SelfSignedCertificateGenerator.java | 240 ++++++++++++++++++ .../org/apache/camel/main/MainSSLTest.java | 66 +++++ 3 files changed, 360 insertions(+), 20 deletions(-) create mode 100644 core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index e73e1565752cb..7a71216f850b8 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -1526,6 +1526,13 @@ protected void doConfigureCamelContextFromMainConfiguration( mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties); camelContext.setRestConfiguration(rest); } + // SSL must be configured before HTTP servers, so global SSL context is available + if (!sslProperties.isEmpty() || mainConfigurationProperties.hasSslConfiguration()) { + LOG.debug("Auto-configuring SSL from loaded properties: {}", sslProperties.size()); + setSslProperties(camelContext, sslProperties, + mainConfigurationProperties.isAutoConfigurationFailFast(), + autoConfiguredProperties); + } if (!httpServerProperties.isEmpty() || mainConfigurationProperties.hasHttpServerConfiguration()) { LOG.debug("Auto-configuring HTTP Server from loaded properties: {}", httpServerProperties.size()); setHttpServerProperties(camelContext, httpServerProperties, @@ -1594,12 +1601,6 @@ protected void doConfigureCamelContextFromMainConfiguration( mainConfigurationProperties.isAutoConfigurationFailFast(), autoConfiguredProperties); } - if (!sslProperties.isEmpty() || mainConfigurationProperties.hasSslConfiguration()) { - LOG.debug("Auto-configuring SSL from loaded properties: {}", sslProperties.size()); - setSslProperties(camelContext, sslProperties, - mainConfigurationProperties.isAutoConfigurationFailFast(), - autoConfiguredProperties); - } if (!debuggerProperties.isEmpty() || mainConfigurationProperties.hasDebuggerConfiguration()) { LOG.debug("Auto-configuring Debugger from loaded properties: {}", debuggerProperties.size()); setDebuggerProperties(camelContext, debuggerProperties, @@ -2023,6 +2024,12 @@ private void setHttpServerProperties( return; } + // when global SSL is enabled, automatically use it for the HTTP server + // (unless the user has explicitly configured useGlobalSslContextParameters) + if (!server.isUseGlobalSslContextParameters() && camelContext.getSSLContextParameters() != null) { + server.setUseGlobalSslContextParameters(true); + } + // auto-detect camel-platform-http-main on classpath MainHttpServerFactory sf = resolveMainHttpServerFactory(camelContext); // create http server as a service managed by camel context @@ -2047,6 +2054,11 @@ private void setHttpManagementServerProperties( return; } + // when global SSL is enabled, automatically use it for the HTTP management server + if (!server.isUseGlobalSslContextParameters() && camelContext.getSSLContextParameters() != null) { + server.setUseGlobalSslContextParameters(true); + } + // auto-detect camel-platform-http-main on classpath MainHttpServerFactory sf = resolveMainHttpServerFactory(camelContext); // create http management server as a service managed by camel context @@ -2106,7 +2118,8 @@ private void setVaultProperties( private void setSslProperties( CamelContext camelContext, OrderedLocationProperties properties, - boolean failIfNotSet, OrderedLocationProperties autoConfiguredProperties) { + boolean failIfNotSet, OrderedLocationProperties autoConfiguredProperties) + throws Exception { SSLConfigurationProperties sslConfig = mainConfigurationProperties.sslConfig(); setPropertiesOnTarget(camelContext, sslConfig, properties, "camel.ssl.", @@ -2116,19 +2129,40 @@ private void setSslProperties( return; } - KeyStoreParameters ksp = new KeyStoreParameters(); - ksp.setCamelContext(camelContext); - ksp.setResource(sslConfig.getKeyStore()); - ksp.setType(sslConfig.getKeyStoreType()); - ksp.setPassword(sslConfig.getKeystorePassword()); - ksp.setProvider(sslConfig.getKeyStoreProvider()); - - KeyManagersParameters kmp = new KeyManagersParameters(); - kmp.setCamelContext(camelContext); - kmp.setKeyPassword(sslConfig.getKeystorePassword()); - kmp.setKeyStore(ksp); - kmp.setAlgorithm(sslConfig.getKeyManagerAlgorithm()); - kmp.setProvider(sslConfig.getKeyManagerProvider()); + KeyManagersParameters kmp; + if (sslConfig.getKeyStore() != null) { + // use the configured keystore + KeyStoreParameters ksp = new KeyStoreParameters(); + ksp.setCamelContext(camelContext); + ksp.setResource(sslConfig.getKeyStore()); + ksp.setType(sslConfig.getKeyStoreType()); + ksp.setPassword(sslConfig.getKeystorePassword()); + ksp.setProvider(sslConfig.getKeyStoreProvider()); + + kmp = new KeyManagersParameters(); + kmp.setCamelContext(camelContext); + kmp.setKeyPassword(sslConfig.getKeystorePassword()); + kmp.setKeyStore(ksp); + kmp.setAlgorithm(sslConfig.getKeyManagerAlgorithm()); + kmp.setProvider(sslConfig.getKeyManagerProvider()); + } else { + // no keystore configured, generate a self-signed certificate for development use + LOG.info("No SSL keystore configured - generating self-signed certificate for development use." + + " Do NOT use this in production."); + String password = "camel-self-signed"; + KeyStore ks = SelfSignedCertificateGenerator.generateKeyStore(password); + + KeyStoreParameters ksp = new KeyStoreParameters(); + ksp.setCamelContext(camelContext); + ksp.setKeyStore(ks); + ksp.setType("PKCS12"); + ksp.setPassword(password); + + kmp = new KeyManagersParameters(); + kmp.setCamelContext(camelContext); + kmp.setKeyPassword(password); + kmp.setKeyStore(ksp); + } final SSLContextParameters sslContextParameters = createSSLContextParameters(camelContext, sslConfig, kmp); camelContext.setSSLContextParameters(sslContextParameters); diff --git a/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java b/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java new file mode 100644 index 0000000000000..dc1dd0ac8d21e --- /dev/null +++ b/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.main; + +import java.io.ByteArrayInputStream; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +/** + * Generates a self-signed certificate for development use. This allows enabling HTTPS with minimal configuration when + * no keystore is provided. + * + * The generated certificate is NOT suitable for production use. + */ +final class SelfSignedCertificateGenerator { + + private SelfSignedCertificateGenerator() { + } + + /** + * Generates a PKCS12 KeyStore containing a self-signed certificate. + * + * @param password the password for the keystore and key entry + * @return a KeyStore containing the self-signed certificate + * @throws Exception if certificate generation fails + */ + static KeyStore generateKeyStore(String password) throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048, new SecureRandom()); + KeyPair keyPair = keyGen.generateKeyPair(); + + X509Certificate cert = generateCertificate(keyPair); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, password.toCharArray()); + ks.setKeyEntry("camel-self-signed", keyPair.getPrivate(), password.toCharArray(), + new X509Certificate[] { cert }); + + return ks; + } + + @SuppressWarnings("restriction") + private static X509Certificate generateCertificate(KeyPair keyPair) throws Exception { + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + Instant now = Instant.now(); + Date notBefore = Date.from(now); + Date notAfter = Date.from(now.plus(365, ChronoUnit.DAYS)); + + // Build self-signed X.509 certificate using DER encoding + byte[] encoded = buildSelfSignedCertificateDer(publicKey, privateKey, notBefore, notAfter); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(encoded)); + } + + private static byte[] buildSelfSignedCertificateDer( + PublicKey publicKey, PrivateKey privateKey, + Date notBefore, Date notAfter) + throws Exception { + + // DN: CN=localhost, O=Apache Camel (self-signed) + byte[] issuerDn = buildDn(); + + // TBS Certificate + byte[] tbsCertificate = buildTbsCertificate(publicKey, issuerDn, notBefore, notAfter); + + // Sign the TBS certificate + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initSign(privateKey); + sig.update(tbsCertificate); + byte[] signature = sig.sign(); + + // Build the full certificate: SEQUENCE { tbsCertificate, signatureAlgorithm, signature } + byte[] signatureAlgorithm = sha256WithRsaAlgorithmIdentifier(); + byte[] signatureBitString = wrapBitString(signature); + + return wrapSequence(concat(tbsCertificate, signatureAlgorithm, signatureBitString)); + } + + private static byte[] buildTbsCertificate( + PublicKey publicKey, byte[] dn, + Date notBefore, Date notAfter) + throws Exception { + + // Version: v3 (2) + byte[] version = wrapExplicitTag(0, wrapInteger(new byte[] { 2 })); + + // Serial number + byte[] serialBytes = new byte[16]; + new SecureRandom().nextBytes(serialBytes); + serialBytes[0] &= 0x7F; // ensure positive + byte[] serial = wrapInteger(serialBytes); + + // Signature algorithm + byte[] signatureAlgorithm = sha256WithRsaAlgorithmIdentifier(); + + // Issuer DN + byte[] issuer = dn; + + // Validity + byte[] validity = wrapSequence(concat(encodeUtcTime(notBefore), encodeUtcTime(notAfter))); + + // Subject DN (same as issuer for self-signed) + byte[] subject = dn; + + // Subject Public Key Info (from the encoded public key) + byte[] subjectPublicKeyInfo = publicKey.getEncoded(); + + return wrapSequence(concat(version, serial, signatureAlgorithm, issuer, validity, subject, subjectPublicKeyInfo)); + } + + // Builds DN: CN=localhost, O=Apache Camel (self-signed) + private static byte[] buildDn() { + byte[] cn = buildRdn(new byte[] { 0x55, 0x04, 0x03 }, "localhost"); + byte[] o = buildRdn(new byte[] { 0x55, 0x04, 0x0A }, "Apache Camel (self-signed)"); + return wrapSequence(concat(wrapSet(cn), wrapSet(o))); + } + + private static byte[] buildRdn(byte[] oidBytes, String value) { + byte[] oid = new byte[2 + oidBytes.length]; + oid[0] = 0x06; // OID tag + oid[1] = (byte) oidBytes.length; + System.arraycopy(oidBytes, 0, oid, 2, oidBytes.length); + + byte[] valueBytes = value.getBytes(java.nio.charset.StandardCharsets.UTF_8); + byte[] utf8String = new byte[2 + valueBytes.length]; + utf8String[0] = 0x0C; // UTF8String tag + utf8String[1] = (byte) valueBytes.length; + System.arraycopy(valueBytes, 0, utf8String, 2, valueBytes.length); + + return wrapSequence(concat(oid, utf8String)); + } + + private static byte[] sha256WithRsaAlgorithmIdentifier() { + // OID 1.2.840.113549.1.1.11 (sha256WithRSAEncryption) + NULL parameters + byte[] oid = new byte[] { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x0B }; + byte[] nullParam = new byte[] { 0x05, 0x00 }; + return wrapSequence(concat(oid, nullParam)); + } + + @SuppressWarnings("deprecation") + private static byte[] encodeUtcTime(Date date) { + // UTCTime format: YYMMDDHHmmSSZ + String utc = String.format("%02d%02d%02d%02d%02d%02dZ", + date.getYear() % 100, date.getMonth() + 1, date.getDate(), + date.getHours(), date.getMinutes(), date.getSeconds()); + byte[] timeBytes = utc.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + byte[] result = new byte[2 + timeBytes.length]; + result[0] = 0x17; // UTCTime tag + result[1] = (byte) timeBytes.length; + System.arraycopy(timeBytes, 0, result, 2, timeBytes.length); + return result; + } + + private static byte[] wrapSequence(byte[] content) { + return wrapTag(0x30, content); + } + + private static byte[] wrapSet(byte[] content) { + return wrapTag(0x31, content); + } + + private static byte[] wrapInteger(byte[] value) { + return wrapTag(0x02, value); + } + + private static byte[] wrapBitString(byte[] content) { + // BitString: tag + length + 0x00 (no unused bits) + content + byte[] padded = new byte[1 + content.length]; + padded[0] = 0x00; + System.arraycopy(content, 0, padded, 1, content.length); + return wrapTag(0x03, padded); + } + + private static byte[] wrapExplicitTag(int tagNumber, byte[] content) { + return wrapTag(0xA0 | tagNumber, content); + } + + private static byte[] wrapTag(int tag, byte[] content) { + byte[] lengthBytes = encodeLength(content.length); + byte[] result = new byte[1 + lengthBytes.length + content.length]; + result[0] = (byte) tag; + System.arraycopy(lengthBytes, 0, result, 1, lengthBytes.length); + System.arraycopy(content, 0, result, 1 + lengthBytes.length, content.length); + return result; + } + + private static byte[] encodeLength(int length) { + if (length < 128) { + return new byte[] { (byte) length }; + } else if (length < 256) { + return new byte[] { (byte) 0x81, (byte) length }; + } else if (length < 65536) { + return new byte[] { (byte) 0x82, (byte) (length >> 8), (byte) length }; + } else { + return new byte[] { (byte) 0x83, (byte) (length >> 16), (byte) (length >> 8), (byte) length }; + } + } + + private static byte[] concat(byte[]... arrays) { + int totalLength = 0; + for (byte[] array : arrays) { + totalLength += array.length; + } + byte[] result = new byte[totalLength]; + int offset = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, offset, array.length); + offset += array.length; + } + return result; + } +} diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java index 21f22e7f1cb6b..e83d62744b2e2 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java @@ -16,8 +16,11 @@ */ package org.apache.camel.main; +import java.security.KeyStore; import java.util.List; +import javax.net.ssl.SSLContext; + import org.apache.camel.CamelContext; import org.apache.camel.support.jsse.ClientAuthentication; import org.apache.camel.support.jsse.FilterParameters; @@ -387,6 +390,69 @@ public void testMainSSLSignatureSchemesFilter() { main.stop(); } + @Test + public void testMainSSLSelfSigned() { + Main main = new Main(); + + // just enabling SSL without a keystore should generate a self-signed certificate + main.addInitialProperty("camel.ssl.enabled", "true"); + + main.start(); + + CamelContext context = main.getCamelContext(); + assertNotNull(context); + + SSLContextParameters sslParams = context.getSSLContextParameters(); + assertNotNull(sslParams); + + // should have key managers with a self-signed certificate + KeyManagersParameters kmp = sslParams.getKeyManagers(); + assertNotNull(kmp); + + KeyStoreParameters ksp = kmp.getKeyStore(); + assertNotNull(ksp); + // the keystore should be set directly (not via resource) + assertNull(ksp.getResource()); + + // verify that an SSLContext can be created from the parameters + try { + SSLContext sslContext = sslParams.createSSLContext(context); + assertNotNull(sslContext); + } catch (Exception e) { + Assertions.fail("Should be able to create SSLContext from self-signed certificate: " + e.getMessage()); + } + + main.stop(); + } + + @Test + public void testMainSSLSelfSignedFluent() { + Main main = new Main(); + + main.configure().sslConfig() + .withEnabled(true); + + main.start(); + + CamelContext context = main.getCamelContext(); + assertNotNull(context); + + SSLContextParameters sslParams = context.getSSLContextParameters(); + assertNotNull(sslParams); + assertNotNull(sslParams.getKeyManagers()); + + main.stop(); + } + + @Test + public void testSelfSignedCertificateGenerator() throws Exception { + KeyStore ks = SelfSignedCertificateGenerator.generateKeyStore("test-password"); + assertNotNull(ks); + Assertions.assertTrue(ks.containsAlias("camel-self-signed")); + assertNotNull(ks.getKey("camel-self-signed", "test-password".toCharArray())); + assertNotNull(ks.getCertificate("camel-self-signed")); + } + @Test public void testMainSSLSignatureSchemesFilterFluent() { Main main = new Main(); From 1e8673db1252986240ec34b66ac5cc03b0dfa199 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 23 Mar 2026 14:12:15 +0100 Subject: [PATCH 2/3] CAMEL-22497: Address review findings - Remove spurious @SuppressWarnings("restriction") - Replace deprecated Date methods with java.time APIs - Use wrapTag/encodeLength consistently in buildRdn - Import StandardCharsets instead of using fully-qualified names - Add SAN extension (DNS:localhost, IP:127.0.0.1) to self-signed cert - Reuse SecureRandom instance instead of creating multiple - Respect explicit camel.server.useGlobalSslContextParameters=false by checking if the property was explicitly set before auto-enabling - Add test verifying SAN extension in generated certificate Co-Authored-By: Claude Opus 4.6 --- .../apache/camel/main/BaseMainSupport.java | 9 +- .../main/SelfSignedCertificateGenerator.java | 89 ++++++++++--------- .../org/apache/camel/main/MainSSLTest.java | 11 ++- 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 7a71216f850b8..60de5bdda7a40 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -2026,7 +2026,9 @@ private void setHttpServerProperties( // when global SSL is enabled, automatically use it for the HTTP server // (unless the user has explicitly configured useGlobalSslContextParameters) - if (!server.isUseGlobalSslContextParameters() && camelContext.getSSLContextParameters() != null) { + if (!server.isUseGlobalSslContextParameters() + && camelContext.getSSLContextParameters() != null + && !properties.containsKey("useGlobalSslContextParameters")) { server.setUseGlobalSslContextParameters(true); } @@ -2055,7 +2057,10 @@ private void setHttpManagementServerProperties( } // when global SSL is enabled, automatically use it for the HTTP management server - if (!server.isUseGlobalSslContextParameters() && camelContext.getSSLContextParameters() != null) { + // (unless the user has explicitly configured useGlobalSslContextParameters) + if (!server.isUseGlobalSslContextParameters() + && camelContext.getSSLContextParameters() != null + && !properties.containsKey("useGlobalSslContextParameters")) { server.setUseGlobalSslContextParameters(true); } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java b/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java index dc1dd0ac8d21e..457115c88b9c5 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java @@ -17,6 +17,7 @@ package org.apache.camel.main; import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; @@ -26,9 +27,8 @@ import java.security.Signature; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; /** * Generates a self-signed certificate for development use. This allows enabling HTTPS with minimal configuration when @@ -42,18 +42,20 @@ private SelfSignedCertificateGenerator() { } /** - * Generates a PKCS12 KeyStore containing a self-signed certificate. + * Generates a PKCS12 KeyStore containing a self-signed certificate with Subject Alternative Names for localhost and + * 127.0.0.1. * * @param password the password for the keystore and key entry * @return a KeyStore containing the self-signed certificate * @throws Exception if certificate generation fails */ static KeyStore generateKeyStore(String password) throws Exception { + SecureRandom random = new SecureRandom(); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); - keyGen.initialize(2048, new SecureRandom()); + keyGen.initialize(2048, random); KeyPair keyPair = keyGen.generateKeyPair(); - X509Certificate cert = generateCertificate(keyPair); + X509Certificate cert = generateCertificate(keyPair, random); KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(null, password.toCharArray()); @@ -63,17 +65,15 @@ static KeyStore generateKeyStore(String password) throws Exception { return ks; } - @SuppressWarnings("restriction") - private static X509Certificate generateCertificate(KeyPair keyPair) throws Exception { + private static X509Certificate generateCertificate(KeyPair keyPair, SecureRandom random) throws Exception { PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); - Instant now = Instant.now(); - Date notBefore = Date.from(now); - Date notAfter = Date.from(now.plus(365, ChronoUnit.DAYS)); + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + ZonedDateTime expiry = now.plusDays(365); // Build self-signed X.509 certificate using DER encoding - byte[] encoded = buildSelfSignedCertificateDer(publicKey, privateKey, notBefore, notAfter); + byte[] encoded = buildSelfSignedCertificateDer(publicKey, privateKey, now, expiry, random); CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(encoded)); @@ -81,14 +81,15 @@ private static X509Certificate generateCertificate(KeyPair keyPair) throws Excep private static byte[] buildSelfSignedCertificateDer( PublicKey publicKey, PrivateKey privateKey, - Date notBefore, Date notAfter) + ZonedDateTime notBefore, ZonedDateTime notAfter, + SecureRandom random) throws Exception { // DN: CN=localhost, O=Apache Camel (self-signed) byte[] issuerDn = buildDn(); // TBS Certificate - byte[] tbsCertificate = buildTbsCertificate(publicKey, issuerDn, notBefore, notAfter); + byte[] tbsCertificate = buildTbsCertificate(publicKey, issuerDn, notBefore, notAfter, random); // Sign the TBS certificate Signature sig = Signature.getInstance("SHA256withRSA"); @@ -105,34 +106,32 @@ private static byte[] buildSelfSignedCertificateDer( private static byte[] buildTbsCertificate( PublicKey publicKey, byte[] dn, - Date notBefore, Date notAfter) - throws Exception { + ZonedDateTime notBefore, ZonedDateTime notAfter, + SecureRandom random) { // Version: v3 (2) byte[] version = wrapExplicitTag(0, wrapInteger(new byte[] { 2 })); // Serial number byte[] serialBytes = new byte[16]; - new SecureRandom().nextBytes(serialBytes); + random.nextBytes(serialBytes); serialBytes[0] &= 0x7F; // ensure positive byte[] serial = wrapInteger(serialBytes); // Signature algorithm byte[] signatureAlgorithm = sha256WithRsaAlgorithmIdentifier(); - // Issuer DN - byte[] issuer = dn; - // Validity byte[] validity = wrapSequence(concat(encodeUtcTime(notBefore), encodeUtcTime(notAfter))); - // Subject DN (same as issuer for self-signed) - byte[] subject = dn; - // Subject Public Key Info (from the encoded public key) byte[] subjectPublicKeyInfo = publicKey.getEncoded(); - return wrapSequence(concat(version, serial, signatureAlgorithm, issuer, validity, subject, subjectPublicKeyInfo)); + // Extensions: Subject Alternative Name (localhost, 127.0.0.1) + byte[] extensions = wrapExplicitTag(3, wrapSequence(buildSanExtension())); + + return wrapSequence( + concat(version, serial, signatureAlgorithm, dn, validity, dn, subjectPublicKeyInfo, extensions)); } // Builds DN: CN=localhost, O=Apache Camel (self-signed) @@ -143,18 +142,25 @@ private static byte[] buildDn() { } private static byte[] buildRdn(byte[] oidBytes, String value) { - byte[] oid = new byte[2 + oidBytes.length]; - oid[0] = 0x06; // OID tag - oid[1] = (byte) oidBytes.length; - System.arraycopy(oidBytes, 0, oid, 2, oidBytes.length); + byte[] oid = wrapTag(0x06, oidBytes); + byte[] utf8String = wrapTag(0x0C, value.getBytes(StandardCharsets.UTF_8)); + return wrapSequence(concat(oid, utf8String)); + } - byte[] valueBytes = value.getBytes(java.nio.charset.StandardCharsets.UTF_8); - byte[] utf8String = new byte[2 + valueBytes.length]; - utf8String[0] = 0x0C; // UTF8String tag - utf8String[1] = (byte) valueBytes.length; - System.arraycopy(valueBytes, 0, utf8String, 2, valueBytes.length); + // Builds SAN extension with DNS:localhost and IP:127.0.0.1 + private static byte[] buildSanExtension() { + // OID 2.5.29.17 (subjectAltName) + byte[] sanOid = wrapTag(0x06, new byte[] { 0x55, 0x1D, 0x11 }); - return wrapSequence(concat(oid, utf8String)); + // SAN value: SEQUENCE { [2] "localhost", [7] 127.0.0.1 } + byte[] dnsName = wrapTag(0x82, "localhost".getBytes(StandardCharsets.US_ASCII)); // context [2] = dNSName + byte[] ipAddress = wrapTag(0x87, new byte[] { 127, 0, 0, 1 }); // context [7] = iPAddress + byte[] sanSequence = wrapSequence(concat(dnsName, ipAddress)); + + // Wrap the SAN value as an OCTET STRING (extension value must be wrapped) + byte[] sanOctetString = wrapTag(0x04, sanSequence); + + return wrapSequence(concat(sanOid, sanOctetString)); } private static byte[] sha256WithRsaAlgorithmIdentifier() { @@ -165,18 +171,13 @@ private static byte[] sha256WithRsaAlgorithmIdentifier() { return wrapSequence(concat(oid, nullParam)); } - @SuppressWarnings("deprecation") - private static byte[] encodeUtcTime(Date date) { + private static byte[] encodeUtcTime(ZonedDateTime dateTime) { // UTCTime format: YYMMDDHHmmSSZ String utc = String.format("%02d%02d%02d%02d%02d%02dZ", - date.getYear() % 100, date.getMonth() + 1, date.getDate(), - date.getHours(), date.getMinutes(), date.getSeconds()); - byte[] timeBytes = utc.getBytes(java.nio.charset.StandardCharsets.US_ASCII); - byte[] result = new byte[2 + timeBytes.length]; - result[0] = 0x17; // UTCTime tag - result[1] = (byte) timeBytes.length; - System.arraycopy(timeBytes, 0, result, 2, timeBytes.length); - return result; + dateTime.getYear() % 100, dateTime.getMonthValue(), dateTime.getDayOfMonth(), + dateTime.getHour(), dateTime.getMinute(), dateTime.getSecond()); + byte[] timeBytes = utc.getBytes(StandardCharsets.US_ASCII); + return wrapTag(0x17, timeBytes); } private static byte[] wrapSequence(byte[] content) { diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java index e83d62744b2e2..a70b256c00a30 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java @@ -450,7 +450,16 @@ public void testSelfSignedCertificateGenerator() throws Exception { assertNotNull(ks); Assertions.assertTrue(ks.containsAlias("camel-self-signed")); assertNotNull(ks.getKey("camel-self-signed", "test-password".toCharArray())); - assertNotNull(ks.getCertificate("camel-self-signed")); + + java.security.cert.X509Certificate cert + = (java.security.cert.X509Certificate) ks.getCertificate("camel-self-signed"); + assertNotNull(cert); + + // verify the certificate has a SAN extension with localhost + java.util.Collection> sans = cert.getSubjectAlternativeNames(); + assertNotNull(sans); + // should have DNS:localhost and IP:127.0.0.1 + Assertions.assertTrue(sans.size() >= 2); } @Test From e1886af6edfea264babc9c370e2e8c51a2f6fdb7 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 25 Mar 2026 16:31:08 +0100 Subject: [PATCH 3/3] CAMEL-22497: Require explicit selfSigned opt-in and mark SSL passwords as secret - Add camel.ssl.selfSigned property to explicitly opt-in to self-signed certificate generation instead of auto-generating when no keystore is configured - Mark camel.ssl.keystorePassword and camel.ssl.trustStorePassword with secret=true in @Metadata annotation - When SSL is enabled without keystore or selfSigned, log a warning and skip SSL context creation Co-Authored-By: Claude Opus 4.6 --- .../camel-main-configuration-metadata.json | 5 +-- .../SSLConfigurationPropertiesConfigurer.java | 7 +++++ .../camel-main-configuration-metadata.json | 5 +-- core/camel-main/src/main/docs/main.adoc | 3 +- .../apache/camel/main/BaseMainSupport.java | 10 ++++-- .../main/SSLConfigurationProperties.java | 31 +++++++++++++++++-- .../org/apache/camel/main/MainSSLTest.java | 8 +++-- 7 files changed, 57 insertions(+), 12 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json index 878f38c171b26..22b8d4bf7ae53 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json @@ -344,7 +344,7 @@ { "name": "camel.ssl.keyManagerAlgorithm", "required": false, "description": "Algorithm name used for creating the KeyManagerFactory. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.keyManagerProvider", "required": false, "description": "To use a specific provider for creating KeyManagerFactory. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.keyStore", "required": false, "description": "The key store to load. The key store is by default loaded from classpath. If you must load from file system, then use file: as prefix. file:nameOfFile (to refer to the file system) classpath:nameOfFile (to refer to the classpath; default) http:uri (to load the resource using HTTP) ref:nameOfBean (to lookup an existing KeyStore instance from the registry, for example for testing and development).", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, - { "name": "camel.ssl.keystorePassword", "required": false, "description": "Sets the SSL Keystore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.keystorePassword", "required": false, "description": "Sets the SSL Keystore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": true }, { "name": "camel.ssl.keyStoreProvider", "required": false, "description": "To use a specific provider for creating KeyStore. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.keyStoreType", "required": false, "description": "The type of the key store to load. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.namedGroups", "required": false, "description": "List of TLS\/SSL named groups (key exchange groups). Multiple names can be separated by comma. Named groups control which key exchange algorithms are available during the TLS handshake, including post-quantum hybrid groups such as X25519MLKEM768.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, @@ -354,13 +354,14 @@ { "name": "camel.ssl.secureRandomAlgorithm", "required": false, "description": "Algorithm name used for creating the SecureRandom. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.secureRandomProvider", "required": false, "description": "To use a specific provider for creating SecureRandom. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.secureSocketProtocol", "required": false, "description": "The protocol for the secure sockets created by the SSLContext. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "TLSv1.3", "secret": false }, + { "name": "camel.ssl.selfSigned", "required": false, "description": "Whether to generate a self-signed certificate for development use when no keystore is configured. This can be used in development environment to easily enable HTTPS without providing a keystore. Do NOT use this in production as the certificate is not trusted and is regenerated on each restart.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, { "name": "camel.ssl.sessionTimeout", "required": false, "description": "Timeout in seconds to use for SSLContext. The default is 24 hours.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 86400, "secret": false }, { "name": "camel.ssl.signatureSchemes", "required": false, "description": "List of TLS\/SSL signature schemes. Multiple names can be separated by comma. Signature schemes control which signature algorithms are available during the TLS handshake, including post-quantum signature algorithms such as ML-DSA.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.signatureSchemesExclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.signatureSchemesInclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for including signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.trustAllCertificates", "required": false, "description": "Allows to trust all SSL certificates without performing certificate validation. This can be used in development environment but may expose the system to security risks. Notice that if the trustAllCertificates option is set to true then the trustStore\/trustStorePassword options are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, { "name": "camel.ssl.trustStore", "required": false, "description": "The trust store to load. The trust store is by default loaded from classpath. If you must load from file system, then use file: as prefix. file:nameOfFile (to refer to the file system) classpath:nameOfFile (to refer to the classpath; default) http:uri (to load the resource using HTTP) ref:nameOfBean (to lookup an existing KeyStore instance from the registry, for example for testing and development).", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, - { "name": "camel.ssl.trustStorePassword", "required": false, "description": "Sets the SSL Truststore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.trustStorePassword", "required": false, "description": "Sets the SSL Truststore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": true }, { "name": "camel.startupcondition.customClassNames", "required": false, "description": "A list of custom class names (FQN). Multiple classes can be separated by comma.", "sourceType": "org.apache.camel.main.StartupConditionConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.startupcondition.enabled", "required": false, "description": "To enable using startup conditions", "sourceType": "org.apache.camel.main.StartupConditionConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, { "name": "camel.startupcondition.environmentVariableExists", "required": false, "description": "Wait for an environment variable with the given name to exists before continuing", "sourceType": "org.apache.camel.main.StartupConditionConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java index 375dd286459ab..1b5b529834b78 100644 --- a/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java +++ b/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java @@ -41,6 +41,7 @@ public class SSLConfigurationPropertiesConfigurer extends org.apache.camel.suppo map.put("SecureRandomAlgorithm", java.lang.String.class); map.put("SecureRandomProvider", java.lang.String.class); map.put("SecureSocketProtocol", java.lang.String.class); + map.put("SelfSigned", boolean.class); map.put("SessionTimeout", int.class); map.put("SignatureSchemes", java.lang.String.class); map.put("SignatureSchemesExclude", java.lang.String.class); @@ -91,6 +92,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "secureRandomProvider": target.setSecureRandomProvider(property(camelContext, java.lang.String.class, value)); return true; case "securesocketprotocol": case "secureSocketProtocol": target.setSecureSocketProtocol(property(camelContext, java.lang.String.class, value)); return true; + case "selfsigned": + case "selfSigned": target.setSelfSigned(property(camelContext, boolean.class, value)); return true; case "sessiontimeout": case "sessionTimeout": target.setSessionTimeout(property(camelContext, int.class, value)); return true; case "signatureschemes": @@ -153,6 +156,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "secureRandomProvider": return java.lang.String.class; case "securesocketprotocol": case "secureSocketProtocol": return java.lang.String.class; + case "selfsigned": + case "selfSigned": return boolean.class; case "sessiontimeout": case "sessionTimeout": return int.class; case "signatureschemes": @@ -211,6 +216,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "secureRandomProvider": return target.getSecureRandomProvider(); case "securesocketprotocol": case "secureSocketProtocol": return target.getSecureSocketProtocol(); + case "selfsigned": + case "selfSigned": return target.isSelfSigned(); case "sessiontimeout": case "sessionTimeout": return target.getSessionTimeout(); case "signatureschemes": diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index 878f38c171b26..22b8d4bf7ae53 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -344,7 +344,7 @@ { "name": "camel.ssl.keyManagerAlgorithm", "required": false, "description": "Algorithm name used for creating the KeyManagerFactory. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.keyManagerProvider", "required": false, "description": "To use a specific provider for creating KeyManagerFactory. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.keyStore", "required": false, "description": "The key store to load. The key store is by default loaded from classpath. If you must load from file system, then use file: as prefix. file:nameOfFile (to refer to the file system) classpath:nameOfFile (to refer to the classpath; default) http:uri (to load the resource using HTTP) ref:nameOfBean (to lookup an existing KeyStore instance from the registry, for example for testing and development).", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, - { "name": "camel.ssl.keystorePassword", "required": false, "description": "Sets the SSL Keystore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.keystorePassword", "required": false, "description": "Sets the SSL Keystore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": true }, { "name": "camel.ssl.keyStoreProvider", "required": false, "description": "To use a specific provider for creating KeyStore. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.keyStoreType", "required": false, "description": "The type of the key store to load. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.namedGroups", "required": false, "description": "List of TLS\/SSL named groups (key exchange groups). Multiple names can be separated by comma. Named groups control which key exchange algorithms are available during the TLS handshake, including post-quantum hybrid groups such as X25519MLKEM768.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, @@ -354,13 +354,14 @@ { "name": "camel.ssl.secureRandomAlgorithm", "required": false, "description": "Algorithm name used for creating the SecureRandom. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.secureRandomProvider", "required": false, "description": "To use a specific provider for creating SecureRandom. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.secureSocketProtocol", "required": false, "description": "The protocol for the secure sockets created by the SSLContext. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "TLSv1.3", "secret": false }, + { "name": "camel.ssl.selfSigned", "required": false, "description": "Whether to generate a self-signed certificate for development use when no keystore is configured. This can be used in development environment to easily enable HTTPS without providing a keystore. Do NOT use this in production as the certificate is not trusted and is regenerated on each restart.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, { "name": "camel.ssl.sessionTimeout", "required": false, "description": "Timeout in seconds to use for SSLContext. The default is 24 hours.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 86400, "secret": false }, { "name": "camel.ssl.signatureSchemes", "required": false, "description": "List of TLS\/SSL signature schemes. Multiple names can be separated by comma. Signature schemes control which signature algorithms are available during the TLS handshake, including post-quantum signature algorithms such as ML-DSA.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.signatureSchemesExclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.signatureSchemesInclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for including signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.trustAllCertificates", "required": false, "description": "Allows to trust all SSL certificates without performing certificate validation. This can be used in development environment but may expose the system to security risks. Notice that if the trustAllCertificates option is set to true then the trustStore\/trustStorePassword options are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, { "name": "camel.ssl.trustStore", "required": false, "description": "The trust store to load. The trust store is by default loaded from classpath. If you must load from file system, then use file: as prefix. file:nameOfFile (to refer to the file system) classpath:nameOfFile (to refer to the classpath; default) http:uri (to load the resource using HTTP) ref:nameOfBean (to lookup an existing KeyStore instance from the registry, for example for testing and development).", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, - { "name": "camel.ssl.trustStorePassword", "required": false, "description": "Sets the SSL Truststore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.trustStorePassword", "required": false, "description": "Sets the SSL Truststore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": true }, { "name": "camel.startupcondition.customClassNames", "required": false, "description": "A list of custom class names (FQN). Multiple classes can be separated by comma.", "sourceType": "org.apache.camel.main.StartupConditionConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.startupcondition.enabled", "required": false, "description": "To enable using startup conditions", "sourceType": "org.apache.camel.main.StartupConditionConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, { "name": "camel.startupcondition.environmentVariableExists", "required": false, "description": "Wait for an environment variable with the given name to exists before continuing", "sourceType": "org.apache.camel.main.StartupConditionConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 28f1b035ab10f..99f2f44be283e 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -303,7 +303,7 @@ The camel.trace supports 14 options, which are listed below. === Camel SSL configurations -The camel.ssl supports 26 options, which are listed below. +The camel.ssl supports 27 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -327,6 +327,7 @@ The camel.ssl supports 26 options, which are listed below. | *camel.ssl.secureRandom{zwsp}Algorithm* | Algorithm name used for creating the SecureRandom. See \https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html | | String | *camel.ssl.secureRandomProvider* | To use a specific provider for creating SecureRandom. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol. | | String | *camel.ssl.secureSocketProtocol* | The protocol for the secure sockets created by the SSLContext. See \https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html | TLSv1.3 | String +| *camel.ssl.selfSigned* | Whether to generate a self-signed certificate for development use when no keystore is configured. This can be used in development environment to easily enable HTTPS without providing a keystore. Do NOT use this in production as the certificate is not trusted and is regenerated on each restart. | false | boolean | *camel.ssl.sessionTimeout* | Timeout in seconds to use for SSLContext. The default is 24 hours. | 86400 | int | *camel.ssl.signatureSchemes* | List of TLS/SSL signature schemes. Multiple names can be separated by comma. Signature schemes control which signature algorithms are available during the TLS handshake, including post-quantum signature algorithms such as ML-DSA. | | String | *camel.ssl.signatureSchemes{zwsp}Exclude* | Filters TLS/SSL signature schemes. This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include/exclude filters are not in use. | | String diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 60de5bdda7a40..c2636557c280d 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -2150,9 +2150,9 @@ private void setSslProperties( kmp.setKeyStore(ksp); kmp.setAlgorithm(sslConfig.getKeyManagerAlgorithm()); kmp.setProvider(sslConfig.getKeyManagerProvider()); - } else { - // no keystore configured, generate a self-signed certificate for development use - LOG.info("No SSL keystore configured - generating self-signed certificate for development use." + } else if (sslConfig.isSelfSigned()) { + // generate a self-signed certificate for development use + LOG.warn("Generating self-signed SSL certificate for development use." + " Do NOT use this in production."); String password = "camel-self-signed"; KeyStore ks = SelfSignedCertificateGenerator.generateKeyStore(password); @@ -2167,6 +2167,10 @@ private void setSslProperties( kmp.setCamelContext(camelContext); kmp.setKeyPassword(password); kmp.setKeyStore(ksp); + } else { + LOG.warn("SSL is enabled but no keystore is configured." + + " Set camel.ssl.keyStore or camel.ssl.selfSigned=true for development."); + return; } final SSLContextParameters sslContextParameters = createSSLContextParameters(camelContext, sslConfig, kmp); diff --git a/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java index 77b84bd3b5a7f..08049ba9eb11e 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java @@ -62,14 +62,16 @@ public class SSLConfigurationProperties implements BootstrapCloseable { private String keyStoreType; @Metadata(label = "advanced") private String keyStoreProvider; - @Metadata + @Metadata(secret = true) private String keystorePassword; @Metadata private String trustStore; - @Metadata + @Metadata(secret = true) private String trustStorePassword; @Metadata private boolean trustAllCertificates; + @Metadata + private boolean selfSigned; @Metadata(label = "advanced") private String keyManagerAlgorithm; @Metadata(label = "advanced") @@ -385,6 +387,20 @@ public void setTrustAllCertificates(boolean trustAllCertificates) { this.trustAllCertificates = trustAllCertificates; } + public boolean isSelfSigned() { + return selfSigned; + } + + /** + * Whether to generate a self-signed certificate for development use when no keystore is configured. This can be + * used in development environment to easily enable HTTPS without providing a keystore. + * + * Do NOT use this in production as the certificate is not trusted and is regenerated on each restart. + */ + public void setSelfSigned(boolean selfSigned) { + this.selfSigned = selfSigned; + } + public String getKeyManagerAlgorithm() { return keyManagerAlgorithm; } @@ -678,6 +694,17 @@ public SSLConfigurationProperties withTrustAllCertificates(boolean trustAllCerti return this; } + /** + * Whether to generate a self-signed certificate for development use when no keystore is configured. This can be + * used in development environment to easily enable HTTPS without providing a keystore. + * + * Do NOT use this in production as the certificate is not trusted and is regenerated on each restart. + */ + public SSLConfigurationProperties withSelfSigned(boolean selfSigned) { + this.selfSigned = selfSigned; + return this; + } + /** * Algorithm name used for creating the KeyManagerFactory. *

diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java index a70b256c00a30..b9f18ad2e938e 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java @@ -136,6 +136,7 @@ public void testMainSSLTrustAll() { Main main = new Main(); main.addInitialProperty("camel.ssl.enabled", "true"); + main.addInitialProperty("camel.ssl.selfSigned", "true"); main.addInitialProperty("camel.ssl.trustAllCertificates", "true"); main.start(); @@ -154,6 +155,7 @@ public void testMainSSLTrustAllFluent() { main.configure().sslConfig() .withEnabled(true) + .withSelfSigned(true) .withTrustAllCertificates(true); main.start(); @@ -394,8 +396,9 @@ public void testMainSSLSignatureSchemesFilter() { public void testMainSSLSelfSigned() { Main main = new Main(); - // just enabling SSL without a keystore should generate a self-signed certificate + // enabling SSL with selfSigned should generate a self-signed certificate main.addInitialProperty("camel.ssl.enabled", "true"); + main.addInitialProperty("camel.ssl.selfSigned", "true"); main.start(); @@ -430,7 +433,8 @@ public void testMainSSLSelfSignedFluent() { Main main = new Main(); main.configure().sslConfig() - .withEnabled(true); + .withEnabled(true) + .withSelfSigned(true); main.start();