diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index bd840b2f52..1e54d8d48f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -82,6 +82,9 @@ public class DefaultCacheCleanupService implements CacheCleanupService { log.info("Clearing cache to upgrade from version 3.4.2 to 3.4.3 ..."); clearCacheByName("repositorySettings"); break; + case "3.4.4": + log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0"); + clearCacheByName("deviceProfiles"); default: //Do nothing, since cache cleanup is optional. } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index ba0c2e48ca..ca886cf49b 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -174,7 +174,7 @@ public class DefaultTransportApiService implements TransportApiService { result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasValidateOrCreateX509CertRequestMsg()) { TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateOrCreateX509CertRequestMsg(); - result = validateOrCreateDeviceX509Certificate(msg.getCertificateChain(), DeviceCredentialsType.X509_CERTIFICATE); + result = validateOrCreateDeviceX509Certificate(msg.getCertificateChain()); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -242,19 +242,12 @@ public class DefaultTransportApiService implements TransportApiService { } } - protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain) { List chain = convertX509CertificateChainToList(certChain); - String updateDeviceCertificateValue = chain.get(0); - String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); - String deviceCommonName = ""; - try { - deviceCommonName = SslUtil.parseCommonName(readCertFile(chain.get(0))); - } catch (Exception ignored) { - } for (String certificateValue : chain) { String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); - if (credentials != null && credentials.getCredentialsType() == credentialsType) { + if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return getDeviceInfo(credentials); } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); @@ -262,26 +255,31 @@ public class DefaultTransportApiService implements TransportApiService { X509CertificateChainProvisionConfiguration x509Configuration = new X509CertificateChainProvisionConfiguration(); if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + } else { + log.warn("Device Profile provision configuration is not X509CertificateChainProvisionConfiguration"); } - String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, x509Configuration.getCertificateRegExPattern()); + String deviceName = extractDeviceNameFromCertificateCNByRegEx(chain.get(0), x509Configuration.getCertificateRegExPattern()); if (deviceName == null) { - log.error("Device name cannot be unmatched from CN!"); - return getEmptyTransportApiResponseFuture(); + log.warn("Device name has to be extract by regex from CN."); } Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); + String updateDeviceCertificateValue = chain.get(0); + String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); if (device != null) { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); - if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { - deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + deviceCredentials = updateDeviceCredentials(device.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); } else if (deviceCredentials == null) { - deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); } return getDeviceInfo(deviceCredentials); } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN && x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { Device savedDevice = createDevice(deviceProfile.getTenantId(), deviceProfile.getId(), deviceName, deviceProfile.getName()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); - deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, DeviceCredentialsType.X509_CERTIFICATE); return getDeviceInfo(deviceCredentials); + } else { + log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); } } } @@ -728,12 +726,16 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } - private String extractDeviceNameFromCNByRegEx(String commonName, String regex) { - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(commonName); - if (matcher.find()) { - return matcher.group(0); - } + private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { + try { + String commonName = SslUtil.parseCommonName(readCertFile(x509Value)); + log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(commonName); + if (matcher.find()) { + return matcher.group(0); + } + } catch (Exception ignored) {} return null; } @@ -748,23 +750,26 @@ public class DefaultTransportApiService implements TransportApiService { return chain; } - private X509Certificate readCertFile(String fileContent) throws Exception { + private X509Certificate readCertFile(String fileContent) { X509Certificate certificate = null; - if (fileContent != null && !fileContent.trim().isEmpty()) { - fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replaceAll("\\s", ""); - byte[] decoded = Base64.decodeBase64(fileContent); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - try (InputStream inStream = new ByteArrayInputStream(decoded)) { - certificate = (X509Certificate) certFactory.generateCertificate(inStream); + try { + if (fileContent != null && !fileContent.trim().isEmpty()) { + fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s", ""); + byte[] decoded = Base64.decodeBase64(fileContent); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + try (InputStream inStream = new ByteArrayInputStream(decoded)) { + certificate = (X509Certificate) certFactory.generateCertificate(inStream); + } } - } + } catch (Exception ignored) {} return certificate; } private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue, String certificateHash, DeviceCredentialsType credentialsType) { + log.trace("Updating device credentials [{}] with certificate id [{}]", deviceCredentials, certificateHash); deviceCredentials.setCredentialsId(certificateHash); deviceCredentials.setCredentialsValue(certificateValue); deviceCredentials.setCredentialsType(credentialsType); @@ -773,6 +778,7 @@ public class DefaultTransportApiService implements TransportApiService { private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, String certificateValue, String certificateHash, DeviceCredentialsType credentialsType) { + log.trace("Creating new deviceCredentials for device [{}] with certificate id [{}]", deviceId, certificateHash); DeviceCredentials createDevCredentials = new DeviceCredentials(); createDevCredentials.setDeviceId(deviceId); createDevCredentials.setCredentialsType(credentialsType); @@ -782,11 +788,14 @@ public class DefaultTransportApiService implements TransportApiService { } private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId, String deviceName, String type) { + log.trace("Creating new device for deviceProfile [{}] with device name [{}]", deviceProfileId, deviceName); Device device = new Device(); device.setTenantId(tenantId); device.setDeviceProfileId(deviceProfileId); device.setName(deviceName); device.setType(type); - return deviceService.saveDevice(device); + device = deviceService.saveDevice(device); + tbClusterService.onDeviceUpdated(device, null); + return device; } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d96e50ca7f..c7102f40a0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -735,7 +735,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:false}" + enabled: "${MQTT_SSL_ENABLED:true}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -749,11 +749,11 @@ transport: # 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}" + cert_file: "${MQTT_SSL_PEM_CERT:/home/developer/server.pem}" # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; - key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:/home/developer/server_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index 0309bec131..e98513826a 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -96,33 +96,34 @@ public class DefaultTransportApiServiceTest { private final String deviceCertificate = "-----BEGIN CERTIFICATE-----Device certificate value-----END CERTIFICATE-----"; private final String deviceProfileCertificate = "-----BEGIN CERTIFICATE-----Device profile certificate value-----END CERTIFICATE-----"; + private final String[] chain = new String[]{deviceCertificate, deviceProfileCertificate}; @Test public void validateExistingDeviceX509Certificate() { var device = createDevice(); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); - var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(deviceCertificate, DeviceCredentialsType.X509_CERTIFICATE); + service.validateOrCreateDeviceX509Certificate(chain[0]); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); } @Test public void updateExistingDeviceX509Certificate() { - var deviceProfile = createDeviceProfile(deviceProfileCertificate); + var deviceProfile = createDeviceProfile(chain[1]); when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); var device = createDevice(); when(deviceService.findDeviceByTenantIdAndName(any(), any())).thenReturn(device); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); - var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + service.validateOrCreateDeviceX509Certificate(chain[1]); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); @@ -131,18 +132,18 @@ public class DefaultTransportApiServiceTest { @Test public void createDeviceByX509Provision() { - var deviceProfile = createDeviceProfile(deviceProfileCertificate); + var deviceProfile = createDeviceProfile(chain[1]); when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); var device = createDevice(); when(deviceService.saveDevice(any())).thenReturn(device); when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); - var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + service.validateOrCreateDeviceX509Certificate(chain[1]); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java index 9257053b4f..34fee34ab3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java @@ -24,11 +24,16 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; @NoArgsConstructor public class X509CertificateChainProvisionConfiguration implements DeviceProfileProvisionConfiguration { - private String provisionDeviceSecret; private String certificateValue; private String certificateRegExPattern; private boolean allowCreateNewDevicesByX509Certificate; + @Override + public String getProvisionDeviceSecret() { + // ignore device secret for this strategy + return null; + } + @Override public DeviceProfileProvisionType getType() { return DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN; 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 6025a44cb4..4b6e15292a 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 @@ -164,8 +164,14 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { - throw new CertificateException("Invalid Certificate's chain. Cannot find such device credentials."); + if (chain.length == 1) { + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + throw new CertificateException("Invalid Device Certificate"); + } + } else { + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + throw new CertificateException("Invalid Chain of X509 Certificates"); + } } } catch (Exception e) { log.error(e.getMessage(), e); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index 51ec881490..da04d50f50 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -58,6 +58,7 @@ public class SslUtil { try { x500name = new JcaX509CertificateHolder(certificate).getSubject(); } catch (CertificateEncodingException e) { + log.warn("Cannot parse CN from device certificate"); throw new RuntimeException(e); } RDN cn = x500name.getRDNs(BCStyle.CN)[0]; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index c4ee12395f..3b4be7aef4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -135,12 +135,11 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService