Add ability to upgrade device credentials if chain match with uploaded device profile certificate.

This commit is contained in:
Andrii Landiak 2023-01-12 17:18:29 +02:00
parent dccc19a9b6
commit 5d35fa1f26
20 changed files with 388 additions and 73 deletions

View File

@ -65,8 +65,8 @@ import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceProvisionService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
@ -90,11 +90,15 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRespon
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
@ -108,6 +112,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;
@ -128,6 +134,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbTenantProfileCache tenantProfileCache;
private final TbApiUsageStateService apiUsageStateService;
private final DeviceService deviceService;
private final DeviceProfileService deviceProfileService;
private final RelationService relationService;
private final DeviceCredentialsService deviceCredentialsService;
private final DbCallbackExecutorService dbCallbackExecutorService;
@ -159,6 +166,13 @@ public class DefaultTransportApiService implements TransportApiService {
} else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) {
ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg();
result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE);
} else if (transportApiRequestMsg.hasValidateProfileX509CertRequestMsg()) {
ValidateDeviceProfileX509CertRequestMsg msg = transportApiRequestMsg.getValidateProfileX509CertRequestMsg();
result = validateDeviceProfileCertificate(msg.getHash());
} else if (transportApiRequestMsg.hasUpdateOrCreateDeviceCertRequestMsg()) {
UpdateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getUpdateOrCreateDeviceCertRequestMsg();
DeviceProfile deviceProfile = deviceProfileCache.find(new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB())));
result = updateOrCreateDeviceCredentials(msg.getHash(), msg.getValue(), msg.getCommonName(), deviceProfile, DeviceCredentialsType.X509_CERTIFICATE);
} else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg());
} else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) {
@ -226,6 +240,31 @@ public class DefaultTransportApiService implements TransportApiService {
}
}
private ListenableFuture<TransportApiResponseMsg> validateDeviceProfileCertificate(String credentialsId) {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(credentialsId);
if (deviceProfile != null) {
return getDeviceProfileInfo(deviceProfile);
}
return getEmptyTransportApiResponseFuture();
}
private ListenableFuture<TransportApiResponseMsg> updateOrCreateDeviceCredentials(String credentialsId,
String credentialsValue,
String deviceCN,
DeviceProfile deviceProfile,
DeviceCredentialsType credentialsType) {
String deviceName = extractRegex(deviceCN, deviceProfile.getCertificateRegexPattern());
// find deviceCredentials by deviceName (device exists)
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByTenantIdAndDeviceName(deviceProfile.getTenantId(), deviceName);
if (deviceCredentials != null && deviceCredentials.getCredentialsType() == credentialsType) {
deviceCredentials.setCredentialsId(credentialsId);
deviceCredentials.setCredentialsValue(credentialsValue);
deviceCredentialsService.updateDeviceCredentials(deviceProfile.getTenantId(), deviceCredentials);
return getDeviceInfo(deviceCredentials);
}
return getEmptyTransportApiResponseFuture();
}
private ListenableFuture<TransportApiResponseMsg> validateUserNameCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
if (credentials != null) {
@ -507,6 +546,18 @@ public class DefaultTransportApiService implements TransportApiService {
}, MoreExecutors.directExecutor());
}
private ListenableFuture<TransportApiResponseMsg> getDeviceProfileInfo(DeviceProfile deviceProfile) {
ValidateDeviceProfileCredentialsResponseMsg.Builder builder = ValidateDeviceProfileCredentialsResponseMsg.newBuilder()
.setDeviceProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits())
.setIsDeviceProfileFound(true);
return Futures.immediateFuture(
TransportApiResponseMsg.newBuilder()
.setValidateDeviceProfileResponseMsg(builder.build())
.build());
}
private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException {
DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder()
.setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits())
@ -664,4 +715,13 @@ public class DefaultTransportApiService implements TransportApiService {
private Long checkLong(Long l) {
return l != null ? l : 0;
}
private String extractRegex(String commonName, String regex) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(commonName);
if (matcher.find()) {
return matcher.group(0);
}
return commonName;
}
}

View File

@ -169,6 +169,18 @@ message ValidateDeviceX509CertRequestMsg {
string hash = 1;
}
message ValidateDeviceProfileX509CertRequestMsg {
string hash = 1;
}
message UpdateOrCreateDeviceX509CertRequestMsg {
string hash = 1;
string value = 2;
string commonName = 3;
int64 deviceProfileIdMSB = 4;
int64 deviceProfileIdLSB = 5;
}
message ValidateBasicMqttCredRequestMsg {
string clientId = 1;
string userName = 2;
@ -181,6 +193,12 @@ message ValidateDeviceCredentialsResponseMsg {
bytes profileBody = 3;
}
message ValidateDeviceProfileCredentialsResponseMsg {
int64 deviceProfileIdMSB = 1;
int64 deviceProfileIdLSB = 2;
bool isDeviceProfileFound = 3;
}
message GetOrCreateDeviceFromGatewayRequestMsg {
int64 gatewayIdMSB = 1;
int64 gatewayIdLSB = 2;
@ -885,33 +903,36 @@ message VersionControlResponseMsg {
message TransportApiRequestMsg {
ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1;
ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2;
GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3;
GetEntityProfileRequestMsg entityProfileRequestMsg = 4;
LwM2MRequestMsg lwM2MRequestMsg = 5;
ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
GetResourceRequestMsg resourceRequestMsg = 9;
GetOtaPackageRequestMsg otaPackageRequestMsg = 10;
GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11;
GetDeviceRequestMsg deviceRequestMsg = 12;
GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13;
GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14;
ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 3;
GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 4;
GetEntityProfileRequestMsg entityProfileRequestMsg = 5;
LwM2MRequestMsg lwM2MRequestMsg = 6;
ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 7;
ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 8;
ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 9;
GetResourceRequestMsg resourceRequestMsg = 10;
GetOtaPackageRequestMsg otaPackageRequestMsg = 11;
GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 12;
GetDeviceRequestMsg deviceRequestMsg = 13;
GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 14;
GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 15;
UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 16;
}
/* Response from ThingsBoard Core Service to Transport Service */
message TransportApiResponseMsg {
ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1;
GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
GetEntityProfileResponseMsg entityProfileResponseMsg = 3;
ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5;
LwM2MResponseMsg lwM2MResponseMsg = 6;
GetResourceResponseMsg resourceResponseMsg = 7;
GetOtaPackageResponseMsg otaPackageResponseMsg = 8;
GetDeviceResponseMsg deviceResponseMsg = 9;
GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10;
repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11;
ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 2;
GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 3;
GetEntityProfileResponseMsg entityProfileResponseMsg = 4;
ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 5;
GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 6;
LwM2MResponseMsg lwM2MResponseMsg = 7;
GetResourceResponseMsg resourceResponseMsg = 8;
GetOtaPackageResponseMsg otaPackageResponseMsg = 9;
GetDeviceResponseMsg deviceResponseMsg = 10;
GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 11;
repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 12;
}
/* Messages that are handled by ThingsBoard Core Service */

View File

@ -15,10 +15,10 @@
*/
package org.thingsboard.server.dao.device;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import com.fasterxml.jackson.databind.JsonNode;
public interface DeviceCredentialsService {
@ -26,6 +26,8 @@ public interface DeviceCredentialsService {
DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId);
DeviceCredentials findDeviceCredentialsByTenantIdAndDeviceName(TenantId tenantId, String deviceName);
DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials);
DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials);

View File

@ -39,6 +39,8 @@ public interface DeviceProfileService extends EntityDaoService {
PageData<DeviceProfileInfo> findDeviceProfileInfos(TenantId tenantId, PageLink pageLink, String transportType);
DeviceProfile findDeviceProfileByCertificateHash(String credentialsId);
DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName);
DeviceProfile createDefaultDeviceProfile(TenantId tenantId);

View File

@ -16,7 +16,6 @@
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -28,7 +27,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
@ -68,6 +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")
private String certificateValue;
@ApiModelProperty(position = 17, value = "CA certificate hash")
private String certificateHash;
@ApiModelProperty(position = 18, value = "Regex for fetch deviceName from CN")
private String certificateRegexPattern;
@ApiModelProperty(position = 7, value = "Reference to the rule chain. " +
"If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " +
"Otherwise, the root rule chain will be used to process those messages.")

View File

@ -24,12 +24,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig;
import org.thingsboard.server.common.transport.util.SslUtil;
@ -142,8 +143,15 @@ public class MqttSslHandlerProvider {
}
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
String deviceCN = SslUtil.parseCommonName(chain[0]);
String deviceCert;
String deviceCredentialsValue = SslUtil.getCertificateString(chain[0]);
try {
deviceCert = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0]));
} catch (CertificateEncodingException e) {
throw new RuntimeException(e);
}
String credentialsBody = null;
for (X509Certificate cert : chain) {
try {
@ -152,13 +160,53 @@ public class MqttSslHandlerProvider {
final String[] credentialsBodyHolder = new String[1];
CountDownLatch latch = new CountDownLatch(1);
transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
new TransportServiceCallback<ValidateDeviceCredentialsResponse>() {
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (!StringUtils.isEmpty(msg.getCredentials())) {
credentialsBodyHolder[0] = msg.getCredentials();
}
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(deviceCert)
.setCommonName(deviceCN)
.setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits())
.build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
System.out.println("msg.getCredentials() = " + msg.getCredentials());
credentialsBodyHolder[0] = msg.getCredentials();
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
@ -168,7 +216,7 @@ public class MqttSslHandlerProvider {
}
});
latch.await(10, TimeUnit.SECONDS);
if (strCert.equals(credentialsBodyHolder[0])) {
if (deviceCredentialsValue.equals(credentialsBodyHolder[0])) {
credentialsBody = credentialsBodyHolder[0];
break;
}

View File

@ -21,7 +21,6 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.mqtt.MqttConnAckMessage;
import io.netty.handler.codec.mqtt.MqttConnAckVariableHeader;
import io.netty.handler.codec.mqtt.MqttConnectMessage;
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
@ -63,12 +62,12 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
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.SessionMetaData;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
@ -80,7 +79,7 @@ import org.thingsboard.server.transport.mqtt.util.ReturnCodeResolver;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
@ -90,16 +89,15 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.amazonaws.util.StringUtils.UTF8;
import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT;
import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP;
import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK;
import static io.netty.handler.codec.mqtt.MqttMessageType.UNSUBACK;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_LEAST_ONCE;
import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE;
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED;
@ -810,9 +808,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
deviceSessionCtx.setProvisionOnly(true);
ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SUCCESS, msg));
} else {
X509Certificate cert;
if (sslHandler != null && (cert = getX509Certificate()) != null) {
processX509CertConnect(ctx, cert, msg);
X509Certificate[] chain;
if (sslHandler != null && (chain = getX509Certificate()) != null) {
processX509CertConnect(ctx, chain, msg);
} else {
processAuthTokenConnect(ctx, msg);
}
@ -848,27 +846,82 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
});
}
private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) {
private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate[] chain, MqttConnectMessage connectMessage) {
try {
String deviceCN = SslUtil.parseCommonName(chain[0]);
String deviceCertHash = EncryptionUtil.getSha3Hash(SslUtil.getCertificateString(chain[0]));
for (X509Certificate cert : chain) {
try {
if (!context.isSkipValidityCheckForClientCert()) {
cert.checkValidity();
}
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
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) {
onValidateDeviceResponse(msg, ctx, connectMessage);
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(deviceCertHash)
.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.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e);
ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage));
ctx.close();
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);
}
}
} catch (Exception e) {
context.onAuthFailure(address);
ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage));
@ -877,17 +930,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
}
private X509Certificate getX509Certificate() {
private X509Certificate[] getX509Certificate() {
try {
Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates();
if (certChain.length > 0) {
return (X509Certificate) certChain[0];
}
return (X509Certificate[]) sslHandler.engine().getSession().getPeerCertificates();
} catch (SSLPeerUnverifiedException e) {
log.warn(e.getMessage());
return null;
}
return null;
}
private MqttConnAckMessage createMqttConnAckMsg(ReturnCode returnCode, MqttConnectMessage msg) {

View File

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse;
import org.thingsboard.server.common.transport.service.SessionMetaData;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
@ -52,8 +53,10 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceProfileX509CertRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
@ -87,6 +90,12 @@ public interface TransportService {
void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(DeviceTransportType transportType, ValidateDeviceProfileX509CertRequestMsg msg,
TransportServiceCallback<ValidateDeviceProfileCredentialsResponse> callback);
void process(DeviceTransportType transportType, UpdateOrCreateDeviceX509CertRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(ValidateDeviceLwM2MCredentialsRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);

View File

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2022 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.common.transport.auth;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@Data
@Builder
public class ValidateDeviceProfileCredentialsResponse {
private final DeviceProfileId deviceProfileId;
private final boolean isDeviceProfileFound;
}

View File

@ -71,8 +71,8 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceProfileCredentialsResponse;
import org.thingsboard.server.common.transport.limits.TransportRateLimitService;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
@ -97,6 +97,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.provider.TbTransportQueueFactory;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbTransportComponent;
import javax.annotation.PostConstruct;
@ -427,6 +428,32 @@ public class DefaultTransportService implements TransportService {
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
@Override
public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceProfileX509CertRequestMsg requestMsg,
TransportServiceCallback<ValidateDeviceProfileCredentialsResponse> callback) {
log.trace("Processing msg: {}", requestMsg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(),
TransportApiRequestMsg.newBuilder().setValidateProfileX509CertRequestMsg(requestMsg).build());
ListenableFuture<ValidateDeviceProfileCredentialsResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
TransportProtos.ValidateDeviceProfileCredentialsResponseMsg msg = tmp.getValue().getValidateDeviceProfileResponseMsg();
ValidateDeviceProfileCredentialsResponse.ValidateDeviceProfileCredentialsResponseBuilder result = ValidateDeviceProfileCredentialsResponse.builder();
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(msg.getDeviceProfileIdMSB(), msg.getDeviceProfileIdLSB()));
result.deviceProfileId(deviceProfileId);
result.isDeviceProfileFound(msg.getIsDeviceProfileFound());
return result.build();
}, MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
@Override
public void process(DeviceTransportType transportType, TransportProtos.UpdateOrCreateDeviceX509CertRequestMsg requestMsg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
log.trace("Processing msg: {}", requestMsg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder()
.setUpdateOrCreateDeviceCertRequestMsg(requestMsg).build());
doProcess(transportType, protoMsg, callback);
}
@Override
public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
log.trace("Processing msg: {}", msg);

View File

@ -16,11 +16,17 @@
package org.thingsboard.server.common.transport.util;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.springframework.util.Base64Utils;
import org.thingsboard.server.common.msg.EncryptionUtil;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
/**
* @author Valerii Sosliuk
@ -35,4 +41,15 @@ public class SslUtil {
throws CertificateEncodingException {
return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded()));
}
public static String parseCommonName(X509Certificate certificate) {
X500Name x500name;
try {
x500name = new JcaX509CertificateHolder(certificate).getSubject();
} catch (CertificateEncodingException e) {
throw new RuntimeException(e);
}
RDN cn = x500name.getRDNs(BCStyle.CN)[0];
return IETFUtils.valueToString(cn.getFirst().getValue());
}
}

View File

@ -53,4 +53,12 @@ public interface DeviceCredentialsDao extends Dao<DeviceCredentials> {
*/
DeviceCredentials findByCredentialsId(TenantId tenantId, String credentialsId);
/**
* Find device credentials by device name.
*
* @param deviceName the device name
* @return the device credentials object
*/
DeviceCredentials findByTenantIdAndDeviceName(TenantId tenantId, String deviceName);
}

View File

@ -16,15 +16,12 @@
package org.thingsboard.server.dao.device;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.util.SecurityUtil;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
@ -86,6 +83,13 @@ public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService<St
false);
}
@Override
public DeviceCredentials findDeviceCredentialsByTenantIdAndDeviceName(TenantId tenantId, String deviceName) {
log.trace("Executing findDeviceCredentialsByDeviceName [{}]", deviceName);
validateString(deviceName, "Incorrect deviceName " + deviceName);
return deviceCredentialsDao.findByTenantIdAndDeviceName(tenantId, deviceName);
}
@Override
public DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) {
return saveOrUpdate(tenantId, deviceCredentials);

View File

@ -29,25 +29,31 @@ public class DeviceProfileCacheKey implements Serializable {
private final TenantId tenantId;
private final String name;
private final DeviceProfileId deviceProfileId;
private final String certificateHash;
private final boolean defaultProfile;
private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, boolean defaultProfile) {
private DeviceProfileCacheKey(TenantId tenantId, String name, DeviceProfileId deviceProfileId, String certificateHash, boolean defaultProfile) {
this.tenantId = tenantId;
this.name = name;
this.deviceProfileId = deviceProfileId;
this.certificateHash = certificateHash;
this.defaultProfile = defaultProfile;
}
public static DeviceProfileCacheKey fromName(TenantId tenantId, String name) {
return new DeviceProfileCacheKey(tenantId, name, null, false);
return new DeviceProfileCacheKey(tenantId, name, null, null, false);
}
public static DeviceProfileCacheKey fromId(DeviceProfileId id) {
return new DeviceProfileCacheKey(null, null, id, false);
return new DeviceProfileCacheKey(null, null, id, null, false);
}
public static DeviceProfileCacheKey fromCertificateHash(String certificateHash) {
return new DeviceProfileCacheKey(null, null, null, certificateHash, false);
}
public static DeviceProfileCacheKey defaultProfile(TenantId tenantId) {
return new DeviceProfileCacheKey(tenantId, null, null, true);
return new DeviceProfileCacheKey(tenantId, null, null, null, true);
}
@Override

View File

@ -45,4 +45,6 @@ public interface DeviceProfileDao extends Dao<DeviceProfile>, ExportableEntityDa
DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey);
DeviceProfile findByName(TenantId tenantId, String profileName);
DeviceProfile findByCertificateHash(String certificateHash);
}

View File

@ -53,6 +53,7 @@ import java.util.Map;
import java.util.Optional;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString;
@Service("DeviceProfileDaoService")
@Slf4j
@ -61,6 +62,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
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_NAME = "Incorrect deviceProfileName ";
private static final String INCORRECT_DEVICE_PROFILE_CREDENTIALS_HASH = "Incorrect deviceProfileCredentialsHash ";
private static final String DEVICE_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS = "Device profile with such name already exists!";
@Autowired
@ -197,6 +199,14 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
return deviceProfileDao.findDeviceProfileInfos(tenantId, pageLink, transportType);
}
@Override
public DeviceProfile findDeviceProfileByCertificateHash(String certificateHash) {
log.trace("Executing findDeviceProfileIdByCredentialsId credentialId [{}]", certificateHash);
validateString(certificateHash, INCORRECT_DEVICE_PROFILE_CREDENTIALS_HASH + certificateHash);
return cache.getAndPutInTransaction(DeviceProfileCacheKey.fromCertificateHash(certificateHash),
() -> deviceProfileDao.findByCertificateHash(certificateHash), true);
}
@Override
public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String name) {
log.trace("Executing findOrCreateDefaultDeviceProfile");

View File

@ -16,6 +16,8 @@
package org.thingsboard.server.dao.sql.device;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.DeviceCredentialsEntity;
import java.util.UUID;
@ -28,4 +30,7 @@ public interface DeviceCredentialsRepository extends JpaRepository<DeviceCredent
DeviceCredentialsEntity findByDeviceId(UUID deviceId);
DeviceCredentialsEntity findByCredentialsId(String credentialsId);
@Query("SELECT dc FROM DeviceCredentialsEntity dc INNER JOIN DeviceEntity de ON dc.deviceId = de.id WHERE de.tenantId = :tenantId AND de.name = :deviceName ")
DeviceCredentialsEntity findByTenantIdAndDeviceName(@Param("tenantId") UUID tenantId, @Param("deviceName") String deviceName);
}

View File

@ -64,6 +64,8 @@ public interface DeviceProfileRepository extends JpaRepository<DeviceProfileEnti
"WHERE d.tenantId = :tenantId AND d.isDefault = true")
DeviceProfileInfo findDefaultDeviceProfileInfo(@Param("tenantId") UUID tenantId);
DeviceProfileEntity findDeviceProfileByCertificateHash(String certificateHash);
DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName);
DeviceProfileEntity findByProvisionDeviceKey(@Param("provisionDeviceKey") String provisionDeviceKey);

View File

@ -66,4 +66,9 @@ public class JpaDeviceCredentialsDao extends JpaAbstractDao<DeviceCredentialsEnt
public DeviceCredentials findByCredentialsId(TenantId tenantId, String credentialsId) {
return DaoUtil.getData(deviceCredentialsRepository.findByCredentialsId(credentialsId));
}
@Override
public DeviceCredentials findByTenantIdAndDeviceName(TenantId tenantId, String deviceName) {
return DaoUtil.getData(deviceCredentialsRepository.findByTenantIdAndDeviceName(tenantId, deviceName));
}
}

View File

@ -115,6 +115,11 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE
return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName));
}
@Override
public DeviceProfile findByCertificateHash(String certificateHash) {
return DaoUtil.getData(deviceProfileRepository.findDeviceProfileByCertificateHash(certificateHash));
}
@Override
public DeviceProfile findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndExternalId(tenantId, externalId));