Browse Source

Add validation on device profile create or update

pull/7935/head
Andrii Landiak 4 years ago
parent
commit
18c1440a48
  1. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  2. 4
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  3. 111
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  4. 24
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  5. 54
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java

2
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -70,7 +70,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
private String certificateValue; private String certificateValue;
@ApiModelProperty(position = 17, value = "CA certificate hash") @ApiModelProperty(position = 17, value = "CA certificate hash")
private String certificateHash; private String certificateHash;
@ApiModelProperty(position = 18, value = "Regex for fetch deviceName from CN") @ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN")
private String certificateRegexPattern; private String certificateRegexPattern;
@ApiModelProperty(position = 7, value = "Reference to the rule chain. " + @ApiModelProperty(position = 7, value = "Reference to the rule chain. " +

4
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java

@ -124,7 +124,7 @@ public class MqttSslHandlerProvider {
static class ThingsboardMqttX509TrustManager implements X509TrustManager { static class ThingsboardMqttX509TrustManager implements X509TrustManager {
private final X509TrustManager trustManager; private final X509TrustManager trustManager;
private TransportService transportService; private final TransportService transportService;
ThingsboardMqttX509TrustManager(X509TrustManager trustManager, TransportService transportService) { ThingsboardMqttX509TrustManager(X509TrustManager trustManager, TransportService transportService) {
this.trustManager = trustManager; this.trustManager = trustManager;
@ -220,7 +220,7 @@ public class MqttSslHandlerProvider {
} }
} }
if (credentialsBody == null) { if (credentialsBody == null) {
throw new CertificateException("Invalid Device Certificate"); throw new CertificateException("Invalid Certificate's chain");
} }
} }
} }

111
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -62,7 +62,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse;
import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.SessionMetaData;
import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.common.transport.util.SslUtil;
@ -79,7 +78,7 @@ import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver;
import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPeerUnverifiedException;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.security.cert.CertificateEncodingException; import java.security.cert.Certificate;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -89,7 +88,6 @@ import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -808,9 +806,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
deviceSessionCtx.setProvisionOnly(true); deviceSessionCtx.setProvisionOnly(true);
ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg)); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg));
} else { } else {
X509Certificate[] chain; X509Certificate cert;
if (sslHandler != null && (chain = getX509Certificate()) != null) { if (sslHandler != null && (cert = getX509Certificate()) != null) {
processX509CertConnect(ctx, chain, msg); processX509CertConnect(ctx, cert, msg);
} else { } else {
processAuthTokenConnect(ctx, msg); processAuthTokenConnect(ctx, msg);
} }
@ -846,84 +844,25 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}); });
} }
private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) { private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) {
try { try {
String deviceCN = SslUtil.parseCommonName(chain[0]); if (!context.isSkipValidityCheckForClientCert()) {
String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]); cert.checkValidity();
String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue);
for (X509Certificate cert : chain) {
try {
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
final ValidateDeviceCredentialsResponse[] validateDeviceCredentialsResponses = new ValidateDeviceCredentialsResponse[1];
CountDownLatch latch = new CountDownLatch(1);
transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (!StringUtils.isEmpty(msg.getCredentials())) {
validateDeviceCredentialsResponses[0] = msg;
latch.countDown();
} else {
transportService.process(DeviceTransportType.MQTT,
TransportProtos.ValidateDeviceProfileX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceProfileCredentialsResponse msg) {
if (msg.isDeviceProfileFound()) {
transportService.process(DeviceTransportType.MQTT,
TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg.newBuilder()
.setHash(clientDeviceCertHash)
.setValue(clientDeviceCertValue)
.setCommonName(deviceCN)
.setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits())
.build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (!StringUtils.isEmpty(msg.getCredentials())) {
validateDeviceCredentialsResponses[0] = msg;
latch.countDown();
}
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
latch.countDown();
}
}
);
} else {
latch.countDown();
}
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
latch.countDown();
}
});
}
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
latch.countDown();
}
});
latch.await(10, TimeUnit.SECONDS);
if (validateDeviceCredentialsResponses[0] != null && validateDeviceCredentialsResponses[0].hasDeviceInfo()) {
onValidateDeviceResponse(validateDeviceCredentialsResponses[0], ctx, connectMessage);
break;
}
} catch (InterruptedException | CertificateEncodingException e) {
log.error(e.getMessage(), e);
}
} }
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
onValidateDeviceResponse(msg, ctx, connectMessage);
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
}
});
} catch (Exception e) { } catch (Exception e) {
context.onAuthFailure(address); context.onAuthFailure(address);
ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage)); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage));
@ -932,13 +871,17 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
private X509Certificate[] getX509Certificate() { private X509Certificate getX509Certificate() {
try { try {
return (X509Certificate[]) sslHandler.engine().getSession().getPeerCertificates(); Certificate[] certChains = sslHandler.engine().getSession().getPeerCertificates();
if (certChains.length > 1) {
return (X509Certificate) certChains[0];
}
} catch (SSLPeerUnverifiedException e) { } catch (SSLPeerUnverifiedException e) {
log.warn(e.getMessage()); log.warn(e.getMessage());
return null; return null;
} }
return null;
} }
private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) { private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) {

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

@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
@ -51,6 +52,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString; import static org.thingsboard.server.dao.service.Validator.validateString;
@ -62,7 +65,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId ";
private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName "; private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName ";
private static final String INCORRECT_DEVICE_PROFILE_CREDENTIALS_HASH = "Incorrect deviceProfileCredentialsHash "; private static final String INCORRECT_DEVICE_PROFILE_CREDENTIALS_HASH = "Incorrect deviceProfileCertificateHash ";
private static final String DEVICE_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS = "Device profile with such name already exists!"; private static final String DEVICE_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS = "Device profile with such name already exists!";
@Autowired @Autowired
@ -124,6 +127,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Override @Override
public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile) { public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile) {
log.trace("Executing saveDeviceProfile [{}]", deviceProfile); log.trace("Executing saveDeviceProfile [{}]", deviceProfile);
if (deviceProfile.getCertificateValue() != null) {
formatDeviceProfileCertificate(deviceProfile);
}
DeviceProfile oldDeviceProfile = deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId); DeviceProfile oldDeviceProfile = deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId);
DeviceProfile savedDeviceProfile; DeviceProfile savedDeviceProfile;
try { try {
@ -329,4 +335,20 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
profile.getDefaultDashboardId(), profile.getType(), profile.getTransportType()); profile.getDefaultDashboardId(), profile.getType(), profile.getTransportType());
} }
private void formatDeviceProfileCertificate(DeviceProfile deviceProfile) {
String cert = regexCertificateChain(deviceProfile.getCertificateValue());
String sha3Hash = EncryptionUtil.getSha3Hash(cert);
deviceProfile.setCertificateHash(sha3Hash);
}
private String regexCertificateChain(String chain) {
String regex = "-----BEGIN CERTIFICATE-----\\s*((.+\\s+)*?)-----END CERTIFICATE----";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(chain);
if (matcher.find()) {
return matcher.group(0);
}
return chain;
}
} }

54
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java

@ -21,6 +21,7 @@ import org.eclipse.leshan.core.util.SecurityUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
@ -55,6 +56,19 @@ import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -118,6 +132,15 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
if (deviceProfile.getProvisionType() == null) { if (deviceProfile.getProvisionType() == null) {
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
} }
if (deviceProfile.getCertificateHash() != null) {
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!");
}
}
if (getRootCAFromJavaCacerts(deviceProfile.getCertificateHash())) {
throw new DataValidationException("Device profile certificate cannot be well known root CA!");
}
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
transportConfiguration.validate(); transportConfiguration.validate();
if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) {
@ -211,6 +234,13 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
throw new DataValidationException(message); throw new DataValidationException(message);
} }
} }
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!");
}
if (getRootCAFromJavaCacerts(deviceProfile.getCertificateHash())) {
throw new DataValidationException("Device profile certificate cannot be well known root CA!");
}
return old; return old;
} }
@ -363,4 +393,28 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
break; break;
} }
} }
private boolean getRootCAFromJavaCacerts(String deviceProfileHash) {
Set<String> rootCa = new HashSet<>();
try {
String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
FileInputStream is = new FileInputStream(filename);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "changeit";
keystore.load(is, password.toCharArray());
PKIXParameters params = new PKIXParameters(keystore);
for (TrustAnchor ta : params.getTrustAnchors()) {
X509Certificate cert = ta.getTrustedCert();
rootCa.add(EncryptionUtil.getSha3Hash(getCertificateString(cert)));
}
} catch (CertificateException | KeyStoreException | NoSuchAlgorithmException |
InvalidAlgorithmParameterException | IOException ignored) {
}
return rootCa.contains(deviceProfileHash);
}
private String getCertificateString(Certificate cert) throws CertificateEncodingException {
return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded()));
}
} }

Loading…
Cancel
Save