Browse Source

Implementation draft

pull/4546/head
Andrii Shvaika 5 years ago
parent
commit
7322afac0b
  1. 1
      application/src/main/resources/thingsboard.yml
  2. 1
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java
  3. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  4. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  5. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  6. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java
  7. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java
  8. 28
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java
  9. 177
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java
  10. 26
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java
  11. 81
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  12. 19
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  13. 29
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  14. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  15. 26
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

1
application/src/main/resources/thingsboard.yml

@ -643,6 +643,7 @@ transport:
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
# Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
bootstrap:
enable: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}"

1
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java

@ -145,7 +145,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
// empty implementation
}
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -65,7 +65,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')")
@RequiredArgsConstructor
public class LwM2MTransportBootstrapServerConfiguration {
//TODO: @ybondarenko please refactor this to be similar to DefaultLwM2mTransportService
public class LwM2MTransportBootstrapService {
private PublicKey publicKey;
private PrivateKey privateKey;
private boolean pskMode = false;

22
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java

@ -103,7 +103,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
BootstrapConfig bsConfig = store.getBootstrapConfig();
if (bsConfig.security != null) {
try {
bootstrapConfigStore.add(store.getEndPoint(), bsConfig);
bootstrapConfigStore.add(store.getEndpoint(), bsConfig);
} catch (InvalidConfigurationException e) {
log.error("", e);
}
@ -121,22 +121,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) {
/* Use RPK only */
case PSK:
store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndPoint(),
store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(),
lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(),
Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray())));
store.setSecurityMode(SecurityMode.PSK.code);
break;
case RPK:
try {
store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndPoint(),
store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(),
SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray()))));
store.setSecurityMode(SecurityMode.RPK.code);
break;
} catch (IOException | GeneralSecurityException e) {
log.error("Unable to decode Client public key for [{}] [{}]", store.getEndPoint(), e.getMessage());
log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage());
}
case X509:
store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndPoint()));
store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint()));
store.setSecurityMode(SecurityMode.X509.code);
break;
case NO_SEC:
@ -166,22 +166,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint());
helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo);
return lwM2MBootstrapConfig;
} else {
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo);
return null;
}
}
} catch (JsonProcessingException e) {
log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage());
log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndpoint(), e.getMessage());
return null;
}
log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint());
log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint());
return null;
}

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -148,9 +148,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
keyStoreValue = KeyStore.getInstance(keyStoreType);
keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray());
} catch (Exception e) {
log.warn("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage());
// Absence of the key store should not block user from using plain LwM2M
// throw new RuntimeException("Failed to lookup LwM2M keystore: " + (uri != null ? uri.toString() : ""), e);
log.info("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage());
}
}
}

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java

@ -20,19 +20,20 @@ import lombok.Data;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE;
@Data
public class EndpointSecurityInfo {
private ValidateDeviceCredentialsResponseMsg msg;
private ValidateDeviceCredentialsResponse msg;
private SecurityInfo securityInfo;
private int securityMode = DEFAULT_MODE.code;
/** bootstrap */
private DeviceProfile deviceProfile;
private JsonObject bootstrapJsonCredential;
private String endPoint;
private String endpoint;
private BootstrapConfig bootstrapConfig;
}

10
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java

@ -24,6 +24,7 @@ import org.eclipse.leshan.server.security.SecurityInfo;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
@ -59,12 +60,11 @@ public class LwM2mCredentialsSecurityInfoValidator {
context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
String credentialsBody = msg.getCredentialsBody();
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
String credentialsBody = msg.getCredentials();
resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue);
resultSecurityStore[0].setMsg(msg);
Optional<DeviceProfile> deviceProfileOpt = LwM2mTransportUtil.decode(msg.getProfileBody().toByteArray());
deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile));
resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile());
latch.countDown();
}
@ -105,7 +105,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
if (object != null && !object.isJsonNull()) {
if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) {
result.setBootstrapJsonCredential(object);
result.setEndPoint(endpoint);
result.setEndpoint(endpoint);
result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code);
} else {
LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase());

28
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2021 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.transport.lwm2m.secure;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.core.request.UplinkRequest;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.Authorizer;
public class TbLwM2MAuthorizer implements Authorizer {
@Override
public Registration isAuthorized(UplinkRequest<?> request, Registration registration, Identity senderIdentity) {
return null;
}
}

177
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java

@ -0,0 +1,177 @@
/**
* Copyright © 2016-2021 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.transport.lwm2m.secure;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.CertPathUtil;
import org.eclipse.californium.scandium.dtls.AlertMessage;
import org.eclipse.californium.scandium.dtls.CertificateMessage;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.CertificateVerificationResult;
import org.eclipse.californium.scandium.dtls.ConnectionId;
import org.eclipse.californium.scandium.dtls.DTLSSession;
import org.eclipse.californium.scandium.dtls.HandshakeException;
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier;
import org.eclipse.californium.scandium.util.ServerNames;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DeviceProfile;
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.util.SslUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import javax.annotation.PostConstruct;
import javax.security.auth.x500.X500Principal;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@RequiredArgsConstructor
public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
private final TransportService transportService;
private final TbLwM2MDtlsSessionStorage sessionStorage;
private final LwM2MTransportServerConfig config;
@SuppressWarnings("deprecation")
private StaticCertificateVerifier staticCertificateVerifier;
@Value("${transport.lwm2m.server.security.skip_validity_check_for_client_cert:false}")
private boolean skipValidityCheckForClientCert;
@Override
public List<CertificateType> getSupportedCertificateType() {
return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY);
}
@PostConstruct
public void init() {
try {
/* by default trust all */
X509Certificate[] trustedCertificates = new X509Certificate[0];
if (config.getKeyStoreValue() != null) {
X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias());
if (rootCAX509Cert != null) {
trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
}
}
staticCertificateVerifier = new StaticCertificateVerifier(trustedCertificates);
} catch (Exception e) {
log.info("Failed to initialize the ");
}
}
@Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
CertPath certChain = message.getCertificateChain();
if (certChain == null) {
//We trust all RPK on this layer, and use TbLwM2MAuthorizer
PublicKey publicKey = message.getPublicKey();
return new CertificateVerificationResult(cid, publicKey, null);
} else {
try {
String credentialsBody = null;
CertPath certpath = message.getCertificateChain();
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
for (X509Certificate cert : chain) {
try {
if (!skipValidityCheckForClientCert) {
cert.checkValidity();
}
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1];
CountDownLatch latch = new CountDownLatch(1);
transportService.process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(sha3Hash).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (!StringUtils.isEmpty(msg.getCredentials())) {
deviceCredentialsResponse[0] = msg;
}
latch.countDown();
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
latch.countDown();
}
});
latch.await(10, TimeUnit.SECONDS);
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
if (msg != null && strCert.equals(msg.getCredentials())) {
credentialsBody = msg.getCredentials();
DeviceProfile deviceProfile = msg.getDeviceProfile();
if (msg.hasDeviceInfo() && deviceProfile != null) {
String endpoint = sha3Hash; //TODO: extract endpoint from credentials body and push to storage
sessionStorage.put(endpoint, msg);
}
break;
}
} catch (InterruptedException |
CertificateEncodingException |
CertificateExpiredException |
CertificateNotYetValidException e) {
log.error(e.getMessage(), e);
}
}
if (credentialsBody == null) {
if (staticCertificateVerifier != null) {
staticCertificateVerifier.verifyCertificate(message, session);
} else {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR,
session.getPeer());
throw new HandshakeException("x509 verification not enabled!", alert);
}
}
return new CertificateVerificationResult(cid, certpath, null);
} catch (HandshakeException e) {
log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null);
}
}
}
@Override
public List<X500Principal> getAcceptedIssuers() {
return CertPathUtil.toSubjects(null);
}
@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
}
}

26
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2021 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.transport.lwm2m.secure;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
public interface TbLwM2MDtlsSessionStorage {
void put(String endpoint, ValidateDeviceCredentialsResponse msg);
ValidateDeviceCredentialsResponse get(String endpoint);
}

81
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
import org.eclipse.leshan.core.util.Hex;
@ -25,14 +26,14 @@ import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.security.DefaultAuthorizer;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
@ -41,7 +42,6 @@ import javax.annotation.PreDestroy;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
@ -70,9 +70,10 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
@RequiredArgsConstructor
public class DefaultLwM2mTransportService implements LwM2MTransportService {
public static final CipherSuite[] RPK_OR_X509_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256};
public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256};
private PublicKey publicKey;
private PrivateKey privateKey;
private boolean pskMode = false;
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
@ -81,6 +82,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private final CaliforniumRegistrationStore registrationStore;
private final EditableSecurityStore securityStore;
private final LwM2mClientContext lwM2mClientContext;
private final TbLwM2MDtlsCertificateVerifier certificateVerifier;
private final TbLwM2MAuthorizer authorizer;
private LeshanServer server;
@ -128,9 +131,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
config.setModelProvider(modelProvider);
builder.setObjectModelProvider(modelProvider);
/* Create credentials */
this.setServerWithCredentials(builder);
/* Set securityStore with new registrationStore */
builder.setSecurityStore(securityStore);
builder.setRegistrationStore(registrationStore);
@ -142,18 +142,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
dtlsConfig.setServerOnly(true);
dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers());
if (this.pskMode) {
dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256);
} else {
dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256);
}
/* Create credentials */
this.setServerWithCredentials(builder, dtlsConfig);
/* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig);
@ -162,40 +152,21 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
return builder.build();
}
private void setServerWithCredentials(LeshanServerBuilder builder) {
try {
if (config.getKeyStoreValue() != null) {
if (this.setBuilderX509(builder)) {
X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias());
if (rootCAX509Cert != null) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
builder.setTrustedCertificates(trustedCertificates);
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
/* Set securityStore with registrationStore*/
builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() {
@Override
protected boolean matchX509Identity(String endpoint, String receivedX509CommonName,
String expectedX509CommonName) {
return endpoint.startsWith(expectedX509CommonName);
}
}));
}
} else if (this.setServerRPK(builder)) {
this.infoPramsUri("RPK");
this.infoParamsServerKey(this.publicKey, this.privateKey);
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
log.info("Unable to load X509 files for LWM2MServer");
this.pskMode = true;
this.infoPramsUri("PSK");
}
} catch (KeyStoreException ex) {
log.error("[{}] Unable to load X509 files server", ex.getMessage());
private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) {
if (config.getKeyStoreValue() != null && this.setBuilderX509(builder)) {
dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier);
builder.setAuthorizer(authorizer);
dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES);
} else if (this.setServerRPK(builder)) {
this.infoPramsUri("RPK");
this.infoParamsServerKey(this.publicKey, this.privateKey);
dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES);
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
log.info("Unable to load X509 files for LWM2MServer");
dtlsConfig.setSupportedCipherSuites(PSK_CIPHER_SUITES);
this.infoPramsUri("PSK");
}
}
@ -241,7 +212,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private boolean setServerRPK(LeshanServerBuilder builder) {
try {
this.generateKeyForRPK();
this.loadOrGenerateRPKKeys();
if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
this.privateKey != null && this.privateKey.getEncoded().length > 0) {
builder.setPublicKey(this.publicKey);
@ -254,7 +225,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
return false;
}
private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException {
private void loadOrGenerateRPKKeys() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException {
/* Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));

19
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java

@ -40,6 +40,7 @@ import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
@ -106,21 +107,21 @@ public class LwM2mTransportServerHelper {
/**
* @return - sessionInfo after access connect client
*/
public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponse msg, long mostSignificantBits, long leastSignificantBits) {
return SessionInfoProto.newBuilder()
.setNodeId(context.getNodeId())
.setSessionIdMSB(mostSignificantBits)
.setSessionIdLSB(leastSignificantBits)
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
.setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
.setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits())
.setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits())
.setDeviceName(msg.getDeviceInfo().getDeviceName())
.setDeviceType(msg.getDeviceInfo().getDeviceType())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits())
.build();
}

29
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -27,6 +27,7 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
@ -79,7 +80,7 @@ public class LwM2mClient implements Cloneable {
@Setter
private Registration registration;
private ValidateDeviceCredentialsResponseMsg credentialsResponse;
private ValidateDeviceCredentialsResponse credentials;
@Getter
private final Map<String, ResourceValue> resources;
@Getter
@ -98,11 +99,11 @@ public class LwM2mClient implements Cloneable {
return super.clone();
}
public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileId, UUID sessionId) {
public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) {
this.endpoint = endpoint;
this.identity = identity;
this.securityInfo = securityInfo;
this.credentialsResponse = credentialsResponse;
this.credentials = credentials;
this.delayedRequests = new ConcurrentHashMap<>();
this.pendingReadRequests = new CopyOnWriteArrayList<>();
this.resources = new ConcurrentHashMap<>();
@ -112,8 +113,8 @@ public class LwM2mClient implements Cloneable {
this.updateFw = false;
this.queuedRequests = new ConcurrentLinkedQueue<>();
this.frUpdate = new LwM2mFirmwareUpdate();
if (this.credentialsResponse != null && this.credentialsResponse.hasDeviceInfo()) {
this.session = createSession(nodeId, sessionId, credentialsResponse);
if (this.credentials != null && this.credentials.hasDeviceInfo()) {
this.session = createSession(nodeId, sessionId, credentials);
this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB());
this.profileId = new UUID(session.getDeviceProfileIdMSB(), session.getDeviceProfileIdLSB());
this.deviceName = session.getDeviceName();
@ -146,21 +147,21 @@ public class LwM2mClient implements Cloneable {
builder.setDeviceType(this.deviceProfileName);
}
private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponseMsg msg) {
private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponse msg) {
return SessionInfoProto.newBuilder()
.setNodeId(nodeId)
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
.setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
.setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
.setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits())
.setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits())
.setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits())
.setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits())
.setDeviceName(msg.getDeviceInfo().getDeviceName())
.setDeviceType(msg.getDeviceInfo().getDeviceType())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
.setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits())
.build();
}

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

@ -79,7 +79,7 @@ public interface TransportService {
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(ValidateDeviceLwM2MCredentialsRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback);
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);

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

@ -348,11 +348,25 @@ public class DefaultTransportService implements TransportService {
}
@Override
public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback<TransportProtos.ValidateDeviceCredentialsResponseMsg> callback) {
log.trace("Processing msg: {}", msg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(msg).build());
AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg),
response -> callback.onSuccess(response.getValue().getValidateCredResponseMsg()), callback::onError, transportCallbackExecutor);
public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg requestMsg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
log.trace("Processing msg: {}", requestMsg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(requestMsg).build());
ListenableFuture<ValidateDeviceCredentialsResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg();
ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder();
if (msg.hasDeviceInfo()) {
result.credentials(msg.getCredentialsBody());
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (!profileBody.isEmpty()) {
DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
result.deviceProfile(profile);
}
}
return result.build();
}, MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
@Override
@ -372,7 +386,7 @@ public class DefaultTransportService implements TransportService {
TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) {
if (!profileBody.isEmpty()) {
DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
if (transportType != DeviceTransportType.DEFAULT
&& profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) {

Loading…
Cancel
Save