Refactor after review and add tests for extracting by regex
This commit is contained in:
parent
5d84945ccd
commit
fbeb56cf70
@ -100,7 +100,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) {
|
public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) throws ProvisionFailedException {
|
||||||
if (targetProfile == null) {
|
if (targetProfile == null) {
|
||||||
throw new ProvisionFailedException("Device profile is not specified!");
|
throw new ProvisionFailedException("Device profile is not specified!");
|
||||||
}
|
}
|
||||||
@ -110,9 +110,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
|
|||||||
X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration();
|
X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration();
|
||||||
String certificateValue = provisionRequest.getCredentialsData().getX509CertHash();
|
String certificateValue = provisionRequest.getCredentialsData().getX509CertHash();
|
||||||
String certificateRegEx = configuration.getCertificateRegExPattern();
|
String certificateRegEx = configuration.getCertificateRegExPattern();
|
||||||
String deviceName = extractDeviceNameFromCertificateCNByRegEx(targetProfile, certificateValue, certificateRegEx);
|
String commonName = getCNFromX509Certificate(certificateValue);
|
||||||
|
String deviceName = extractDeviceNameFromCNByRegEx(targetProfile, commonName, certificateRegEx);
|
||||||
if (StringUtils.isBlank(deviceName)) {
|
if (StringUtils.isBlank(deviceName)) {
|
||||||
log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]", certificateRegEx, certificateValue);
|
log.warn("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", targetProfile.getTenantId(), targetProfile.getId(), certificateRegEx, certificateValue);
|
||||||
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
|
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
|
||||||
}
|
}
|
||||||
provisionRequest.setDeviceName(deviceName);
|
provisionRequest.setDeviceName(deviceName);
|
||||||
@ -120,7 +121,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
|
|||||||
X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration();
|
X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration();
|
||||||
if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) {
|
if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) {
|
||||||
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId());
|
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId());
|
||||||
if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) {
|
if (DeviceCredentialsType.X509_CERTIFICATE.equals(deviceCredentials.getCredentialsType())) {
|
||||||
String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash();
|
String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash();
|
||||||
deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials,
|
deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials,
|
||||||
updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE);
|
updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE);
|
||||||
@ -295,21 +296,25 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
|
|||||||
auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest);
|
auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String extractDeviceNameFromCertificateCNByRegEx(DeviceProfile profile, String x509Value, String regex) {
|
private String getCNFromX509Certificate(String x509Value) {
|
||||||
try {
|
try {
|
||||||
String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value));
|
return SslUtil.parseCommonName(SslUtil.readCertFile(x509Value));
|
||||||
log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex);
|
} catch (Exception e) {
|
||||||
Pattern pattern = Pattern.compile(regex);
|
|
||||||
Matcher matcher = pattern.matcher(commonName);
|
|
||||||
if (matcher.find()) {
|
|
||||||
return matcher.group(1);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
log.trace("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", profile.getTenantId(), profile.getId(), regex, x509Value);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String extractDeviceNameFromCNByRegEx(DeviceProfile profile, String commonName, String regex) {
|
||||||
|
try {
|
||||||
|
log.trace("Extract device name from CN [{}] by regex pattern [{}]", commonName, regex);
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(commonName);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
log.trace("[{}][{}] Failed to match device name using [{}] from CN: [{}]", profile.getTenantId(), profile.getId(), regex, commonName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.ApiUsageState;
|
|||||||
import org.thingsboard.server.common.data.DataConstants;
|
import org.thingsboard.server.common.data.DataConstants;
|
||||||
import org.thingsboard.server.common.data.Device;
|
import org.thingsboard.server.common.data.Device;
|
||||||
import org.thingsboard.server.common.data.DeviceProfile;
|
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.DeviceTransportType;
|
||||||
import org.thingsboard.server.common.data.EntityType;
|
import org.thingsboard.server.common.data.EntityType;
|
||||||
import org.thingsboard.server.common.data.OtaPackage;
|
import org.thingsboard.server.common.data.OtaPackage;
|
||||||
@ -113,7 +114,6 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
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.PASSWORD_MISMATCH;
|
||||||
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
|
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ public class DefaultTransportApiService implements TransportApiService {
|
|||||||
return getDeviceInfo(credentials);
|
return getDeviceInfo(credentials);
|
||||||
}
|
}
|
||||||
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash);
|
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash);
|
||||||
if (deviceProfile != null && X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) {
|
if (deviceProfile != null && DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) {
|
||||||
String updatedDeviceProvisionSecret = chain.get(0);
|
String updatedDeviceProvisionSecret = chain.get(0);
|
||||||
ProvisionRequest provisionRequest = createProvisionRequest(updatedDeviceProvisionSecret);
|
ProvisionRequest provisionRequest = createProvisionRequest(updatedDeviceProvisionSecret);
|
||||||
try {
|
try {
|
||||||
@ -259,7 +259,7 @@ public class DefaultTransportApiService implements TransportApiService {
|
|||||||
return getEmptyTransportApiResponseFuture();
|
return getEmptyTransportApiResponseFuture();
|
||||||
}
|
}
|
||||||
} else if (deviceProfile != null) {
|
} else if (deviceProfile != null) {
|
||||||
log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getId(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType());
|
log.warn("[{}][{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getTenantId(), deviceProfile.getId(), DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getEmptyTransportApiResponseFuture();
|
return getEmptyTransportApiResponseFuture();
|
||||||
|
|||||||
@ -0,0 +1,267 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* 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.device.provision;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.Before;
|
||||||
|
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.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.Tenant;
|
||||||
|
import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
|
||||||
|
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
|
||||||
|
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;
|
||||||
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
|
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.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.DeviceProfileService;
|
||||||
|
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.gen.transport.TransportProtos;
|
||||||
|
import org.thingsboard.server.queue.TbQueueProducer;
|
||||||
|
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.service.device.DeviceProvisionServiceImpl;;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;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@ContextConfiguration(classes = DeviceProvisionServiceImpl.class)
|
||||||
|
public class DeviceProvisionServiceTest {
|
||||||
|
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
protected TbQueueProducerProvider producerProvider;
|
||||||
|
@MockBean
|
||||||
|
protected TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> ruleEngineMsgProducer;
|
||||||
|
@MockBean
|
||||||
|
protected TbClusterService clusterService;
|
||||||
|
@MockBean
|
||||||
|
protected DeviceProfileService deviceProfileService;
|
||||||
|
@MockBean
|
||||||
|
protected DeviceService deviceService;
|
||||||
|
@MockBean
|
||||||
|
protected DeviceCredentialsService deviceCredentialsService;
|
||||||
|
@MockBean
|
||||||
|
protected AttributesService attributesService;
|
||||||
|
@MockBean
|
||||||
|
protected AuditLogService auditLogService;
|
||||||
|
@MockBean
|
||||||
|
protected PartitionService partitionService;
|
||||||
|
@SpyBean
|
||||||
|
DeviceProvisionServiceImpl service;
|
||||||
|
|
||||||
|
private String[] chain;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
String filePath = "src/test/resources/provision/x509ChainProvisionTest.pem";
|
||||||
|
try {
|
||||||
|
String certificateChain = Files.readString(Paths.get(filePath));
|
||||||
|
certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain);
|
||||||
|
chain = fetchLeafCertificateFromChain(certificateChain);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void provisionDeviceViaX509Certificate() {
|
||||||
|
var tenant = createTenant();
|
||||||
|
var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], true);
|
||||||
|
|
||||||
|
var device = createDevice(tenant.getId(), deviceProfile.getId());
|
||||||
|
when(deviceService.findDeviceByTenantIdAndName(any(), any())).thenReturn(device);
|
||||||
|
|
||||||
|
var deviceCredentials = createDeviceCredentials(chain[0], device.getId());
|
||||||
|
when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials);
|
||||||
|
when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials);
|
||||||
|
|
||||||
|
ProvisionResponse response = service.provisionDeviceViaX509Chain(deviceProfile, createProvisionRequest(chain[0]));
|
||||||
|
|
||||||
|
verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any());
|
||||||
|
verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any());
|
||||||
|
verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any());
|
||||||
|
|
||||||
|
Assertions.assertThat(response.getResponseStatus()).isEqualTo(ProvisionResponseStatus.SUCCESS);
|
||||||
|
Assertions.assertThat(response.getDeviceCredentials()).isEqualTo(deviceCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void provisionDeviceWithIncorrectConfiguration() {
|
||||||
|
var tenant = createTenant();
|
||||||
|
var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], false);
|
||||||
|
|
||||||
|
Assertions.assertThatThrownBy(() ->
|
||||||
|
service.provisionDeviceViaX509Chain(deviceProfile, createProvisionRequest(chain[0])))
|
||||||
|
.isInstanceOf(ProvisionFailedException.class);
|
||||||
|
|
||||||
|
verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchDeviceNameFromX509CNCertificateByRegex() {
|
||||||
|
var tenant = createTenant();
|
||||||
|
var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], true);
|
||||||
|
X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration();
|
||||||
|
String CN = getCNFromX509Certificate(chain[0]);
|
||||||
|
String deviceName = service.extractDeviceNameFromCNByRegEx(deviceProfile, CN, configuration.getCertificateRegExPattern());
|
||||||
|
|
||||||
|
Assertions.assertThat(deviceName).isNotBlank();
|
||||||
|
Assertions.assertThat(deviceName).isEqualTo("deviceCertificate");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchDeviceNameFromCNByRegex() {
|
||||||
|
var CN = "DeviceA.company.com";
|
||||||
|
var regex = "(.*)\\.company.com";
|
||||||
|
var result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
|
||||||
|
Assertions.assertThat(result).isNotBlank();
|
||||||
|
Assertions.assertThat(result).isEqualTo("DeviceA");
|
||||||
|
|
||||||
|
CN = "DeviceA@company.com";
|
||||||
|
regex = "(.*)@company.com";
|
||||||
|
result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
|
||||||
|
Assertions.assertThat(result).isNotBlank();
|
||||||
|
Assertions.assertThat(result).isEqualTo("DeviceA");
|
||||||
|
|
||||||
|
CN = "prefixDeviceAsuffix@company.com";
|
||||||
|
regex = "prefix(.*)suffix@company.com";
|
||||||
|
result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
|
||||||
|
Assertions.assertThat(result).isNotBlank();
|
||||||
|
Assertions.assertThat(result).isEqualTo("DeviceA");
|
||||||
|
|
||||||
|
CN = "prefixDeviceAsufix@company.com";
|
||||||
|
regex = "prefix(.*)sufix@company.com";
|
||||||
|
result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
|
||||||
|
Assertions.assertThat(result).isNotBlank();
|
||||||
|
Assertions.assertThat(result).isEqualTo("DeviceA");
|
||||||
|
|
||||||
|
CN = "region.DeviceA.220423@company.com";
|
||||||
|
regex = "\\D+\\.(.*)\\.\\d+@company.com";
|
||||||
|
result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
|
||||||
|
Assertions.assertThat(result).isNotBlank();
|
||||||
|
Assertions.assertThat(result).isEqualTo("DeviceA");
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeviceProfile createDeviceProfile(TenantId tenantId, String certificateValue, boolean isAllowToCreateNewDevices) {
|
||||||
|
X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration();
|
||||||
|
provision.setProvisionDeviceSecret(certificateValue);
|
||||||
|
provision.setCertificateRegExPattern("([^@]+)");
|
||||||
|
provision.setAllowCreateNewDevicesByX509Certificate(isAllowToCreateNewDevices);
|
||||||
|
|
||||||
|
DeviceProfileData deviceProfileData = new DeviceProfileData();
|
||||||
|
deviceProfileData.setProvisionConfiguration(provision);
|
||||||
|
|
||||||
|
DeviceProfile deviceProfile = new DeviceProfile();
|
||||||
|
deviceProfile.setId(new DeviceProfileId(UUID.randomUUID()));
|
||||||
|
deviceProfile.setProfileData(deviceProfileData);
|
||||||
|
deviceProfile.setProvisionDeviceKey(EncryptionUtil.getSha3Hash(certificateValue));
|
||||||
|
deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN);
|
||||||
|
deviceProfile.setTenantId(tenantId);
|
||||||
|
return deviceProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId) {
|
||||||
|
Device device = new Device();
|
||||||
|
device.setTenantId(tenantId);
|
||||||
|
device.setId(new DeviceId(UUID.randomUUID()));
|
||||||
|
device.setDeviceProfileId(deviceProfileId);
|
||||||
|
device.setCustomerId(new CustomerId(UUID.randomUUID()));
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tenant createTenant() {
|
||||||
|
Tenant tenant = new Tenant();
|
||||||
|
tenant.setId(new TenantId(UUID.randomUUID()));
|
||||||
|
return tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ProvisionRequest createProvisionRequest(String certificateValue) {
|
||||||
|
return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE,
|
||||||
|
new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> 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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCNFromX509Certificate(String x509Value) {
|
||||||
|
try {
|
||||||
|
return SslUtil.parseCommonName(SslUtil.readCertFile(x509Value));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -109,7 +109,8 @@ public class DefaultTransportApiServiceTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
String filePath = "src/test/resources/mqtt/x509ChainProvisionTest.pem";
|
|
||||||
|
String filePath = "src/test/resources/provision/x509ChainProvisionTest.pem";
|
||||||
try {
|
try {
|
||||||
certificateChain = Files.readString(Paths.get(filePath));
|
certificateChain = Files.readString(Paths.get(filePath));
|
||||||
certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain);
|
certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain);
|
||||||
@ -120,7 +121,7 @@ public class DefaultTransportApiServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validateExistingDeviceX509Certificate() {
|
public void validateExistingDeviceByX509CertificateStrategy() {
|
||||||
var device = createDevice();
|
var device = createDevice();
|
||||||
when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device));
|
when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device));
|
||||||
|
|
||||||
@ -145,13 +146,13 @@ public class DefaultTransportApiServiceTest {
|
|||||||
when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials);
|
when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials);
|
||||||
|
|
||||||
var provisionResponse = createProvisionResponse(deviceCredentials);
|
var provisionResponse = createProvisionResponse(deviceCredentials);
|
||||||
when(deviceProvisionService.provisionDevice(any())).thenReturn(provisionResponse);
|
when(deviceProvisionService.provisionDeviceViaX509Chain(any(), any())).thenReturn(provisionResponse);
|
||||||
|
|
||||||
service.validateOrCreateDeviceX509Certificate(certificateChain);
|
service.validateOrCreateDeviceX509Certificate(certificateChain);
|
||||||
verify(deviceProfileService, times(1)).findDeviceProfileByProvisionDeviceKey(any());
|
verify(deviceProfileService, times(1)).findDeviceProfileByProvisionDeviceKey(any());
|
||||||
verify(deviceService, times(1)).findDeviceByIdAsync(any(), any());
|
verify(deviceService, times(1)).findDeviceByIdAsync(any(), any());
|
||||||
verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any());
|
verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any());
|
||||||
verify(deviceProvisionService, times(1)).provisionDevice(any());
|
verify(deviceProvisionService, times(1)).provisionDeviceViaX509Chain(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeviceProfile createDeviceProfile(String certificateValue) {
|
private DeviceProfile createDeviceProfile(String certificateValue) {
|
||||||
|
|||||||
@ -24,5 +24,5 @@ public interface DeviceProvisionService {
|
|||||||
|
|
||||||
ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException;
|
ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException;
|
||||||
|
|
||||||
ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest);
|
ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest) throws ProvisionFailedException;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -408,8 +408,8 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception e) {
|
||||||
log.trace("Failed to validate certificate due to: ", ignored);
|
log.trace("Failed to validate certificate due to: ", e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
#### Examples of RegEx usage
|
#### Examples of RegEx usage
|
||||||
|
|
||||||
* **Pattern:** <code>.*</code> - matches any character (until line terminators)
|
The regular expression is required to extract device name from the X509 certificate's common name.
|
||||||
<br>**CN sample:** <code>DeviceName\nAdditionalInfo</code>
|
The regular expression syntax is based on Java [Pattern](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html).
|
||||||
<br>**Pattern matches:** <code>DeviceName</code>
|
You may also use this [resource](https://regex101.com/) to test your expressions but make sure you select Java 8 flavor.
|
||||||
|
|
||||||
* **Pattern:** <code>^([^@]+)</code> - matches any string that starts with one or more characters that are not the <code>@</code> symbol (<code>@</code> could be replaced by any other symbol)
|
* **Pattern:**<code>(.*)\.company.com</code>- matches any characters before the ".company.com".
|
||||||
<br>**CN sample:** <code>DeviceName@AdditionalInfo</code>
|
<br>**CN sample:**<code>DeviceA.company.com</code>
|
||||||
<br>**Pattern matches:** <code>DeviceName</code>
|
<br>**Result:**<code>DeviceA</code>
|
||||||
|
|
||||||
* **Pattern:** <code>[\w]*$</code> (equivalent to <code>[a-zA-Z0-9_]\*$</code>) - matches zero or more occurences of any word character (letter, digit or underscore) at the end of the string
|
* **Pattern:** <code>(.*)@company.com</code>- matches any characters before the "@company.com".
|
||||||
<br>**CN sample:** <code>AdditionalInfo2110#DeviceName_01</code>
|
<br>**CN sample:**<code>DeviceA@company.com</code>
|
||||||
<br>**Pattern matches:** <code>DeviceName_01</code>
|
<br>**Result:**<code>DeviceA</code>
|
||||||
|
|
||||||
**Note:** Client will get error response in case regex is failed to match.
|
* **Pattern:** <code>prefix(.*)suffix@company.com</code>- matches characters between "prefix" and "suffix@company.com".
|
||||||
|
<br>**CN sample:**<code>prefixDeviceAsuffix@company.com</code>
|
||||||
|
<br>**Pattern matches:** <code>DeviceA</code>
|
||||||
|
|
||||||
|
* **Pattern:** <code>\\D+\\.(.*)\\.\\d+@company.com</code>- matches characters between not digits prefix followed by period and sequence of digits with "@company.com" ending.
|
||||||
|
<br>**CN sample:**<code>region.DeviceA.220423@company.com</code>
|
||||||
|
<br>**Pattern matches:** <code>DeviceA</code>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user