From 362ccf9f31a026a7195eb42c2774402e7767bc74 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 26 Oct 2021 19:40:38 +0300 Subject: [PATCH] Unified transport SSL credentials --- .../LwM2MServerSecurityInfoRepository.java | 26 +-- .../src/main/resources/thingsboard.yml | 145 ++++++++++--- .../server/coapserver/TbCoapDtlsSettings.java | 37 ++-- .../server/common/data/ResourceUtils.java | 24 +++ .../LwM2MTransportBootstrapService.java | 52 ++--- .../lwm2m/config/LwM2MSecureServerConfig.java | 6 +- .../config/LwM2MTransportBootstrapConfig.java | 24 ++- .../config/LwM2MTransportServerConfig.java | 69 +++---- .../TbLwM2MDtlsCertificateVerifier.java | 8 +- .../server/DefaultLwM2mTransportService.java | 27 +-- .../mqtt/MqttSslHandlerProvider.java | 42 ++-- common/transport/transport-api/pom.xml | 8 + .../config/ssl/AbstractSslCredentials.java | 191 ++++++++++++++++++ .../config/ssl/KeystoreSslCredentials.java | 52 +++++ .../config/ssl/PemSslCredentials.java | 130 ++++++++++++ .../transport/config/ssl/SslCredentials.java | 45 +++++ .../config/ssl/SslCredentialsConfig.java | 66 ++++++ .../config/ssl/SslCredentialsType.java | 21 ++ .../src/main/resources/tb-coap-transport.yml | 36 +++- .../src/main/resources/tb-lwm2m-transport.yml | 83 ++++++-- .../src/main/resources/tb-mqtt-transport.yml | 30 ++- 21 files changed, 883 insertions(+), 239 deletions(-) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/KeystoreSslCredentials.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/PemSslCredentials.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsType.java diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java index b4ac3d30fd..fcf68bf13a 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java @@ -22,28 +22,11 @@ import org.eclipse.leshan.core.util.Hex; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import java.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.KeySpec; - @Slf4j @Service @RequiredArgsConstructor @@ -72,10 +55,9 @@ public class LwM2MServerSecurityInfoRepository { private String getPublicKey(LwM2MSecureServerConfig config) { try { - KeyStore keyStore = serverConfig.getKeyStoreValue(); - if (keyStore != null) { - X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(config.getCertificateAlias()); - return Hex.encodeHexString(serverCertificate.getPublicKey().getEncoded()); + SslCredentials sslCredentials = config.getSslCredentials(); + if (sslCredentials != null) { + return Hex.encodeHexString(sslCredentials.getPublicKey().getEncoded()); } } catch (Exception e) { log.trace("Failed to fetch public key from key store!", e); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 57a6a638af..f88d5f297c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -619,14 +619,28 @@ transport: bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" - # Path to the key store that holds the SSL certificate - key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" - # Password used to access the key store - key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" - # Password used to access the key - key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" - # Type of the key store - key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" + # Server SSL credentials + credentials: + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${MQTT_SSL_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" + # Password used to access the key store + store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" # Skip certificate validity check for client certificates. skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" # Local CoAP transport parameters @@ -645,14 +659,30 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Path to the key store that holds the certificate - key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}" - # Password used to access the key store - key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}" - # Password used to access the key - key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}" - # Key alias - key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}" + # Server DTLS credentials + credentials: + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${COAP_DTLS_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${COAP_DTLS_PEM_CERT:coapserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${COAP_DTLS_PEM_KEY:coapserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${COAP_DTLS_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${COAP_DTLS_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${COAP_DTLS_KEY_STORE:coapserver.jks}" + # Password used to access the key store + store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}" + # Key alias + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}" x509: # Skip certificate validity check for client certificates. skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" @@ -669,9 +699,33 @@ transport: security: bind_address: "${LWM2M_SECURITY_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_SECURITY_BIND_PORT:5686}" + # Server X509 Certificates support + credentials: + # Whether to enable LWM2M server X509 Certificate/RPK support + enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:false}" + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${LWM2M_SERVER_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${LWM2M_SERVER_PEM_CERT:lwm2mserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${LWM2M_SERVER_PEM_KEY:lwm2mserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${LWM2M_SERVER_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${LWM2M_SERVER_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${LWM2M_SERVER_KEY_STORE:lwm2mserver.jks}" + # Password used to access the key store + store_password: "${LWM2M_SERVER_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_key_password}" + # Key alias + key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}" # Only Certificate_x509: - key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}" - key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}" skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: enable: "${LWM2M_ENABLED_BS:true}" @@ -681,18 +735,51 @@ transport: security: bind_address: "${LWM2M_BS_SECURITY_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_BS_SECURITY_BIND_PORT:5688}" - # Only Certificate_x509: - key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}" - key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}" + # Bootstrap server X509 Certificates support + credentials: + # Whether to enable LWM2M bootstrap server X509 Certificate/RPK support + enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:false}" + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${LWM2M_BS_PEM_CERT:lwm2mserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${LWM2M_BS_PEM_KEY:lwm2mserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${LWM2M_BS_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${LWM2M_BS_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${LWM2M_BS_KEY_STORE:lwm2mserver.jks}" + # Password used to access the key store + store_password: "${LWM2M_BS_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${LWM2M_BS_KEY_PASSWORD:server_key_password}" + # Key alias + key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}" security: - # Certificate_x509: - # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format - # Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh - key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}" - # key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks" - key_store: "${LWM2M_KEYSTORE:lwm2mserver.jks}" - key_store_password: "${LWM2M_KEYSTORE_PASSWORD:server_ks_password}" - root_alias: "${LWM2M_SERVER_ROOT_CA_ALIAS:rootca}" + # X509 trust certificates + trust-credentials: + # Whether to load X509 trust certificates + enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:false}" + # Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore) + type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}" + # PEM certificates + pem: + # Path to the certificates file (holds trust certificates) + cert_file: "${LWM2M_TRUST_PEM_CERT:lwm2mserver.pem}" + # Keystore with trust certificates + keystore: + # Type of the key store + type: "${LWM2M_TRUST_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the X509 certificates + store_file: "${LWM2M_TRUST_KEY_STORE:lwm2mserver.jks}" + # Password used to access the key store + store_password: "${LWM2M_TRUST_KEY_STORE_PASSWORD:server_ks_password}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" timeout: "${LWM2M_TIMEOUT:120000}" diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java index f433909242..6f82c79041 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java @@ -20,11 +20,16 @@ import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.CertificateType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import java.io.IOException; @@ -45,17 +50,15 @@ public class TbCoapDtlsSettings { @Value("${transport.coap.dtls.bind_port}") private Integer port; - @Value("${transport.coap.dtls.key_store}") - private String keyStoreFile; - - @Value("${transport.coap.dtls.key_store_password}") - private String keyStorePassword; - - @Value("${transport.coap.dtls.key_password}") - private String keyPassword; + @Bean + @ConfigurationProperties(prefix = "transport.coap.dtls.credentials") + public SslCredentialsConfig coapDtlsCredentials() { + return new SslCredentialsConfig("COAP DTLS Credentials", false); + } - @Value("${transport.coap.dtls.key_alias}") - private String keyAlias; + @Autowired + @Qualifier("coapDtlsCredentials") + private SslCredentialsConfig coapDtlsCredentialsConfig; @Value("${transport.coap.dtls.x509.skip_validity_check_for_client_cert:false}") private boolean skipValidityCheckForClientCert; @@ -75,8 +78,9 @@ public class TbCoapDtlsSettings { public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException { DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(); configBuilder.setAddress(getInetSocketAddress()); - String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile); - SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); + SslCredentials sslCredentials = this.coapDtlsCredentialsConfig.getCredentials(); + SslContextUtil.Credentials serverCredentials = + new SslContextUtil.Credentials(sslCredentials.getPrivateKey(), null, sslCredentials.getCertificateChain()); configBuilder.setServerOnly(true); configBuilder.setClientAuthenticationRequired(false); configBuilder.setClientAuthenticationWanted(true); @@ -94,15 +98,6 @@ public class TbCoapDtlsSettings { return configBuilder.build(); } - private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) { - try { - return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(), - keyPassword.toCharArray()); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Failed to load serverCredentials due to: ", e); - } - } - private InetSocketAddress getInetSocketAddress() throws UnknownHostException { InetAddress addr = InetAddress.getByName(host); return new InetSocketAddress(addr, port); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.java index 754476bc26..b354b98bdb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.java @@ -27,6 +27,30 @@ import java.net.URL; @Slf4j public class ResourceUtils { + public static boolean resourceExists(Object classLoaderSource, String filePath) { + return resourceExists(classLoaderSource.getClass().getClassLoader(), filePath); + } + + public static boolean resourceExists(ClassLoader classLoader, String filePath) { + File resourceFile = new File(filePath); + if (resourceFile.exists()) { + return true; + } else { + InputStream classPathStream = classLoader.getResourceAsStream(filePath); + if (classPathStream != null) { + return true; + } else { + try { + URL url = Resources.getResource(filePath); + if (url != null) { + return true; + } + } catch (IllegalArgumentException e) {} + } + } + return false; + } + public static InputStream getInputStream(Object classLoaderSource, String filePath) { return getInputStream(classLoaderSource.getClass().getClassLoader(), filePath); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 16f9443547..0ea9be4388 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -27,6 +27,7 @@ import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigurationAdapter; @@ -114,49 +115,22 @@ public class LwM2MTransportBootstrapService { } private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { - try { - if (serverConfig.getKeyStoreValue() != null) { - KeyStore keyStoreServer = serverConfig.getKeyStoreValue(); - if (this.setBuilderX509(builder)) { - X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(serverConfig.getRootCertificateAlias()); - if (rootCAX509Cert != null) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - builder.setTrustedCertificates(trustedCertificates); - } else { - /* by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); - } - } + if (this.bootstrapConfig.getSslCredentials() != null) { + SslCredentials sslCredentials = this.bootstrapConfig.getSslCredentials(); + builder.setPublicKey(sslCredentials.getPublicKey()); + builder.setPrivateKey(sslCredentials.getPrivateKey()); + builder.setCertificateChain(sslCredentials.getCertificateChain()); + if (this.serverConfig.getTrustSslCredentials() != null) { + builder.setTrustedCertificates(this.serverConfig.getTrustSslCredentials().getTrustedCertificates()); } else { /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); - log.info("Unable to load X509 files for BootStrapServer"); - this.pskMode = true; } - } catch (KeyStoreException ex) { - log.error("[{}] Unable to load X509 files server", ex.getMessage()); + } else { + /* by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + log.info("Unable to load X509 files for BootStrapServer"); + this.pskMode = true; } } - - private boolean setBuilderX509(LeshanBootstrapServerBuilder builder) { - try { - X509Certificate[] certificateChain = SslContextUtil.asX509Certificates(serverConfig.getKeyStoreValue().getCertificateChain(this.bootstrapConfig.getCertificateAlias())); - X509Certificate serverCertificate = certificateChain[0]; - PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.bootstrapConfig.getCertificateAlias(), serverConfig.getCertificatePassword() == null ? null : serverConfig.getCertificatePassword().toCharArray()); - PublicKey publicKey = serverCertificate.getPublicKey(); - if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) { - builder.setPublicKey(serverCertificate.getPublicKey()); - builder.setPrivateKey(privateKey); - builder.setCertificateChain(certificateChain); - return true; - } else { - return false; - } - } catch (Exception ex) { - log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); - return false; - } - } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java index a525c5f157..b8a4f2c976 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.config; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; + public interface LwM2MSecureServerConfig { Integer getId(); @@ -27,8 +29,6 @@ public interface LwM2MSecureServerConfig { Integer getSecurePort(); - String getCertificateAlias(); - - String getCertificatePassword(); + SslCredentials getSslCredentials(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java index 0806ef9e90..749f7352ba 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java @@ -17,9 +17,15 @@ package org.thingsboard.server.transport.lwm2m.config; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; @Slf4j @Component @@ -46,12 +52,18 @@ public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig { @Value("${transport.lwm2m.bootstrap.security.bind_port:}") private Integer securePort; - @Getter - @Value("${transport.lwm2m.bootstrap.security.key_alias:}") - private String certificateAlias; + @Bean + @ConfigurationProperties(prefix = "transport.lwm2m.bootstrap.security.credentials") + public SslCredentialsConfig lwm2mBootstrapCredentials() { + return new SslCredentialsConfig("LWM2M Bootstrap DTLS Credentials", false); + } - @Getter - @Value("${transport.lwm2m.bootstrap.security.key_password:}") - private String certificatePassword; + @Autowired + @Qualifier("lwm2mBootstrapCredentials") + private SslCredentialsConfig credentialsConfig; + @Override + public SslCredentials getSslCredentials() { + return this.credentialsConfig.getCredentials(); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index bde8741f3b..f444b74c2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -18,10 +18,16 @@ package org.thingsboard.server.transport.lwm2m.config; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ResourceUtils; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; import javax.annotation.PostConstruct; import java.io.InputStream; @@ -64,26 +70,6 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @Value("${transport.lwm2m.clean_period_in_sec:}") private int cleanPeriodInSec; - @Getter - @Value("${transport.lwm2m.security.key_store_type:}") - private String keyStoreType; - - @Getter - @Value("${transport.lwm2m.security.key_store:}") - private String keyStoreFilePath; - - @Getter - @Setter - private KeyStore keyStoreValue; - - @Getter - @Value("${transport.lwm2m.security.key_store_password:}") - private String keyStorePassword; - - @Getter - @Value("${transport.lwm2m.security.root_alias:}") - private String rootCertificateAlias; - @Getter @Value("${transport.lwm2m.server.id:}") private Integer id; @@ -104,14 +90,6 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @Value("${transport.lwm2m.server.security.bind_port:}") private Integer securePort; - @Getter - @Value("${transport.lwm2m.server.security.key_alias:}") - private String certificateAlias; - - @Getter - @Value("${transport.lwm2m.server.security.key_password:}") - private String certificatePassword; - @Getter @Value("${transport.lwm2m.log_max_length:}") private int logMaxLength; @@ -124,15 +102,32 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @Value("${transport.lwm2m.paging_transmission_window:10000}") private long pagingTransmissionWindow; - @PostConstruct - public void init() { - try { - InputStream keyStoreInputStream = ResourceUtils.getInputStream(this, keyStoreFilePath); - keyStoreValue = KeyStore.getInstance(keyStoreType); - keyStoreValue.load(keyStoreInputStream, keyStorePassword == null ? null : keyStorePassword.toCharArray()); - } catch (Exception e) { - log.info("Unable to lookup LwM2M keystore. Reason: {}, {}", keyStoreFilePath, e.getMessage()); - } + @Bean + @ConfigurationProperties(prefix = "transport.lwm2m.server.security.credentials") + public SslCredentialsConfig lwm2mServerCredentials() { + return new SslCredentialsConfig("LWM2M Server DTLS Credentials", false); + } + + @Autowired + @Qualifier("lwm2mServerCredentials") + private SslCredentialsConfig credentialsConfig; + + @Bean + @ConfigurationProperties(prefix = "transport.lwm2m.security.trust-credentials") + public SslCredentialsConfig lwm2mTrustCredentials() { + return new SslCredentialsConfig("LWM2M Trust Credentials", true); } + @Autowired + @Qualifier("lwm2mTrustCredentials") + private SslCredentialsConfig trustCredentialsConfig; + + @Override + public SslCredentials getSslCredentials() { + return this.credentialsConfig.getCredentials(); + } + + public SslCredentials getTrustSslCredentials() { + return this.trustCredentialsConfig.getCredentials(); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index fe04410c17..1837093e8c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -87,12 +87,8 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer try { /* by default trust all */ X509Certificate[] trustedCertificates = new X509Certificate[0]; - if (config.getKeyStoreValue() != null) { - X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias()); - if (rootCAX509Cert != null) { - trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - } + if (config.getTrustSslCredentials() != null) { + trustedCertificates = config.getTrustSslCredentials().getTrustedCertificates(); } staticCertificateVerifier = new StaticCertificateVerifier(trustedCertificates); } catch (Exception e) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 5a02f3b194..3efc71b5fe 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -29,6 +29,7 @@ import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; @@ -141,7 +142,11 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { } private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) { - if (config.getKeyStoreValue() != null && this.setBuilderX509(builder)) { + if (this.config.getSslCredentials() != null) { + SslCredentials sslCredentials = this.config.getSslCredentials(); + builder.setPublicKey(sslCredentials.getPublicKey()); + builder.setPrivateKey(sslCredentials.getPrivateKey()); + builder.setCertificateChain(sslCredentials.getCertificateChain()); dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier); builder.setAuthorizer(authorizer); dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES); @@ -153,26 +158,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { } } - private boolean setBuilderX509(LeshanServerBuilder builder) { - try { - X509Certificate[] certificateChain = SslContextUtil.asX509Certificates(config.getKeyStoreValue().getCertificateChain(config.getCertificateAlias())); - X509Certificate serverCertificate = certificateChain[0]; - PrivateKey privateKey = (PrivateKey) config.getKeyStoreValue().getKey(config.getCertificateAlias(), config.getCertificatePassword() == null ? null : config.getCertificatePassword().toCharArray()); - PublicKey publicKey = serverCertificate.getPublicKey(); - if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) { - builder.setPublicKey(serverCertificate.getPublicKey()); - builder.setPrivateKey(privateKey); - builder.setCertificateChain(certificateChain); - return true; - } else { - return false; - } - } catch (Exception ex) { - log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); - return false; - } - } - @Override public String getName() { return DataConstants.LWM2M_TRANSPORT_NAME; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index fad743aec2..215aef408c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -18,16 +18,20 @@ package org.thingsboard.server.transport.mqtt; import io.netty.handler.ssl.SslHandler; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; @@ -38,8 +42,6 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import java.io.InputStream; -import java.security.KeyStore; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -56,18 +58,20 @@ public class MqttSslHandlerProvider { @Value("${transport.mqtt.ssl.protocol}") private String sslProtocol; - @Value("${transport.mqtt.ssl.key_store}") - private String keyStoreFile; - @Value("${transport.mqtt.ssl.key_store_password}") - private String keyStorePassword; - @Value("${transport.mqtt.ssl.key_password}") - private String keyPassword; - @Value("${transport.mqtt.ssl.key_store_type}") - private String keyStoreType; @Autowired private TransportService transportService; + @Bean + @ConfigurationProperties(prefix = "transport.mqtt.ssl.credentials") + public SslCredentialsConfig mqttSslCredentials() { + return new SslCredentialsConfig("MQTT SSL Credentials", false); + } + + @Autowired + @Qualifier("mqttSslCredentials") + private SslCredentialsConfig mqttSslCredentialsConfig; + private SSLContext sslContext; public SslHandler getSslHandler() { @@ -86,19 +90,9 @@ public class MqttSslHandlerProvider { private SSLContext createSslContext() { try { - TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - KeyStore trustStore = KeyStore.getInstance(keyStoreType); - try (InputStream tsFileInputStream = ResourceUtils.getInputStream(this, keyStoreFile)) { - trustStore.load(tsFileInputStream, keyStorePassword.toCharArray()); - } - tmFactory.init(trustStore); - - KeyStore ks = KeyStore.getInstance(keyStoreType); - try (InputStream ksFileInputStream = ResourceUtils.getInputStream(this, keyStoreFile)) { - ks.load(ksFileInputStream, keyStorePassword.toCharArray()); - } - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, keyPassword.toCharArray()); + SslCredentials sslCredentials = this.mqttSslCredentialsConfig.getCredentials(); + TrustManagerFactory tmFactory = sslCredentials.createTrustManagerFactory(); + KeyManagerFactory kmf = sslCredentials.createKeyManagerFactory(); KeyManager[] km = kmf.getKeyManagers(); TrustManager x509wrapped = getX509TrustManager(tmFactory); diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index fad019a1b5..b66676e61c 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -129,6 +129,14 @@ org.eclipse.leshan leshan-server-cf + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java new file mode 100644 index 0000000000..04dafc56fd --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/AbstractSslCredentials.java @@ -0,0 +1,191 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.common.transport.config.ssl; + +import org.thingsboard.server.common.data.StringUtils; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableEntryException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +public abstract class AbstractSslCredentials implements SslCredentials { + + private char[] keyPasswordArray; + + private KeyStore keyStore; + + private PrivateKey privateKey; + + private PublicKey publicKey; + + private X509Certificate[] chain; + + private X509Certificate[] trusts; + + @Override + public void init(boolean trustsOnly) throws IOException, GeneralSecurityException { + String keyPassword = getKeyPassword(); + if (StringUtils.isEmpty(keyPassword)) { + this.keyPasswordArray = new char[0]; + } else { + this.keyPasswordArray = keyPassword.toCharArray(); + } + this.keyStore = this.loadKeyStore(trustsOnly, this.keyPasswordArray); + Set trustedCerts = getTrustedCerts(this.keyStore); + this.trusts = trustedCerts.toArray(new X509Certificate[0]); + if (!trustsOnly) { + PrivateKeyEntry privateKeyEntry = null; + String keyAlias = this.getKeyAlias(); + if (!StringUtils.isEmpty(keyAlias)) { + privateKeyEntry = tryGetPrivateKeyEntry(this.keyStore, keyAlias, this.keyPasswordArray); + } else { + for (Enumeration e = this.keyStore.aliases(); e.hasMoreElements(); ) { + String alias = e.nextElement(); + privateKeyEntry = tryGetPrivateKeyEntry(this.keyStore, alias, this.keyPasswordArray); + if (privateKeyEntry != null) { + break; + } + } + } + if (privateKeyEntry == null) { + throw new IllegalArgumentException("Failed to get private key from the keystore or pem files. " + + "Please check if the private key exists in the keystore or pem files and if the provided private key password is valid."); + } + this.chain = asX509Certificates(privateKeyEntry.getCertificateChain()); + this.privateKey = privateKeyEntry.getPrivateKey(); + if (this.chain.length > 0) { + this.publicKey = this.chain[0].getPublicKey(); + } + } + } + + @Override + public PrivateKey getPrivateKey() { + return this.privateKey; + } + + @Override + public PublicKey getPublicKey() { + return this.publicKey; + } + + @Override + public X509Certificate[] getCertificateChain() { + return this.chain; + } + + @Override + public X509Certificate[] getTrustedCertificates() { + return this.trusts; + } + + @Override + public TrustManagerFactory createTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException { + TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmFactory.init(this.keyStore); + return tmFactory; + } + + @Override + public KeyManagerFactory createKeyManagerFactory() throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(this.keyStore, this.keyPasswordArray); + return kmf; + } + + protected abstract boolean canUse(); + + protected abstract String getKeyPassword(); + + protected abstract String getKeyAlias(); + + protected abstract KeyStore loadKeyStore(boolean isPrivateKeyRequired, char[] keyPasswordArray) throws IOException, GeneralSecurityException; + + private static X509Certificate[] asX509Certificates(Certificate[] certificates) { + if (null == certificates || 0 == certificates.length) { + throw new IllegalArgumentException("certificates missing!"); + } + X509Certificate[] x509Certificates = new X509Certificate[certificates.length]; + for (int index = 0; certificates.length > index; ++index) { + if (null == certificates[index]) { + throw new IllegalArgumentException("[" + index + "] is null!"); + } + try { + x509Certificates[index] = (X509Certificate) certificates[index]; + } catch (ClassCastException e) { + throw new IllegalArgumentException("[" + index + "] is not a x509 certificate! Instead it's a " + + certificates[index].getClass().getName()); + } + } + return x509Certificates; + } + + private static PrivateKeyEntry tryGetPrivateKeyEntry(KeyStore keyStore, String alias, char[] pwd) { + PrivateKeyEntry entry = null; + try { + if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { + try { + entry = (KeyStore.PrivateKeyEntry) keyStore + .getEntry(alias, new KeyStore.PasswordProtection(pwd)); + } catch (UnsupportedOperationException e) { + PrivateKey key = (PrivateKey) keyStore.getKey(alias, pwd); + Certificate[] certs = keyStore.getCertificateChain(alias); + entry = new KeyStore.PrivateKeyEntry(key, certs); + } + } + } catch (KeyStoreException | UnrecoverableEntryException | NoSuchAlgorithmException ignored) {} + return entry; + } + + private static Set getTrustedCerts(KeyStore ks) { + Set set = new HashSet<>(); + try { + for (Enumeration e = ks.aliases(); e.hasMoreElements(); ) { + String alias = e.nextElement(); + if (ks.isCertificateEntry(alias)) { + Certificate cert = ks.getCertificate(alias); + if (cert instanceof X509Certificate) { + set.add((X509Certificate)cert); + } + } else if (ks.isKeyEntry(alias)) { + Certificate[] certs = ks.getCertificateChain(alias); + if ((certs != null) && (certs.length > 0) && + (certs[0] instanceof X509Certificate)) { + set.add((X509Certificate)certs[0]); + } + } + } + } catch (KeyStoreException ignored) {} + return Collections.unmodifiableSet(set); + } + + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/KeystoreSslCredentials.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/KeystoreSslCredentials.java new file mode 100644 index 0000000000..56f4009b0d --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/KeystoreSslCredentials.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.common.transport.config.ssl; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.ResourceUtils; +import org.thingsboard.server.common.data.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +@Data +@EqualsAndHashCode(callSuper = false) +public class KeystoreSslCredentials extends AbstractSslCredentials { + + private String type; + private String storeFile; + private String storePassword; + private String keyPassword; + private String keyAlias; + + @Override + protected boolean canUse() { + return ResourceUtils.resourceExists(this, this.storeFile); + } + + @Override + protected KeyStore loadKeyStore(boolean trustsOnly, char[] keyPasswordArray) throws IOException, GeneralSecurityException { + String keyStoreType = StringUtils.isEmpty(this.type) ? KeyStore.getDefaultType() : this.type; + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + try (InputStream tsFileInputStream = ResourceUtils.getInputStream(this, this.storeFile)) { + keyStore.load(tsFileInputStream, StringUtils.isEmpty(this.storePassword) ? new char[0] : this.storePassword.toCharArray()); + } + return keyStore; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/PemSslCredentials.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/PemSslCredentials.java new file mode 100644 index 0000000000..0575d68149 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/PemSslCredentials.java @@ -0,0 +1,130 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.common.transport.config.ssl; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.thingsboard.server.common.data.ResourceUtils; +import org.thingsboard.server.common.data.StringUtils; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Data +@EqualsAndHashCode(callSuper = false) +public class PemSslCredentials extends AbstractSslCredentials { + + private String certFile; + private String keyFile; + private String keyPassword; + private final String keyAlias = "serveralias"; + + @Override + protected boolean canUse() { + return ResourceUtils.resourceExists(this, this.certFile); + } + + @Override + protected KeyStore loadKeyStore(boolean trustsOnly, char[] keyPasswordArray) throws IOException, GeneralSecurityException { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + List certificates = new ArrayList<>(); + PrivateKey privateKey = null; + JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter(); + JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter(); + try (InputStream inStream = ResourceUtils.getInputStream(this, this.certFile)) { + try (PEMParser pemParser = new PEMParser(new InputStreamReader(inStream))) { + Object object; + while((object = pemParser.readObject()) != null) { + if (object instanceof X509CertificateHolder) { + X509Certificate x509Cert = certConverter.getCertificate((X509CertificateHolder) object); + certificates.add(x509Cert); + } else if (object instanceof PEMEncryptedKeyPair) { + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(keyPasswordArray); + privateKey = keyConverter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)).getPrivate(); + } else if (object instanceof PEMKeyPair) { + privateKey = keyConverter.getKeyPair((PEMKeyPair) object).getPrivate(); + } else if (object instanceof PrivateKeyInfo) { + privateKey = keyConverter.getPrivateKey((PrivateKeyInfo) object); + } + } + } + } + if (privateKey == null && !StringUtils.isEmpty(this.keyFile)) { + if (ResourceUtils.resourceExists(this, this.keyFile)) { + try (InputStream inStream = ResourceUtils.getInputStream(this, this.keyFile)) { + try (PEMParser pemParser = new PEMParser(new InputStreamReader(inStream))) { + Object object; + while ((object = pemParser.readObject()) != null) { + if (object instanceof PEMEncryptedKeyPair) { + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(keyPasswordArray); + privateKey = keyConverter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)).getPrivate(); + break; + } else if (object instanceof PEMKeyPair) { + privateKey = keyConverter.getKeyPair((PEMKeyPair) object).getPrivate(); + break; + } else if (object instanceof PrivateKeyInfo) { + privateKey = keyConverter.getPrivateKey((PrivateKeyInfo) object); + } + } + } + } + } + } + if (certificates.isEmpty()) { + throw new IllegalArgumentException("No certificates found in certFile: " + this.certFile); + } + if (privateKey == null && !trustsOnly) { + throw new IllegalArgumentException("Unable to load private key neither from certFile: " + this.certFile + " nor from keyFile: " + this.keyFile); + } + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); + List unique = certificates.stream().distinct().collect(Collectors.toList()); + for (int i = 0; i < unique.size(); i++) { + keyStore.setCertificateEntry("root-" + i, unique.get(i)); + } + if (privateKey != null) { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + CertPath certPath = factory.generateCertPath(certificates); + List path = certPath.getCertificates(); + Certificate[] x509Certificates = path.toArray(new Certificate[0]); + keyStore.setKeyEntry(this.keyAlias, privateKey, keyPasswordArray, x509Certificates); + } + return keyStore; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java new file mode 100644 index 0000000000..ef412815d7 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentials.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.common.transport.config.ssl; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.X509Certificate; + +public interface SslCredentials { + + void init(boolean trustsOnly) throws IOException, GeneralSecurityException; + + PrivateKey getPrivateKey(); + + PublicKey getPublicKey(); + + X509Certificate[] getCertificateChain(); + + X509Certificate[] getTrustedCertificates(); + + TrustManagerFactory createTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException; + + KeyManagerFactory createKeyManagerFactory() throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException; + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java new file mode 100644 index 0000000000..8b43f36574 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java @@ -0,0 +1,66 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.common.transport.config.ssl; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.PostConstruct; + +@Slf4j +@Data +public class SslCredentialsConfig { + + private boolean enabled = true; + private SslCredentialsType type; + private PemSslCredentials pem; + private KeystoreSslCredentials keystore; + + private SslCredentials credentials; + + private final String name; + private final boolean trustsOnly; + + public SslCredentialsConfig(String name, boolean trustsOnly) { + this.name = name; + this.trustsOnly = trustsOnly; + } + + @PostConstruct + public void init() { + if (this.enabled) { + log.info("{}: Initializing SSL credentials.", name); + if (SslCredentialsType.PEM.equals(type) && pem.canUse()) { + this.credentials = this.pem; + } else if (keystore.canUse()) { + if (SslCredentialsType.PEM.equals(type)) { + log.warn("{}: Specified PEM configuration is not valid. Using SSL keystore configuration as fallback.", name); + } + this.credentials = this.keystore; + } else { + throw new RuntimeException(name + ": Invalid SSL credentials configuration. None of the PEM or KEYSTORE configurations can be used!"); + } + try { + this.credentials.init(this.trustsOnly); + } catch (Exception e) { + throw new RuntimeException(name + ": Failed to init SSL credentials configuration.", e); + } + } else { + log.info("{}: Skipping initialization of disabled SSL credentials.", name); + } + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsType.java new file mode 100644 index 0000000000..64115e57b6 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsType.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.common.transport.config.ssl; + +public enum SslCredentialsType { + PEM, + KEYSTORE +} diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 9aab46b824..adaffed7de 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -98,17 +98,33 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Path to the key store that holds the certificate - key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}" - # Password used to access the key store - key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}" - # Password used to access the key - key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}" - # Key alias - key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}" - # Skip certificate validity check for client certificates. - skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" + # Server DTLS credentials + credentials: + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${COAP_DTLS_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${COAP_DTLS_PEM_CERT:coapserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${COAP_DTLS_PEM_KEY:coapserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${COAP_DTLS_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${COAP_DTLS_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${COAP_DTLS_KEY_STORE:coapserver.jks}" + # Password used to access the key store + store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}" + # Key alias + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}" x509: + # Skip certificate validity check for client certificates. + skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}" dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}" sessions: diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 4280ccb1af..be5bb62a00 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -111,9 +111,33 @@ transport: security: bind_address: "${LWM2M_SECURITY_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_SECURITY_BIND_PORT:5686}" + # Server X509 Certificates support + credentials: + # Whether to enable LWM2M server X509 Certificate/RPK support + enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:false}" + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${LWM2M_SERVER_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${LWM2M_SERVER_PEM_CERT:lwm2mserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${LWM2M_SERVER_PEM_KEY:lwm2mserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${LWM2M_SERVER_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${LWM2M_SERVER_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${LWM2M_SERVER_KEY_STORE:lwm2mserver.jks}" + # Password used to access the key store + store_password: "${LWM2M_SERVER_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_key_password}" + # Key alias + key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}" # Only Certificate_x509: - key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}" - key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}" skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: enable: "${LWM2M_ENABLED_BS:true}" @@ -123,18 +147,51 @@ transport: security: bind_address: "${LWM2M_BS_SECURITY_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_BS_SECURITY_BIND_PORT:5688}" - # Only Certificate_x509: - key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}" - key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}" + # Bootstrap server X509 Certificates support + credentials: + # Whether to enable LWM2M bootstrap server X509 Certificate/RPK support + enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:false}" + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${LWM2M_BS_PEM_CERT:lwm2mserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${LWM2M_BS_PEM_KEY:lwm2mserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${LWM2M_BS_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${LWM2M_BS_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${LWM2M_BS_KEY_STORE:lwm2mserver.jks}" + # Password used to access the key store + store_password: "${LWM2M_BS_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${LWM2M_BS_KEY_PASSWORD:server_key_password}" + # Key alias + key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}" security: - # Certificate_x509: - # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format - # Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh - key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}" - # key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks" - key_store: "${LWM2M_KEYSTORE:lwm2mserver.jks}" - key_store_password: "${LWM2M_KEYSTORE_PASSWORD:server_ks_password}" - root_alias: "${LWM2M_SERVER_ROOT_CA_ALIAS:rootca}" + # X509 trust certificates + trust-credentials: + # Whether to load X509 trust certificates + enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:false}" + # Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore) + type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}" + # PEM certificates + pem: + # Path to the certificates file (holds trust certificates) + cert_file: "${LWM2M_TRUST_PEM_CERT:lwm2mserver.pem}" + # Keystore with trust certificates + keystore: + # Type of the key store + type: "${LWM2M_TRUST_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the X509 certificates + store_file: "${LWM2M_TRUST_KEY_STORE:lwm2mserver.jks}" + # Password used to access the key store + store_password: "${LWM2M_TRUST_KEY_STORE_PASSWORD:server_ks_password}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" timeout: "${LWM2M_TIMEOUT:120000}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index e0d0144d84..75538e6f61 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -106,14 +106,28 @@ transport: bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" - # Path to the key store that holds the SSL certificate - key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" - # Password used to access the key store - key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" - # Password used to access the key - key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" - # Type of the key store - key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" + # Server SSL credentials + credentials: + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + type: "${MQTT_SSL_CREDENTIALS_TYPE:PEM}" + # PEM server credentials + pem: + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) + cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}" + # Path to the server certificate private key file (optional) + key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" + # Server certificate private key password (optional) + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" + # Keystore server credentials + keystore: + # Type of the key store + type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" + # Path to the key store that holds the SSL certificate + store_file: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" + # Password used to access the key store + store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" # Skip certificate validity check for client certificates. skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" sessions: