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);
if (deviceProfile != null) {
String deviceCN = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern());
String deviceName = extractDeviceNameFromCNByRegEx(deviceCN, deviceProfile.getCertificateRegexPattern());
String deviceName = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern());
Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName);
if (device != null) {
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);
return getDeviceInfo(deviceCredentials);
}
}
}
} catch (CertificateEncodingException e) {

View File

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

View File

@ -66,13 +66,13 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
private DeviceTransportType transportType;
@ApiModelProperty(position = 15, value = "Provisioning strategy.")
private DeviceProfileProvisionType provisionType;
@ApiModelProperty(position = 16, value = "CA certificate value. ")
@ApiModelProperty(position = 18, value = "CA certificate value. ")
private String certificateValue;
@ApiModelProperty(position = 17, value = "CA certificate hash. ")
@ApiModelProperty(position = 19, value = "CA certificate hash. ")
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;
@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;

View File

@ -17,7 +17,6 @@ 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;
@ -161,14 +160,13 @@ public class MqttSslHandlerProvider {
@Override
public void onError(Throwable e) {
// to fix this error, cuz no one can understand this ...
log.error(e.getMessage(), e);
log.trace("Failed to process certificate chain: {}", certificateChain, e);
latch.countDown();
}
});
latch.await(10, TimeUnit.SECONDS);
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) {
log.error(e.getMessage(), e);

View File

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

View File

@ -56,14 +56,18 @@ public class DeviceProfileCacheKey implements Serializable {
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
public String toString() {
if (deviceProfileId != null) {
return deviceProfileId.toString();
} else if (defaultProfile) {
return tenantId.toString();
} else {
return tenantId + "_" + name;
} else if (certificateHash != null) {
return certificateHash;
}
return tenantId + "_" + name;
}
}

View File

@ -27,5 +27,6 @@ public class DeviceProfileEvictEvent {
private final String oldName;
private final DeviceProfileId deviceProfileId;
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())) {
keys.add(DeviceProfileCacheKey.fromName(event.getTenantId(), event.getOldName()));
}
if (event.getCertificateHash() != null) {
keys.add(DeviceProfileCacheKey.fromCertificateHash(event.getCertificateHash()));
}
cache.evict(keys);
}
@ -135,10 +138,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
try {
savedDeviceProfile = deviceProfileDao.saveAndFlush(deviceProfile.getTenantId(), deviceProfile);
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) {
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,
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!",
@ -178,7 +183,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
deleteEntityRelations(tenantId, deviceProfileId);
deviceProfileDao.removeById(tenantId, deviceProfileId.getId());
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) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
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;
if (previousDefaultDeviceProfile == null) {
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;
} else if (!previousDefaultDeviceProfile.getId().equals(deviceProfile.getId())) {
previousDefaultDeviceProfile.setDefault(false);
deviceProfileDao.save(tenantId, previousDefaultDeviceProfile);
deviceProfileDao.save(tenantId, deviceProfile);
publishEvictEvent(new DeviceProfileEvictEvent(previousDefaultDeviceProfile.getTenantId(), previousDefaultDeviceProfile.getName(), null, previousDefaultDeviceProfile.getId(), false));
publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), null, deviceProfile.getId(), true));
publishEvictEvent(new DeviceProfileEvictEvent(previousDefaultDeviceProfile.getTenantId(), previousDefaultDeviceProfile.getName(), null, previousDefaultDeviceProfile.getId(), false, null));
publishEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), null, deviceProfile.getId(), true, null));
changed = true;
}
return changed;
@ -336,13 +342,17 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
}
private void formatDeviceProfileCertificate(DeviceProfile deviceProfile) {
String cert = regexCertificateChain(deviceProfile.getCertificateValue());
String certificateValue = deviceProfile.getCertificateValue();
String cert = regexCertificateChain(certificateValue);
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
deviceProfile.setCertificateHash(sha3Hash);
if (!isCertificateChain(certificateValue)) {
deviceProfile.setCertificateValue(EncryptionUtil.certTrimNewLines(certificateValue));
}
}
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);
Matcher matcher = pattern.matcher(chain);
if (matcher.find()) {
@ -351,4 +361,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
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());
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();
@ -240,7 +240,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
}
DeviceProfile existingDeviceProfileCertificate = deviceProfileDao.findByCertificateHash(deviceProfile.getCertificateHash());
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;
@ -396,8 +396,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
}
}
private boolean getRootCAFromJavaCacerts(String deviceProfileHash) {
Set<String> rootCa = new HashSet<>();
boolean getRootCAFromJavaCacerts(String deviceProfileHash) {
try {
String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
FileInputStream is = new FileInputStream(filename);
@ -408,12 +407,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
PKIXParameters params = new PKIXParameters(keystore);
for (TrustAnchor ta : params.getTrustAnchors()) {
X509Certificate cert = ta.getTrustedCert();
rootCa.add(EncryptionUtil.getSha3Hash(getCertificateString(cert)));
if (EncryptionUtil.getSha3Hash(getCertificateString(cert)).equals(deviceProfileHash)) {
return true;
}
}
} catch (CertificateException | KeyStoreException | NoSuchAlgorithmException |
InvalidAlgorithmParameterException | IOException ignored) {
}
return rootCa.contains(deviceProfileHash);
return false;
}
private String getCertificateString(Certificate cert) throws CertificateEncodingException {