Fix CacheKey toString and validation logs

This commit is contained in:
Andrii Landiak 2023-01-24 16:37:05 +02:00
parent ba0ae08719
commit e8543f39d5
9 changed files with 46 additions and 28 deletions

View File

@ -258,8 +258,7 @@ public class DefaultTransportApiService implements TransportApiService {
} }
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash);
if (deviceProfile != null) { if (deviceProfile != null) {
String deviceCN = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern()); String deviceName = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern());
String deviceName = extractDeviceNameFromCNByRegEx(deviceCN, deviceProfile.getCertificateRegexPattern());
Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName); Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName);
if (device != null) { if (device != null) {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId());
@ -275,7 +274,6 @@ public class DefaultTransportApiService implements TransportApiService {
deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType); deviceCredentials = updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials, updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType);
return getDeviceInfo(deviceCredentials); return getDeviceInfo(deviceCredentials);
} }
} }
} }
} catch (CertificateEncodingException e) { } catch (CertificateEncodingException e) {

View File

@ -422,6 +422,7 @@ message CredentialsDataProto {
ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1; ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1;
ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2; ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2;
ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3;
ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateDeviceX509CertRequestMsg = 4;
} }
message ProvisionDeviceRequestMsg { message ProvisionDeviceRequestMsg {

View File

@ -66,13 +66,13 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
private DeviceTransportType transportType; private DeviceTransportType transportType;
@ApiModelProperty(position = 15, value = "Provisioning strategy.") @ApiModelProperty(position = 15, value = "Provisioning strategy.")
private DeviceProfileProvisionType provisionType; private DeviceProfileProvisionType provisionType;
@ApiModelProperty(position = 16, value = "CA certificate value. ") @ApiModelProperty(position = 18, value = "CA certificate value. ")
private String certificateValue; private String certificateValue;
@ApiModelProperty(position = 17, value = "CA certificate hash. ") @ApiModelProperty(position = 19, value = "CA certificate hash. ")
private String certificateHash; private String certificateHash;
@ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN. ") @ApiModelProperty(position = 20, value = "Regex to fetch deviceName from CN. ")
private String certificateRegexPattern; private String certificateRegexPattern;
@ApiModelProperty(position = 19, value = "Allow to create new devices by x509 provision strategy. ") @ApiModelProperty(position = 21, value = "Allow to create new devices by x509 provision strategy. ")
private boolean allowCreateNewDevicesByX509Strategy; private boolean allowCreateNewDevicesByX509Strategy;

View File

@ -17,7 +17,6 @@ package org.thingsboard.server.transport.mqtt;
import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j; 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.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -161,14 +160,13 @@ public class MqttSslHandlerProvider {
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
// to fix this error, cuz no one can understand this ... log.trace("Failed to process certificate chain: {}", certificateChain, e);
log.error(e.getMessage(), e);
latch.countDown(); latch.countDown();
} }
}); });
latch.await(10, TimeUnit.SECONDS); latch.await(10, TimeUnit.SECONDS);
if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) { if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) {
throw new CertificateException("Invalid Certificate's chain"); throw new CertificateException("Invalid Certificate's chain. Cannot find such device credentials.");
} }
} catch (CertificateEncodingException | InterruptedException e) { } catch (CertificateEncodingException | InterruptedException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);

View File

@ -877,7 +877,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private X509Certificate getX509Certificate() { private X509Certificate getX509Certificate() {
try { try {
Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates();
if (certChain.length > 1) { if (certChain.length > 0) {
return (X509Certificate) certChain[0]; return (X509Certificate) certChain[0];
} }
} catch (SSLPeerUnverifiedException e) { } catch (SSLPeerUnverifiedException e) {

View File

@ -56,14 +56,18 @@ public class DeviceProfileCacheKey implements Serializable {
return new DeviceProfileCacheKey(tenantId, null, null, null, true); return new DeviceProfileCacheKey(tenantId, null, null, null, true);
} }
/**
* IMPORTANT: Method toString() has to return unique value, if you add additional field to this class, please also refactor toString().
*/
@Override @Override
public String toString() { public String toString() {
if (deviceProfileId != null) { if (deviceProfileId != null) {
return deviceProfileId.toString(); return deviceProfileId.toString();
} else if (defaultProfile) { } else if (defaultProfile) {
return tenantId.toString(); return tenantId.toString();
} else { } else if (certificateHash != null) {
return tenantId + "_" + name; return certificateHash;
} }
return tenantId + "_" + name;
} }
} }

View File

@ -27,5 +27,6 @@ public class DeviceProfileEvictEvent {
private final String oldName; private final String oldName;
private final DeviceProfileId deviceProfileId; private final DeviceProfileId deviceProfileId;
private final boolean defaultProfile; private final boolean defaultProfile;
private final String certificateHash;
} }

View File

@ -98,6 +98,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
if (StringUtils.isNotEmpty(event.getOldName()) && !event.getOldName().equals(event.getNewName())) { if (StringUtils.isNotEmpty(event.getOldName()) && !event.getOldName().equals(event.getNewName())) {
keys.add(DeviceProfileCacheKey.fromName(event.getTenantId(), event.getOldName())); keys.add(DeviceProfileCacheKey.fromName(event.getTenantId(), event.getOldName()));
} }
if (event.getCertificateHash() != null) {
keys.add(DeviceProfileCacheKey.fromCertificateHash(event.getCertificateHash()));
}
cache.evict(keys); cache.evict(keys);
} }
@ -135,10 +138,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
try { try {
savedDeviceProfile = deviceProfileDao.saveAndFlush(deviceProfile.getTenantId(), deviceProfile); savedDeviceProfile = deviceProfileDao.saveAndFlush(deviceProfile.getTenantId(), deviceProfile);
publishEvictEvent(new DeviceProfileEvictEvent(savedDeviceProfile.getTenantId(), savedDeviceProfile.getName(), publishEvictEvent(new DeviceProfileEvictEvent(savedDeviceProfile.getTenantId(), savedDeviceProfile.getName(),
oldDeviceProfile != null ? oldDeviceProfile.getName() : null, savedDeviceProfile.getId(), savedDeviceProfile.isDefault())); oldDeviceProfile != null ? oldDeviceProfile.getName() : null, savedDeviceProfile.getId(), savedDeviceProfile.isDefault(),
deviceProfile.getCertificateHash() != null ? deviceProfile.getCertificateHash() : null));
} catch (Exception t) { } catch (Exception t) {
handleEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), handleEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(),
oldDeviceProfile != null ? oldDeviceProfile.getName() : null, null, deviceProfile.isDefault())); oldDeviceProfile != null ? oldDeviceProfile.getName() : null, null, deviceProfile.isDefault(),
deviceProfile.getCertificateHash() != null ? deviceProfile.getCertificateHash() : null));
checkConstraintViolation(t, checkConstraintViolation(t,
Map.of("device_profile_name_unq_key", DEVICE_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS, Map.of("device_profile_name_unq_key", DEVICE_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS,
"device_provision_key_unq_key", "Device profile with such provision device key already exists!", "device_provision_key_unq_key", "Device profile with such provision device key already exists!",
@ -178,7 +183,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
deleteEntityRelations(tenantId, deviceProfileId); deleteEntityRelations(tenantId, deviceProfileId);
deviceProfileDao.removeById(tenantId, deviceProfileId.getId()); deviceProfileDao.removeById(tenantId, deviceProfileId.getId());
publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(),
null, deviceProfile.getId(), deviceProfile.isDefault())); null, deviceProfile.getId(), deviceProfile.isDefault(),
deviceProfile.getCertificateHash() != null ? deviceProfile.getCertificateHash() : null));
} catch (Exception t) { } catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_device_profile")) { if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_device_profile")) {
@ -284,14 +290,14 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
boolean changed = false; boolean changed = false;
if (previousDefaultDeviceProfile == null) { if (previousDefaultDeviceProfile == null) {
deviceProfileDao.save(tenantId, deviceProfile); deviceProfileDao.save(tenantId, deviceProfile);
publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), null, deviceProfile.getId(), true)); publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), null, deviceProfile.getId(), true, null));
changed = true; changed = true;
} else if (!previousDefaultDeviceProfile.getId().equals(deviceProfile.getId())) { } else if (!previousDefaultDeviceProfile.getId().equals(deviceProfile.getId())) {
previousDefaultDeviceProfile.setDefault(false); previousDefaultDeviceProfile.setDefault(false);
deviceProfileDao.save(tenantId, previousDefaultDeviceProfile); deviceProfileDao.save(tenantId, previousDefaultDeviceProfile);
deviceProfileDao.save(tenantId, deviceProfile); deviceProfileDao.save(tenantId, deviceProfile);
publishEvictEvent(new DeviceProfileEvictEvent(previousDefaultDeviceProfile.getTenantId(), previousDefaultDeviceProfile.getName(), null, previousDefaultDeviceProfile.getId(), false)); publishEvictEvent(new DeviceProfileEvictEvent(previousDefaultDeviceProfile.getTenantId(), previousDefaultDeviceProfile.getName(), null, previousDefaultDeviceProfile.getId(), false, null));
publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), null, deviceProfile.getId(), true)); publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), null, deviceProfile.getId(), true, null));
changed = true; changed = true;
} }
return changed; return changed;
@ -336,13 +342,17 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
} }
private void formatDeviceProfileCertificate(DeviceProfile deviceProfile) { private void formatDeviceProfileCertificate(DeviceProfile deviceProfile) {
String cert = regexCertificateChain(deviceProfile.getCertificateValue()); String certificateValue = deviceProfile.getCertificateValue();
String cert = regexCertificateChain(certificateValue);
String sha3Hash = EncryptionUtil.getSha3Hash(cert); String sha3Hash = EncryptionUtil.getSha3Hash(cert);
deviceProfile.setCertificateHash(sha3Hash); deviceProfile.setCertificateHash(sha3Hash);
if (!isCertificateChain(certificateValue)) {
deviceProfile.setCertificateValue(EncryptionUtil.certTrimNewLines(certificateValue));
}
} }
private String regexCertificateChain(String chain) { private String regexCertificateChain(String chain) {
String regex = "-----BEGIN CERTIFICATE-----\\s*((.+\\s+)*?)-----END CERTIFICATE----"; String regex = "-----BEGIN CERTIFICATE-----\\s*((.+\\s+)*?)-----END CERTIFICATE-----";
Pattern pattern = Pattern.compile(regex); Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(chain); Matcher matcher = pattern.matcher(chain);
if (matcher.find()) { if (matcher.find()) {
@ -351,4 +361,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
return chain; return chain;
} }
private boolean isCertificateChain(String certificateValue) {
int count = certificateValue.split("-----BEGIN CERTIFICATE", -1).length - 1;
return count > 1;
}
} }

View File

@ -138,7 +138,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
} }
DeviceProfile existingDeviceProfileCertificate = deviceProfileDao.findByCertificateHash(deviceProfile.getCertificateHash()); DeviceProfile existingDeviceProfileCertificate = deviceProfileDao.findByCertificateHash(deviceProfile.getCertificateHash());
if (existingDeviceProfileCertificate != null && !existingDeviceProfileCertificate.getId().equals(deviceProfile.getId())) { if (existingDeviceProfileCertificate != null && !existingDeviceProfileCertificate.getId().equals(deviceProfile.getId())) {
throw new DataValidationException("Cannot create device profile with certificate because such certificate already exists!"); throw new DataValidationException("Device profile with such certificate hash already exists!");
} }
} }
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
@ -240,7 +240,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
} }
DeviceProfile existingDeviceProfileCertificate = deviceProfileDao.findByCertificateHash(deviceProfile.getCertificateHash()); DeviceProfile existingDeviceProfileCertificate = deviceProfileDao.findByCertificateHash(deviceProfile.getCertificateHash());
if (existingDeviceProfileCertificate != null && !existingDeviceProfileCertificate.getId().equals(old.getId())) { if (existingDeviceProfileCertificate != null && !existingDeviceProfileCertificate.getId().equals(old.getId())) {
throw new DataValidationException("Can't change device profile certificate because such certificate already exists!"); throw new DataValidationException("Device profile with such certificate hash already exists!");
} }
} }
return old; return old;
@ -396,8 +396,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
} }
} }
private boolean getRootCAFromJavaCacerts(String deviceProfileHash) { boolean getRootCAFromJavaCacerts(String deviceProfileHash) {
Set<String> rootCa = new HashSet<>();
try { try {
String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar); String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
FileInputStream is = new FileInputStream(filename); FileInputStream is = new FileInputStream(filename);
@ -408,12 +407,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
PKIXParameters params = new PKIXParameters(keystore); PKIXParameters params = new PKIXParameters(keystore);
for (TrustAnchor ta : params.getTrustAnchors()) { for (TrustAnchor ta : params.getTrustAnchors()) {
X509Certificate cert = ta.getTrustedCert(); X509Certificate cert = ta.getTrustedCert();
rootCa.add(EncryptionUtil.getSha3Hash(getCertificateString(cert))); if (EncryptionUtil.getSha3Hash(getCertificateString(cert)).equals(deviceProfileHash)) {
return true;
}
} }
} catch (CertificateException | KeyStoreException | NoSuchAlgorithmException | } catch (CertificateException | KeyStoreException | NoSuchAlgorithmException |
InvalidAlgorithmParameterException | IOException ignored) { InvalidAlgorithmParameterException | IOException ignored) {
} }
return rootCa.contains(deviceProfileHash); return false;
} }
private String getCertificateString(Certificate cert) throws CertificateEncodingException { private String getCertificateString(Certificate cert) throws CertificateEncodingException {