Browse Source

Refactoring: fix conflict, add logs

pull/7935/head
Andrii Landiak 3 years ago
parent
commit
2ac861876e
  1. 3
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  2. 77
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  3. 8
      application/src/main/resources/thingsboard.yml
  4. 17
      application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java
  5. 7
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/X509CertificateChainProvisionConfiguration.java
  6. 10
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  7. 1
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java
  8. 22
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

3
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.
}

77
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<TransportApiResponseMsg> validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) {
protected ListenableFuture<TransportApiResponseMsg> validateOrCreateDeviceX509Certificate(String certChain) {
List<String> 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;
}
}

8
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)

17
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());

7
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;

10
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);

1
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];

22
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -135,12 +135,11 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Override
public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile) {
log.trace("Executing saveDeviceProfile [{}]", deviceProfile);
X509CertificateChainProvisionConfiguration x509Configuration = new X509CertificateChainProvisionConfiguration();
if (deviceProfile.getProfileData() != null && deviceProfile.getProfileData().getProvisionConfiguration() instanceof X509CertificateChainProvisionConfiguration) {
x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration();
}
if (x509Configuration.getCertificateValue() != null) {
formatDeviceProfileCertificate(deviceProfile, x509Configuration);
X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration();
if (x509Configuration.getCertificateValue() != null) {
formatDeviceProfileCertificate(deviceProfile, x509Configuration);
}
}
DeviceProfile oldDeviceProfile = deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId);
DeviceProfile savedDeviceProfile;
@ -351,24 +350,25 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
}
private void formatDeviceProfileCertificate(DeviceProfile deviceProfile, X509CertificateChainProvisionConfiguration x509Configuration) {
String certificateValue = formatCertificateValue(x509Configuration.getCertificateValue());
String cert = regexCertificateChain(certificateValue);
String formattedCertificateValue = formatCertificateValue(x509Configuration.getCertificateValue());
String cert = fetchLeafCertificateFromChain(formattedCertificateValue);
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
DeviceProfileData deviceProfileData = deviceProfile.getProfileData();
x509Configuration.setCertificateValue(certificateValue);
x509Configuration.setCertificateValue(formattedCertificateValue);
deviceProfileData.setProvisionConfiguration(x509Configuration);
deviceProfile.setProfileData(deviceProfileData);
deviceProfile.setCertificateHash(sha3Hash);
}
private String regexCertificateChain(String chain) {
private String fetchLeafCertificateFromChain(String value) {
String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(chain);
Matcher matcher = pattern.matcher(value);
if (matcher.find()) {
// if the method receives a chain it fetches the leaf (end-entity) certificate, else if it gets a single certificate, it returns the single certificate
return matcher.group(0);
}
return chain;
return value;
}
private String formatCertificateValue(String certificateValue) {

Loading…
Cancel
Save