From dccc19a9b6312f16f715d6da40f0d507d6b708c2 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 12 Jan 2023 17:16:17 +0200 Subject: [PATCH 01/45] Improve Device Profile Entity --- .../server/dao/model/ModelConstants.java | 3 + .../dao/model/sql/DeviceProfileEntity.java | 15 ++++ .../main/resources/sql/schema-entities.sql | 71 ++++++++++++++++--- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index e24bde91fb..3736a236e6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -185,6 +185,9 @@ public class ModelConstants { public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_PROFILE_SOFTWARE_ID_PROPERTY = "software_id"; + public static final String DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY = "certificate_hash"; + public static final String DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY = "certificate_value"; + public static final String DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY = "certificate_regex_pattern"; /** * Asset profile constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 893c9d549c..2aaaf61197 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -106,6 +106,15 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) private UUID externalId; + @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY) + private String certificateValue; + + @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY) + private String certificateHash; + + @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY) + private String certificateRegexPattern; + public DeviceProfileEntity() { super(); } @@ -123,6 +132,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.image = deviceProfile.getImage(); this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); + this.certificateHash = deviceProfile.getCertificateHash(); + this.certificateValue = deviceProfile.getCertificateValue(); + this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -182,6 +194,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setDefaultDashboardId(new DashboardId(defaultDashboardId)); } deviceProfile.setProvisionDeviceKey(provisionDeviceKey); + deviceProfile.setCertificateHash(certificateHash); + deviceProfile.setCertificateValue(certificateValue); + deviceProfile.setCertificateRegexPattern(certificateRegexPattern); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 73039274a6..c4c3317009 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -271,24 +271,75 @@ CREATE TABLE IF NOT EXISTS device_profile ( provision_type varchar(255), profile_data jsonb, description varchar, - search_text varchar(255), + search_text varchar +( + 255 +), is_default boolean, tenant_id uuid, firmware_id uuid, software_id uuid, default_rule_chain_id uuid, default_dashboard_id uuid, - default_queue_name varchar(255), + default_queue_name varchar +( + 255 +), provision_device_key varchar, + certificate_value varchar; + certificate_hash varchar, + certificate_regex_pattern varchar +( + 255 +), external_id uuid, - CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), - CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), - CONSTRAINT device_profile_external_id_unq_key UNIQUE (tenant_id, external_id), - CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), - CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id), - CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES ota_package(id), - CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id) -); + CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE +( + certificate_hash +), + CONSTRAINT device_profile_name_unq_key UNIQUE +( + tenant_id, + name +), + CONSTRAINT device_provision_key_unq_key UNIQUE +( + provision_device_key +), + CONSTRAINT device_profile_external_id_unq_key UNIQUE +( + tenant_id, + external_id +), + CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY +( + default_rule_chain_id +) REFERENCES rule_chain +( + id +), + CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY +( + default_dashboard_id +) REFERENCES dashboard +( + id +), + CONSTRAINT fk_firmware_device_profile FOREIGN KEY +( + firmware_id +) REFERENCES ota_package +( + id +), + CONSTRAINT fk_software_device_profile FOREIGN KEY +( + software_id +) REFERENCES ota_package +( + id +) + ); DO $$ From 5d35fa1f261dfa2e421a786a184b87b04776f461 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 12 Jan 2023 17:18:29 +0200 Subject: [PATCH 02/45] Add ability to upgrade device credentials if chain match with uploaded device profile certificate. --- .../transport/DefaultTransportApiService.java | 62 +++++++++- common/cluster-api/src/main/proto/queue.proto | 65 ++++++---- .../dao/device/DeviceCredentialsService.java | 4 +- .../dao/device/DeviceProfileService.java | 2 + .../server/common/data/DeviceProfile.java | 9 +- .../mqtt/MqttSslHandlerProvider.java | 60 ++++++++- .../transport/mqtt/MqttTransportHandler.java | 115 +++++++++++++----- .../common/transport/TransportService.java | 9 ++ ...idateDeviceProfileCredentialsResponse.java | 28 +++++ .../service/DefaultTransportService.java | 29 ++++- .../server/common/transport/util/SslUtil.java | 17 +++ .../dao/device/DeviceCredentialsDao.java | 8 ++ .../device/DeviceCredentialsServiceImpl.java | 10 +- .../dao/device/DeviceProfileCacheKey.java | 14 ++- .../server/dao/device/DeviceProfileDao.java | 2 + .../dao/device/DeviceProfileServiceImpl.java | 10 ++ .../device/DeviceCredentialsRepository.java | 5 + .../sql/device/DeviceProfileRepository.java | 2 + .../sql/device/JpaDeviceCredentialsDao.java | 5 + .../dao/sql/device/JpaDeviceProfileDao.java | 5 + 20 files changed, 388 insertions(+), 73 deletions(-) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java 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 f8b42ecc1e..9bc61d6493 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 @@ -65,8 +65,8 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; @@ -90,11 +90,15 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRespon import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -108,6 +112,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH; @@ -128,6 +134,7 @@ public class DefaultTransportApiService implements TransportApiService { private final TbTenantProfileCache tenantProfileCache; private final TbApiUsageStateService apiUsageStateService; private final DeviceService deviceService; + private final DeviceProfileService deviceProfileService; private final RelationService relationService; private final DeviceCredentialsService deviceCredentialsService; private final DbCallbackExecutorService dbCallbackExecutorService; @@ -159,6 +166,13 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); + } else if (transportApiRequestMsg.hasValidateProfileX509CertRequestMsg()) { + ValidateDeviceProfileX509CertRequestMsg msg = transportApiRequestMsg.getValidateProfileX509CertRequestMsg(); + result = validateDeviceProfileCertificate(msg.getHash()); + } else if (transportApiRequestMsg.hasUpdateOrCreateDeviceCertRequestMsg()) { + UpdateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getUpdateOrCreateDeviceCertRequestMsg(); + DeviceProfile deviceProfile = deviceProfileCache.find(new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB()))); + result = updateOrCreateDeviceCredentials(msg.getHash(), msg.getValue(), msg.getCommonName(), deviceProfile, DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -226,6 +240,31 @@ public class DefaultTransportApiService implements TransportApiService { } } + private ListenableFuture validateDeviceProfileCertificate(String credentialsId) { + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(credentialsId); + if (deviceProfile != null) { + return getDeviceProfileInfo(deviceProfile); + } + return getEmptyTransportApiResponseFuture(); + } + + private ListenableFuture updateOrCreateDeviceCredentials(String credentialsId, + String credentialsValue, + String deviceCN, + DeviceProfile deviceProfile, + DeviceCredentialsType credentialsType) { + String deviceName = extractRegex(deviceCN, deviceProfile.getCertificateRegexPattern()); + // find deviceCredentials by deviceName (device exists) + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByTenantIdAndDeviceName(deviceProfile.getTenantId(), deviceName); + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { + deviceCredentials.setCredentialsId(credentialsId); + deviceCredentials.setCredentialsValue(credentialsValue); + deviceCredentialsService.updateDeviceCredentials(deviceProfile.getTenantId(), deviceCredentials); + return getDeviceInfo(deviceCredentials); + } + return getEmptyTransportApiResponseFuture(); + } + private ListenableFuture validateUserNameCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); if (credentials != null) { @@ -507,6 +546,18 @@ public class DefaultTransportApiService implements TransportApiService { }, MoreExecutors.directExecutor()); } + private ListenableFuture getDeviceProfileInfo(DeviceProfile deviceProfile) { + ValidateDeviceProfileCredentialsResponseMsg.Builder builder = ValidateDeviceProfileCredentialsResponseMsg.newBuilder() + .setDeviceProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) + .setIsDeviceProfileFound(true); + + return Futures.immediateFuture( + TransportApiResponseMsg.newBuilder() + .setValidateDeviceProfileResponseMsg(builder.build()) + .build()); + } + private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder() .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) @@ -664,4 +715,13 @@ public class DefaultTransportApiService implements TransportApiService { private Long checkLong(Long l) { return l != null ? l : 0; } + + private String extractRegex(String commonName, String regex) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(commonName); + if (matcher.find()) { + return matcher.group(0); + } + return commonName; + } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 5a28d42546..405b96726a 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -169,6 +169,18 @@ message ValidateDeviceX509CertRequestMsg { string hash = 1; } +message ValidateDeviceProfileX509CertRequestMsg { + string hash = 1; +} + +message UpdateOrCreateDeviceX509CertRequestMsg { + string hash = 1; + string value = 2; + string commonName = 3; + int64 deviceProfileIdMSB = 4; + int64 deviceProfileIdLSB = 5; +} + message ValidateBasicMqttCredRequestMsg { string clientId = 1; string userName = 2; @@ -181,6 +193,12 @@ message ValidateDeviceCredentialsResponseMsg { bytes profileBody = 3; } +message ValidateDeviceProfileCredentialsResponseMsg { + int64 deviceProfileIdMSB = 1; + int64 deviceProfileIdLSB = 2; + bool isDeviceProfileFound = 3; +} + message GetOrCreateDeviceFromGatewayRequestMsg { int64 gatewayIdMSB = 1; int64 gatewayIdLSB = 2; @@ -885,33 +903,36 @@ message VersionControlResponseMsg { message TransportApiRequestMsg { ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; - GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; - GetEntityProfileRequestMsg entityProfileRequestMsg = 4; - LwM2MRequestMsg lwM2MRequestMsg = 5; - ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; - ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; - ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; - GetResourceRequestMsg resourceRequestMsg = 9; - GetOtaPackageRequestMsg otaPackageRequestMsg = 10; - GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11; - GetDeviceRequestMsg deviceRequestMsg = 12; - GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; - GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14; + ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 3; + GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 4; + GetEntityProfileRequestMsg entityProfileRequestMsg = 5; + LwM2MRequestMsg lwM2MRequestMsg = 6; + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 7; + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 8; + ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 9; + GetResourceRequestMsg resourceRequestMsg = 10; + GetOtaPackageRequestMsg otaPackageRequestMsg = 11; + GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 12; + GetDeviceRequestMsg deviceRequestMsg = 13; + GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 14; + GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 15; + UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 16; } /* Response from ThingsBoard Core Service to Transport Service */ message TransportApiResponseMsg { ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; - GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; - GetEntityProfileResponseMsg entityProfileResponseMsg = 3; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; - GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5; - LwM2MResponseMsg lwM2MResponseMsg = 6; - GetResourceResponseMsg resourceResponseMsg = 7; - GetOtaPackageResponseMsg otaPackageResponseMsg = 8; - GetDeviceResponseMsg deviceResponseMsg = 9; - GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; - repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11; + ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 2; + GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 3; + GetEntityProfileResponseMsg entityProfileResponseMsg = 4; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 5; + GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 6; + LwM2MResponseMsg lwM2MResponseMsg = 7; + GetResourceResponseMsg resourceResponseMsg = 8; + GetOtaPackageResponseMsg otaPackageResponseMsg = 9; + GetDeviceResponseMsg deviceResponseMsg = 10; + GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 11; + repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 12; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java index 42d6d10a1a..150106e3cb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java @@ -15,10 +15,10 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; -import com.fasterxml.jackson.databind.JsonNode; public interface DeviceCredentialsService { @@ -26,6 +26,8 @@ public interface DeviceCredentialsService { DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId); + DeviceCredentials findDeviceCredentialsByTenantIdAndDeviceName(TenantId tenantId, String deviceName); + DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java index 2e65e159cd..6ead3e08c5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java @@ -39,6 +39,8 @@ public interface DeviceProfileService extends EntityDaoService { PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); + DeviceProfile findDeviceProfileByCertificateHash(String credentialsId); + DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName); DeviceProfile createDefaultDeviceProfile(TenantId tenantId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index a66f3a40d9..2a5b11f1ff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -16,7 +16,6 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -28,7 +27,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; @@ -68,6 +66,13 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; + @ApiModelProperty(position = 16, value = "CA certificate value") + private String certificateValue; + @ApiModelProperty(position = 17, value = "CA certificate hash") + private String certificateHash; + @ApiModelProperty(position = 18, value = "Regex for fetch deviceName from CN") + private String certificateRegexPattern; + @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the root rule chain will be used to process those messages.") 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 98aaca7f06..ed2326946f 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 @@ -24,12 +24,13 @@ 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.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; 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.auth.ValidateDeviceProfileCredentialsResponse; 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; @@ -142,8 +143,15 @@ public class MqttSslHandlerProvider { } @Override - public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + String deviceCN = SslUtil.parseCommonName(chain[0]); + String deviceCert; + String deviceCredentialsValue = SslUtil.getCertificateString(chain[0]); + try { + deviceCert = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); + } catch (CertificateEncodingException e) { + throw new RuntimeException(e); + } String credentialsBody = null; for (X509Certificate cert : chain) { try { @@ -152,13 +160,53 @@ public class MqttSslHandlerProvider { final String[] credentialsBodyHolder = new String[1]; CountDownLatch latch = new CountDownLatch(1); transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback() { + new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { if (!StringUtils.isEmpty(msg.getCredentials())) { credentialsBodyHolder[0] = msg.getCredentials(); + latch.countDown(); + } else { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { + if (msg.isDeviceProfileFound()) { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() + .setHash(deviceCert) + .setCommonName(deviceCN) + .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) + .build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + System.out.println("msg.getCredentials() = " + msg.getCredentials()); + credentialsBodyHolder[0] = msg.getCredentials(); + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + } + ); + } else { + latch.countDown(); + } + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); } - latch.countDown(); } @Override @@ -168,7 +216,7 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (strCert.equals(credentialsBodyHolder[0])) { + if (deviceCredentialsValue.equals(credentialsBodyHolder[0])) { credentialsBody = credentialsBodyHolder[0]; break; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 7515a1fcd2..5044d5a35e 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -21,7 +21,6 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.mqtt.MqttConnAckMessage; -import io.netty.handler.codec.mqtt.MqttConnAckVariableHeader; import io.netty.handler.codec.mqtt.MqttConnectMessage; import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import io.netty.handler.codec.mqtt.MqttFixedHeader; @@ -63,12 +62,12 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; @@ -80,7 +79,7 @@ import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; import java.net.InetSocketAddress; -import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -90,16 +89,15 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.amazonaws.util.StringUtils.UTF8; -import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT; import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; -import static io.netty.handler.codec.mqtt.MqttMessageType.UNSUBACK; import static io.netty.handler.codec.mqtt.MqttQoS.AT_LEAST_ONCE; import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; @@ -810,9 +808,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement deviceSessionCtx.setProvisionOnly(true); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg)); } else { - X509Certificate cert; - if (sslHandler != null && (cert = getX509Certificate()) != null) { - processX509CertConnect(ctx, cert, msg); + X509Certificate[] chain; + if (sslHandler != null && (chain = getX509Certificate()) != null) { + processX509CertConnect(ctx, chain, msg); } else { processAuthTokenConnect(ctx, msg); } @@ -848,27 +846,82 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement }); } - private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) { + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) { try { - if (!context.isSkipValidityCheckForClientCert()) { - cert.checkValidity(); - } - String strCert = SslUtil.getCertificateString(cert); - String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - onValidateDeviceResponse(msg, ctx, connectMessage); - } + String deviceCN = SslUtil.parseCommonName(chain[0]); + String deviceCertHash = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); + for (X509Certificate cert : chain) { + try { + String strCert = SslUtil.getCertificateString(cert); + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); + final ValidateDeviceCredentialsResponse[] validateDeviceCredentialsResponses = new ValidateDeviceCredentialsResponse[1]; + CountDownLatch latch = new CountDownLatch(1); + transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + validateDeviceCredentialsResponses[0] = msg; + latch.countDown(); + } else { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { + if (msg.isDeviceProfileFound()) { + transportService.process(DeviceTransportType.MQTT, + TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() + .setHash(deviceCertHash) + .setCommonName(deviceCN) + .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) + .build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + validateDeviceCredentialsResponses[0] = msg; + latch.countDown(); + } + } - @Override - public void onError(Throwable e) { - log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e); - ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); - ctx.close(); - } - }); + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + } + ); + } else { + latch.countDown(); + } + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + } + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + if (validateDeviceCredentialsResponses[0] != null && validateDeviceCredentialsResponses[0].hasDeviceInfo()) { + onValidateDeviceResponse(validateDeviceCredentialsResponses[0], ctx, connectMessage); + break; + } + } catch (InterruptedException | CertificateEncodingException e) { + log.error(e.getMessage(), e); + } + } } catch (Exception e) { context.onAuthFailure(address); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage)); @@ -877,17 +930,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private X509Certificate getX509Certificate() { + private X509Certificate[] getX509Certificate() { try { - Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); - if (certChain.length > 0) { - return (X509Certificate) certChain[0]; - } + return (X509Certificate[]) sslHandler.engine().getSession().getPeerCertificates(); } catch (SSLPeerUnverifiedException e) { log.warn(e.getMessage()); return null; } - return null; } private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index f1d3f7e26c..21e668cf37 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -52,8 +53,10 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; @@ -87,6 +90,12 @@ public interface TransportService { void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback); + void process(DeviceTransportType transportType, ValidateDeviceProfileX509CertRequestMsg msg, + TransportServiceCallback callback); + + void process(DeviceTransportType transportType, UpdateOrCreateDeviceX509CertRequestMsg msg, + TransportServiceCallback callback); + void process(ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java new file mode 100644 index 0000000000..cfbe79bf96 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 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.auth; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceProfileId; + +@Data +@Builder +public class ValidateDeviceProfileCredentialsResponse { + + private final DeviceProfileId deviceProfileId; + private final boolean isDeviceProfileFound; +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index bf03122b18..a41c1d6055 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -71,8 +71,8 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -97,6 +97,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.AfterStartUp; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -427,6 +428,32 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } + @Override + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceProfileX509CertRequestMsg requestMsg, + TransportServiceCallback callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), + TransportApiRequestMsg.newBuilder().setValidateProfileX509CertRequestMsg(requestMsg).build()); + ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { + TransportProtos.ValidateDeviceProfileCredentialsResponseMsg msg = tmp.getValue().getValidateDeviceProfileResponseMsg(); + ValidateDeviceProfileCredentialsResponse.ValidateDeviceProfileCredentialsResponseBuilder result = ValidateDeviceProfileCredentialsResponse.builder(); + DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB())); + result.deviceProfileId(deviceProfileId); + result.isDeviceProfileFound(msg.getIsDeviceProfileFound()); + return result.build(); + }, MoreExecutors.directExecutor()); + AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); + } + + @Override + public void process(DeviceTransportType transportType, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg requestMsg, + TransportServiceCallback callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder() + .setUpdateOrCreateDeviceCertRequestMsg(requestMsg).build()); + doProcess(transportType, protoMsg, callback); + } + @Override public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); 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 45a09a9899..51ed2cd948 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 @@ -16,11 +16,17 @@ package org.thingsboard.server.common.transport.util; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.springframework.util.Base64Utils; import org.thingsboard.server.common.msg.EncryptionUtil; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; /** * @author Valerii Sosliuk @@ -35,4 +41,15 @@ public class SslUtil { throws CertificateEncodingException { return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); } + + public static String parseCommonName(X509Certificate certificate) { + X500Name x500name; + try { + x500name = new JcaX509CertificateHolder(certificate).getSubject(); + } catch (CertificateEncodingException e) { + throw new RuntimeException(e); + } + RDN cn = x500name.getRDNs(BCStyle.CN)[0]; + return IETFUtils.valueToString(cn.getFirst().getValue()); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java index 10363ac5d7..b6758af59c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java @@ -53,4 +53,12 @@ public interface DeviceCredentialsDao extends Dao { */ DeviceCredentials findByCredentialsId(TenantId tenantId, String credentialsId); + /** + * Find device credentials by device name. + * + * @param deviceName the device name + * @return the device credentials object + */ + DeviceCredentials findByTenantIdAndDeviceName(TenantId tenantId, String deviceName); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index c4cbabfb3d..c21031fe65 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -16,15 +16,12 @@ package org.thingsboard.server.dao.device; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.SecurityUtil; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; @@ -86,6 +83,13 @@ public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService, ExportableEntityDa DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findByName(TenantId tenantId, String profileName); + + DeviceProfile findByCertificateHash(String certificateHash); } 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 9dad6a22d9..41a51176f4 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 @@ -53,6 +53,7 @@ import java.util.Map; import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateString; @Service("DeviceProfileDaoService") @Slf4j @@ -61,6 +62,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByCertificateHash(certificateHash), true); + } + @Override public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String name) { log.trace("Executing findOrCreateDefaultDeviceProfile"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java index 0dd35a39b8..7642b45d5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.sql.device; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.DeviceCredentialsEntity; import java.util.UUID; @@ -28,4 +30,7 @@ public interface DeviceCredentialsRepository extends JpaRepository Date: Fri, 13 Jan 2023 17:27:08 +0200 Subject: [PATCH 03/45] Fix sql formatting and incorrect proto-message updating --- common/cluster-api/src/main/proto/queue.proto | 50 ++++++------ .../mqtt/MqttSslHandlerProvider.java | 15 ++-- .../transport/mqtt/MqttTransportHandler.java | 6 +- .../sql/device/JpaDeviceCredentialsDao.java | 2 +- .../main/resources/sql/schema-entities.sql | 76 +++++-------------- 5 files changed, 52 insertions(+), 97 deletions(-) diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 405b96726a..ace1f3cf6d 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -903,36 +903,36 @@ message VersionControlResponseMsg { message TransportApiRequestMsg { ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; - ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 3; - GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 4; - GetEntityProfileRequestMsg entityProfileRequestMsg = 5; - LwM2MRequestMsg lwM2MRequestMsg = 6; - ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 7; - ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 8; - ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 9; - GetResourceRequestMsg resourceRequestMsg = 10; - GetOtaPackageRequestMsg otaPackageRequestMsg = 11; - GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 12; - GetDeviceRequestMsg deviceRequestMsg = 13; - GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 14; - GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 15; - UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 16; + GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; + GetEntityProfileRequestMsg entityProfileRequestMsg = 4; + LwM2MRequestMsg lwM2MRequestMsg = 5; + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; + ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; + GetResourceRequestMsg resourceRequestMsg = 9; + GetOtaPackageRequestMsg otaPackageRequestMsg = 10; + GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11; + GetDeviceRequestMsg deviceRequestMsg = 12; + GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; + GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14; + UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 15; + ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 16; } /* Response from ThingsBoard Core Service to Transport Service */ message TransportApiResponseMsg { ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; - ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 2; - GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 3; - GetEntityProfileResponseMsg entityProfileResponseMsg = 4; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 5; - GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 6; - LwM2MResponseMsg lwM2MResponseMsg = 7; - GetResourceResponseMsg resourceResponseMsg = 8; - GetOtaPackageResponseMsg otaPackageResponseMsg = 9; - GetDeviceResponseMsg deviceResponseMsg = 10; - GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 11; - repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 12; + GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; + GetEntityProfileResponseMsg entityProfileResponseMsg = 3; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; + GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5; + LwM2MResponseMsg lwM2MResponseMsg = 6; + GetResourceResponseMsg resourceResponseMsg = 7; + GetOtaPackageResponseMsg otaPackageResponseMsg = 8; + GetDeviceResponseMsg deviceResponseMsg = 9; + GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; + repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11; + ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 12; } /* Messages that are handled by ThingsBoard Core Service */ 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 ed2326946f..237ff86375 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 @@ -145,13 +145,8 @@ public class MqttSslHandlerProvider { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { String deviceCN = SslUtil.parseCommonName(chain[0]); - String deviceCert; - String deviceCredentialsValue = SslUtil.getCertificateString(chain[0]); - try { - deviceCert = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); - } catch (CertificateEncodingException e) { - throw new RuntimeException(e); - } + String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); + String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); String credentialsBody = null; for (X509Certificate cert : chain) { try { @@ -175,7 +170,8 @@ public class MqttSslHandlerProvider { if (msg.isDeviceProfileFound()) { transportService.process(DeviceTransportType.MQTT, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(deviceCert) + .setHash(clientDeviceCertHash) + .setValue(clientDeviceCertValue) .setCommonName(deviceCN) .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) @@ -183,7 +179,6 @@ public class MqttSslHandlerProvider { new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { - System.out.println("msg.getCredentials() = " + msg.getCredentials()); credentialsBodyHolder[0] = msg.getCredentials(); latch.countDown(); } @@ -216,7 +211,7 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (deviceCredentialsValue.equals(credentialsBodyHolder[0])) { + if (clientDeviceCertValue.equals(credentialsBodyHolder[0])) { credentialsBody = credentialsBodyHolder[0]; break; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 5044d5a35e..803a8ff3ca 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -849,7 +849,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) { try { String deviceCN = SslUtil.parseCommonName(chain[0]); - String deviceCertHash = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0])); + String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); + String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); for (X509Certificate cert : chain) { try { String strCert = SslUtil.getCertificateString(cert); @@ -872,7 +873,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (msg.isDeviceProfileFound()) { transportService.process(DeviceTransportType.MQTT, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(deviceCertHash) + .setHash(clientDeviceCertHash) + .setValue(clientDeviceCertValue) .setCommonName(deviceCN) .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java index a56a13c98a..01a746c9b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java @@ -69,6 +69,6 @@ public class JpaDeviceCredentialsDao extends JpaAbstractDao Date: Thu, 19 Jan 2023 15:18:15 +0200 Subject: [PATCH 04/45] Add device profile upgrade --- .../src/main/data/upgrade/3.4.3/schema_update.sql | 12 ++++++++++++ dao/src/main/resources/sql/schema-entities.sql | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index e6fd32bc2d..87e9e98aef 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -24,3 +24,15 @@ CREATE TABLE IF NOT EXISTS alarm_comment ( CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE ) PARTITION BY RANGE (created_time); CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id); + + + +-- DEVICE PROFILE CERTIFICATE START + +ALTER TABLE device_profile + ADD COLUMN IF NOT EXISTS certificate_hash varchar, + ADD COLUMN IF NOT EXISTS certificate_value varchar, + ADD COLUMN IF NOT EXISTS certificate_regex_pattern varchar(255), + DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, + ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); +-- DEVICE PROFILE CERTIFICATE END diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 5d2921370a..6a2996bff7 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -292,7 +292,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( default_dashboard_id uuid, default_queue_name varchar(255), provision_device_key varchar, - certificate_value varchar; + certificate_value varchar, certificate_hash varchar, certificate_regex_pattern varchar(255), default_edge_rule_chain_id uuid, From 18c1440a488dcc6594d2a5dbb5457ec0e37e32de Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 19 Jan 2023 15:21:48 +0200 Subject: [PATCH 05/45] Add validation on device profile create or update --- .../server/common/data/DeviceProfile.java | 2 +- .../mqtt/MqttSslHandlerProvider.java | 4 +- .../transport/mqtt/MqttTransportHandler.java | 111 +++++------------- .../dao/device/DeviceProfileServiceImpl.java | 24 +++- .../validator/DeviceProfileDataValidator.java | 54 +++++++++ 5 files changed, 107 insertions(+), 88 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index d0efc8cb5b..54989e1aab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -70,7 +70,7 @@ public class DeviceProfile extends SearchTextBased implements H private String certificateValue; @ApiModelProperty(position = 17, value = "CA certificate hash") private String certificateHash; - @ApiModelProperty(position = 18, value = "Regex for fetch deviceName from CN") + @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN") private String certificateRegexPattern; @ApiModelProperty(position = 7, value = "Reference to the rule 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 237ff86375..2b4a24e116 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 @@ -124,7 +124,7 @@ public class MqttSslHandlerProvider { static class ThingsboardMqttX509TrustManager implements X509TrustManager { private final X509TrustManager trustManager; - private TransportService transportService; + private final TransportService transportService; ThingsboardMqttX509TrustManager(X509TrustManager trustManager, TransportService transportService) { this.trustManager = trustManager; @@ -220,7 +220,7 @@ public class MqttSslHandlerProvider { } } if (credentialsBody == null) { - throw new CertificateException("Invalid Device Certificate"); + throw new CertificateException("Invalid Certificate's chain"); } } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 803a8ff3ca..9159759c28 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -62,7 +62,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.util.SslUtil; @@ -79,7 +78,7 @@ import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; import java.net.InetSocketAddress; -import java.security.cert.CertificateEncodingException; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -89,7 +88,6 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -808,9 +806,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement deviceSessionCtx.setProvisionOnly(true); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg)); } else { - X509Certificate[] chain; - if (sslHandler != null && (chain = getX509Certificate()) != null) { - processX509CertConnect(ctx, chain, msg); + X509Certificate cert; + if (sslHandler != null && (cert = getX509Certificate()) != null) { + processX509CertConnect(ctx, cert, msg); } else { processAuthTokenConnect(ctx, msg); } @@ -846,84 +844,25 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement }); } - private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) { + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) { try { - String deviceCN = SslUtil.parseCommonName(chain[0]); - String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); - String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); - for (X509Certificate cert : chain) { - try { - String strCert = SslUtil.getCertificateString(cert); - String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - final ValidateDeviceCredentialsResponse[] validateDeviceCredentialsResponses = new ValidateDeviceCredentialsResponse[1]; - CountDownLatch latch = new CountDownLatch(1); - transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (!StringUtils.isEmpty(msg.getCredentials())) { - validateDeviceCredentialsResponses[0] = msg; - latch.countDown(); - } else { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { - if (msg.isDeviceProfileFound()) { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(clientDeviceCertHash) - .setValue(clientDeviceCertValue) - .setCommonName(deviceCN) - .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) - .build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (!StringUtils.isEmpty(msg.getCredentials())) { - validateDeviceCredentialsResponses[0] = msg; - latch.countDown(); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - } - ); - } else { - latch.countDown(); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - latch.await(10, TimeUnit.SECONDS); - if (validateDeviceCredentialsResponses[0] != null && validateDeviceCredentialsResponses[0].hasDeviceInfo()) { - onValidateDeviceResponse(validateDeviceCredentialsResponses[0], ctx, connectMessage); - break; - } - } catch (InterruptedException | CertificateEncodingException e) { - log.error(e.getMessage(), e); - } + if (!context.isSkipValidityCheckForClientCert()) { + cert.checkValidity(); } + String strCert = SslUtil.getCertificateString(cert); + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); + transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + onValidateDeviceResponse(msg, ctx, connectMessage); + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + } + }); } catch (Exception e) { context.onAuthFailure(address); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage)); @@ -932,13 +871,17 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private X509Certificate[] getX509Certificate() { + private X509Certificate getX509Certificate() { try { - return (X509Certificate[]) sslHandler.engine().getSession().getPeerCertificates(); + Certificate[] certChains = sslHandler.engine().getSession().getPeerCertificates(); + if (certChains.length > 1) { + return (X509Certificate) certChains[0]; + } } catch (SSLPeerUnverifiedException e) { log.warn(e.getMessage()); return null; } + return null; } private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) { 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 41a51176f4..8cd08739b3 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 @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.queue.QueueService; @@ -51,6 +52,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -62,7 +65,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService rootCa = new HashSet<>(); + try { + String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar); + FileInputStream is = new FileInputStream(filename); + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + String password = "changeit"; + keystore.load(is, password.toCharArray()); + + PKIXParameters params = new PKIXParameters(keystore); + for (TrustAnchor ta : params.getTrustAnchors()) { + X509Certificate cert = ta.getTrustedCert(); + rootCa.add(EncryptionUtil.getSha3Hash(getCertificateString(cert))); + } + } catch (CertificateException | KeyStoreException | NoSuchAlgorithmException | + InvalidAlgorithmParameterException | IOException ignored) { + } + return rootCa.contains(deviceProfileHash); + } + + private String getCertificateString(Certificate cert) throws CertificateEncodingException { + return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); + } } From b4a9ba26e7b77f09bd367a54f34bdaef2bcc7676 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 20 Jan 2023 19:00:19 +0200 Subject: [PATCH 06/45] Add new field: update database and upgrade script --- application/src/main/data/upgrade/3.4.3/schema_update.sql | 2 ++ dao/src/main/resources/sql/schema-entities.sql | 1 + 2 files changed, 3 insertions(+) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 87e9e98aef..78203951ec 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -33,6 +33,8 @@ ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS certificate_hash varchar, ADD COLUMN IF NOT EXISTS certificate_value varchar, ADD COLUMN IF NOT EXISTS certificate_regex_pattern varchar(255), + ADD COLUMN IF NOT EXISTS allow_create_device_by_x509 boolean, DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); + -- DEVICE PROFILE CERTIFICATE END diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 6a2996bff7..eed6f237b6 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -297,6 +297,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( certificate_regex_pattern varchar(255), default_edge_rule_chain_id uuid, external_id uuid, + allow_create_device_by_x509 boolean, CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash), CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), From 7957da4a9e2ab7922074848018e8dcde8e2bab23 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 20 Jan 2023 19:02:14 +0200 Subject: [PATCH 07/45] Updated: send full chain to kafka with single message and do whole logic on core --- .../transport/DefaultTransportApiService.java | 148 +++++++++++++----- common/cluster-api/src/main/proto/queue.proto | 22 +-- .../dao/device/DeviceCredentialsService.java | 2 - .../server/common/data/DeviceProfile.java | 13 +- .../data/DeviceProfileProvisionType.java | 3 +- ...509CertificateProvisionConfiguration.java} | 18 ++- .../DeviceProfileProvisionConfiguration.java | 3 +- .../server/common/msg/EncryptionUtil.java | 5 + .../mqtt/MqttSslHandlerProvider.java | 102 ++++-------- .../transport/mqtt/MqttTransportHandler.java | 13 +- .../common/transport/TransportService.java | 11 +- .../service/DefaultTransportService.java | 30 +--- .../server/common/transport/util/SslUtil.java | 12 ++ .../dao/device/DeviceCredentialsDao.java | 8 - .../device/DeviceCredentialsServiceImpl.java | 7 - .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/DeviceProfileEntity.java | 5 + .../validator/DeviceProfileDataValidator.java | 20 +-- .../device/DeviceCredentialsRepository.java | 5 - .../sql/device/JpaDeviceCredentialsDao.java | 5 - 20 files changed, 210 insertions(+), 223 deletions(-) rename common/{transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java => data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java} (58%) 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 9bc61d6493..0bf0ff8035 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 @@ -25,6 +25,7 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.ota.OtaPackageDataCache; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; @@ -65,6 +67,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; @@ -90,11 +93,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRespon import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileCredentialsResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -105,6 +105,13 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -166,13 +173,9 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); - } else if (transportApiRequestMsg.hasValidateProfileX509CertRequestMsg()) { - ValidateDeviceProfileX509CertRequestMsg msg = transportApiRequestMsg.getValidateProfileX509CertRequestMsg(); - result = validateDeviceProfileCertificate(msg.getHash()); - } else if (transportApiRequestMsg.hasUpdateOrCreateDeviceCertRequestMsg()) { - UpdateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getUpdateOrCreateDeviceCertRequestMsg(); - DeviceProfile deviceProfile = deviceProfileCache.find(new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB()))); - result = updateOrCreateDeviceCredentials(msg.getHash(), msg.getValue(), msg.getCommonName(), deviceProfile, DeviceCredentialsType.X509_CERTIFICATE); + } else if (transportApiRequestMsg.hasValidateOrCreateX509CertRequestMsg()) { + TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateOrCreateX509CertRequestMsg(); + result = validateOrCreateDeviceX509Certificate(msg.getCertificate(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -240,27 +243,43 @@ public class DefaultTransportApiService implements TransportApiService { } } - private ListenableFuture validateDeviceProfileCertificate(String credentialsId) { - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(credentialsId); - if (deviceProfile != null) { - return getDeviceProfileInfo(deviceProfile); - } - return getEmptyTransportApiResponseFuture(); - } + private ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + try { + List chain = getX509CertificateChainFromString(certChain); + String updateDeviceCertificateValue = SslUtil.getCertificateString(chain.get(0)); + String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); - private ListenableFuture updateOrCreateDeviceCredentials(String credentialsId, - String credentialsValue, - String deviceCN, - DeviceProfile deviceProfile, - DeviceCredentialsType credentialsType) { - String deviceName = extractRegex(deviceCN, deviceProfile.getCertificateRegexPattern()); - // find deviceCredentials by deviceName (device exists) - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByTenantIdAndDeviceName(deviceProfile.getTenantId(), deviceName); - if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) { - deviceCredentials.setCredentialsId(credentialsId); - deviceCredentials.setCredentialsValue(credentialsValue); - deviceCredentialsService.updateDeviceCredentials(deviceProfile.getTenantId(), deviceCredentials); - return getDeviceInfo(deviceCredentials); + for (X509Certificate cert: chain) { + String certificateValue = SslUtil.getCertificateString(cert); + String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); + if (credentials != null && credentials.getCredentialsType() == credentialsType) { + return getDeviceInfo(credentials); + } + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); + if (deviceProfile != null) { + String deviceCN = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); + String deviceName = extractDeviceNameFromCNByRegEx(deviceCN, deviceProfile.getCertificateRegexPattern()); + Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); + 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); + } else if (deviceCredentials == null) { + deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); + } + return getDeviceInfo(deviceCredentials); + } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { + 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); + return getDeviceInfo(deviceCredentials); + } + + } + } + } catch (CertificateEncodingException e) { + throw new RuntimeException(e); } return getEmptyTransportApiResponseFuture(); } @@ -546,18 +565,6 @@ public class DefaultTransportApiService implements TransportApiService { }, MoreExecutors.directExecutor()); } - private ListenableFuture getDeviceProfileInfo(DeviceProfile deviceProfile) { - ValidateDeviceProfileCredentialsResponseMsg.Builder builder = ValidateDeviceProfileCredentialsResponseMsg.newBuilder() - .setDeviceProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) - .setIsDeviceProfileFound(true); - - return Futures.immediateFuture( - TransportApiResponseMsg.newBuilder() - .setValidateDeviceProfileResponseMsg(builder.build()) - .build()); - } - private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder() .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) @@ -716,7 +723,7 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } - private String extractRegex(String commonName, String regex) { + private String extractDeviceNameFromCNByRegEx(String commonName, String regex) { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(commonName); if (matcher.find()) { @@ -724,4 +731,59 @@ public class DefaultTransportApiService implements TransportApiService { } return commonName; } + + private List getX509CertificateChainFromString(String certificateChain) { + List chain = new ArrayList<>(); + String[] test = Arrays.stream(certificateChain.split("-----BEGIN CERTIFICATE-----")).filter(e -> e.trim().length() > 0).map(EncryptionUtil::certTrimNewLines).toArray(String[]::new); + Arrays.stream(test).forEach(s -> { + try { + chain.add(readCertFile(s)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + return chain; + } + + private X509Certificate readCertFile(String fileContent) throws Exception { + 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); + } + } + return certificate; + } + + private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue, + String certificateHash, DeviceCredentialsType credentialsType) { + deviceCredentials.setCredentialsId(certificateHash); + deviceCredentials.setCredentialsValue(certificateValue); + deviceCredentials.setCredentialsType(credentialsType); + return deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); + } + + private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, String certificateValue, + String certificateHash, DeviceCredentialsType credentialsType) { + DeviceCredentials createDevCredentials = new DeviceCredentials(); + createDevCredentials.setDeviceId(deviceId); + createDevCredentials.setCredentialsType(credentialsType); + createDevCredentials.setCredentialsId(certificateHash); + createDevCredentials.setCredentialsValue(certificateValue); + return deviceCredentialsService.createDeviceCredentials(tenantId, createDevCredentials); + } + + private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId, String deviceName, String type) { + Device device = new Device(); + device.setTenantId(tenantId); + device.setDeviceProfileId(deviceProfileId); + device.setName(deviceName); + device.setType(type); + return deviceService.saveDevice(device); + } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index ace1f3cf6d..b9741429f7 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -169,16 +169,8 @@ message ValidateDeviceX509CertRequestMsg { string hash = 1; } -message ValidateDeviceProfileX509CertRequestMsg { - string hash = 1; -} - -message UpdateOrCreateDeviceX509CertRequestMsg { - string hash = 1; - string value = 2; - string commonName = 3; - int64 deviceProfileIdMSB = 4; - int64 deviceProfileIdLSB = 5; +message ValidateOrCreateDeviceX509CertRequestMsg { + string certificate = 1; } message ValidateBasicMqttCredRequestMsg { @@ -193,12 +185,6 @@ message ValidateDeviceCredentialsResponseMsg { bytes profileBody = 3; } -message ValidateDeviceProfileCredentialsResponseMsg { - int64 deviceProfileIdMSB = 1; - int64 deviceProfileIdLSB = 2; - bool isDeviceProfileFound = 3; -} - message GetOrCreateDeviceFromGatewayRequestMsg { int64 gatewayIdMSB = 1; int64 gatewayIdLSB = 2; @@ -915,8 +901,7 @@ message TransportApiRequestMsg { GetDeviceRequestMsg deviceRequestMsg = 12; GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14; - UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 15; - ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 16; + ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateX509CertRequestMsg = 15; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -932,7 +917,6 @@ message TransportApiResponseMsg { GetDeviceResponseMsg deviceResponseMsg = 9; GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11; - ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 12; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java index 150106e3cb..1c0f2578ed 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java @@ -26,8 +26,6 @@ public interface DeviceCredentialsService { DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId); - DeviceCredentials findDeviceCredentialsByTenantIdAndDeviceName(TenantId tenantId, String deviceName); - DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 54989e1aab..b9b1053d99 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,12 +66,15 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 16, value = "CA certificate value") + @ApiModelProperty(position = 16, value = "CA certificate value. ") private String certificateValue; - @ApiModelProperty(position = 17, value = "CA certificate hash") + @ApiModelProperty(position = 17, value = "CA certificate hash. ") private String certificateHash; - @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN") + @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN. ") private String certificateRegexPattern; + @ApiModelProperty(position = 19, value = "Allow to create new devices by x509 provision strategy. ") + private boolean allowCreateNewDevicesByX509Strategy; + @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " + @@ -128,6 +131,10 @@ public class DeviceProfile extends SearchTextBased implements H this.firmwareId = deviceProfile.getFirmwareId(); this.softwareId = deviceProfile.getSoftwareId(); this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId(); + this.allowCreateNewDevicesByX509Strategy = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); + this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); + this.certificateValue = deviceProfile.getCertificateValue(); + this.certificateHash = deviceProfile.getCertificateHash(); this.externalId = deviceProfile.getExternalId(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java index 319ebadcbc..7ee03d7227 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java @@ -18,5 +18,6 @@ package org.thingsboard.server.common.data; public enum DeviceProfileProvisionType { DISABLED, ALLOW_CREATE_NEW_DEVICES, - CHECK_PRE_PROVISIONED_DEVICES + CHECK_PRE_PROVISIONED_DEVICES, + ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java similarity index 58% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java index cfbe79bf96..e84d09829f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java @@ -13,16 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.auth; -import lombok.Builder; +package org.thingsboard.server.common.data.device.profile; + import lombok.Data; -import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; @Data -@Builder -public class ValidateDeviceProfileCredentialsResponse { +public class AllowCreatingNewDevicesByX509CertificateProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE; + } - private final DeviceProfileId deviceProfileId; - private final boolean isDeviceProfileFound; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java index b29641fe6f..7a70c00b98 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -31,7 +31,8 @@ import java.io.Serializable; @JsonSubTypes({ @JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"), @JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"), - @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")}) + @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES"), + @JsonSubTypes.Type(value = AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.class, name = "ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE")}) public interface DeviceProfileProvisionConfiguration extends Serializable { String getProvisionDeviceSecret(); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index c57c3a885b..6bccac1591 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -35,6 +35,11 @@ public class EncryptionUtil { .replaceAll("-----END CERTIFICATE-----", ""); } + public static String certTrimNewLinesWithoutBeginEnd(String input) { + return input.replaceAll("\n", "") + .replaceAll("\r", ""); + } + public static String pubkTrimNewLines(String input) { return input.replaceAll("-----BEGIN PUBLIC KEY-----", "") .replaceAll("\n", "") 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 2b4a24e116..67b48464c8 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt; import io.netty.handler.ssl.SslHandler; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.units.qual.C; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -26,11 +27,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; -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.auth.ValidateDeviceProfileCredentialsResponse; 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; @@ -144,84 +143,37 @@ public class MqttSslHandlerProvider { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - String deviceCN = SslUtil.parseCommonName(chain[0]); String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); - String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue); - String credentialsBody = null; - for (X509Certificate cert : chain) { - try { - String strCert = SslUtil.getCertificateString(cert); - String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - final String[] credentialsBodyHolder = new String[1]; - CountDownLatch latch = new CountDownLatch(1); - transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (!StringUtils.isEmpty(msg.getCredentials())) { - credentialsBodyHolder[0] = msg.getCredentials(); - latch.countDown(); - } else { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) { - if (msg.isDeviceProfileFound()) { - transportService.process(DeviceTransportType.MQTT, - TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder() - .setHash(clientDeviceCertHash) - .setValue(clientDeviceCertValue) - .setCommonName(deviceCN) - .setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits()) - .build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - credentialsBodyHolder[0] = msg.getCredentials(); - latch.countDown(); - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - } - ); - } else { - latch.countDown(); - } - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - } + final String[] credentialsBodyHolder = new String[1]; + CountDownLatch latch = new CountDownLatch(1); + try { + String certificateChain = SslUtil.getCertificateChainString(chain); + transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg + .newBuilder().setCertificate(certificateChain).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + credentialsBodyHolder[0] = msg.getCredentials(); } + latch.countDown(); + } - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - latch.await(10, TimeUnit.SECONDS); - if (clientDeviceCertValue.equals(credentialsBodyHolder[0])) { - credentialsBody = credentialsBodyHolder[0]; - break; - } - } catch (InterruptedException | CertificateEncodingException e) { - log.error(e.getMessage(), e); + @Override + public void onError(Throwable e) { + // to fix this error, cuz no one can understand this ... + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + throw new CertificateException("Invalid Certificate's chain"); } - } - if (credentialsBody == null) { - throw new CertificateException("Invalid Certificate's chain"); + } catch (CertificateEncodingException | InterruptedException e) { + log.error(e.getMessage(), e); } } + } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 9159759c28..813a6f1ed2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -67,6 +67,7 @@ import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; @@ -851,7 +852,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } String strCert = SslUtil.getCertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { @@ -860,7 +861,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { - log.error(e.getMessage(), e); + log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e); + ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); + ctx.close(); } }); } catch (Exception e) { @@ -873,9 +876,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private X509Certificate getX509Certificate() { try { - Certificate[] certChains = sslHandler.engine().getSession().getPeerCertificates(); - if (certChains.length > 1) { - return (X509Certificate) certChains[0]; + Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); + if (certChain.length > 1) { + return (X509Certificate) certChain[0]; } } catch (SSLPeerUnverifiedException e) { log.warn(e.getMessage()); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 21e668cf37..3e2e23e43a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -53,12 +52,11 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; -import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg; import java.util.List; import java.util.concurrent.ExecutorService; @@ -90,10 +88,7 @@ public interface TransportService { void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback); - void process(DeviceTransportType transportType, ValidateDeviceProfileX509CertRequestMsg msg, - TransportServiceCallback callback); - - void process(DeviceTransportType transportType, UpdateOrCreateDeviceX509CertRequestMsg msg, + void process(DeviceTransportType transportType, ValidateOrCreateDeviceX509CertRequestMsg msg, TransportServiceCallback callback); void process(ValidateDeviceLwM2MCredentialsRequestMsg msg, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index a41c1d6055..4639ce0430 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -71,7 +71,6 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; @@ -429,35 +428,16 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceProfileX509CertRequestMsg requestMsg, - TransportServiceCallback callback) { - log.trace("Processing msg: {}", requestMsg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), - TransportApiRequestMsg.newBuilder().setValidateProfileX509CertRequestMsg(requestMsg).build()); - ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - TransportProtos.ValidateDeviceProfileCredentialsResponseMsg msg = tmp.getValue().getValidateDeviceProfileResponseMsg(); - ValidateDeviceProfileCredentialsResponse.ValidateDeviceProfileCredentialsResponseBuilder result = ValidateDeviceProfileCredentialsResponse.builder(); - DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB())); - result.deviceProfileId(deviceProfileId); - result.isDeviceProfileFound(msg.getIsDeviceProfileFound()); - return result.build(); - }, MoreExecutors.directExecutor()); - AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); - } - - @Override - public void process(DeviceTransportType transportType, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg requestMsg, - TransportServiceCallback callback) { - log.trace("Processing msg: {}", requestMsg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder() - .setUpdateOrCreateDeviceCertRequestMsg(requestMsg).build()); + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", msg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + public void process(DeviceTransportType transportType, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateOrCreateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); } 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 51ed2cd948..bc6f38f42c 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 @@ -27,6 +27,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.util.Arrays; /** * @author Valerii Sosliuk @@ -42,6 +43,17 @@ public class SslUtil { return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); } + public static String getCertificateChainString(Certificate[] chain) + throws CertificateEncodingException { + String begin = "-----BEGIN CERTIFICATE-----"; + String end = "-----END CERTIFICATE-----"; + StringBuilder stringBuilder = new StringBuilder(); + for (Certificate cert: chain) { + stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLinesWithoutBeginEnd(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n"); + } + return stringBuilder.toString(); + } + public static String parseCommonName(X509Certificate certificate) { X500Name x500name; try { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java index b6758af59c..10363ac5d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java @@ -53,12 +53,4 @@ public interface DeviceCredentialsDao extends Dao { */ DeviceCredentials findByCredentialsId(TenantId tenantId, String credentialsId); - /** - * Find device credentials by device name. - * - * @param deviceName the device name - * @return the device credentials object - */ - DeviceCredentials findByTenantIdAndDeviceName(TenantId tenantId, String deviceName); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index c21031fe65..138a8aaa9d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -83,13 +83,6 @@ public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService impl @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY) private String certificateRegexPattern; + @Column(name = ModelConstants.ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION) + private boolean allowCreateDevice; + public DeviceProfileEntity() { super(); } @@ -138,6 +141,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.certificateHash = deviceProfile.getCertificateHash(); this.certificateValue = deviceProfile.getCertificateValue(); this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); + this.allowCreateDevice = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -203,6 +207,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setCertificateHash(certificateHash); deviceProfile.setCertificateValue(certificateValue); deviceProfile.setCertificateRegexPattern(certificateRegexPattern); + deviceProfile.setAllowCreateNewDevicesByX509Strategy(allowCreateDevice); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 492ecf939a..0bf618e367 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -133,14 +133,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Tue, 24 Jan 2023 16:37:05 +0200 Subject: [PATCH 08/45] Fix CacheKey toString and validation logs --- .../transport/DefaultTransportApiService.java | 4 +-- common/cluster-api/src/main/proto/queue.proto | 1 + .../server/common/data/DeviceProfile.java | 8 ++--- .../mqtt/MqttSslHandlerProvider.java | 6 ++-- .../transport/mqtt/MqttTransportHandler.java | 2 +- .../dao/device/DeviceProfileCacheKey.java | 8 +++-- .../dao/device/DeviceProfileEvictEvent.java | 1 + .../dao/device/DeviceProfileServiceImpl.java | 31 ++++++++++++++----- .../validator/DeviceProfileDataValidator.java | 13 ++++---- 9 files changed, 46 insertions(+), 28 deletions(-) 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 0bf0ff8035..24f6de76d7 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 @@ -258,8 +258,7 @@ public class DefaultTransportApiService implements TransportApiService { } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); if (deviceProfile != null) { - String deviceCN = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); - String deviceName = extractDeviceNameFromCNByRegEx(deviceCN, deviceProfile.getCertificateRegexPattern()); + String deviceName = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); if (device != null) { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); @@ -275,7 +274,6 @@ public class DefaultTransportApiService implements TransportApiService { deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); return getDeviceInfo(deviceCredentials); } - } } } catch (CertificateEncodingException e) { diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index b9741429f7..9f62eca36b 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -422,6 +422,7 @@ message CredentialsDataProto { ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3; + ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateDeviceX509CertRequestMsg = 4; } message ProvisionDeviceRequestMsg { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index b9b1053d99..e226261227 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,13 +66,13 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 16, value = "CA certificate value. ") + @ApiModelProperty(position = 18, value = "CA certificate value. ") private String certificateValue; - @ApiModelProperty(position = 17, value = "CA certificate hash. ") + @ApiModelProperty(position = 19, value = "CA certificate hash. ") private String certificateHash; - @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN. ") + @ApiModelProperty(position = 20, value = "Regex to fetch deviceName from CN. ") private String certificateRegexPattern; - @ApiModelProperty(position = 19, value = "Allow to create new devices by x509 provision strategy. ") + @ApiModelProperty(position = 21, value = "Allow to create new devices by x509 provision strategy. ") private boolean allowCreateNewDevicesByX509Strategy; 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 67b48464c8..3224556e09 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 @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.mqtt; import io.netty.handler.ssl.SslHandler; import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.units.qual.C; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -161,14 +160,13 @@ public class MqttSslHandlerProvider { @Override public void onError(Throwable e) { - // to fix this error, cuz no one can understand this ... - log.error(e.getMessage(), e); + log.trace("Failed to process certificate chain: {}", certificateChain, e); latch.countDown(); } }); latch.await(10, TimeUnit.SECONDS); if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { - throw new CertificateException("Invalid Certificate's chain"); + throw new CertificateException("Invalid Certificate's chain. Cannot find such device credentials."); } } catch (CertificateEncodingException | InterruptedException e) { log.error(e.getMessage(), e); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 813a6f1ed2..00d318544f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -877,7 +877,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private X509Certificate getX509Certificate() { try { Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); - if (certChain.length > 1) { + if (certChain.length > 0) { return (X509Certificate) certChain[0]; } } catch (SSLPeerUnverifiedException e) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index 228720f0e7..fb44092bd4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -56,14 +56,18 @@ public class DeviceProfileCacheKey implements Serializable { return new DeviceProfileCacheKey(tenantId, null, null, null, true); } + /** + * IMPORTANT: Method toString() has to return unique value, if you add additional field to this class, please also refactor toString(). + */ @Override public String toString() { if (deviceProfileId != null) { return deviceProfileId.toString(); } else if (defaultProfile) { return tenantId.toString(); - } else { - return tenantId + "_" + name; + } else if (certificateHash != null) { + return certificateHash; } + return tenantId + "_" + name; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index b5b4b27a6b..acd34ff8e5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -27,5 +27,6 @@ public class DeviceProfileEvictEvent { private final String oldName; private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; + private final String certificateHash; } 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 8cd08739b3..ffa25d16d6 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 @@ -98,6 +98,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService 1; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 0bf618e367..d737a48a9b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -138,7 +138,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator rootCa = new HashSet<>(); + boolean getRootCAFromJavaCacerts(String deviceProfileHash) { try { String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar); FileInputStream is = new FileInputStream(filename); @@ -408,12 +407,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Tue, 31 Jan 2023 18:04:45 +0200 Subject: [PATCH 09/45] Add unit tests and update regex pattern to convert chain to list --- .../transport/DefaultTransportApiService.java | 85 +++++---- .../BaseDeviceProfileControllerTest.java | 19 ++ .../DefaultTransportApiServiceTest.java | 174 ++++++++++++++++++ .../mqtt/AbstractMqttIntegrationTest.java | 7 + .../mqtt/MqttTestConfigProperties.java | 5 + common/cluster-api/src/main/proto/queue.proto | 1 - .../server/common/msg/EncryptionUtil.java | 6 +- .../mqtt/MqttSslHandlerProvider.java | 3 +- .../server/common/transport/util/SslUtil.java | 3 +- .../dao/device/DeviceProfileServiceImpl.java | 27 ++- 10 files changed, 272 insertions(+), 58 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java 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 24f6de76d7..f434df2faa 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 @@ -107,11 +107,9 @@ import org.thingsboard.server.service.resource.TbResourceService; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -243,41 +241,44 @@ public class DefaultTransportApiService implements TransportApiService { } } - private ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) { + List chain = convertX509CertificateChainToList(certChain); + String updateDeviceCertificateValue = chain.get(0); + String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); + String deviceCommonName = ""; try { - List chain = getX509CertificateChainFromString(certChain); - String updateDeviceCertificateValue = SslUtil.getCertificateString(chain.get(0)); - String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue); - - for (X509Certificate cert: chain) { - String certificateValue = SslUtil.getCertificateString(cert); - String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); - if (credentials != null && credentials.getCredentialsType() == credentialsType) { - return getDeviceInfo(credentials); + 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) { + return getDeviceInfo(credentials); + } + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); + if (deviceProfile != null) { + String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, deviceProfile.getCertificateRegexPattern()); + if (deviceName == null) { + log.error("Device name cannot be unmatched from CN!"); + return getEmptyTransportApiResponseFuture(); } - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); - if (deviceProfile != null) { - String deviceName = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); - Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); - 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); - } else if (deviceCredentials == null) { - deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); - } - return getDeviceInfo(deviceCredentials); - } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { - 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); - return getDeviceInfo(deviceCredentials); + Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); + 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); + } else if (deviceCredentials == null) { + deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); } + return getDeviceInfo(deviceCredentials); + } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { + 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); + return getDeviceInfo(deviceCredentials); } } - } catch (CertificateEncodingException e) { - throw new RuntimeException(e); } return getEmptyTransportApiResponseFuture(); } @@ -727,19 +728,17 @@ public class DefaultTransportApiService implements TransportApiService { if (matcher.find()) { return matcher.group(0); } - return commonName; + return null; } - private List getX509CertificateChainFromString(String certificateChain) { - List chain = new ArrayList<>(); - String[] test = Arrays.stream(certificateChain.split("-----BEGIN CERTIFICATE-----")).filter(e -> e.trim().length() > 0).map(EncryptionUtil::certTrimNewLines).toArray(String[]::new); - Arrays.stream(test).forEach(s -> { - try { - chain.add(readCertFile(s)); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + private List convertX509CertificateChainToList(String certificateChain) { + List chain = new ArrayList<>(); + String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(certificateChain); + while (matcher.find()) { + chain.add(EncryptionUtil.certTrimNewLines(matcher.group())); + } return chain; } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 3fe80b1112..43b8e51025 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -298,6 +298,25 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } + @Test + public void testSaveDeviceProfileWithSameCertificateHash() throws Exception { + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setCertificateHash("Certificate Hash"); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + deviceProfile2.setCertificateHash("Certificate Hash"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Device profile with such certificate hash already exists"; + doPost("/api/deviceProfile", deviceProfile2) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + } + @Test public void testChangeDeviceProfileTypeNull() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); 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 new file mode 100644 index 0000000000..4a2ff07978 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -0,0 +1,174 @@ +/** + * Copyright © 2016-2022 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.service.transport; + + +import com.google.common.util.concurrent.Futures; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceProvisionService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.queue.QueueService; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.resource.TbResourceService; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Slf4j +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = DefaultTransportApiService.class) +public class DefaultTransportApiServiceTest { + + @MockBean + protected TbDeviceProfileCache deviceProfileCache; + @MockBean + protected TbTenantProfileCache tenantProfileCache; + @MockBean + protected TbApiUsageStateService apiUsageStateService; + @MockBean + protected DeviceService deviceService; + @MockBean + protected DeviceProfileService deviceProfileService; + @MockBean + protected RelationService relationService; + @MockBean + protected DeviceCredentialsService deviceCredentialsService; + @MockBean + protected DbCallbackExecutorService dbCallbackExecutorService; + @MockBean + protected TbClusterService tbClusterService; + @MockBean + protected DataDecodingEncodingService dataDecodingEncodingService; + @MockBean + protected DeviceProvisionService deviceProvisionService; + @MockBean + protected TbResourceService resourceService; + @MockBean + protected OtaPackageService otaPackageService; + @MockBean + protected OtaPackageDataCache otaPackageDataCache; + @MockBean + protected QueueService queueService; + @SpyBean + DefaultTransportApiService service; + + private final String deviceCertificate = "-----BEGIN CERTIFICATE-----Device certificate value-----END CERTIFICATE-----"; + private final String deviceProfileCertificate = "-----BEGIN CERTIFICATE-----Device profile certificate value-----END CERTIFICATE-----"; + + @Test + public void validateExistingDeviceX509Certificate() { + var device = createDevice(); + when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); + + var deviceCredentials = createDeviceCredentials(deviceCertificate, device.getId()); + when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(deviceCredentials); + + service.validateOrCreateDeviceX509Certificate(deviceCertificate, DeviceCredentialsType.X509_CERTIFICATE); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); + } + + @Test + public void updateExistingDeviceX509Certificate() { + var deviceProfile = createDeviceProfile(deviceProfileCertificate); + 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()); + when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); + when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + + service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); + verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); + verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + } + + @Test + public void createDeviceByX509Provision() { + var deviceProfile = createDeviceProfile(deviceProfileCertificate); + 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()); + when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); + when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + + service.validateOrCreateDeviceX509Certificate(deviceProfileCertificate, DeviceCredentialsType.X509_CERTIFICATE); + verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); + verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); + verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + } + + private DeviceCredentials createDeviceCredentials(String certificateValue, DeviceId deviceId) { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setDeviceId(deviceId); + deviceCredentials.setCredentialsValue(certificateValue); + deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash(certificateValue)); + deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + return deviceCredentials; + } + + private DeviceProfile createDeviceProfile(String certificateValue) { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setCertificateValue(certificateValue); + deviceProfile.setCertificateHash(EncryptionUtil.getSha3Hash(certificateValue)); + deviceProfile.setCertificateRegexPattern("^$"); + deviceProfile.setAllowCreateNewDevicesByX509Strategy(true); + deviceProfile.setProvisionType(DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE); + return deviceProfile; + } + + private Device createDevice() { + Device device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + return device; + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index 917e41d1b4..69b1f87502 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.AllowCreatingNewDevicesByX509CertificateProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -94,6 +95,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg deviceProfile.setProvisionType(provisionType); deviceProfile.setProvisionDeviceKey(config.getProvisionKey()); deviceProfile.setDescription(transportPayloadType.name() + " Test"); + deviceProfile.setAllowCreateNewDevicesByX509Strategy(config.allowCreatingNewDeviceByX509Strategy); + deviceProfile.setCertificateValue("Device Profile certificate value"); + deviceProfile.setCertificateRegexPattern(config.getRegEx()); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); @@ -144,6 +148,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg case CHECK_PRE_PROVISIONED_DEVICES: provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); break; + case ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE: + provisionConfiguration = new AllowCreatingNewDevicesByX509CertificateProvisionConfiguration(config.getProvisionSecret()); + break; case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(config.getProvisionSecret()); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java index bc535b424b..4c57f413c9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java @@ -40,9 +40,14 @@ public class MqttTestConfigProperties { boolean enableCompatibilityWithJsonPayloadFormat; boolean useJsonPayloadFormatForDefaultDownlinkTopics; boolean sendAckOnValidationException; + boolean allowCreatingNewDeviceByX509Strategy; DeviceProfileProvisionType provisionType; String provisionKey; String provisionSecret; + String x509DeviceCertificate; + String x509DeviceProfileCertificate; + String commonName; + String regEx; } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 9f62eca36b..b9741429f7 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -422,7 +422,6 @@ message CredentialsDataProto { ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3; - ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateDeviceX509CertRequestMsg = 4; } message ProvisionDeviceRequestMsg { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index 6bccac1591..4cdd61eeec 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -35,9 +35,11 @@ public class EncryptionUtil { .replaceAll("-----END CERTIFICATE-----", ""); } - public static String certTrimNewLinesWithoutBeginEnd(String input) { + public static String certTrimNewLinesForChainInDeviceProfile(String input) { return input.replaceAll("\n", "") - .replaceAll("\r", ""); + .replaceAll("\r", "") + .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n") + .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n"); } public static String pubkTrimNewLines(String input) { 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 3224556e09..d73743e6b5 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 @@ -41,7 +41,6 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.CountDownLatch; @@ -168,7 +167,7 @@ public class MqttSslHandlerProvider { if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { throw new CertificateException("Invalid Certificate's chain. Cannot find such device credentials."); } - } catch (CertificateEncodingException | InterruptedException e) { + } 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 bc6f38f42c..4245cae349 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 @@ -27,7 +27,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.util.Arrays; /** * @author Valerii Sosliuk @@ -49,7 +48,7 @@ public class SslUtil { String end = "-----END CERTIFICATE-----"; StringBuilder stringBuilder = new StringBuilder(); for (Certificate cert: chain) { - stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLinesWithoutBeginEnd(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n"); + stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n"); } return stringBuilder.toString(); } 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 ffa25d16d6..48e6a5b6b6 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 @@ -48,6 +48,10 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import java.io.ByteArrayInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -342,17 +346,15 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService 1; + private String formatCertificateValue(String certificateValue) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + ByteArrayInputStream inputStream = new ByteArrayInputStream(certificateValue.getBytes()); + Certificate[] certificates = cf.generateCertificates(inputStream).toArray(new Certificate[0]); + if (certificates.length > 1) { + return EncryptionUtil.certTrimNewLinesForChainInDeviceProfile(certificateValue); + } + return EncryptionUtil.certTrimNewLines(certificateValue); + } catch (CertificateException e) { + throw new RuntimeException(e); + } } } From 0943b80d8e4f074950648b3e0047462e037699f8 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 1 Feb 2023 16:49:00 +0200 Subject: [PATCH 10/45] Fix schema and micro refactoring --- .../main/data/upgrade/3.4.3/schema_update.sql | 3 --- .../transport/DefaultTransportApiService.java | 11 ++++++--- .../DefaultTransportApiServiceTest.java | 15 ++++++++---- .../mqtt/AbstractMqttIntegrationTest.java | 7 ------ .../mqtt/MqttTestConfigProperties.java | 5 ---- common/cluster-api/src/main/proto/queue.proto | 2 +- .../server/common/data/DeviceProfile.java | 11 +-------- .../data/DeviceProfileProvisionType.java | 2 +- .../DeviceProfileProvisionConfiguration.java | 2 +- ...rtificateChainProvisionConfiguration.java} | 11 ++++++--- .../mqtt/MqttSslHandlerProvider.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 23 ++++++++++++------- .../dao/model/sql/DeviceProfileEntity.java | 15 ------------ .../main/resources/sql/schema-entities.sql | 3 --- 14 files changed, 47 insertions(+), 65 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java => X509CertificateChainProvisionConfiguration.java} (67%) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 78203951ec..2aaa574bb4 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -31,9 +31,6 @@ CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id) ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS certificate_hash varchar, - ADD COLUMN IF NOT EXISTS certificate_value varchar, - ADD COLUMN IF NOT EXISTS certificate_regex_pattern varchar(255), - ADD COLUMN IF NOT EXISTS allow_create_device_by_x509 boolean, DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); 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 f434df2faa..6a3780d529 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 @@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfig import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -173,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.getCertificate(), DeviceCredentialsType.X509_CERTIFICATE); + result = validateOrCreateDeviceX509Certificate(msg.getCertificateChain(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { @@ -258,7 +259,11 @@ public class DefaultTransportApiService implements TransportApiService { } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); if (deviceProfile != null) { - String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, deviceProfile.getCertificateRegexPattern()); + X509CertificateChainProvisionConfiguration x509Configuration = new X509CertificateChainProvisionConfiguration(); + if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + } + String deviceName = extractDeviceNameFromCNByRegEx(deviceCommonName, x509Configuration.getCertificateRegExPattern()); if (deviceName == null) { log.error("Device name cannot be unmatched from CN!"); return getEmptyTransportApiResponseFuture(); @@ -272,7 +277,7 @@ public class DefaultTransportApiService implements TransportApiService { deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); } return getDeviceInfo(deviceCredentials); - } else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) { + } 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); 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 4a2ff07978..71a2f8b70c 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 @@ -29,6 +29,9 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -158,11 +161,15 @@ public class DefaultTransportApiServiceTest { private DeviceProfile createDeviceProfile(String certificateValue) { DeviceProfile deviceProfile = new DeviceProfile(); - deviceProfile.setCertificateValue(certificateValue); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); + provision.setCertificateValue(certificateValue); + provision.setCertificateRegExPattern("^$"); + provision.setAllowCreateNewDevicesByX509Certificate(true); + deviceProfileData.setProvisionConfiguration(provision); + deviceProfile.setProfileData(deviceProfileData); deviceProfile.setCertificateHash(EncryptionUtil.getSha3Hash(certificateValue)); - deviceProfile.setCertificateRegexPattern("^$"); - deviceProfile.setAllowCreateNewDevicesByX509Strategy(true); - deviceProfile.setProvisionType(DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE); + deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN); return deviceProfile; } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index 69b1f87502..917e41d1b4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.AllowCreatingNewDevicesByX509CertificateProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -95,9 +94,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg deviceProfile.setProvisionType(provisionType); deviceProfile.setProvisionDeviceKey(config.getProvisionKey()); deviceProfile.setDescription(transportPayloadType.name() + " Test"); - deviceProfile.setAllowCreateNewDevicesByX509Strategy(config.allowCreatingNewDeviceByX509Strategy); - deviceProfile.setCertificateValue("Device Profile certificate value"); - deviceProfile.setCertificateRegexPattern(config.getRegEx()); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); @@ -148,9 +144,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg case CHECK_PRE_PROVISIONED_DEVICES: provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); break; - case ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE: - provisionConfiguration = new AllowCreatingNewDevicesByX509CertificateProvisionConfiguration(config.getProvisionSecret()); - break; case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(config.getProvisionSecret()); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java index 4c57f413c9..bc535b424b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java @@ -40,14 +40,9 @@ public class MqttTestConfigProperties { boolean enableCompatibilityWithJsonPayloadFormat; boolean useJsonPayloadFormatForDefaultDownlinkTopics; boolean sendAckOnValidationException; - boolean allowCreatingNewDeviceByX509Strategy; DeviceProfileProvisionType provisionType; String provisionKey; String provisionSecret; - String x509DeviceCertificate; - String x509DeviceProfileCertificate; - String commonName; - String regEx; } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index b9741429f7..6e9ec38be8 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -170,7 +170,7 @@ message ValidateDeviceX509CertRequestMsg { } message ValidateOrCreateDeviceX509CertRequestMsg { - string certificate = 1; + string certificateChain = 1; } message ValidateBasicMqttCredRequestMsg { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index e226261227..05f8ae19ba 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,14 +66,8 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 18, value = "CA certificate value. ") - private String certificateValue; - @ApiModelProperty(position = 19, value = "CA certificate hash. ") + @ApiModelProperty(position = 18, value = "CA certificate hash. ") private String certificateHash; - @ApiModelProperty(position = 20, value = "Regex to fetch deviceName from CN. ") - private String certificateRegexPattern; - @ApiModelProperty(position = 21, value = "Allow to create new devices by x509 provision strategy. ") - private boolean allowCreateNewDevicesByX509Strategy; @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + @@ -131,9 +125,6 @@ public class DeviceProfile extends SearchTextBased implements H this.firmwareId = deviceProfile.getFirmwareId(); this.softwareId = deviceProfile.getSoftwareId(); this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId(); - this.allowCreateNewDevicesByX509Strategy = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); - this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); - this.certificateValue = deviceProfile.getCertificateValue(); this.certificateHash = deviceProfile.getCertificateHash(); this.externalId = deviceProfile.getExternalId(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java index 7ee03d7227..f2daf71cca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java @@ -19,5 +19,5 @@ public enum DeviceProfileProvisionType { DISABLED, ALLOW_CREATE_NEW_DEVICES, CHECK_PRE_PROVISIONED_DEVICES, - ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE + X509_CERTIFICATE_CHAIN } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java index 7a70c00b98..048e10739f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -32,7 +32,7 @@ import java.io.Serializable; @JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"), @JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"), @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES"), - @JsonSubTypes.Type(value = AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.class, name = "ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE")}) + @JsonSubTypes.Type(value = X509CertificateChainProvisionConfiguration.class, name = "X509_CERTIFICATE_CHAIN")}) public interface DeviceProfileProvisionConfiguration extends Serializable { String getProvisionDeviceSecret(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java similarity index 67% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java index e84d09829f..0b500f3e52 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java @@ -17,16 +17,21 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; +import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @Data -public class AllowCreatingNewDevicesByX509CertificateProvisionConfiguration implements DeviceProfileProvisionConfiguration { +@NoArgsConstructor +public class X509CertificateChainProvisionConfiguration implements DeviceProfileProvisionConfiguration { - private final String provisionDeviceSecret; + private String provisionDeviceSecret; + private String certificateValue; + private String certificateRegExPattern; + private boolean allowCreateNewDevicesByX509Certificate; @Override public DeviceProfileProvisionType getType() { - return DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE; + 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 d73743e6b5..d5ff77837b 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 @@ -147,7 +147,7 @@ public class MqttSslHandlerProvider { try { String certificateChain = SslUtil.getCertificateChainString(chain); transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg - .newBuilder().setCertificate(certificateChain).build(), + .newBuilder().setCertificateChain(certificateChain).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { 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 48e6a5b6b6..f558eb2c7e 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 @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; @@ -134,8 +135,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService impl @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) private UUID externalId; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY) - private String certificateValue; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY) private String certificateHash; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY) - private String certificateRegexPattern; - - @Column(name = ModelConstants.ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION) - private boolean allowCreateDevice; - public DeviceProfileEntity() { super(); } @@ -139,9 +130,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); this.certificateHash = deviceProfile.getCertificateHash(); - this.certificateValue = deviceProfile.getCertificateValue(); - this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern(); - this.allowCreateDevice = deviceProfile.isAllowCreateNewDevicesByX509Strategy(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -205,9 +193,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl } deviceProfile.setProvisionDeviceKey(provisionDeviceKey); deviceProfile.setCertificateHash(certificateHash); - deviceProfile.setCertificateValue(certificateValue); - deviceProfile.setCertificateRegexPattern(certificateRegexPattern); - deviceProfile.setAllowCreateNewDevicesByX509Strategy(allowCreateDevice); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index eed6f237b6..bd61d9a7c3 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -292,12 +292,9 @@ CREATE TABLE IF NOT EXISTS device_profile ( default_dashboard_id uuid, default_queue_name varchar(255), provision_device_key varchar, - certificate_value varchar, certificate_hash varchar, - certificate_regex_pattern varchar(255), default_edge_rule_chain_id uuid, external_id uuid, - allow_create_device_by_x509 boolean, CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash), CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), From 058c97e4de6e6af470a4e5d7dae661253f6bb4e7 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 2 Feb 2023 10:20:24 +0200 Subject: [PATCH 11/45] Fix headers and remove extra constants from Model Constants --- .../service/transport/DefaultTransportApiServiceTest.java | 5 ++--- .../profile/X509CertificateChainProvisionConfiguration.java | 2 +- .../server/dao/device/DeviceProfileServiceImpl.java | 3 +-- .../org/thingsboard/server/dao/model/ModelConstants.java | 3 --- 4 files changed, 4 insertions(+), 9 deletions(-) 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 71a2f8b70c..0309bec131 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 @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 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. @@ -29,9 +29,8 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; 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 0b500f3e52..9257053b4f 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 @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 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. 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 9d5c0608d3..767fd805cf 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 @@ -379,9 +379,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService 1) { return EncryptionUtil.certTrimNewLinesForChainInDeviceProfile(certificateValue); } - return EncryptionUtil.certTrimNewLines(certificateValue); } catch (CertificateException ignored) {} - return certificateValue; + return EncryptionUtil.certTrimNewLines(certificateValue); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 76a5423359..7b0ca50d0d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -186,10 +186,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_PROFILE_SOFTWARE_ID_PROPERTY = "software_id"; public static final String DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY = "certificate_hash"; - public static final String DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY = "certificate_value"; - public static final String DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY = "certificate_regex_pattern"; public static final String DEVICE_PROFILE_DEFAULT_EDGE_RULE_CHAIN_ID_PROPERTY = "default_edge_rule_chain_id"; - public static final String ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION = "allow_create_device_by_x509"; /** * Asset profile constants. From 5bf7b6162e95ece7e2a7a6607e64c6ffb1381fb6 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 10 Mar 2023 15:48:52 +0200 Subject: [PATCH 12/45] Fix sql schema --- application/src/main/data/upgrade/3.4.4/schema_update.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 9f8a81daaf..d208585ed7 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -353,7 +353,7 @@ $$; ALTER TABLE device_profile ADD COLUMN IF NOT EXISTS certificate_hash varchar, -DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, + DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); -- DEVICE PROFILE CERTIFICATE END From 0e019967e2e45bdc33139e80b1fb720ae53c1e78 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 22 Mar 2023 12:13:59 +0200 Subject: [PATCH 13/45] Feature X509 Device provision added ui form --- ...ile-provision-configuration.component.html | 36 ++++++++++++++++--- ...ofile-provision-configuration.component.ts | 16 ++++++++- ui-ngx/src/app/shared/models/device.models.ts | 6 ++-- .../assets/locale/locale.constant-en_US.json | 13 +++++++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index b588a5ec34..ffe4a4732a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -27,18 +27,20 @@ {{ 'device-profile.provision-strategy-required' | translate }} -
+
device-profile.provision-device-key - + --> {{ 'device-profile.provision-device-key-required' | translate }} @@ -46,17 +48,41 @@ device-profile.provision-device-secret - + --> {{ 'device-profile.provision-device-secret-required' | translate }}
+
+
+ + {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} + +
+ + device-profile.provision-strategy-x509.certificate-value + + + {{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }} + + + + device-profile.provision-strategy-x509.cn-regex-variable + + + {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 872571dc20..17ccd40df0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -86,7 +86,10 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup = this.fb.group({ type: [DeviceProvisionType.DISABLED, Validators.required], provisionDeviceSecret: [{value: null, disabled: true}, Validators.required], - provisionDeviceKey: [{value: null, disabled: true}, Validators.required] + provisionDeviceKey: [{value: null, disabled: true}, Validators.required], + certificateValue: [{value: null, disabled: true}, Validators.required], + certificateRegExPattern: [{value: '[\\w]*', disabled: true}, Validators.required], + allowCreateNewDevicesByX509Certificate: [{value: true, disabled: true}] }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { @@ -94,6 +97,17 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null, {emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); + this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateValue').patchValue(null); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue(null); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(null); + + } else if (type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); } else { const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; if (!provisionDeviceSecret || !provisionDeviceSecret.length) { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index ae601b0039..382b32799b 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -62,7 +62,8 @@ export enum CoapTransportDeviceType { export enum DeviceProvisionType { DISABLED = 'DISABLED', ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES', - CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES' + CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES', + X509_CERTIFICATE_CHAIN = 'X509_CERTIFICATE_CHAIN' } export interface DeviceConfigurationFormInfo { @@ -110,7 +111,8 @@ export const deviceProvisionTypeTranslationMap = new MapMore", + "allow-create-new-devices": "Create new devices", + "hint-allow-create-new-devices": "Hint: if selected new devices will be created with correct device credentials from client certificate.", + "certificate-value": "Certificate in PEM format", + "certificate-value-required": "Certificate in PEM format is required", + "cn-regex-variable": "CN Regular Expression variable", + "cn-regex-variable-required": "CN Regular Expression variable is required", + "hint-cn-regex-variable-title": "Examples of RegEx usage:", + "hint-cn-regex-variable-examples": "1. Pattern: .* - matches any character (until line terminators)
CN sample: DeviceName\\nAdditionalInfo
Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the “@” symbol (@ could be replaced by any other symbol)
CN sample: DeviceName@AdditionalInfo
Pattern matches: DeviceName

3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
CN sample: AdditionalInfo2110#DeviceName_01
Pattern matches: DeviceNane_01", + "hint-cn-regex-variable-note": "Note: Client will get error response in case regex is failed to match." + }, "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From d3803f828dba5b90545f47ecf7280caee71cb2e5 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 22 Mar 2023 13:51:56 +0200 Subject: [PATCH 14/45] Device profile provision strategy -> fix patch values form for X509 Certificate Chain --- ...ile-provision-configuration.component.html | 13 ++++++------- ...ofile-provision-configuration.component.ts | 19 ++++++++++++++++--- .../profile/device-profile.component.ts | 10 ++++++++-- ui-ngx/src/app/shared/models/device.models.ts | 3 +++ .../assets/locale/locale.constant-en_US.json | 5 ++--- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index ffe4a4732a..8b44586f50 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -33,14 +33,14 @@ device-profile.provision-device-key - + {{ 'device-profile.provision-device-key-required' | translate }} @@ -48,14 +48,14 @@ device-profile.provision-device-secret - + {{ 'device-profile.provision-device-secret-required' | translate }} @@ -80,9 +80,8 @@ {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} + device-profile.provision-strategy-x509.hint-cn-regex-variable -
-
-
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 17ccd40df0..bb936080da 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -88,8 +88,8 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu provisionDeviceSecret: [{value: null, disabled: true}, Validators.required], provisionDeviceKey: [{value: null, disabled: true}, Validators.required], certificateValue: [{value: null, disabled: true}, Validators.required], - certificateRegExPattern: [{value: '[\\w]*', disabled: true}, Validators.required], - allowCreateNewDevicesByX509Certificate: [{value: true, disabled: true}] + certificateRegExPattern: [{value: null, disabled: true}, Validators.required], + allowCreateNewDevicesByX509Certificate: [{value: null, disabled: true}] }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { @@ -103,8 +103,19 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue(null); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(null); - } else if (type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + const certificateValue: string = this.provisionConfigurationFormGroup.get('certificateValue').value; + if (!certificateValue || !certificateValue.length) { + this.provisionConfigurationFormGroup.get('certificateValue').patchValue(null, {emitEvent: false}); + } + const certificateRegExPattern: string = this.provisionConfigurationFormGroup.get('certificateRegExPattern').value; + if (!certificateRegExPattern || !certificateRegExPattern.length) { + this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue('[\\w]*', {emitEvent: false}); + } + const allowCreateNewDevicesByX509Certificate: boolean | null = this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').value; + if (typeof allowCreateNewDevicesByX509Certificate !== 'boolean') { + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(true, {emitEvent: false}); + } this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); @@ -119,6 +130,8 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); } }); this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 9adcf9c168..9a6d150ac1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -104,7 +104,10 @@ export class DeviceProfileComponent extends EntityComponent { const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, - provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret + provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, + certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, + certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, + allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; const form = this.fb.group( { @@ -185,7 +188,10 @@ export class DeviceProfileComponent extends EntityComponent { const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, - provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret + provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, + certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, + certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, + allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 382b32799b..de240934c4 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -322,6 +322,9 @@ export interface DeviceProvisionConfiguration { type: DeviceProvisionType; provisionDeviceSecret?: string; provisionDeviceKey?: string; + certificateValue?: string; + certificateRegExPattern?: string; + allowCreateNewDevicesByX509Certificate?: boolean; } export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index d6e70ddd51..09ae98d682 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1504,9 +1504,8 @@ "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", "cn-regex-variable-required": "CN Regular Expression variable is required", - "hint-cn-regex-variable-title": "Examples of RegEx usage:", - "hint-cn-regex-variable-examples": "1. Pattern: .* - matches any character (until line terminators)
CN sample: DeviceName\\nAdditionalInfo
Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the “@” symbol (@ could be replaced by any other symbol)
CN sample: DeviceName@AdditionalInfo
Pattern matches: DeviceName

3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
CN sample: AdditionalInfo2110#DeviceName_01
Pattern matches: DeviceNane_01", - "hint-cn-regex-variable-note": "Note: Client will get error response in case regex is failed to match." + "hint-cn-regex-variable": "Required to fetch device name from device's X509 certificate's common name.", + "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceNane_01
Note: Client will get error response in case regex is failed to match." }, "condition": "Condition", "condition-type": "Condition type", From 86423fdc7b2e2e3755e61efd172b2303de5b0c67 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 22 Mar 2023 17:31:19 +0200 Subject: [PATCH 15/45] Device profile provision X509 Certificate Chain added dynamic readMore for hint; updated text in hint --- ...ice-profile-provision-configuration.component.html | 11 +++++++---- ...evice-profile-provision-configuration.component.ts | 1 + ui-ngx/src/assets/locale/locale.constant-en_US.json | 8 +++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 8b44586f50..99cddbd576 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -27,13 +27,13 @@ {{ 'device-profile.provision-strategy-required' | translate }} -
device-profile.provision-device-key - <
-
+ {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index bb936080da..687bc069b1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -62,6 +62,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu deviceProvisionType = DeviceProvisionType; deviceProvisionTypes = Object.keys(DeviceProvisionType); deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap; + readMore: boolean; private requiredValue: boolean; get required(): boolean { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 09ae98d682..e9e839ce8b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -61,7 +61,9 @@ "done": "Done", "print": "Print", "restore": "Restore", - "confirm": "Confirm" + "confirm": "Confirm", + "more": "More", + "less": "Less" }, "aggregation": { "aggregation": "Aggregation", @@ -1497,7 +1499,7 @@ "provision-secret-copied-message": "Provision secret has been copied to clipboard", "provision-strategy-x509": { "certificate-chain": "X509 Certificates Chain", - "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN)... More", + "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN).

Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain.

Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known Certificate Authority (CA).", "allow-create-new-devices": "Create new devices", "hint-allow-create-new-devices": "Hint: if selected new devices will be created with correct device credentials from client certificate.", "certificate-value": "Certificate in PEM format", @@ -1505,7 +1507,7 @@ "cn-regex-variable": "CN Regular Expression variable", "cn-regex-variable-required": "CN Regular Expression variable is required", "hint-cn-regex-variable": "Required to fetch device name from device's X509 certificate's common name.", - "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName OR DeviceName\\nAdditionalInfo

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceNane_01
Note: Client will get error response in case regex is failed to match." + "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceName_01
Note: Client will get error response in case regex is failed to match." }, "condition": "Condition", "condition-type": "Condition type", From 8703a2083efd9de64e51e03ddae7271be43b9d27 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 11:21:44 +0200 Subject: [PATCH 16/45] Device provision feature X509: 1. Added ng-templates with ngSwitchCase attribute 2. Fix NG0300: Multiple components match node with tagname button 3. Fix max-width for tb-hints --- ...ile-provision-configuration.component.html | 130 ++++++++++-------- ...ofile-provision-configuration.component.ts | 4 +- 2 files changed, 72 insertions(+), 62 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 99cddbd576..3b2b92ce98 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -27,64 +27,74 @@ {{ 'device-profile.provision-strategy-required' | translate }} -
- - device-profile.provision-device-key - - - - {{ 'device-profile.provision-device-key-required' | translate }} - - - - device-profile.provision-device-secret - - - - {{ 'device-profile.provision-device-secret-required' | translate }} - - -
-
- - - {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} - -
- - device-profile.provision-strategy-x509.certificate-value - - - {{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }} - - - - device-profile.provision-strategy-x509.cn-regex-variable - - - {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} - - device-profile.provision-strategy-x509.hint-cn-regex-variable - -
-
+
+ + + + + + + + + + {{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} + +
+ + device-profile.provision-strategy-x509.certificate-value + + + {{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }} + + + + device-profile.provision-strategy-x509.cn-regex-variable + + + {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} + + device-profile.provision-strategy-x509.hint-cn-regex-variable + +
+
+
+ +
+ + device-profile.provision-device-key + + + + {{ 'device-profile.provision-device-key-required' | translate }} + + + + device-profile.provision-device-secret + + + + {{ 'device-profile.provision-device-secret-required' | translate }} + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 687bc069b1..beab4828a5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -32,7 +32,7 @@ import { DeviceProvisionType, deviceProvisionTypeTranslationMap } from '@shared/models/device.models'; -import { generateSecret, isDefinedAndNotNull } from '@core/utils'; +import { generateSecret, isBoolean, isDefinedAndNotNull } from '@core/utils'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -114,7 +114,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue('[\\w]*', {emitEvent: false}); } const allowCreateNewDevicesByX509Certificate: boolean | null = this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').value; - if (typeof allowCreateNewDevicesByX509Certificate !== 'boolean') { + if (isBoolean(allowCreateNewDevicesByX509Certificate)) { this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(true, {emitEvent: false}); } this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); From d603e7dbb709c59f0ba4143c869eeabff9b1dde3 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 11:52:41 +0200 Subject: [PATCH 17/45] Device profile Transport configuration MQTT profile set max-width tb-hint --- .../mqtt-device-profile-transport-configuration.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 1ef7892ecb..9e3ae02098 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -163,6 +163,6 @@ {{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }} -
+ From 1498f264f68f2252c0f2f7ee4eaa1cd6824db390 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 12:20:20 +0200 Subject: [PATCH 18/45] MQTT device profile fix tb-hint --- .../mqtt-device-profile-transport-configuration.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 9e3ae02098..4dc8b2a7b8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -163,6 +163,6 @@ {{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }} - +
From a3615f11fe1c286da9eab4444168ba03873ebd5a Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 23 Mar 2023 14:03:06 +0200 Subject: [PATCH 19/45] Device profile feature X509: - fix edit form after creation; - added fields iteration in provisionConfigurationFormGroup for type DISABLED; --- ...ofile-provision-configuration.component.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index beab4828a5..465588fa24 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -94,16 +94,13 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null, {emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); - this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateValue').patchValue(null); - this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue(null); - this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(null); + for (const field in this.provisionConfigurationFormGroup.controls) { + if (field !== 'type') { + const control = this.provisionConfigurationFormGroup.get(field); + control.disable({emitEvent: false}); + control.patchValue(null, {emitEvent: false}); + } + } } else if (type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { const certificateValue: string = this.provisionConfigurationFormGroup.get('certificateValue').value; if (!certificateValue || !certificateValue.length) { @@ -114,12 +111,14 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateRegExPattern').patchValue('[\\w]*', {emitEvent: false}); } const allowCreateNewDevicesByX509Certificate: boolean | null = this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').value; - if (isBoolean(allowCreateNewDevicesByX509Certificate)) { + if (!isBoolean(allowCreateNewDevicesByX509Certificate)) { this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').patchValue(true, {emitEvent: false}); } this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); } else { const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; if (!provisionDeviceSecret || !provisionDeviceSecret.length) { @@ -133,6 +132,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); } }); this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { From 24d69651cb39fe6bedfdac64be89f8dc0b41dd8c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 23 Mar 2023 15:20:08 +0200 Subject: [PATCH 20/45] Fix space inside EncryptionUtil and fix failed-test, while doing validation --- .../java/org/thingsboard/server/common/msg/EncryptionUtil.java | 3 ++- .../server/dao/device/DeviceProfileServiceImpl.java | 2 +- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index 668dd28edc..e944a8dd92 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -39,7 +39,8 @@ public class EncryptionUtil { return input.replaceAll("\n", "") .replaceAll("\r", "") .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n") - .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n"); + .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n") + .trim(); } public static String pubkTrimNewLines(String input) { 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 6d19190199..c4ee12395f 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 @@ -136,7 +136,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Fri, 24 Mar 2023 13:33:28 +0200 Subject: [PATCH 21/45] Device profile provision - fix for X509 provision strategy in provisionConfigurationFormGroup: - added resetFormControls() to reset redundant form controls; - allowCreateNewDevicesByX509Certificate added required validator; --- ...ofile-provision-configuration.component.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 465588fa24..caebc701d2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -90,7 +90,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu provisionDeviceKey: [{value: null, disabled: true}, Validators.required], certificateValue: [{value: null, disabled: true}, Validators.required], certificateRegExPattern: [{value: null, disabled: true}, Validators.required], - allowCreateNewDevicesByX509Certificate: [{value: null, disabled: true}] + allowCreateNewDevicesByX509Certificate: [{value: null, disabled: true}, Validators.required] }); this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { @@ -117,8 +117,6 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('certificateValue').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('certificateRegExPattern').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').enable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); } else { const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; if (!provisionDeviceSecret || !provisionDeviceSecret.length) { @@ -130,9 +128,6 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateValue').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('certificateRegExPattern').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').disable({emitEvent: false}); } }); this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { @@ -178,6 +173,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu private updateModel(): void { let deviceProvisionConfiguration: DeviceProvisionConfiguration = null; + this.resetFormControls(this.provisionConfigurationFormGroup.value); if (this.provisionConfigurationFormGroup.valid) { deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue(); } @@ -194,4 +190,15 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu horizontalPosition: 'right' })); } + + private resetFormControls(value: DeviceProvisionConfiguration) { + if (value.type === DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES || value.type === DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES) { + this.provisionConfigurationFormGroup.get('certificateValue').reset({value: null, disabled: true}, {emitEvent: false}); + this.provisionConfigurationFormGroup.get('certificateRegExPattern').reset({value: null, disabled: true}, {emitEvent: false}); + this.provisionConfigurationFormGroup.get('allowCreateNewDevicesByX509Certificate').reset({value: null, disabled: true}, {emitEvent: false}); + } else if (value.type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').reset({value: null, disabled: true}, {emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').reset({value: null, disabled: true}, {emitEvent: false}); + } + } } From be826687eb9bee9b29b969f4d7d366f3f3c89745 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 24 Mar 2023 14:42:01 +0200 Subject: [PATCH 22/45] Device profile provision strategy X509: updated hint locale hint-allow-create-new-devices --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e9e839ce8b..3dceb760ef 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1501,7 +1501,7 @@ "certificate-chain": "X509 Certificates Chain", "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN).

Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain.

Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known Certificate Authority (CA).", "allow-create-new-devices": "Create new devices", - "hint-allow-create-new-devices": "Hint: if selected new devices will be created with correct device credentials from client certificate.", + "hint-allow-create-new-devices": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", "certificate-value": "Certificate in PEM format", "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", From 2ac861876e1bc0f78e618cce7a15285158a8f0ce Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 29 Mar 2023 11:40:41 +0300 Subject: [PATCH 23/45] Refactoring: fix conflict, add logs --- .../update/DefaultCacheCleanupService.java | 3 + .../transport/DefaultTransportApiService.java | 77 +++++++++++-------- .../src/main/resources/thingsboard.yml | 8 +- .../DefaultTransportApiServiceTest.java | 17 ++-- ...ertificateChainProvisionConfiguration.java | 7 +- .../mqtt/MqttSslHandlerProvider.java | 10 ++- .../server/common/transport/util/SslUtil.java | 1 + .../dao/device/DeviceProfileServiceImpl.java | 22 +++--- 8 files changed, 85 insertions(+), 60 deletions(-) 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 Date: Wed, 29 Mar 2023 13:26:14 +0300 Subject: [PATCH 24/45] Fix yml extra-config --- application/src/main/resources/thingsboard.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c7102f40a0..d96e50ca7f 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:true}" + enabled: "${MQTT_SSL_ENABLED:false}" # 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:/home/developer/server.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.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:/home/developer/server_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) From e842c3da080cc8ff3af08ca1a6ff330a93f2d243 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 29 Mar 2023 18:53:36 +0300 Subject: [PATCH 25/45] Rewrite test using self-signed test x509 certificate --- .../transport/DefaultTransportApiService.java | 6 ++- .../DefaultTransportApiServiceTest.java | 52 ++++++++++++++++--- .../resources/mqtt/x509ChainProvision.pem | 28 ++++++++++ 3 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 application/src/test/resources/mqtt/x509ChainProvision.pem 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 ca886cf49b..f1ff95d3f2 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 @@ -252,15 +252,17 @@ public class DefaultTransportApiService implements TransportApiService { } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); if (deviceProfile != null) { - X509CertificateChainProvisionConfiguration x509Configuration = new X509CertificateChainProvisionConfiguration(); + X509CertificateChainProvisionConfiguration x509Configuration; if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); } else { log.warn("Device Profile provision configuration is not X509CertificateChainProvisionConfiguration"); + return getEmptyTransportApiResponseFuture(); } String deviceName = extractDeviceNameFromCertificateCNByRegEx(chain.get(0), x509Configuration.getCertificateRegExPattern()); if (deviceName == null) { - log.warn("Device name has to be extract by regex from CN."); + log.warn("Cannot extract device name from device's CN using regex [{}]", x509Configuration.getCertificateRegExPattern()); + return getEmptyTransportApiResponseFuture(); } Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); String updateDeviceCertificateValue = chain.get(0); 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 e98513826a..a31d867242 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.service.transport; import com.google.common.util.concurrent.Futures; import lombok.extern.slf4j.Slf4j; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.mock.mockito.MockBean; @@ -49,7 +50,14 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; @@ -94,9 +102,20 @@ public class DefaultTransportApiServiceTest { @SpyBean DefaultTransportApiService service; - 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}; + private String certificateChain; + private String[] chain; + + @Before + public void setUp() { + String filePath = "src/test/resources/mqtt/x509ChainProvisionTest.pem"; + try { + certificateChain = Files.readString(Paths.get(filePath)); + certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain); + chain = fetchLeafCertificateFromChain(certificateChain); + } catch (IOException e) { + throw new RuntimeException(e); + } + } @Test public void validateExistingDeviceX509Certificate() { @@ -106,7 +125,7 @@ public class DefaultTransportApiServiceTest { var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(chain[0]); + service.validateOrCreateDeviceX509Certificate(certificateChain); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); } @@ -123,7 +142,7 @@ public class DefaultTransportApiServiceTest { when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(chain[1]); + service.validateOrCreateDeviceX509Certificate(certificateChain); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); @@ -143,7 +162,7 @@ public class DefaultTransportApiServiceTest { when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - service.validateOrCreateDeviceX509Certificate(chain[1]); + service.validateOrCreateDeviceX509Certificate(certificateChain); verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); @@ -164,7 +183,7 @@ public class DefaultTransportApiServiceTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); provision.setCertificateValue(certificateValue); - provision.setCertificateRegExPattern("^$"); + provision.setCertificateRegExPattern("([^@]+)"); provision.setAllowCreateNewDevicesByX509Certificate(true); deviceProfileData.setProvisionConfiguration(provision); deviceProfile.setProfileData(deviceProfileData); @@ -178,4 +197,23 @@ public class DefaultTransportApiServiceTest { device.setId(new DeviceId(UUID.randomUUID())); return device; } + + public static String certTrimNewLinesForChainInDeviceProfile(String input) { + return input.replaceAll("\n", "") + .replaceAll("\r", "") + .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n") + .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n") + .trim(); + } + + private String[] fetchLeafCertificateFromChain(String value) { + List chain = new ArrayList<>(); + String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) { + chain.add(matcher.group(0)); + } + return chain.toArray(new String[0]); + } } diff --git a/application/src/test/resources/mqtt/x509ChainProvision.pem b/application/src/test/resources/mqtt/x509ChainProvision.pem new file mode 100644 index 0000000000..b2ec300f78 --- /dev/null +++ b/application/src/test/resources/mqtt/x509ChainProvision.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIICMTCCAdegAwIBAgIUI9dBuwN6pTtK6uZ03rkiCwV4wEYwCgYIKoZIzj0EAwIw +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlQ2VydGlmaWNhdGVAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MB4XDTIzMDMyOTE0NTYxN1oXDTI0MDMyODE0NTYxN1ow +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlQ2VydGlmaWNhdGVAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9Zo791qK +QiGNBm11r4ZGxh+w+ossZL3xc46ufq5QckQHP7zkD2XDAcmP5GvdkM1sBFN9AWaC +kQfNnWmfERsOOKNTMFEwHQYDVR0OBBYEFFFc5uyCyglQoZiKhzXzMcQ3BKORMB8G +A1UdIwQYMBaAFFFc5uyCyglQoZiKhzXzMcQ3BKORMA8GA1UdEwEB/wQFMAMBAf8w +CgYIKoZIzj0EAwIDSAAwRQIhANbA9CuhoOifZMMmqkpuld+65CR+ItKdXeRAhLMZ +uccuAiB0FSQB34zMutXrZj1g8Gl5OkE7YryFHbei1z0SveHR8g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICMTCCAdegAwIBAgIUUEKxS9hTz4l+oLUMF0LV6TC/gCIwCgYIKoZIzj0EAwIw +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlUHJvZmlsZUNlcnRAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MB4XDTIzMDMyOTE0NTczNloXDTI0MDMyODE0NTczNlow +bjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGlu +Z3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlUHJvZmlsZUNlcnRAWDUwOVBy +b3Zpc2lvblN0cmF0ZWd5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECMlWO72k +rDoUL9FQjUmSCetkhaEGJUfQkdSfkLSNa0GyAEIMbfmzI4zITeapunu4rGet3EMy +LydQzuQanBicp6NTMFEwHQYDVR0OBBYEFHpZ78tPnztNii4Da/yCw6mhEIL3MB8G +A1UdIwQYMBaAFHpZ78tPnztNii4Da/yCw6mhEIL3MA8GA1UdEwEB/wQFMAMBAf8w +CgYIKoZIzj0EAwIDSAAwRQIgJ7qyMFqNcwSYkH6o+UlQXzLWfwZbNjVk+aR7foAZ +NGsCIQDsd7v3WQIGHiArfZeDs1DLEDuV/2h6L+ZNoGNhEKL+1A== +-----END CERTIFICATE----- From 07e00e487c666ce000d1144a0c2b14d2e893ba1c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 30 Mar 2023 09:45:38 +0300 Subject: [PATCH 26/45] Rename file for x509 test chain --- .../mqtt/{x509ChainProvision.pem => x509ChainProvisionTest.pem} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename application/src/test/resources/mqtt/{x509ChainProvision.pem => x509ChainProvisionTest.pem} (100%) diff --git a/application/src/test/resources/mqtt/x509ChainProvision.pem b/application/src/test/resources/mqtt/x509ChainProvisionTest.pem similarity index 100% rename from application/src/test/resources/mqtt/x509ChainProvision.pem rename to application/src/test/resources/mqtt/x509ChainProvisionTest.pem From 99bc639cbe33eff4d26449584913a113c3326fe3 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 30 Mar 2023 16:03:39 +0300 Subject: [PATCH 27/45] Device profile -> Device provisioning -> X509 feature: - change tb-hints Info and Regex examples as tb-help-popup - fix locales for hint- --- ...ile-provision-configuration.component.html | 22 ++++++++++++------- .../en_US/device-profile/x509-chain-hint.md | 18 +++++++++++++++ .../x509-chain-regex-examples.md | 15 +++++++++++++ .../assets/locale/locale.constant-en_US.json | 7 +++--- 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md create mode 100644 ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 3b2b92ce98..6c5da8b531 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -35,14 +35,18 @@ -
- - {{ readMore ? (' ' + ('action.less' | translate)) : ('action.more' | translate) }} +
+ +
{{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} -
+
device-profile.provision-strategy-x509.certificate-value @@ -53,14 +57,16 @@ device-profile.provision-strategy-x509.cn-regex-variable +
{{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} - device-profile.provision-strategy-x509.hint-cn-regex-variable + device-profile.provision-strategy-x509.cn-regex-variable-hint
-
-
diff --git a/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md new file mode 100644 index 0000000000..f1aa10f921 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-hint.md @@ -0,0 +1,18 @@ +##### X509 Certificate Chain info + +X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. + +This strategy can: +* check for pre-provisioned devices +* update X.509 device credentials +* create new devices + +The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from *Common Name (CN)*. + +Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. + +The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). + +If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain. + +Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known *Certificate Authority (CA)*. diff --git a/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md new file mode 100644 index 0000000000..bb8a069473 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/device-profile/x509-chain-regex-examples.md @@ -0,0 +1,15 @@ +#### Examples of RegEx usage + +* **Pattern:** .* - matches any character (until line terminators) +
**CN sample:** DeviceName\nAdditionalInfo +
**Pattern matches:** DeviceName + +* **Pattern:** ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol) +
**CN sample:** DeviceName@AdditionalInfo +
**Pattern matches:** DeviceName + +* **Pattern:** [\w]*$ (equivalent to [a-zA-Z0-9_]\*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string +
**CN sample:** AdditionalInfo2110#DeviceName_01 +
**Pattern matches:** DeviceName_01 + +**Note:** Client will get error response in case regex is failed to match. diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ec9a151ede..de26259c00 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1505,15 +1505,14 @@ "provision-secret-copied-message": "Provision secret has been copied to clipboard", "provision-strategy-x509": { "certificate-chain": "X509 Certificates Chain", - "hint-certificate-chain": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication. This strategy can check for pre-provisioned devices, update X.509 device credentials, or create new devices. The user uploads X.509 certificate to the device profile and sets a regular expression to fetch the device name from Common Name (CN).

Client certificates must be signed by X.509 certificate, pre-uploaded for this device profile to provision devices by the strategy. The client must establish a TLS connection using the entire chain of certificates (this chain must include device profile X.509 certificate on the last level). If a device already exists with outdated X.509 credentials, this strategy automatically updates it with the device certificate's credentials from the chain.

Important: Uploaded certificates should be neither root nor intermediate certificates that are provided by a well-known Certificate Authority (CA).", + "certificate-chain-hint": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication.", "allow-create-new-devices": "Create new devices", - "hint-allow-create-new-devices": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", + "allow-create-new-devices-hint": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", "certificate-value": "Certificate in PEM format", "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", "cn-regex-variable-required": "CN Regular Expression variable is required", - "hint-cn-regex-variable": "Required to fetch device name from device's X509 certificate's common name.", - "regex-examples": "Examples of RegEx usage:
  1. Pattern: .* - matches any character (until line terminators)
    CN sample: DeviceName\\nAdditionalInfo
    Pattern matches: DeviceName

  2. Pattern: ^([^@]+) - matches any string that starts with one or more characters that are not the @ symbol (@ could be replaced by any other symbol)
    CN sample: DeviceName@AdditionalInfo
    Pattern matches: DeviceName

  3. Pattern: [\\w]*$ (equivalent to [a-zA-Z0-9_]*$) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
    CN sample: AdditionalInfo2110#DeviceName_01
    Pattern matches: DeviceName_01
Note: Client will get error response in case regex is failed to match." + "cn-regex-variable-hint": "Required to fetch device name from device's X509 certificate's common name." }, "condition": "Condition", "condition-type": "Condition type", From 722b6f992cc5d61d979cee219ed93aedf8d1a71a Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 30 Mar 2023 16:37:47 +0300 Subject: [PATCH 28/45] Device profile -> Device provision stratetgy X509 minor changes in tb-help-popup html --- ...ce-profile-provision-configuration.component.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 6c5da8b531..afbaa924ac 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -37,11 +37,11 @@
-
+ trigger-text="{{ 'action.more' | translate }}">
{{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }} @@ -57,11 +57,11 @@ device-profile.provision-strategy-x509.cn-regex-variable -
+ [tb-help-popup-style]="{maxWidth: '820px'}"> {{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }} From 98528f73eba0d81c44320223cd81483f8f1b30e6 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 30 Mar 2023 16:54:06 +0300 Subject: [PATCH 29/45] Remove 'Hint:' in locale allow-create-new-devices-hint --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index de26259c00..6f9653ca96 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1507,7 +1507,7 @@ "certificate-chain": "X509 Certificates Chain", "certificate-chain-hint": "X.509 certificates strategy is used to provision devices by client certificates in two-way TLS communication.", "allow-create-new-devices": "Create new devices", - "allow-create-new-devices-hint": "Hint: if selected new devices will be created and client certificate will be used as device credentials.", + "allow-create-new-devices-hint": "If selected new devices will be created and client certificate will be used as device credentials.", "certificate-value": "Certificate in PEM format", "certificate-value-required": "Certificate in PEM format is required", "cn-regex-variable": "CN Regular Expression variable", From bf3346d1ec8c5215d3a5ee9e008d0d945c0bad55 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 7 Apr 2023 11:51:45 +0300 Subject: [PATCH 30/45] Device Profile provision X.509 strategy update: save certificateValue as provisionDeviceSecret --- ...device-profile-provision-configuration.component.ts | 10 ++++++---- .../components/profile/device-profile.component.ts | 2 -- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index caebc701d2..1ad497ddbb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -62,7 +62,6 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu deviceProvisionType = DeviceProvisionType; deviceProvisionTypes = Object.keys(DeviceProvisionType); deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap; - readMore: boolean; private requiredValue: boolean; get required(): boolean { @@ -143,16 +142,16 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } writeValue(value: DeviceProvisionConfiguration | null): void { - if (isDefinedAndNotNull(value)){ + if (isDefinedAndNotNull(value)) { this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false}); } else { this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED}); } } - setDisabledState(isDisabled: boolean){ + setDisabledState(isDisabled: boolean) { this.disabled = isDisabled; - if (this.disabled){ + if (this.disabled) { this.provisionConfigurationFormGroup.disable({emitEvent: false}); } else { if (this.provisionConfigurationFormGroup.get('type').value !== DeviceProvisionType.DISABLED) { @@ -176,6 +175,9 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.resetFormControls(this.provisionConfigurationFormGroup.value); if (this.provisionConfigurationFormGroup.valid) { deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue(); + if (deviceProvisionConfiguration.type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + deviceProvisionConfiguration.provisionDeviceSecret = deviceProvisionConfiguration.certificateValue; + } } this.propagateChange(deviceProvisionConfiguration); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 9a6d150ac1..db6539318d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -105,7 +105,6 @@ export class DeviceProfileComponent extends EntityComponent { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, - certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; @@ -189,7 +188,6 @@ export class DeviceProfileComponent extends EntityComponent { type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret, - certificateValue: entity?.profileData?.provisionConfiguration?.certificateValue, certificateRegExPattern: entity?.profileData?.provisionConfiguration?.certificateRegExPattern, allowCreateNewDevicesByX509Certificate: entity?.profileData?.provisionConfiguration?.allowCreateNewDevicesByX509Certificate }; From d1a21900f0e713155d4f2cf29a161e8695826aca Mon Sep 17 00:00:00 2001 From: deaflynx Date: Fri, 7 Apr 2023 12:00:38 +0300 Subject: [PATCH 31/45] DeviceProfileProvisionConfigurationComponent writeValue set certificateValue as provisionDeviceSecret --- .../device-profile-provision-configuration.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 1ad497ddbb..54a9b98aa5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -143,6 +143,9 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu writeValue(value: DeviceProvisionConfiguration | null): void { if (isDefinedAndNotNull(value)) { + if (value.type === DeviceProvisionType.X509_CERTIFICATE_CHAIN) { + value.certificateValue = value.provisionDeviceSecret; + } this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false}); } else { this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED}); From 55adb3d12a56a0a044263e4fc5eb486c4fa48c92 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Apr 2023 13:07:08 +0300 Subject: [PATCH 32/45] Refactor x509: add logic of rotating x509 credentials and creating new device to DeviceProvisionService --- .../main/data/upgrade/3.4.4/schema_update.sql | 10 -- .../device/DeviceProvisionServiceImpl.java | 76 ++++++----- .../transport/DefaultTransportApiService.java | 124 ++++++------------ .../src/main/resources/thingsboard.yml | 8 +- .../BaseDeviceProfileControllerTest.java | 11 +- .../DefaultTransportApiServiceTest.java | 65 ++++----- .../dao/device/DeviceProfileService.java | 2 +- .../server/common/data/DeviceProfile.java | 5 - ...ertificateChainProvisionConfiguration.java | 8 +- .../mqtt/MqttSslHandlerProvider.java | 9 +- .../server/common/transport/util/SslUtil.java | 21 +++ .../dao/device/DeviceProfileCacheKey.java | 14 +- .../server/dao/device/DeviceProfileDao.java | 1 - .../dao/device/DeviceProfileEvictEvent.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 40 +++--- .../server/dao/model/ModelConstants.java | 1 - .../dao/model/sql/DeviceProfileEntity.java | 5 - .../validator/DeviceProfileDataValidator.java | 24 ++-- .../sql/device/DeviceProfileRepository.java | 2 - .../dao/sql/device/JpaDeviceProfileDao.java | 5 - .../main/resources/sql/schema-entities.sql | 2 - 21 files changed, 188 insertions(+), 247 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 9bb85e3447..c9406cb38c 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -424,16 +424,6 @@ $$; -- ALARM FUNCTIONS END --- DEVICE PROFILE CERTIFICATE START - -ALTER TABLE device_profile - ADD COLUMN IF NOT EXISTS certificate_hash varchar, - DROP CONSTRAINT IF EXISTS device_profile_credentials_hash_unq_key, - ADD CONSTRAINT device_profile_credentials_hash_unq_key UNIQUE (certificate_hash); - --- DEVICE PROFILE CERTIFICATE END - - -- TTL DROP PARTITIONS FUNCTIONS UPDATE START DROP PROCEDURE IF EXISTS drop_partitions_by_max_ttl(character varying, bigint, bigint); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 80b51f7ad5..dacddc8a1d 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; @@ -29,6 +28,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -36,15 +36,19 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceProfileDao; +import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; @@ -77,35 +81,23 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private static final String DEVICE_PROVISION_STATE = "provisionState"; private static final String PROVISIONED_STATE = "provisioned"; - @Autowired - TbClusterService clusterService; + private final TbClusterService clusterService; + private final DeviceProfileService deviceProfileService; + private final DeviceService deviceService; + private final DeviceCredentialsService deviceCredentialsService; + private final AttributesService attributesService; + private final AuditLogService auditLogService; + private final PartitionService partitionService; - @Autowired - DeviceDao deviceDao; - - @Autowired - DeviceProfileDao deviceProfileDao; - - @Autowired - DeviceService deviceService; - - @Autowired - DeviceCredentialsService deviceCredentialsService; - - @Autowired - AttributesService attributesService; - - @Autowired - DeviceStateService deviceStateService; - - @Autowired - AuditLogService auditLogService; - - @Autowired - PartitionService partitionService; - - public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider) { + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, TbClusterService clusterService, DeviceDao deviceDao, DeviceProfileDao deviceProfileDao, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, DeviceStateService deviceStateService, AuditLogService auditLogService, PartitionService partitionService) { ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); + this.clusterService = clusterService; + this.deviceProfileService = deviceProfileService; + this.deviceService = deviceService; + this.deviceCredentialsService = deviceCredentialsService; + this.attributesService = attributesService; + this.auditLogService = auditLogService; + this.partitionService = partitionService; } @Override @@ -124,14 +116,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } - DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); + DeviceProfile targetProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequestKey); if (targetProfile == null || targetProfile.getProfileData().getProvisionConfiguration() == null || targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() == null) { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } - Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); + Device targetDevice = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); switch (targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: @@ -155,6 +147,22 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } } break; + case X509_CERTIFICATE_CHAIN: + X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId()); + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials, + updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE); + } + return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); + } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { + return createDevice(provisionRequest, targetProfile); + } else { + log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); + } } throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } @@ -209,6 +217,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } } + private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue, + DeviceCredentialsType credentialsType) { + log.trace("Updating device credentials [{}] with certificate value [{}]", deviceCredentials, certificateValue); + deviceCredentials.setCredentialsValue(certificateValue); + deviceCredentials.setCredentialsType(credentialsType); + return deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); + } + private ListenableFuture> saveProvisionStateAttribute(Device device) { return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), 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 f1ff95d3f2..dada1985f3 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 @@ -25,7 +25,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.binary.Base64; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.ota.OtaPackageDataCache; @@ -34,7 +33,6 @@ import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; @@ -49,6 +47,7 @@ import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfigu import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; @@ -76,6 +75,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -106,10 +106,6 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -250,38 +246,13 @@ public class DefaultTransportApiService implements TransportApiService { if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return getDeviceInfo(credentials); } - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash); if (deviceProfile != null) { - X509CertificateChainProvisionConfiguration x509Configuration; - if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { - x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); - } else { - log.warn("Device Profile provision configuration is not X509CertificateChainProvisionConfiguration"); - return getEmptyTransportApiResponseFuture(); - } - String deviceName = extractDeviceNameFromCertificateCNByRegEx(chain.get(0), x509Configuration.getCertificateRegExPattern()); - if (deviceName == null) { - log.warn("Cannot extract device name from device's CN using regex [{}]", x509Configuration.getCertificateRegExPattern()); - return getEmptyTransportApiResponseFuture(); - } - 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() == 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, 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, DeviceCredentialsType.X509_CERTIFICATE); - return getDeviceInfo(deviceCredentials); - } else { - log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); + String updatedDeviceProvisionSecret = chain.get(0); + ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); + ProvisionResponse provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); + if (provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { + return getDeviceInfo(provisionResponse.getDeviceCredentials()); } } } @@ -728,9 +699,37 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } + private ProvisionRequest createProvisionRequest(DeviceProfile deviceProfile, String certificateValue) { + String deviceName = getDeviceName(deviceProfile, certificateValue); + + ProvisionDeviceProfileCredentials provisionDeviceProfileCredentials = new ProvisionDeviceProfileCredentials( + deviceProfile.getProvisionDeviceKey(), + deviceProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() + ); + ProvisionDeviceCredentialsData provisionDeviceCredentialsData = new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue); + + return new ProvisionRequest(deviceName, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); + } + + + private String getDeviceName(DeviceProfile deviceProfile, String certificateValue) { + X509CertificateChainProvisionConfiguration configuration = new X509CertificateChainProvisionConfiguration(); + String deviceName = null; + if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, configuration.getCertificateRegExPattern()); + if (deviceName == null) { + log.warn("Cannot extract device name from device's CN using regex [{}]", configuration.getCertificateRegExPattern()); + } + } else { + log.warn("Device Profile configuration: expected [{}], actual [{}]", configuration.getType(), deviceProfile.getProvisionType()); + } + return deviceName; + } + private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { try { - String commonName = SslUtil.parseCommonName(readCertFile(x509Value)); + String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value)); log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(commonName); @@ -751,53 +750,4 @@ public class DefaultTransportApiService implements TransportApiService { } return chain; } - - private X509Certificate readCertFile(String fileContent) { - X509Certificate certificate = null; - 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); - return deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); - } - - 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); - createDevCredentials.setCredentialsId(certificateHash); - createDevCredentials.setCredentialsValue(certificateValue); - return deviceCredentialsService.createDeviceCredentials(tenantId, createDevCredentials); - } - - 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); - 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..1a845b2188 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/x509/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/x509/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/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index e3017e4e19..e715e418ec 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -302,14 +302,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfileWithSameCertificateHash() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); - deviceProfile.setCertificateHash("Certificate Hash"); - doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); + deviceProfile.setProvisionDeviceKey("Certificate hash"); + + doPost("/api/deviceProfile", deviceProfile) + .andExpect(status().isOk()); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); - deviceProfile2.setCertificateHash("Certificate Hash"); + deviceProfile2.setProvisionDeviceKey("Certificate hash"); Mockito.reset(tbClusterService, auditLogService); - String msgError = "Device profile with such certificate hash already exists"; + String msgError = "Device profile with such provision device key already exists!"; doPost("/api/deviceProfile", deviceProfile2) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); 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 a31d867242..21a03c6167 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 @@ -40,6 +40,8 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -130,43 +132,42 @@ public class DefaultTransportApiServiceTest { } @Test - public void updateExistingDeviceX509Certificate() { + public void provisionDeviceX509Certificate() { var deviceProfile = createDeviceProfile(chain[1]); - when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); + when(deviceProfileService.findDeviceProfileByProvisionDeviceKey(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(chain[0], device.getId()); - when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); + when(deviceCredentialsService.findDeviceCredentialsByCredentialsId(any())).thenReturn(null); when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); + var provisionResponse = createProvisionResponse(deviceCredentials); + when(deviceProvisionService.provisionDevice(any())).thenReturn(provisionResponse); + service.validateOrCreateDeviceX509Certificate(certificateChain); - verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); - verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); - verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); - verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + verify(deviceProfileService, times(1)).findDeviceProfileByProvisionDeviceKey(any()); + verify(deviceService, times(1)).findDeviceByIdAsync(any(), any()); + verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any()); + verify(deviceProvisionService, times(1)).provisionDevice(any()); } - @Test - public void createDeviceByX509Provision() { - var deviceProfile = createDeviceProfile(chain[1]); - when(deviceProfileService.findDeviceProfileByCertificateHash(any())).thenReturn(deviceProfile); + private DeviceProfile createDeviceProfile(String certificateValue) { + X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); + provision.setProvisionDeviceSecret(certificateValue); + provision.setCertificateRegExPattern("([^@]+)"); + provision.setAllowCreateNewDevicesByX509Certificate(true); - var device = createDevice(); - when(deviceService.saveDevice(any())).thenReturn(device); - when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device)); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setProvisionConfiguration(provision); - var deviceCredentials = createDeviceCredentials(chain[0], device.getId()); - when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials); - when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials); - - service.validateOrCreateDeviceX509Certificate(certificateChain); - verify(deviceProfileService, times(1)).findDeviceProfileByCertificateHash(any()); - verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any()); - verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); - verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any()); + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setProvisionDeviceKey(EncryptionUtil.getSha3Hash(certificateValue)); + deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN); + return deviceProfile; } private DeviceCredentials createDeviceCredentials(String certificateValue, DeviceId deviceId) { @@ -178,26 +179,16 @@ public class DefaultTransportApiServiceTest { return deviceCredentials; } - private DeviceProfile createDeviceProfile(String certificateValue) { - DeviceProfile deviceProfile = new DeviceProfile(); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration(); - provision.setCertificateValue(certificateValue); - provision.setCertificateRegExPattern("([^@]+)"); - provision.setAllowCreateNewDevicesByX509Certificate(true); - deviceProfileData.setProvisionConfiguration(provision); - deviceProfile.setProfileData(deviceProfileData); - deviceProfile.setCertificateHash(EncryptionUtil.getSha3Hash(certificateValue)); - deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN); - return deviceProfile; - } - private Device createDevice() { Device device = new Device(); device.setId(new DeviceId(UUID.randomUUID())); return device; } + private ProvisionResponse createProvisionResponse(DeviceCredentials deviceCredentials) { + return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); + } + public static String certTrimNewLinesForChainInDeviceProfile(String input) { return input.replaceAll("\n", "") .replaceAll("\r", "") diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java index 90915c3ffe..e765cc5030 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java @@ -39,7 +39,7 @@ public interface DeviceProfileService extends EntityDaoService { PageData findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType); - DeviceProfile findDeviceProfileByCertificateHash(String credentialsId); + DeviceProfile findDeviceProfileByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 6d2c37e67d..7ff7d72023 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -66,10 +66,6 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; @ApiModelProperty(position = 15, value = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 18, value = "CA certificate hash. ") - private String certificateHash; - - @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the root rule chain will be used to process those messages.") @@ -125,7 +121,6 @@ public class DeviceProfile extends SearchTextBased implements H this.firmwareId = deviceProfile.getFirmwareId(); this.softwareId = deviceProfile.getSoftwareId(); this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId(); - this.certificateHash = deviceProfile.getCertificateHash(); this.externalId = deviceProfile.getExternalId(); } 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 34fee34ab3..d5edafca69 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,16 +24,10 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; @NoArgsConstructor public class X509CertificateChainProvisionConfiguration implements DeviceProfileProvisionConfiguration { - private String certificateValue; + private String provisionDeviceSecret; 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 4b6e15292a..f7a85425fe 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,12 +164,10 @@ public class MqttSslHandlerProvider { } }); latch.await(10, TimeUnit.SECONDS); - if (chain.length == 1) { - if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + if (chain.length == 1) { throw new CertificateException("Invalid Device Certificate"); - } - } else { - if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + } else { throw new CertificateException("Invalid Chain of X509 Certificates"); } } @@ -177,6 +175,5 @@ public class MqttSslHandlerProvider { 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 da04d50f50..089718ec05 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 @@ -16,6 +16,7 @@ package org.thingsboard.server.common.transport.util; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; @@ -24,8 +25,11 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.springframework.util.Base64Utils; import org.thingsboard.server.common.msg.EncryptionUtil; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; /** @@ -53,6 +57,23 @@ public class SslUtil { return stringBuilder.toString(); } + public static X509Certificate readCertFile(String fileContent) { + X509Certificate certificate = null; + 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; + } + public static String parseCommonName(X509Certificate certificate) { X500Name x500name; try { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index d15bde226b..715923f19b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -29,14 +29,14 @@ public class DeviceProfileCacheKey implements Serializable { private final TenantId tenantId; private final String name; private final DeviceProfileId deviceProfileId; - private final String certificateHash; + private final String provisionDeviceKey; private final boolean defaultProfile; - private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, String certificateHash, boolean defaultProfile) { + private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, String provisionDeviceKey, boolean defaultProfile) { this.tenantId = tenantId; this.name = name; this.deviceProfileId = deviceProfileId; - this.certificateHash = certificateHash; + this.provisionDeviceKey = provisionDeviceKey; this.defaultProfile = defaultProfile; } @@ -48,8 +48,8 @@ public class DeviceProfileCacheKey implements Serializable { return new DeviceProfileCacheKey(null, null, id, null, false); } - public static DeviceProfileCacheKey fromCertificateHash(String certificateHash) { - return new DeviceProfileCacheKey(null, null, null, certificateHash, false); + public static DeviceProfileCacheKey fromProvisionDeviceKey(String provisionDeviceKey) { + return new DeviceProfileCacheKey(null, null, null, provisionDeviceKey, false); } public static DeviceProfileCacheKey defaultProfile(TenantId tenantId) { @@ -65,8 +65,8 @@ public class DeviceProfileCacheKey implements Serializable { return deviceProfileId.toString(); } else if (defaultProfile) { return tenantId.toString(); - } else if (certificateHash != null) { - return certificateHash; + } else if (provisionDeviceKey != null) { + return provisionDeviceKey; } return tenantId + "_" + name; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 206f139b8c..387042c627 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -46,5 +46,4 @@ public interface DeviceProfileDao extends Dao, ExportableEntityDa DeviceProfile findByName(TenantId tenantId, String profileName); - DeviceProfile findByCertificateHash(String certificateHash); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index 55b5f994c2..6470bfc584 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -27,6 +27,6 @@ public class DeviceProfileEvictEvent { private final String oldName; private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; - private final String certificateHash; + private final String provisionDeviceKey; } 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 3b4be7aef4..d601bc4ef2 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 @@ -70,7 +70,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findById(tenantId, deviceProfileId.getId()), true); } @@ -120,7 +120,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByName(tenantId, profileName), true); } @@ -128,7 +128,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByCertificateHash(certificateHash), true); + public DeviceProfile findDeviceProfileByProvisionDeviceKey(String provisionDeviceKey) { + log.trace("Executing findDeviceProfileIdByCredentialsId credentialId [{}]", provisionDeviceKey); + validateString(provisionDeviceKey, INCORRECT_PROVISION_DEVICE_KEY + provisionDeviceKey); + return cache.getAndPutInTransaction(DeviceProfileCacheKey.fromProvisionDeviceKey(provisionDeviceKey), + () -> deviceProfileDao.findByProvisionDeviceKey(provisionDeviceKey), true); } @Override @@ -290,7 +290,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService impl @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) private UUID externalId; - @Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_HASH_PROPERTY) - private String certificateHash; - public DeviceProfileEntity() { super(); } @@ -129,7 +126,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.image = deviceProfile.getImage(); this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); - this.certificateHash = deviceProfile.getCertificateHash(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); @@ -192,7 +188,6 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setDefaultDashboardId(new DashboardId(defaultDashboardId)); } deviceProfile.setProvisionDeviceKey(provisionDeviceKey); - deviceProfile.setCertificateHash(certificateHash); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 8f4f89e2a8..dfd8d7b832 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -132,14 +132,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Fri, 7 Apr 2023 13:54:48 +0300 Subject: [PATCH 33/45] Refactoring: remove extra comments, remove spaces etc --- .../main/data/upgrade/3.4.4/schema_update.sql | 1 - .../device/DeviceProvisionServiceImpl.java | 2 +- .../src/main/resources/thingsboard.yml | 8 ++++---- .../dao/device/DeviceProfileCacheKey.java | 20 +++++++++---------- .../server/dao/device/DeviceProfileDao.java | 1 - .../validator/DeviceProfileDataValidator.java | 8 -------- 6 files changed, 15 insertions(+), 25 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 2781c9119d..2eb6a0d6cc 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -424,7 +424,6 @@ $$; -- ALARM FUNCTIONS END - -- TTL DROP PARTITIONS FUNCTIONS UPDATE START DROP PROCEDURE IF EXISTS drop_partitions_by_max_ttl(character varying, bigint, bigint); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index dacddc8a1d..d4bfe9c625 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -89,7 +89,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private final AuditLogService auditLogService; private final PartitionService partitionService; - public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, TbClusterService clusterService, DeviceDao deviceDao, DeviceProfileDao deviceProfileDao, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, DeviceStateService deviceStateService, AuditLogService auditLogService, PartitionService partitionService) { + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, TbClusterService clusterService, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, AuditLogService auditLogService, PartitionService partitionService) { ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); this.clusterService = clusterService; this.deviceProfileService = deviceProfileService; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ef42460108..f745e03ffc 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -741,7 +741,7 @@ transport: # MQTT SSL configuration ssl: # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:true}" + enabled: "${MQTT_SSL_ENABLED:false}" # MQTT SSL bind address bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" # MQTT SSL bind port @@ -755,11 +755,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:/home/developer/x509/server.pem}" + cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.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:/home/developer/x509/server_key.pem}" + key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}" # Server certificate private key password (optional) - key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:}" + key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: # Type of the key store (JKS or PKCS12) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index 715923f19b..6e55b0b6ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -29,31 +29,31 @@ public class DeviceProfileCacheKey implements Serializable { private final TenantId tenantId; private final String name; private final DeviceProfileId deviceProfileId; - private final String provisionDeviceKey; private final boolean defaultProfile; + private final String provisionDeviceKey; - private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, String provisionDeviceKey, boolean defaultProfile) { + private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, boolean defaultProfile, String provisionDeviceKey) { this.tenantId = tenantId; this.name = name; this.deviceProfileId = deviceProfileId; - this.provisionDeviceKey = provisionDeviceKey; this.defaultProfile = defaultProfile; + this.provisionDeviceKey = provisionDeviceKey; } public static DeviceProfileCacheKey fromName(TenantId tenantId, String name) { - return new DeviceProfileCacheKey(tenantId, name, null, null, false); + return new DeviceProfileCacheKey(tenantId, name, null, false, null); } public static DeviceProfileCacheKey fromId(DeviceProfileId id) { - return new DeviceProfileCacheKey(null, null, id, null, false); - } - - public static DeviceProfileCacheKey fromProvisionDeviceKey(String provisionDeviceKey) { - return new DeviceProfileCacheKey(null, null, null, provisionDeviceKey, false); + return new DeviceProfileCacheKey(null, null, id, false, null); } public static DeviceProfileCacheKey defaultProfile(TenantId tenantId) { - return new DeviceProfileCacheKey(tenantId, null, null, null, true); + return new DeviceProfileCacheKey(tenantId, null, null, true, null); + } + + public static DeviceProfileCacheKey fromProvisionDeviceKey(String provisionDeviceKey) { + return new DeviceProfileCacheKey(null, null, null, false, provisionDeviceKey); } /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 387042c627..c221002413 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -45,5 +45,4 @@ public interface DeviceProfileDao extends Dao, ExportableEntityDa DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findByName(TenantId tenantId, String profileName); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index dfd8d7b832..073b685167 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -136,10 +136,6 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Fri, 7 Apr 2023 17:49:56 +0300 Subject: [PATCH 34/45] Add more detailed logs and move extract logic by regex to deviceProvisionService --- .../device/DeviceProvisionServiceImpl.java | 39 +++++++++++++-- .../transport/DefaultTransportApiService.java | 49 ++++++------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index d4bfe9c625..33b2af55fe 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -46,8 +45,6 @@ import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.device.DeviceCredentialsService; -import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; @@ -63,12 +60,13 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Service @@ -102,6 +100,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @Override public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { + fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(provisionRequest); String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) { @@ -160,7 +159,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { return createDevice(provisionRequest, targetProfile); } else { - log.info("Device doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration"); + log.warn("Device with name {} doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration", provisionRequest.getDeviceName()); throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } } @@ -273,4 +272,34 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE; auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); } + + private void fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(ProvisionRequest provisionRequest) { + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequest.getCredentials().getProvisionDeviceKey()); + X509CertificateChainProvisionConfiguration configuration; + if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + String certificateRegEx = configuration.getCertificateRegExPattern(); + String deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, certificateRegEx); + if (deviceName == null) { + log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]",certificateRegEx, certificateValue); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + provisionRequest.setDeviceName(deviceName); + } + } + + private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { + try { + String commonName = SslUtil.parseCommonName(SslUtil.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); + } + return null; + } catch (Exception ignored) {} + return null; + } } 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 dada1985f3..031564aecf 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 @@ -47,9 +47,7 @@ import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfigu import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; -import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -67,7 +65,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; @@ -118,6 +115,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH; import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID; @@ -247,13 +245,17 @@ public class DefaultTransportApiService implements TransportApiService { return getDeviceInfo(credentials); } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash); - if (deviceProfile != null) { + if (deviceProfile != null && deviceProfile.getProvisionType() == X509_CERTIFICATE_CHAIN) { String updatedDeviceProvisionSecret = chain.get(0); ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); - ProvisionResponse provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); - if (provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { + ProvisionResponse provisionResponse = provisionDeviceRequestAndGetResponse(provisionRequest); + if (provisionResponse != null && provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { return getDeviceInfo(provisionResponse.getDeviceCredentials()); + } else { + return getEmptyTransportApiResponseFuture(); } + } else if (deviceProfile != null) { + log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getName(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); } } return getEmptyTransportApiResponseFuture(); @@ -700,44 +702,21 @@ public class DefaultTransportApiService implements TransportApiService { } private ProvisionRequest createProvisionRequest(DeviceProfile deviceProfile, String certificateValue) { - String deviceName = getDeviceName(deviceProfile, certificateValue); - ProvisionDeviceProfileCredentials provisionDeviceProfileCredentials = new ProvisionDeviceProfileCredentials( deviceProfile.getProvisionDeviceKey(), deviceProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() ); ProvisionDeviceCredentialsData provisionDeviceCredentialsData = new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue); - return new ProvisionRequest(deviceName, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); + return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); } - - private String getDeviceName(DeviceProfile deviceProfile, String certificateValue) { - X509CertificateChainProvisionConfiguration configuration = new X509CertificateChainProvisionConfiguration(); - String deviceName = null; - if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { - configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); - deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, configuration.getCertificateRegExPattern()); - if (deviceName == null) { - log.warn("Cannot extract device name from device's CN using regex [{}]", configuration.getCertificateRegExPattern()); - } - } else { - log.warn("Device Profile configuration: expected [{}], actual [{}]", configuration.getType(), deviceProfile.getProvisionType()); - } - return deviceName; - } - - private String extractDeviceNameFromCertificateCNByRegEx(String x509Value, String regex) { + private ProvisionResponse provisionDeviceRequestAndGetResponse(ProvisionRequest provisionRequest) { + ProvisionResponse provisionResponse = null; try { - String commonName = SslUtil.parseCommonName(SslUtil.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; + provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); + } catch (ProvisionFailedException ignored) {} + return provisionResponse; } private List convertX509CertificateChainToList(String certificateChain) { From df18155c815366cfa8c3ca5c9f7b75abc771e39b Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Apr 2023 18:02:25 +0300 Subject: [PATCH 35/45] Refactoring --- .../server/service/transport/DefaultTransportApiService.java | 2 +- .../server/transport/mqtt/MqttSslHandlerProvider.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 031564aecf..8e1a05945f 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 @@ -255,7 +255,7 @@ public class DefaultTransportApiService implements TransportApiService { return getEmptyTransportApiResponseFuture(); } } else if (deviceProfile != null) { - log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getName(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); + log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getId(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType()); } } return getEmptyTransportApiResponseFuture(); 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 f7a85425fe..1cc4f90915 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 @@ -165,6 +165,7 @@ public class MqttSslHandlerProvider { }); latch.await(10, TimeUnit.SECONDS); if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { + log.debug("Failed to find credentials for device certificate chain: {}", chain); if (chain.length == 1) { throw new CertificateException("Invalid Device Certificate"); } else { From b0d35372aad702cf38f0e4a52cc30dbfdfd390bc Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 7 Apr 2023 18:49:04 +0300 Subject: [PATCH 36/45] Replace arguments in comparison to avoid null pointer --- .../server/service/transport/DefaultTransportApiService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8e1a05945f..16aa2d97d3 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 @@ -249,7 +249,7 @@ public class DefaultTransportApiService implements TransportApiService { String updatedDeviceProvisionSecret = chain.get(0); ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); ProvisionResponse provisionResponse = provisionDeviceRequestAndGetResponse(provisionRequest); - if (provisionResponse != null && provisionResponse.getResponseStatus().equals(ProvisionResponseStatus.SUCCESS)) { + if (provisionResponse != null && ProvisionResponseStatus.SUCCESS.equals(provisionResponse.getResponseStatus())) { return getDeviceInfo(provisionResponse.getDeviceCredentials()); } else { return getEmptyTransportApiResponseFuture(); From e71f6bcc267c16b7ca9ddf8daa88f04689ccb5f7 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 10 Apr 2023 09:35:32 +0300 Subject: [PATCH 37/45] Refactoring x509 provisioning, fix tests --- .../service/device/DeviceProvisionServiceImpl.java | 6 ++---- .../service/transport/DefaultTransportApiService.java | 9 +++++---- .../server/dao/device/DeviceProfileServiceImpl.java | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 33b2af55fe..f9f24e43b2 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -275,9 +275,8 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(ProvisionRequest provisionRequest) { DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequest.getCredentials().getProvisionDeviceKey()); - X509CertificateChainProvisionConfiguration configuration; - if (deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { - configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); + if (deviceProfile != null && deviceProfile.getProfileData() != null && deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); String certificateRegEx = configuration.getCertificateRegExPattern(); String deviceName = extractDeviceNameFromCertificateCNByRegEx(certificateValue, certificateRegEx); @@ -298,7 +297,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { if (matcher.find()) { return matcher.group(0); } - return null; } catch (Exception ignored) {} return null; } 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 16aa2d97d3..226f716225 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 @@ -712,11 +712,12 @@ public class DefaultTransportApiService implements TransportApiService { } private ProvisionResponse provisionDeviceRequestAndGetResponse(ProvisionRequest provisionRequest) { - ProvisionResponse provisionResponse = null; try { - provisionResponse = deviceProvisionService.provisionDevice(provisionRequest); - } catch (ProvisionFailedException ignored) {} - return provisionResponse; + return deviceProvisionService.provisionDevice(provisionRequest); + } catch (ProvisionFailedException e) { + log.error(e.getMessage()); + } + return null; } private List convertX509CertificateChainToList(String certificateChain) { 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 d601bc4ef2..6883ec567a 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 @@ -221,7 +221,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByProvisionDeviceKey(provisionDeviceKey), true); From 4a216070d5bcb4b3cc002cb34075c129755cb845 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 10 Apr 2023 10:45:50 +0300 Subject: [PATCH 38/45] Replace converting from string chain to list from method to streams --- .../transport/DefaultTransportApiService.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) 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 226f716225..27f20bb5fb 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 @@ -103,7 +103,6 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -111,7 +110,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -130,6 +128,8 @@ public class DefaultTransportApiService implements TransportApiService { private static final ObjectMapper mapper = new ObjectMapper(); + private static final Pattern X509_CERTIFICATE_TRIM_CHAIN_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"); + private final TbDeviceProfileCache deviceProfileCache; private final TbTenantProfileCache tenantProfileCache; private final TbApiUsageStateService apiUsageStateService; @@ -236,8 +236,9 @@ public class DefaultTransportApiService implements TransportApiService { } } - protected ListenableFuture validateOrCreateDeviceX509Certificate(String certChain) { - List chain = convertX509CertificateChainToList(certChain); + protected ListenableFuture validateOrCreateDeviceX509Certificate(String certificateChain) { + List chain = X509_CERTIFICATE_TRIM_CHAIN_PATTERN.matcher(certificateChain).results().map(match -> + EncryptionUtil.certTrimNewLines(match.group())).collect(Collectors.toList()); for (String certificateValue : chain) { String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); @@ -720,14 +721,4 @@ public class DefaultTransportApiService implements TransportApiService { return null; } - private List convertX509CertificateChainToList(String certificateChain) { - List chain = new ArrayList<>(); - String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----"; - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(certificateChain); - while (matcher.find()) { - chain.add(EncryptionUtil.certTrimNewLines(matcher.group())); - } - return chain; - } } From 38ee1e01e07965f258638ca1dc78ef349046d7ce Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 10 Apr 2023 13:58:11 +0300 Subject: [PATCH 39/45] Micro refactoring --- .../device/DeviceProvisionServiceImpl.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index f9f24e43b2..e75b3c4b4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -26,6 +26,7 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration; @@ -147,21 +148,24 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } break; case X509_CERTIFICATE_CHAIN: - X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); - if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId()); - if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash(); - deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials, - updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE); + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { + X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId()); + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials, + updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE); + } + return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); + } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { + return createDevice(provisionRequest, targetProfile); + } else { + log.warn("Device with name {} doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration", provisionRequest.getDeviceName()); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } - return new ProvisionResponse(deviceCredentials, ProvisionResponseStatus.SUCCESS); - } else if (x509Configuration.isAllowCreateNewDevicesByX509Certificate()) { - return createDevice(provisionRequest, targetProfile); - } else { - log.warn("Device with name {} doesn't exist and cannot be created due incorrect configuration for X509CertificateChainProvisionConfiguration", provisionRequest.getDeviceName()); - throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } + break; } throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } @@ -275,7 +279,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(ProvisionRequest provisionRequest) { DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(provisionRequest.getCredentials().getProvisionDeviceKey()); - if (deviceProfile != null && deviceProfile.getProfileData() != null && deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) { + if (deviceProfile != null && DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProfileData().getProvisionConfiguration().getType())) { X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration(); String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); String certificateRegEx = configuration.getCertificateRegExPattern(); From 94b9e43aaead586a2af65a3739ef027b6494f993 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 10 Apr 2023 18:54:39 +0300 Subject: [PATCH 40/45] Fix caching for deviceProfile provision key: no need to safe if null value cuz its crashes --- .../dao/device/DeviceProfileCacheKey.java | 3 +- .../dao/device/DeviceProfileEvictEvent.java | 3 +- .../dao/device/DeviceProfileServiceImpl.java | 32 +++++++++---------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java index 6e55b0b6ab..d032282da9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileCacheKey.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.device; import lombok.Data; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; @@ -65,7 +66,7 @@ public class DeviceProfileCacheKey implements Serializable { return deviceProfileId.toString(); } else if (defaultProfile) { return tenantId.toString(); - } else if (provisionDeviceKey != null) { + } else if (StringUtils.isNotEmpty(provisionDeviceKey)) { return provisionDeviceKey; } return tenantId + "_" + name; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index 6470bfc584..a108113b9e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -27,6 +27,7 @@ public class DeviceProfileEvictEvent { private final String oldName; private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; - private final String provisionDeviceKey; + private final String newProvisionDeviceKey; + private final String oldProvisionDeviceKey; } 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 6883ec567a..906241955a 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 @@ -103,8 +103,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByName(tenantId, profileName), true); } + @Override + public DeviceProfile findDeviceProfileByProvisionDeviceKey(String provisionDeviceKey) { + log.trace("Executing findDeviceProfileByProvisionDeviceKey provisionKey [{}]", provisionDeviceKey); + validateString(provisionDeviceKey, INCORRECT_PROVISION_DEVICE_KEY + provisionDeviceKey); + return cache.getAndPutInTransaction(DeviceProfileCacheKey.fromProvisionDeviceKey(provisionDeviceKey), + () -> deviceProfileDao.findByProvisionDeviceKey(provisionDeviceKey), false); + } + @Override public DeviceProfileInfo findDeviceProfileInfoById(TenantId tenantId, DeviceProfileId deviceProfileId) { log.trace("Executing findDeviceProfileById [{}]", deviceProfileId); @@ -147,11 +155,11 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileDao.findByProvisionDeviceKey(provisionDeviceKey), true); - } - @Override public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String name) { log.trace("Executing findOrCreateDefaultDeviceProfile"); @@ -298,14 +298,14 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Mon, 10 Apr 2023 19:24:25 +0300 Subject: [PATCH 41/45] Micro refactoring --- .../thingsboard/server/dao/device/DeviceProfileServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 906241955a..a8f639f5c4 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 @@ -103,7 +103,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Tue, 11 Apr 2023 09:09:54 +0300 Subject: [PATCH 42/45] Fix incorrect cache clearing on device delete --- .../dao/device/DeviceProfileEvictEvent.java | 1 - .../dao/device/DeviceProfileServiceImpl.java | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index a108113b9e..07d5e86c34 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -28,6 +28,5 @@ public class DeviceProfileEvictEvent { private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; private final String newProvisionDeviceKey; - private final String oldProvisionDeviceKey; } 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 a8f639f5c4..40d7b3a9b8 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 @@ -103,8 +103,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Tue, 11 Apr 2023 09:26:08 +0300 Subject: [PATCH 43/45] Minor refactoring of caching --- .../server/dao/device/DeviceProfileEvictEvent.java | 2 +- .../server/dao/device/DeviceProfileServiceImpl.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java index 07d5e86c34..6470bfc584 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileEvictEvent.java @@ -27,6 +27,6 @@ public class DeviceProfileEvictEvent { private final String oldName; private final DeviceProfileId deviceProfileId; private final boolean defaultProfile; - private final String newProvisionDeviceKey; + private final String provisionDeviceKey; } 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 40d7b3a9b8..a5b3e56341 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 @@ -103,8 +103,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService Date: Wed, 12 Apr 2023 11:03:59 +0300 Subject: [PATCH 44/45] Add yaml config for java cacerts path and password to be able to configure --- .../src/main/resources/thingsboard.yml | 3 ++ .../validator/DeviceProfileDataValidator.java | 29 ++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f745e03ffc..7f29ecfb8c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -130,6 +130,9 @@ security: loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}" githubMapper: emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}" + java_cacerts: + path: "${SECURITY_JAVA_CACERTS_PATH:${java.home}${file.separator}lib${file.separator}security${file.separator}cacerts}" + password: "${SECURITY_JAVA_CACERTS_PASSWORD:changeit}" # Usage statistics parameters usage: diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 073b685167..7b142d08e3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -19,6 +19,7 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import org.eclipse.leshan.core.util.SecurityUtil; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.springframework.util.Base64Utils; @@ -56,14 +57,12 @@ import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; -import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.PKIXParameters; @@ -99,6 +98,12 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Wed, 12 Apr 2023 11:34:37 +0300 Subject: [PATCH 45/45] Add yaml config variables into test properties --- dao/src/test/resources/application-test.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index b55fba1f3b..d89211cb2f 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -82,6 +82,8 @@ redis.connection.password= security.user_login_case_sensitive=true security.claim.allowClaimingByDefault=true security.claim.duration=60000 +security.java_cacerts.path=/path/to/cacerts/file +security.java_cacerts.password=myPassword database.ts_max_intervals=700