Browse Source

Updated: send full chain to kafka with single message and do whole logic on core

pull/7935/head
Andrii Landiak 4 years ago
parent
commit
7957da4a9e
  1. 148
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  2. 22
      common/cluster-api/src/main/proto/queue.proto
  3. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
  4. 13
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  5. 3
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java
  6. 18
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java
  7. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java
  8. 5
      common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java
  9. 104
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  10. 13
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  11. 11
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  12. 30
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  13. 12
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java
  14. 8
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java
  15. 7
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  16. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  17. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java
  18. 20
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
  19. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java
  20. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java

148
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -25,6 +25,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage;
@ -65,6 +67,7 @@ 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.common.transport.util.SslUtil;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceProvisionService;
@ -90,11 +93,8 @@ 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;
@ -105,6 +105,13 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.resource.TbResourceService;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ -166,13 +173,9 @@ 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.hasValidateOrCreateX509CertRequestMsg()) {
TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateOrCreateX509CertRequestMsg();
result = validateOrCreateDeviceX509Certificate(msg.getCertificate(), DeviceCredentialsType.X509_CERTIFICATE);
} else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg());
} else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) {
@ -240,27 +243,43 @@ 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> validateOrCreateDeviceX509Certificate(String certChain, DeviceCredentialsType credentialsType) {
try {
List<X509Certificate> chain = getX509CertificateChainFromString(certChain);
String updateDeviceCertificateValue = SslUtil.getCertificateString(chain.get(0));
String updateDeviceCertificateHash = EncryptionUtil.getSha3Hash(updateDeviceCertificateValue);
for (X509Certificate cert: chain) {
String certificateValue = SslUtil.getCertificateString(cert);
String certificateHash = EncryptionUtil.getSha3Hash(certificateValue);
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(certificateHash);
if (credentials != null && credentials.getCredentialsType() == credentialsType) {
return getDeviceInfo(credentials);
}
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByCertificateHash(certificateHash);
if (deviceProfile != null) {
String deviceCN = extractDeviceNameFromCNByRegEx(SslUtil.parseCommonName(chain.get(0)), deviceProfile.getCertificateRegexPattern());
String deviceName = extractDeviceNameFromCNByRegEx(deviceCN, deviceProfile.getCertificateRegexPattern());
Device device = deviceService.findDeviceByTenantIdAndName(deviceProfile.getTenantId(), deviceName);
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);
} else if (deviceCredentials == null) {
deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), updateDeviceCertificateValue, updateDeviceCertificateHash, credentialsType);
}
return getDeviceInfo(deviceCredentials);
} else if (deviceProfile.getProvisionType() == DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE && deviceProfile.isAllowCreateNewDevicesByX509Strategy()) {
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);
return getDeviceInfo(deviceCredentials);
}
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);
}
}
} catch (CertificateEncodingException e) {
throw new RuntimeException(e);
}
return getEmptyTransportApiResponseFuture();
}
@ -546,18 +565,6 @@ 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())
@ -716,7 +723,7 @@ public class DefaultTransportApiService implements TransportApiService {
return l != null ? l : 0;
}
private String extractRegex(String commonName, String regex) {
private String extractDeviceNameFromCNByRegEx(String commonName, String regex) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(commonName);
if (matcher.find()) {
@ -724,4 +731,59 @@ public class DefaultTransportApiService implements TransportApiService {
}
return commonName;
}
private List<X509Certificate> getX509CertificateChainFromString(String certificateChain) {
List<X509Certificate> chain = new ArrayList<>();
String[] test = Arrays.stream(certificateChain.split("-----BEGIN CERTIFICATE-----")).filter(e -> e.trim().length() > 0).map(EncryptionUtil::certTrimNewLines).toArray(String[]::new);
Arrays.stream(test).forEach(s -> {
try {
chain.add(readCertFile(s));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return chain;
}
private X509Certificate readCertFile(String fileContent) throws Exception {
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);
}
}
return certificate;
}
private DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials, String certificateValue,
String certificateHash, DeviceCredentialsType credentialsType) {
deviceCredentials.setCredentialsId(certificateHash);
deviceCredentials.setCredentialsValue(certificateValue);
deviceCredentials.setCredentialsType(credentialsType);
return deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials);
}
private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, String certificateValue,
String certificateHash, DeviceCredentialsType credentialsType) {
DeviceCredentials createDevCredentials = new DeviceCredentials();
createDevCredentials.setDeviceId(deviceId);
createDevCredentials.setCredentialsType(credentialsType);
createDevCredentials.setCredentialsId(certificateHash);
createDevCredentials.setCredentialsValue(certificateValue);
return deviceCredentialsService.createDeviceCredentials(tenantId, createDevCredentials);
}
private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId, String deviceName, String type) {
Device device = new Device();
device.setTenantId(tenantId);
device.setDeviceProfileId(deviceProfileId);
device.setName(deviceName);
device.setType(type);
return deviceService.saveDevice(device);
}
}

22
common/cluster-api/src/main/proto/queue.proto

@ -169,16 +169,8 @@ 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 ValidateOrCreateDeviceX509CertRequestMsg {
string certificate = 1;
}
message ValidateBasicMqttCredRequestMsg {
@ -193,12 +185,6 @@ message ValidateDeviceCredentialsResponseMsg {
bytes profileBody = 3;
}
message ValidateDeviceProfileCredentialsResponseMsg {
int64 deviceProfileIdMSB = 1;
int64 deviceProfileIdLSB = 2;
bool isDeviceProfileFound = 3;
}
message GetOrCreateDeviceFromGatewayRequestMsg {
int64 gatewayIdMSB = 1;
int64 gatewayIdLSB = 2;
@ -915,8 +901,7 @@ message TransportApiRequestMsg {
GetDeviceRequestMsg deviceRequestMsg = 12;
GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13;
GetAllQueueRoutingInfoRequestMsg getAllQueueRoutingInfoRequestMsg = 14;
UpdateOrCreateDeviceX509CertRequestMsg updateOrCreateDeviceCertRequestMsg = 15;
ValidateDeviceProfileX509CertRequestMsg validateProfileX509CertRequestMsg = 16;
ValidateOrCreateDeviceX509CertRequestMsg validateOrCreateX509CertRequestMsg = 15;
}
/* Response from ThingsBoard Core Service to Transport Service */
@ -932,7 +917,6 @@ message TransportApiResponseMsg {
GetDeviceResponseMsg deviceResponseMsg = 9;
GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10;
repeated GetQueueRoutingInfoResponseMsg getQueueRoutingInfoResponseMsgs = 11;
ValidateDeviceProfileCredentialsResponseMsg validateDeviceProfileResponseMsg = 12;
}
/* Messages that are handled by ThingsBoard Core Service */

2
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java

@ -26,8 +26,6 @@ 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);

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

@ -66,12 +66,15 @@ 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 = 16, value = "CA certificate value. ")
private String certificateValue;
@ApiModelProperty(position = 17, value = "CA certificate hash")
@ApiModelProperty(position = 17, value = "CA certificate hash. ")
private String certificateHash;
@ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN")
@ApiModelProperty(position = 18, value = "Regex to fetch deviceName from CN. ")
private String certificateRegexPattern;
@ApiModelProperty(position = 19, value = "Allow to create new devices by x509 provision strategy. ")
private boolean allowCreateNewDevicesByX509Strategy;
@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. " +
@ -128,6 +131,10 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
this.firmwareId = deviceProfile.getFirmwareId();
this.softwareId = deviceProfile.getSoftwareId();
this.defaultEdgeRuleChainId = deviceProfile.getDefaultEdgeRuleChainId();
this.allowCreateNewDevicesByX509Strategy = deviceProfile.isAllowCreateNewDevicesByX509Strategy();
this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern();
this.certificateValue = deviceProfile.getCertificateValue();
this.certificateHash = deviceProfile.getCertificateHash();
this.externalId = deviceProfile.getExternalId();
}

3
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java

@ -18,5 +18,6 @@ package org.thingsboard.server.common.data;
public enum DeviceProfileProvisionType {
DISABLED,
ALLOW_CREATE_NEW_DEVICES,
CHECK_PRE_PROVISIONED_DEVICES
CHECK_PRE_PROVISIONED_DEVICES,
ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE
}

18
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceProfileCredentialsResponse.java → common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.java

@ -13,16 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.auth;
import lombok.Builder;
package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
@Data
@Builder
public class ValidateDeviceProfileCredentialsResponse {
public class AllowCreatingNewDevicesByX509CertificateProvisionConfiguration implements DeviceProfileProvisionConfiguration {
private final String provisionDeviceSecret;
@Override
public DeviceProfileProvisionType getType() {
return DeviceProfileProvisionType.ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE;
}
private final DeviceProfileId deviceProfileId;
private final boolean isDeviceProfileFound;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java

@ -31,7 +31,8 @@ import java.io.Serializable;
@JsonSubTypes({
@JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"),
@JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"),
@JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")})
@JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES"),
@JsonSubTypes.Type(value = AllowCreatingNewDevicesByX509CertificateProvisionConfiguration.class, name = "ALLOW_CREATING_NEW_DEVICES_BY_X509_CERTIFICATE")})
public interface DeviceProfileProvisionConfiguration extends Serializable {
String getProvisionDeviceSecret();

5
common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java

@ -35,6 +35,11 @@ public class EncryptionUtil {
.replaceAll("-----END CERTIFICATE-----", "");
}
public static String certTrimNewLinesWithoutBeginEnd(String input) {
return input.replaceAll("\n", "")
.replaceAll("\r", "");
}
public static String pubkTrimNewLines(String input) {
return input.replaceAll("-----BEGIN PUBLIC KEY-----", "")
.replaceAll("\n", "")

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

@ -17,6 +17,7 @@ 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;
@ -26,11 +27,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
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;
@ -144,84 +143,37 @@ public class MqttSslHandlerProvider {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
String deviceCN = SslUtil.parseCommonName(chain[0]);
String clientDeviceCertValue = SslUtil.getCertificateString(chain[0]);
String clientDeviceCertHash = EncryptionUtil.getSha3Hash(clientDeviceCertValue);
String credentialsBody = null;
for (X509Certificate cert : chain) {
try {
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
final String[] credentialsBodyHolder = new String[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())) {
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(clientDeviceCertHash)
.setValue(clientDeviceCertValue)
.setCommonName(deviceCN)
.setDeviceProfileIdMSB(msg.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(msg.getDeviceProfileId().getId().getLeastSignificantBits())
.build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
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();
}
});
}
final String[] credentialsBodyHolder = new String[1];
CountDownLatch latch = new CountDownLatch(1);
try {
String certificateChain = SslUtil.getCertificateChainString(chain);
transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg
.newBuilder().setCertificate(certificateChain).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (!StringUtils.isEmpty(msg.getCredentials())) {
credentialsBodyHolder[0] = msg.getCredentials();
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
latch.countDown();
}
});
latch.await(10, TimeUnit.SECONDS);
if (clientDeviceCertValue.equals(credentialsBodyHolder[0])) {
credentialsBody = credentialsBodyHolder[0];
break;
}
} catch (InterruptedException | CertificateEncodingException e) {
log.error(e.getMessage(), e);
latch.countDown();
}
@Override
public void onError(Throwable e) {
// to fix this error, cuz no one can understand this ...
log.error(e.getMessage(), e);
latch.countDown();
}
});
latch.await(10, TimeUnit.SECONDS);
if (!clientDeviceCertValue.equals(credentialsBodyHolder[0])) {
throw new CertificateException("Invalid Certificate's chain");
}
}
if (credentialsBody == null) {
throw new CertificateException("Invalid Certificate's chain");
} catch (CertificateEncodingException | InterruptedException e) {
log.error(e.getMessage(), e);
}
}
}
}

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

@ -67,6 +67,7 @@ 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;
@ -851,7 +852,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
transportService.process(DeviceTransportType.MQTT, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
@ -860,7 +861,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e);
ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage));
ctx.close();
}
});
} catch (Exception e) {
@ -873,9 +876,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private X509Certificate getX509Certificate() {
try {
Certificate[] certChains = sslHandler.engine().getSession().getPeerCertificates();
if (certChains.length > 1) {
return (X509Certificate) certChains[0];
Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates();
if (certChain.length > 1) {
return (X509Certificate) certChain[0];
}
} catch (SSLPeerUnverifiedException e) {
log.warn(e.getMessage());

11
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -20,7 +20,6 @@ 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;
@ -53,12 +52,11 @@ 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;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg;
import java.util.List;
import java.util.concurrent.ExecutorService;
@ -90,10 +88,7 @@ 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,
void process(DeviceTransportType transportType, ValidateOrCreateDeviceX509CertRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(ValidateDeviceLwM2MCredentialsRequestMsg msg,

30
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -71,7 +71,6 @@ 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.common.transport.util.JsonUtils;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -429,35 +428,16 @@ public class DefaultTransportService implements TransportService {
}
@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());
public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
log.trace("Processing msg: {}", msg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build());
doProcess(transportType, protoMsg, callback);
}
@Override
public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
public void process(DeviceTransportType transportType, TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
log.trace("Processing msg: {}", msg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build());
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateOrCreateX509CertRequestMsg(msg).build());
doProcess(transportType, protoMsg, callback);
}

12
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
/**
* @author Valerii Sosliuk
@ -42,6 +43,17 @@ public class SslUtil {
return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded()));
}
public static String getCertificateChainString(Certificate[] chain)
throws CertificateEncodingException {
String begin = "-----BEGIN CERTIFICATE-----";
String end = "-----END CERTIFICATE-----";
StringBuilder stringBuilder = new StringBuilder();
for (Certificate cert: chain) {
stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLinesWithoutBeginEnd(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n");
}
return stringBuilder.toString();
}
public static String parseCommonName(X509Certificate certificate) {
X500Name x500name;
try {

8
dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java

@ -53,12 +53,4 @@ 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);
}

7
dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java

@ -83,13 +83,6 @@ 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);

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -189,6 +189,7 @@ public class ModelConstants {
public static final String DEVICE_PROFILE_CERTIFICATE_VALUE_PROPERTY = "certificate_value";
public static final String DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY = "certificate_regex_pattern";
public static final String DEVICE_PROFILE_DEFAULT_EDGE_RULE_CHAIN_ID_PROPERTY = "default_edge_rule_chain_id";
public static final String ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION = "allow_create_device_by_x509";
/**
* Asset profile constants.

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java

@ -118,6 +118,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
@Column(name = ModelConstants.DEVICE_PROFILE_CERTIFICATE_REGEX_PATTERN_PROPERTY)
private String certificateRegexPattern;
@Column(name = ModelConstants.ALLOW_CREATE_NEW_DEVICES_BY_X509_PROVISION)
private boolean allowCreateDevice;
public DeviceProfileEntity() {
super();
}
@ -138,6 +141,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
this.certificateHash = deviceProfile.getCertificateHash();
this.certificateValue = deviceProfile.getCertificateValue();
this.certificateRegexPattern = deviceProfile.getCertificateRegexPattern();
this.allowCreateDevice = deviceProfile.isAllowCreateNewDevicesByX509Strategy();
this.description = deviceProfile.getDescription();
this.isDefault = deviceProfile.isDefault();
this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class);
@ -203,6 +207,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
deviceProfile.setCertificateHash(certificateHash);
deviceProfile.setCertificateValue(certificateValue);
deviceProfile.setCertificateRegexPattern(certificateRegexPattern);
deviceProfile.setAllowCreateNewDevicesByX509Strategy(allowCreateDevice);
if (firmwareId != null) {
deviceProfile.setFirmwareId(new OtaPackageId(firmwareId));

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

@ -133,14 +133,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
}
if (deviceProfile.getCertificateHash() != null) {
if (getRootCAFromJavaCacerts(deviceProfile.getCertificateHash())) {
throw new DataValidationException("Device profile certificate cannot be well known root CA!");
}
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();
transportConfiguration.validate();
if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) {
@ -234,12 +234,14 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
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!");
if (deviceProfile.getCertificateHash() != null) {
if (getRootCAFromJavaCacerts(deviceProfile.getCertificateHash())) {
throw new DataValidationException("Device profile certificate cannot be well known root CA!");
}
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!");
}
}
return old;
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceCredentialsRepository.java

@ -16,8 +16,6 @@
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;
@ -30,7 +28,4 @@ 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);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java

@ -66,9 +66,4 @@ 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.getId(), deviceName));
}
}

Loading…
Cancel
Save