From 5d84945ccd2d0375bf5fdd66718cd667aab30310 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 12 Apr 2023 14:06:38 +0300 Subject: [PATCH] Review and improvements --- .../device/DeviceProvisionServiceImpl.java | 84 ++++++++++--------- .../transport/DefaultTransportApiService.java | 38 ++++----- .../dao/device/DeviceProvisionService.java | 3 + .../provision/ProvisionFailedException.java | 4 + .../validator/DeviceProfileDataValidator.java | 14 ++-- 5 files changed, 75 insertions(+), 68 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 e75b3c4b4e..15198d210f 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 @@ -99,9 +99,44 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { this.partitionService = partitionService; } + @Override + public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) { + if (targetProfile == null) { + throw new ProvisionFailedException("Device profile is not specified!"); + } + if (!DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN.equals(targetProfile.getProfileData().getProvisionConfiguration().getType())) { + throw new ProvisionFailedException("Device profile provision strategy is not X509_CERTIFICATE_CHAIN!"); + } + X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration(); + String certificateValue = provisionRequest.getCredentialsData().getX509CertHash(); + String certificateRegEx = configuration.getCertificateRegExPattern(); + String deviceName = extractDeviceNameFromCertificateCNByRegEx(targetProfile, certificateValue, certificateRegEx); + if (StringUtils.isBlank(deviceName)) { + log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]", certificateRegEx, certificateValue); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + provisionRequest.setDeviceName(deviceName); + Device targetDevice = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); + 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", + targetProfile.getTenantId(), targetProfile.getId(), provisionRequest.getDeviceName()); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } + @Override public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { - fetchAndApplyDeviceNameForX509ProvisionRequestWithRegEx(provisionRequest); String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) { @@ -148,24 +183,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } break; case X509_CERTIFICATE_CHAIN: - 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()); - } - } - break; + throw new ProvisionFailedException("Invalid provision strategy type!"); } throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } @@ -277,31 +295,21 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { 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()); - 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(); - 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) { + private String extractDeviceNameFromCertificateCNByRegEx(DeviceProfile profile, 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 matcher.group(1); + } else { + return null; } - } catch (Exception ignored) {} - return null; + } catch (Exception ignored) { + log.trace("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", profile.getTenantId(), profile.getId(), regex, x509Value); + 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 27f20bb5fb..b99c9c2509 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 @@ -242,17 +242,20 @@ public class DefaultTransportApiService implements TransportApiService { for (String certificateValue : chain) { String certificateHash = EncryptionUtil.getSha3Hash(certificateValue); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash); - if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + if (credentials != null && DeviceCredentialsType.X509_CERTIFICATE.equals(credentials.getCredentialsType())) { return getDeviceInfo(credentials); } DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash); - if (deviceProfile != null && deviceProfile.getProvisionType() == X509_CERTIFICATE_CHAIN) { + if (deviceProfile != null && X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) { String updatedDeviceProvisionSecret = chain.get(0); - ProvisionRequest provisionRequest = createProvisionRequest(deviceProfile, updatedDeviceProvisionSecret); - ProvisionResponse provisionResponse = provisionDeviceRequestAndGetResponse(provisionRequest); - if (provisionResponse != null && ProvisionResponseStatus.SUCCESS.equals(provisionResponse.getResponseStatus())) { - return getDeviceInfo(provisionResponse.getDeviceCredentials()); - } else { + ProvisionRequest provisionRequest = createProvisionRequest(updatedDeviceProvisionSecret); + try { + ProvisionResponse provisionResponse = deviceProvisionService.provisionDeviceViaX509Chain(deviceProfile, provisionRequest); + if (ProvisionResponseStatus.SUCCESS.equals(provisionResponse.getResponseStatus())) { + return getDeviceInfo(provisionResponse.getDeviceCredentials()); + } + } catch (ProvisionFailedException e) { + log.debug("[{}][{}] Failed to provision device with cert chain: {}", deviceProfile.getTenantId(), deviceProfile.getId(), provisionRequest, e); return getEmptyTransportApiResponseFuture(); } } else if (deviceProfile != null) { @@ -702,23 +705,10 @@ public class DefaultTransportApiService implements TransportApiService { return l != null ? l : 0; } - private ProvisionRequest createProvisionRequest(DeviceProfile deviceProfile, String certificateValue) { - ProvisionDeviceProfileCredentials provisionDeviceProfileCredentials = new ProvisionDeviceProfileCredentials( - deviceProfile.getProvisionDeviceKey(), - deviceProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() - ); - ProvisionDeviceCredentialsData provisionDeviceCredentialsData = new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue); - - return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, provisionDeviceCredentialsData, provisionDeviceProfileCredentials); - } - - private ProvisionResponse provisionDeviceRequestAndGetResponse(ProvisionRequest provisionRequest) { - try { - return deviceProvisionService.provisionDevice(provisionRequest); - } catch (ProvisionFailedException e) { - log.error(e.getMessage()); - } - return null; + private ProvisionRequest createProvisionRequest(String certificateValue) { + return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, + new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), + null); } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 77a8d3edbc..fa47ed0694 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; @@ -22,4 +23,6 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; public interface DeviceProvisionService { ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; + + ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java index 2579b1f9cf..f3fc6f97fc 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java @@ -16,7 +16,11 @@ package org.thingsboard.server.dao.device.provision; public class ProvisionFailedException extends RuntimeException { + + private static final long serialVersionUID = 1673991117668477401L; + public ProvisionFailedException(String errorMsg) { super(errorMsg); } + } 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 7b142d08e3..d092c34861 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service.validator; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.SecurityUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -72,6 +73,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +@Slf4j @Component public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator { @@ -98,10 +100,10 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator