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 e73e1565752cb..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 @@ -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,14 @@ 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 + && !properties.containsKey("useGlobalSslContextParameters")) { + 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 +2056,14 @@ private void setHttpManagementServerProperties( return; } + // when global SSL is enabled, automatically use it for the HTTP management server + // (unless the user has explicitly configured useGlobalSslContextParameters) + if (!server.isUseGlobalSslContextParameters() + && camelContext.getSSLContextParameters() != null + && !properties.containsKey("useGlobalSslContextParameters")) { + 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 +2123,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 +2134,44 @@ 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 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); + + 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); + } 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); camelContext.setSSLContextParameters(sslContextParameters); 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/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..457115c88b9c5
--- /dev/null
+++ b/core/camel-main/src/main/java/org/apache/camel/main/SelfSignedCertificateGenerator.java
@@ -0,0 +1,241 @@
+/*
+ * 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.nio.charset.StandardCharsets;
+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.ZoneOffset;
+import java.time.ZonedDateTime;
+
+/**
+ * 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 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, random);
+ KeyPair keyPair = keyGen.generateKeyPair();
+
+ X509Certificate cert = generateCertificate(keyPair, random);
+
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(null, password.toCharArray());
+ ks.setKeyEntry("camel-self-signed", keyPair.getPrivate(), password.toCharArray(),
+ new X509Certificate[] { cert });
+
+ return ks;
+ }
+
+ private static X509Certificate generateCertificate(KeyPair keyPair, SecureRandom random) throws Exception {
+ PublicKey publicKey = keyPair.getPublic();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ 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, now, expiry, random);
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(encoded));
+ }
+
+ private static byte[] buildSelfSignedCertificateDer(
+ PublicKey publicKey, PrivateKey privateKey,
+ 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, random);
+
+ // 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,
+ ZonedDateTime notBefore, ZonedDateTime notAfter,
+ SecureRandom random) {
+
+ // Version: v3 (2)
+ byte[] version = wrapExplicitTag(0, wrapInteger(new byte[] { 2 }));
+
+ // Serial number
+ byte[] serialBytes = new byte[16];
+ random.nextBytes(serialBytes);
+ serialBytes[0] &= 0x7F; // ensure positive
+ byte[] serial = wrapInteger(serialBytes);
+
+ // Signature algorithm
+ byte[] signatureAlgorithm = sha256WithRsaAlgorithmIdentifier();
+
+ // Validity
+ byte[] validity = wrapSequence(concat(encodeUtcTime(notBefore), encodeUtcTime(notAfter)));
+
+ // Subject Public Key Info (from the encoded public key)
+ byte[] subjectPublicKeyInfo = publicKey.getEncoded();
+
+ // 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)
+ 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 = wrapTag(0x06, oidBytes);
+ byte[] utf8String = wrapTag(0x0C, value.getBytes(StandardCharsets.UTF_8));
+ return wrapSequence(concat(oid, utf8String));
+ }
+
+ // 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 });
+
+ // 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() {
+ // 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));
+ }
+
+ private static byte[] encodeUtcTime(ZonedDateTime dateTime) {
+ // UTCTime format: YYMMDDHHmmSSZ
+ String utc = String.format("%02d%02d%02d%02d%02d%02dZ",
+ 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) {
+ 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..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
@@ -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;
@@ -133,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();
@@ -151,6 +155,7 @@ public void testMainSSLTrustAllFluent() {
main.configure().sslConfig()
.withEnabled(true)
+ .withSelfSigned(true)
.withTrustAllCertificates(true);
main.start();
@@ -387,6 +392,80 @@ public void testMainSSLSignatureSchemesFilter() {
main.stop();
}
+ @Test
+ public void testMainSSLSelfSigned() {
+ Main main = new Main();
+
+ // enabling SSL with selfSigned should generate a self-signed certificate
+ main.addInitialProperty("camel.ssl.enabled", "true");
+ main.addInitialProperty("camel.ssl.selfSigned", "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)
+ .withSelfSigned(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()));
+
+ 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