From 7957da4a9e2ab7922074848018e8dcde8e2bab23 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 20 Jan 2023 19:02:14 +0200 Subject: [PATCH] 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 | 104 ++++-------- .../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, 211 insertions(+), 224 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); + + 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); + } - 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); + } + } + } 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(); } - - @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); + latch.countDown(); + } + + @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