From 7322afac0be4c15be9e47aae8e054367e5a17410 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 5 May 2021 19:09:15 +0300 Subject: [PATCH 01/13] Implementation draft --- .../src/main/resources/thingsboard.yml | 1 + .../TbCoapDtlsCertificateVerifier.java | 1 - ...va => LwM2MTransportBootstrapService.java} | 3 +- .../secure/LwM2MBootstrapSecurityStore.java | 22 +-- .../config/LwM2MTransportServerConfig.java | 4 +- .../lwm2m/secure/EndpointSecurityInfo.java | 5 +- ...LwM2mCredentialsSecurityInfoValidator.java | 10 +- .../lwm2m/secure/TbLwM2MAuthorizer.java | 28 +++ .../TbLwM2MDtlsCertificateVerifier.java | 177 ++++++++++++++++++ .../secure/TbLwM2MDtlsSessionStorage.java | 26 +++ .../server/DefaultLwM2mTransportService.java | 81 +++----- .../server/LwM2mTransportServerHelper.java | 19 +- .../lwm2m/server/client/LwM2mClient.java | 29 +-- .../common/transport/TransportService.java | 2 +- .../service/DefaultTransportService.java | 26 ++- 15 files changed, 326 insertions(+), 108 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/{LwM2MTransportBootstrapServerConfiguration.java => LwM2MTransportBootstrapService.java} (99%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d14adc0573..ae7fbda6d9 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/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}" diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java index 1de7bb1693..2076c7a354 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java +++ b/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 getTbCoapDtlsSessionIdsMap() { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java similarity index 99% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 16bb97aac5..9348cb31a5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/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; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 0a55d2c3cb..22e0540c09 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/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; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index cd8287da4c..109898812b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/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()); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java index 851934a806..f65acf04bd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java +++ b/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; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 0263e72fb2..ec5200602c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/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 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()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java new file mode 100644 index 0000000000..3719ce7246 --- /dev/null +++ b/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; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java new file mode 100644 index 0000000000..c3d9d85d04 --- /dev/null +++ b/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 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 getAcceptedIssuers() { + return CertPathUtil.toSubjects(null); + } + + @Override + public void setResultHandler(HandshakeResultHandler resultHandler) { + + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java new file mode 100644 index 0000000000..26c38a6327 --- /dev/null +++ b/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); + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 3df1263a3f..8bd0fdaff2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/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")); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index 79902348a9..789faecb75 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/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(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 691dd1d390..d20de6ebe4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/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 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(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 4cdf6246e1..ce3bb7345e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -79,7 +79,7 @@ public interface TransportService { TransportServiceCallback callback); void process(ValidateDeviceLwM2MCredentialsRequestMsg msg, - TransportServiceCallback callback); + TransportServiceCallback callback); void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 69de33c8c0..b09b6bf223 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/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 callback) { - log.trace("Processing msg: {}", msg); - TbProtoQueueMsg 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 callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(requestMsg).build()); + ListenableFuture 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) { From 38af4d5d2da99804c2aebe829e9c6aeb9b786701 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 13:48:09 +0300 Subject: [PATCH 02/13] Implementation of custom L2M2M Authorizer --- .../lwm2m/secure/TbLwM2MAuthorizer.java | 41 ++++++++++++++++++- .../TbLwM2MDtlsCertificateVerifier.java | 31 +++++++++----- ...torage.java => TbX509DtlsSessionInfo.java} | 9 ++-- .../server/client/LwM2mClientContext.java | 3 ++ .../server/client/LwM2mClientContextImpl.java | 8 ++++ .../TbL2M2MDtlsSessionInMemoryStore.java | 39 ++++++++++++++++++ .../server/store/TbLwM2MDtlsSessionStore.java | 29 +++++++++++++ .../server/store/TbLwM2mSecurityStore.java | 4 ++ 8 files changed, 149 insertions(+), 15 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/{TbLwM2MDtlsSessionStorage.java => TbX509DtlsSessionInfo.java} (81%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java index 3719ce7246..0236f6d276 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -15,14 +15,53 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import lombok.RequiredArgsConstructor; 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; +import org.eclipse.leshan.server.security.SecurityChecker; +import org.eclipse.leshan.server.security.SecurityInfo; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore; +@Component +@RequiredArgsConstructor +@TbLwM2mTransportComponent public class TbLwM2MAuthorizer implements Authorizer { + + private final TbLwM2MDtlsSessionStore sessionStorage; + private final TbLwM2mSecurityStore securityStore; + private final SecurityChecker securityChecker = new SecurityChecker(); + private final LwM2mClientContext clientContext; + @Override public Registration isAuthorized(UplinkRequest request, Registration registration, Identity senderIdentity) { - return null; + if (senderIdentity.isX509()) { + TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); + if (sessionInfo != null) { + if (senderIdentity.getX509CommonName().equals(sessionInfo.getX509CommonName())) { + clientContext.registerClient(registration, sessionInfo.getCredentials()); + // X509 certificate is valid and matches endpoint. + return registration; + } else { + // X509 certificate is not valid. + return null; + } + } + // If session info is not found, this may be the trusted certificate, so we still need to check all other options below. + } + SecurityInfo expectedSecurityInfo = null; + if (securityStore != null) { + expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + } + if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) { + return registration; + } else { + return null; + } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index c3d9d85d04..57fa8a7169 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.CertPathUtil; @@ -32,6 +33,7 @@ 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.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; @@ -40,6 +42,7 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; import javax.security.auth.x500.X500Principal; @@ -60,7 +63,7 @@ import java.util.concurrent.TimeUnit; public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { private final TransportService transportService; - private final TbLwM2MDtlsSessionStorage sessionStorage; + private final TbLwM2MDtlsSessionStore sessionStorage; private final LwM2MTransportServerConfig config; @SuppressWarnings("deprecation") @@ -130,16 +133,24 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer 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); + if (latch.await(10, TimeUnit.SECONDS)) { + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; + if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { + JsonNode credentialsJson = JacksonUtil.toJsonNode(msg.getCredentials()); + String certBody = credentialsJson.get("cert").asText(); + String endpoint = credentialsJson.get("endpoint").asText(); + if (strCert.equals(certBody)) { + //TODO: extract endpoint from credentials body and push to storage + credentialsBody = msg.getCredentials(); + DeviceProfile deviceProfile = msg.getDeviceProfile(); + if (msg.hasDeviceInfo() && deviceProfile != null) { + sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); + break; + } + } else { + log.trace("[{}][{}] Certificate mismatch. Expected: {}, Actual: {}", endpoint, sha3Hash, strCert, certBody); + } } - break; } } catch (InterruptedException | CertificateEncodingException | diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java similarity index 81% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java index 26c38a6327..1c038a9440 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java @@ -15,12 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import lombok.Data; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -public interface TbLwM2MDtlsSessionStorage { +@Data +public class TbX509DtlsSessionInfo { - void put(String endpoint, ValidateDeviceCredentialsResponse msg); - - ValidateDeviceCredentialsResponse get(String endpoint); + private final String x509CommonName; + private final ValidateDeviceCredentialsResponse credentials; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index 8e7f6a70ec..4aa8db59b6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Collection; @@ -59,4 +60,6 @@ public interface LwM2mClientContext { Set getSupportedIdVerInClient(Registration registration); LwM2mClient getClientByDeviceId(UUID deviceId); + + void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 3a7b652ab3..b8583523d5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; @@ -136,6 +137,13 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } } + @Override + public void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials) { + LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); + lwM2mClientsByEndpoint.put(registration.getEndpoint(), client); + lwM2mClientsByRegistrationId.put(registration.getId(), client); + } + @Override public Collection getLwM2mClients() { return lwM2mClientsByEndpoint.values(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java new file mode 100644 index 0000000000..b71b7b12c1 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java @@ -0,0 +1,39 @@ +/** + * 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.server.store; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +import java.util.concurrent.ConcurrentHashMap; + +@Component +@TbLwM2mTransportComponent +public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore { + + private final ConcurrentHashMap store = new ConcurrentHashMap<>(); + + @Override + public void put(String endpoint, TbX509DtlsSessionInfo msg) { + store.put(endpoint, msg); + } + + @Override + public TbX509DtlsSessionInfo get(String endpoint) { + return store.get(endpoint); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java new file mode 100644 index 0000000000..bc4ddff7ac --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java @@ -0,0 +1,29 @@ +/** + * 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.server.store; + + +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +public interface TbLwM2MDtlsSessionStore { + + void put(String endpoint, TbX509DtlsSessionInfo msg); + + TbX509DtlsSessionInfo get(String endpoint); + + //TODO: add way to delete the session by endpoint. + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java index e6ed99c5e6..512790036f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java @@ -20,12 +20,16 @@ import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityStoreListener; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import java.util.Collection; @Slf4j +@Component +@TbLwM2mTransportComponent public class TbLwM2mSecurityStore implements EditableSecurityStore { private final LwM2mClientContext clientContext; From 7ca626a0868422018a8489dce417b02ee31ef4c5 Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Thu, 6 May 2021 13:49:51 +0300 Subject: [PATCH 03/13] Created LwM2M credentials (#4546) * Created LwM2M credentials * psk endpoint --- .../server/controller/Lwm2mController.java | 5 +- .../LwM2MServerSecurityInfoRepository.java | 11 +- .../secure/LwM2MBootstrapConfig.java | 12 +- .../secure/LwM2MBootstrapSecurityStore.java | 20 ++-- .../secure/LwM2MServerBootstrap.java | 2 +- .../lwm2m/secure/EndpointSecurityInfo.java | 10 +- .../secure/LWM2MGenerationPSkRPkECC.java | 22 +--- .../lwm2m/secure/LwM2MSecurityMode.java | 58 --------- ...LwM2mCredentialsSecurityInfoValidator.java | 111 ++++++++---------- .../lwm2m/secure/credentials/HasKey.java | 17 +++ .../LwM2MClientCredentialsConfig.java | 22 ++++ .../secure/credentials/LwM2MCredentials.java | 10 ++ .../NoSecClientCredentialsConfig.java | 13 ++ .../PSKClientCredentialsConfig.java | 17 +++ .../RPKClientCredentialsConfig.java | 13 ++ .../X509ClientCredentialsConfig.java | 17 +++ .../server/client/LwM2mClientContextImpl.java | 7 +- 17 files changed, 199 insertions(+), 168 deletions(-) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java index 0855e4ee1f..9e6d393b30 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -46,9 +47,11 @@ public class Lwm2mController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) @ResponseBody - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode, + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode, @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException { + checkNotNull(strSecurityMode); try { + SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode); return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java index 012cd3e359..06190cdf70 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.lwm2m; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.Hex; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; @@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import java.math.BigInteger; import java.security.AlgorithmParameters; @@ -55,17 +55,16 @@ public class LwM2MServerSecurityInfoRepository { * @param bootstrapServer * @return ServerSecurityConfig more value is default: Important - port, host, publicKey */ - public ServerSecurityConfig getServerSecurityInfo(String securityMode, boolean bootstrapServer) { - LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase()); - ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode); + public ServerSecurityConfig getServerSecurityInfo(SecurityMode securityMode, boolean bootstrapServer) { + ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, securityMode); result.setBootstrapServerIs(bootstrapServer); return result; } - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) { + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) { ServerSecurityConfig bsServ = new ServerSecurityConfig(); bsServ.setServerId(serverConfig.getId()); - switch (mode) { + switch (securityMode) { case NO_SEC: bsServ.setHost(serverConfig.getHost()); bsServ.setPort(serverConfig.getPort()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 937257b189..2f175a6bce 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -73,17 +73,17 @@ public class LwM2MBootstrapConfig { configBs.servers.put(0, server0); /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ this.bootstrapServer.setBootstrapServerIs(true); - configBs.security.put(0, setServerSecuruty(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); + configBs.security.put(0, setServerSecurity(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */ - configBs.security.put(1, setServerSecuruty(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); + configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); return configBs; } - private BootstrapConfig.ServerSecurity setServerSecuruty(String host, Integer port, boolean bootstrapServer, String securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { + private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity(); serverSecurity.uri = "coaps://" + host + ":" + Integer.toString(port); serverSecurity.bootstrapServer = bootstrapServer; - serverSecurity.securityMode = SecurityMode.valueOf(securityMode); + serverSecurity.securityMode = securityMode; serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode); serverSecurity.serverPublicKey = (serverPublicKey != null && !serverPublicKey.isEmpty()) ? Hex.decodeHex(serverPublicKey.toCharArray()) : new byte[]{}; serverSecurity.secretKey = (secretKey != null && !secretKey.isEmpty()) ? Hex.decodeHex(secretKey.toCharArray()) : new byte[]{}; @@ -91,9 +91,9 @@ public class LwM2MBootstrapConfig { return serverSecurity; } - private byte[] setPublicKeyOrId(String publicKeyOrIdStr, String securityMode) { + private byte[] setPublicKeyOrId(String publicKeyOrIdStr, SecurityMode securityMode) { return (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} : - SecurityMode.valueOf(securityMode).equals(SecurityMode.PSK) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : + SecurityMode.PSK.equals(securityMode) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : Hex.decodeHex(publicKeyOrIdStr.toCharArray()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 22e0540c09..b1edd0a1a5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -31,7 +31,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; @@ -73,7 +72,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfigNew = store.getBootstrapConfig(); @@ -97,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfig = store.getBootstrapConfig(); @@ -118,29 +117,29 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); if (lwM2MBootstrapConfig != null) { /* Security info */ - switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) { + switch (lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode()) { /* Use RPK only */ case PSK: store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(), lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(), Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray()))); - store.setSecurityMode(SecurityMode.PSK.code); + store.setSecurityMode(SecurityMode.PSK); break; case RPK: try { store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(), SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray())))); - store.setSecurityMode(SecurityMode.RPK.code); + store.setSecurityMode(SecurityMode.RPK); break; } catch (IOException | GeneralSecurityException e) { log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage()); } case X509: store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint())); - store.setSecurityMode(SecurityMode.X509.code); + store.setSecurityMode(SecurityMode.X509); break; case NO_SEC: - store.setSecurityMode(SecurityMode.NO_SEC.code); + store.setSecurityMode(SecurityMode.NO_SEC); store.setSecurityInfo(null); break; default: @@ -152,10 +151,9 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) { try { - JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential(); - if (bootstrapJsonCredential != null) { + LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); + if (lwM2MBootstrapConfig != null) { ObjectMapper mapper = new ObjectMapper(); - LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java index 9dca6057da..27d2e8c865 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java @@ -32,7 +32,7 @@ public class LwM2MServerBootstrap { String host = "0.0.0.0"; Integer port = 0; - String securityMode = SecurityMode.NO_SEC.name(); + SecurityMode securityMode = SecurityMode.NO_SEC; Integer serverId = 123; boolean bootstrapServerIs = false; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java index f65acf04bd..e8d3ae3c2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java @@ -15,25 +15,23 @@ */ package org.thingsboard.server.transport.lwm2m.secure; -import com.google.gson.JsonObject; import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; 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; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @Data public class EndpointSecurityInfo { private ValidateDeviceCredentialsResponse msg; private SecurityInfo securityInfo; - private int securityMode = DEFAULT_MODE.code; + private SecurityMode securityMode; /** bootstrap */ private DeviceProfile deviceProfile; - private JsonObject bootstrapJsonCredential; + private LwM2MBootstrapConfig bootstrapCredentialConfig; private String endpoint; private BootstrapConfig bootstrapConfig; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java index b99192ece3..22c5878a58 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java @@ -33,16 +33,6 @@ import java.util.Arrays; @Slf4j public class LWM2MGenerationPSkRPkECC { - public LWM2MGenerationPSkRPkECC(Integer dtlsMode) { - switch (LwM2MSecurityMode.fromSecurityMode(dtlsMode)) { - case PSK: - generationPSkKey(); - break; - case RPK: - generationRPKECCKey(); - } - } - public LWM2MGenerationPSkRPkECC() { generationPSkKey(); generationRPKECCKey(); @@ -102,12 +92,12 @@ public class LWM2MGenerationPSkRPkECC { /* Get Curves params */ String privHex = Hex.encodeHexString(privKey.getEncoded()); log.info("\nCreating new RPK for the next start... \n" + - " Public Key (Hex): [{}]\n" + - " Private Key (Hex): [{}]" + - " public_x : [{}] \n" + - " public_y : [{}] \n" + - " private_encode : [{}] \n" + - " Elliptic Curve parameters : [{}] \n", + " Public Key (Hex): [{}]\n" + + " Private Key (Hex): [{}]" + + " public_x : [{}] \n" + + " public_y : [{}] \n" + + " private_encode : [{}] \n" + + " Elliptic Curve parameters : [{}] \n", Hex.encodeHexString(pubKey.getEncoded()), privHex, Hex.encodeHexString(x), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java deleted file mode 100644 index faf776b76c..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * 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; - -public enum LwM2MSecurityMode { - - PSK(0, "psk"), - RPK(1, "rpk"), - X509(2, "x509"), - NO_SEC(3, "no_sec"), - X509_EST(4, "x509_est"), - REDIS(7, "redis"), - DEFAULT_MODE(255, "default_mode"); - - public int code; - public String subEndpoint; - - LwM2MSecurityMode(int code, String subEndpoint) { - this.code = code; - this.subEndpoint = subEndpoint; - } - - public static LwM2MSecurityMode fromSecurityMode(long code) { - return fromSecurityMode((int) code); - } - - public static LwM2MSecurityMode fromSecurityMode(int code) { - for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) { - if (sm.code == code) { - return sm; - } - } - throw new IllegalArgumentException(String.format("Unsupported security code : %d", code)); - } - - - public static LwM2MSecurityMode fromSecurityMode(String subEndpoint) { - for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) { - if (sm.subEndpoint.equals(subEndpoint)) { - return sm; - } - } - throw new IllegalArgumentException(String.format("Unsupported security subEndpoint : %d", subEndpoint)); - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index ec5200602c..cd78cd4c1e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -15,34 +15,36 @@ */ package org.thingsboard.server.transport.lwm2m.secure; -import com.google.gson.JsonObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; 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; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentialsConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.PSKClientCredentialsConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.RPKClientCredentialsConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PublicKey; -import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; +import static org.eclipse.leshan.core.SecurityMode.PSK; +import static org.eclipse.leshan.core.SecurityMode.RPK; +import static org.eclipse.leshan.core.SecurityMode.X509; @Slf4j @Component @@ -53,7 +55,6 @@ public class LwM2mCredentialsSecurityInfoValidator { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - public EndpointSecurityInfo getEndpointSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final EndpointSecurityInfo[] resultSecurityStore = new EndpointSecurityInfo[1]; @@ -92,39 +93,32 @@ public class LwM2mCredentialsSecurityInfoValidator { */ private EndpointSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { EndpointSecurityInfo result = new EndpointSecurityInfo(); - JsonObject objectMsg = LwM2mTransportUtil.validateJson(jsonStr); - if (objectMsg != null && !objectMsg.isJsonNull()) { - JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null; - /** - * Only PSK - */ - String endpointPsk = (objectMsg.has("client") - && objectMsg.get("client").getAsJsonObject().has("endpoint") - && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null; - endpoint = (endpointPsk == null || endpointPsk.isEmpty()) ? endpoint : endpointPsk; - if (object != null && !object.isJsonNull()) { - if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { - result.setBootstrapJsonCredential(object); - 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()); - switch (lwM2MSecurityMode) { - case NO_SEC: - createClientSecurityInfoNoSec(result); - break; - case PSK: - createClientSecurityInfoPSK(result, endpoint, object); - break; - case RPK: - createClientSecurityInfoRPK(result, endpoint, object); - break; - case X509: - createClientSecurityInfoX509(result, endpoint); - break; - default: - break; - } + LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class); + if (credentials != null) { + if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { + result.setBootstrapCredentialConfig(credentials.getBootstrap()); + if (SecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { + PSKClientCredentialsConfig pskClientConfig = (PSKClientCredentialsConfig) credentials.getClient(); + endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint; + } + result.setEndpoint(endpoint); + result.setSecurityMode(credentials.getBootstrap().getBootstrapServer().getSecurityMode()); + } else { + switch (credentials.getClient().getSecurityConfigClientMode()) { + case NO_SEC: + createClientSecurityInfoNoSec(result); + break; + case PSK: + createClientSecurityInfoPSK(result, endpoint, credentials.getClient()); + break; + case RPK: + createClientSecurityInfoRPK(result, endpoint, credentials.getClient()); + break; + case X509: + createClientSecurityInfoX509(result, endpoint, credentials.getClient()); + break; + default: + break; } } } @@ -133,19 +127,18 @@ public class LwM2mCredentialsSecurityInfoValidator { private void createClientSecurityInfoNoSec(EndpointSecurityInfo result) { result.setSecurityInfo(null); - result.setSecurityMode(NO_SEC.code); + result.setSecurityMode(NO_SEC); } - private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, JsonObject object) { - /** PSK Deserialization */ - String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null; - if (identity != null && !identity.isEmpty()) { + private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + PSKClientCredentialsConfig pskConfig = (PSKClientCredentialsConfig) clientCredentialsConfig; + if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { try { - byte[] key = (object.has("key") && object.get("key").isJsonPrimitive()) ? Hex.decodeHex(object.get("key").getAsString().toCharArray()) : null; - if (key != null && key.length > 0) { + if (pskConfig.getKey() != null && pskConfig.getKey().length > 0) { + endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint; if (endpoint != null && !endpoint.isEmpty()) { - result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, identity, key)); - result.setSecurityMode(PSK.code); + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getKey())); + result.setSecurityMode(PSK); } } } catch (IllegalArgumentException e) { @@ -156,13 +149,13 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, JsonObject object) { + private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + RPKClientCredentialsConfig rpkConfig = (RPKClientCredentialsConfig) clientCredentialsConfig; try { - if (object.has("key") && object.get("key").isJsonPrimitive()) { - byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray()); - PublicKey key = SecurityUtil.publicKey.decode(rpkkey); + if (rpkConfig.getKey() != null) { + PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey()); result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key)); - result.setSecurityMode(RPK.code); + result.setSecurityMode(RPK); } else { log.error("Missing RPK key"); } @@ -171,8 +164,8 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint) { + private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); - result.setSecurityMode(X509.code); + result.setSecurityMode(X509); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java new file mode 100644 index 0000000000..7c75589c4c --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.util.Hex; + +public class HasKey { + private byte[] key; + + public void setKey(String key) { + if (key != null) { + this.key = Hex.decodeHex(key.toLowerCase().toCharArray()); + } + } + + public byte[] getKey() { + return key; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java new file mode 100644 index 0000000000..33a297c91a --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java @@ -0,0 +1,22 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.eclipse.leshan.core.SecurityMode; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "securityConfigClientMode") +@JsonSubTypes({ + @JsonSubTypes.Type(value = NoSecClientCredentialsConfig.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKClientCredentialsConfig.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKClientCredentialsConfig.class, name = "RPK"), + @JsonSubTypes.Type(value = X509ClientCredentialsConfig.class, name = "X509")}) +public interface LwM2MClientCredentialsConfig { + + @JsonIgnore + SecurityMode getSecurityConfigClientMode(); +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java new file mode 100644 index 0000000000..ebb1fc138b --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java @@ -0,0 +1,10 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; + +@Data +public class LwM2MCredentials { + private LwM2MClientCredentialsConfig client; + private LwM2MBootstrapConfig bootstrap; +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java new file mode 100644 index 0000000000..bcac7174aa --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java @@ -0,0 +1,13 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; + +public class NoSecClientCredentialsConfig implements LwM2MClientCredentialsConfig { + + @Override + public SecurityMode getSecurityConfigClientMode() { + return NO_SEC; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java new file mode 100644 index 0000000000..d79083647f --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.PSK; + +@Data +public class PSKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { + private String identity; + private String endpoint; + + @Override + public SecurityMode getSecurityConfigClientMode() { + return PSK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java new file mode 100644 index 0000000000..280e8492f8 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java @@ -0,0 +1,13 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.RPK; + +public class RPKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { + + @Override + public SecurityMode getSecurityConfigClientMode() { + return RPK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java new file mode 100644 index 0000000000..35fe63c376 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.X509; + +@Data +public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { + private boolean allowTrustedOnly; + private String cert; + + @Override + public SecurityMode getSecurityConfigClientMode() { + return X509; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index b8583523d5..02ef1ccfb2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -25,7 +25,6 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -38,7 +37,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @Service @@ -111,7 +110,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public LwM2mClient fetchClientByEndpoint(String endpoint) { EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); - if (securityInfo.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (securityInfo.getSecurityMode() != null) { if (securityInfo.getDeviceProfile() != null) { toClientProfile(securityInfo.getDeviceProfile()); UUID profileUuid = securityInfo.getDeviceProfile().getUuidId(); @@ -120,7 +119,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(), securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(), securityInfo.getMsg(), profileUuid, UUID.randomUUID()); - } else if (securityInfo.getSecurityMode() == NO_SEC.code) { + } else if (NO_SEC.equals(securityInfo.getSecurityMode())) { client = new LwM2mClient(context.getNodeId(), endpoint, null, null, securityInfo.getMsg(), profileUuid, UUID.randomUUID()); From 38843c839c18c294c8526bfc665e7b5f59333cc8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 14:07:29 +0300 Subject: [PATCH 04/13] Merge with the new data structures --- .../TbLwM2MDtlsCertificateVerifier.java | 20 ++++++++++++------- .../X509ClientCredentialsConfig.java | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index 57fa8a7169..d2542192bf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -30,6 +30,7 @@ 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.eclipse.leshan.core.SecurityMode; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -42,6 +43,8 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; @@ -104,7 +107,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer return new CertificateVerificationResult(cid, publicKey, null); } else { try { - String credentialsBody = null; + boolean x509CredentialsFound = false; CertPath certpath = message.getCertificateChain(); X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); for (X509Certificate cert : chain) { @@ -136,12 +139,15 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer if (latch.await(10, TimeUnit.SECONDS)) { ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { - JsonNode credentialsJson = JacksonUtil.toJsonNode(msg.getCredentials()); - String certBody = credentialsJson.get("cert").asText(); - String endpoint = credentialsJson.get("endpoint").asText(); + LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); + if(!credentials.getClient().getSecurityConfigClientMode().equals(SecurityMode.X509)){ + continue; + } + X509ClientCredentialsConfig config = (X509ClientCredentialsConfig) credentials.getClient(); + String certBody = config.getCert(); + String endpoint = config.getEndpoint(); if (strCert.equals(certBody)) { - //TODO: extract endpoint from credentials body and push to storage - credentialsBody = msg.getCredentials(); + x509CredentialsFound = true; DeviceProfile deviceProfile = msg.getDeviceProfile(); if (msg.hasDeviceInfo() && deviceProfile != null) { sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); @@ -159,7 +165,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer log.error(e.getMessage(), e); } } - if (credentialsBody == null) { + if (!x509CredentialsFound) { if (staticCertificateVerifier != null) { staticCertificateVerifier.verifyCertificate(message, session); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java index 35fe63c376..0aff74d189 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -9,6 +9,7 @@ import static org.eclipse.leshan.core.SecurityMode.X509; public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { private boolean allowTrustedOnly; private String cert; + private String endpoint; @Override public SecurityMode getSecurityConfigClientMode() { From 4ec25beeb3a109277260018163832eaa320a27b9 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 17:11:24 +0300 Subject: [PATCH 05/13] LwM2M Integration test --- .../server/controller/AbstractWebTest.java | 3 +- .../transport/TransportSqlTestSuite.java | 3 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 95 ++++ .../lwm2m/NoSecLwM2MIntegrationTest.java | 156 +++++++ .../lwm2m/client/LwM2MTestClient.java | 262 +++++++++++ .../lwm2m/client/SimpleLwM2MDevice.java | 199 +++++++++ application/src/test/resources/logback.xml | 6 +- application/src/test/resources/lwm2m/0.xml | 405 ++++++++++++++++++ application/src/test/resources/lwm2m/1.xml | 360 ++++++++++++++++ application/src/test/resources/lwm2m/2.xml | 123 ++++++ application/src/test/resources/lwm2m/3.xml | 331 ++++++++++++++ .../lwm2m/secure/credentials/HasKey.java | 15 + .../LwM2MClientCredentialsConfig.java | 15 + .../secure/credentials/LwM2MCredentials.java | 15 + .../NoSecClientCredentialsConfig.java | 15 + .../PSKClientCredentialsConfig.java | 15 + .../RPKClientCredentialsConfig.java | 15 + .../X509ClientCredentialsConfig.java | 15 + .../DefaultLwM2MTransportMsgHandler.java | 3 +- 19 files changed, 2045 insertions(+), 6 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java create mode 100644 application/src/test/resources/lwm2m/0.xml create mode 100644 application/src/test/resources/lwm2m/1.xml create mode 100644 application/src/test/resources/lwm2m/2.xml create mode 100644 application/src/test/resources/lwm2m/3.xml diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 2329e8086c..937244fc93 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -22,6 +22,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwts; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; @@ -120,7 +121,7 @@ public abstract class AbstractWebTest { protected String refreshToken; protected String username; - private TenantId tenantId; + protected TenantId tenantId; @SuppressWarnings("rawtypes") private HttpMessageConverter mappingJackson2HttpMessageConverter; diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index d16bbc3885..25df3bee00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -32,7 +32,8 @@ import java.util.Arrays; "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", "org.thingsboard.server.transport.*.attributes.request.sql.*Test", "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test" + "org.thingsboard.server.transport.*.provision.sql.*Test", + "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java new file mode 100644 index 0000000000..97f7bfbc3c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -0,0 +1,95 @@ +/** + * 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; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.controller.AbstractWebsocketTest; +import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Base64; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +@DaoSqlTest +public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { + + protected DeviceProfile deviceProfile; + protected ScheduledExecutorService executor; + protected TbTestWebSocketClient wsClient; + + @Before + public void beforeTest() throws Exception { + executor = Executors.newScheduledThreadPool(10); + loginTenantAdmin(); + + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; + for (String resourceName : resources) { + TbResource lwModel = new TbResource(); + lwModel.setResourceType(ResourceType.LWM2M_MODEL); + lwModel.setTitle(resourceName); + lwModel.setFileName(resourceName); + lwModel.setTenantId(tenantId); + byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName)); + lwModel.setData(Base64.getEncoder().encodeToString(bytes)); + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>(){}); + Assert.assertNotNull(lwModel); + } + wsClient = buildAndConnectWebSocketClient(); + } + + protected void createDeviceProfile(String transportConfiguration) throws Exception { + deviceProfile = new DeviceProfile(); + + deviceProfile.setName("LwM2M No Security"); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTenantId(tenantId); + deviceProfile.setTransportType(DeviceTransportType.LWM2M); + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + deviceProfile.setDescription(deviceProfile.getName()); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); + deviceProfileData.setTransportConfiguration(JacksonUtil.fromString(transportConfiguration, Lwm2mDeviceProfileTransportConfiguration.class)); + deviceProfile.setProfileData(deviceProfileData); + + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertNotNull(deviceProfile); + } + + @After + public void after() { + executor.shutdownNow(); + wsClient.close(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java new file mode 100644 index 0000000000..809f1f609b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -0,0 +1,156 @@ +/** + * 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; + +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.SingleEntityFilter; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCredentialsConfig; + +import java.util.Collections; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected final String TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.0/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrap\": {\n" + + " \"servers\": {\n" + + " \"binding\": \"UQ\",\n" + + " \"shortId\": 123,\n" + + " \"lifetime\": 300,\n" + + " \"notifIfDisabled\": true,\n" + + " \"defaultMinPeriod\": 1\n" + + " },\n" + + " \"lwm2mServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5685,\n" + + " \"serverId\": 123,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " },\n" + + " \"bootstrapServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5687,\n" + + " \"serverId\": 111,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " }\n" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + + @NotNull + private Device createDevice(String deviceAEndpoint) throws Exception { + Device device = new Device(); + device.setName("Device A"); + device.setDeviceProfileId(deviceProfile.getId()); + device.setTenantId(tenantId); + device = doPost("/api/device", device, Device.class); + Assert.assertNotNull(device); + + DeviceCredentials deviceCredentials = + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + + deviceCredentials.setCredentialsId(deviceAEndpoint); + + LwM2MCredentials noSecCredentials = new LwM2MCredentials(); + noSecCredentials.setClient(new NoSecClientCredentialsConfig()); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + return device; + } + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + + String deviceAEndpoint = "deviceAEndpoint"; + + Device device = createDevice(deviceAEndpoint); + + SingleEntityFilter sef = new SingleEntityFilter(); + sef.setSingleEntity(device.getId()); + LatestValueCmd latestCmd = new LatestValueCmd(); + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + + wsClient.send(mapper.writeValueAsString(wrapper)); + wsClient.waitForReply(); + + wsClient.registerWaitForUpdate(); + LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); + client.init(); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); + client.destroy(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java new file mode 100644 index 0000000000..6061813bcf --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -0,0 +1,262 @@ +/** + * 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.client; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.scandium.DTLSConnector; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.ClientHandshaker; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.californium.scandium.dtls.Handshaker; +import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker; +import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker; +import org.eclipse.californium.scandium.dtls.ServerHandshaker; +import org.eclipse.californium.scandium.dtls.SessionAdapter; +import org.eclipse.leshan.client.californium.LeshanClient; +import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; +import org.eclipse.leshan.client.object.Server; +import org.eclipse.leshan.client.observer.LwM2mClientObserver; +import org.eclipse.leshan.client.resource.ObjectsInitializer; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.californium.DefaultEndpointFactory; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.model.ObjectLoader; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; +import org.eclipse.leshan.core.request.BindingMode; +import org.eclipse.leshan.core.request.BootstrapRequest; +import org.eclipse.leshan.core.request.DeregisterRequest; +import org.eclipse.leshan.core.request.RegisterRequest; +import org.eclipse.leshan.core.request.UpdateRequest; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; + +import static org.eclipse.leshan.client.object.Security.noSec; +import static org.eclipse.leshan.core.LwM2mId.DEVICE; +import static org.eclipse.leshan.core.LwM2mId.SECURITY; +import static org.eclipse.leshan.core.LwM2mId.SERVER; + +@Slf4j +@Data +public class LwM2MTestClient { + + private final ScheduledExecutorService executor; + private final String endpoint; + private LeshanClient client; + + public void init() { + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; + List models = new ArrayList<>(); + for (String resourceName : resources) { + models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); + } + LwM2mModel model = new StaticModel(models); + ObjectsInitializer initializer = new ObjectsInitializer(model); + initializer.setInstancesForObject(SECURITY, noSec("coap://localhost:5685", 123)); + initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); + initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); + + NetworkConfig coapConfig = new NetworkConfig(); + coapConfig.setString("COAP_PORT", Integer.toString(5685)); + + DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); + dtlsConfig.setRecommendedCipherSuitesOnly(true); + + DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); + engineFactory.setReconnectOnUpdate(false); + engineFactory.setResumeOnConnect(true); + + DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) { + @Override + protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { + + return new DTLSConnector(dtlsConfig) { + @Override + protected void onInitializeHandshaker(Handshaker handshaker) { + handshaker.addSessionListener(new SessionAdapter() { + + @Override + public void handshakeStarted(Handshaker handshaker) throws HandshakeException { + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : STARTED ..."); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : STARTED ..."); + } + } + + @Override + public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession) + throws HandshakeException { + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker); + } + } + + @Override + public void handshakeFailed(Handshaker handshaker, Throwable error) { + /** get cause */ + String cause; + if (error != null) { + if (error.getMessage() != null) { + cause = error.getMessage(); + } else { + cause = error.getClass().getName(); + } + } else { + cause = "unknown cause"; + } + + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause); + } + } + }); + } + }; + } + }; + + LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); + builder.setLocalAddress("0.0.0.0", 11000); + builder.setObjects(initializer.createAll()); + builder.setCoapConfig(coapConfig); + builder.setDtlsConfig(dtlsConfig); + builder.setRegistrationEngineFactory(engineFactory); + builder.setEndpointFactory(endpointFactory); + builder.setSharedExecutor(executor); + builder.setDecoder(new DefaultLwM2mNodeDecoder(true)); + builder.setEncoder(new DefaultLwM2mNodeEncoder(true)); + client = builder.build(); + + LwM2mClientObserver observer = new LwM2mClientObserver() { + @Override + public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapStarted..."); + } + + @Override + public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapSuccess..."); + } + + @Override + public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver -> onBootstrapFailure..."); + } + + @Override + public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapTimeout..."); + } + + @Override + public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { +// log.info("ClientObserver -> onRegistrationStarted... EndpointName [{}]", request.getEndpointName()); + } + + @Override + public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { + log.info("ClientObserver -> onRegistrationSuccess... EndpointName [{}] [{}]", request.getEndpointName(), registrationID); + } + + @Override + public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver -> onRegistrationFailure... ServerIdentity [{}]", server); + } + + @Override + public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { + log.info("ClientObserver -> onRegistrationTimeout... RegisterRequest [{}]", request); + } + + @Override + public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { +// log.info("ClientObserver -> onUpdateStarted... UpdateRequest [{}]", request); + } + + @Override + public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { +// log.info("ClientObserver -> onUpdateSuccess... UpdateRequest [{}]", request); + } + + @Override + public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + + } + + @Override + public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { + + } + + @Override + public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationStarted... DeregisterRequest [{}]", request.getRegistrationId()); + + } + + @Override + public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationSuccess... DeregisterRequest [{}]", request.getRegistrationId()); + + } + + @Override + public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver ->onDeregistrationFailure... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); + } + + @Override + public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); + } + }; + this.client.addObserver(observer); + + client.start(); + } + + public void destroy() { + client.stop(false); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java new file mode 100644 index 0000000000..4512a94a27 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -0,0 +1,199 @@ +/** + * 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.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteResponse; + +import javax.security.auth.Destroyable; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TimeZone; + +@Slf4j +public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable { + + + private static final Random RANDOM = new Random(); + private static final List supportedResources = Arrays.asList(0, 1, 2, 3 +// , 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21 + ); + + @Override + public ReadResponse read(ServerIdentity identity, int resourceid) { + if (!identity.isSystem()) + log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + switch (resourceid) { + case 0: + return ReadResponse.success(resourceid, getManufacturer()); + case 1: + return ReadResponse.success(resourceid, getModelNumber()); + case 2: + return ReadResponse.success(resourceid, getSerialNumber()); + case 3: + return ReadResponse.success(resourceid, getFirmwareVersion()); + case 9: + return ReadResponse.success(resourceid, getBatteryLevel()); + case 10: + return ReadResponse.success(resourceid, getMemoryFree()); + case 11: + Map errorCodes = new HashMap<>(); + errorCodes.put(0, getErrorCode()); + return ReadResponse.success(resourceid, errorCodes, ResourceModel.Type.INTEGER); + case 14: + return ReadResponse.success(resourceid, getUtcOffset()); + case 15: + return ReadResponse.success(resourceid, getTimezone()); + case 16: + return ReadResponse.success(resourceid, getSupportedBinding()); + case 17: + return ReadResponse.success(resourceid, getDeviceType()); + case 18: + return ReadResponse.success(resourceid, getHardwareVersion()); + case 19: + return ReadResponse.success(resourceid, getSoftwareVersion()); + case 20: + return ReadResponse.success(resourceid, getBatteryStatus()); + case 21: + return ReadResponse.success(resourceid, getMemoryTotal()); + default: + return super.read(identity, resourceid); + } + } + + @Override + public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) { + String withParams = null; + if (params != null && params.length() != 0) { + withParams = " with params " + params; + } + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceid, withParams != null ? withParams : ""); + return ExecuteResponse.success(); + } + + @Override + public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) { + log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + + switch (resourceid) { + case 13: + return WriteResponse.notFound(); + case 14: + setUtcOffset((String) value.getValue()); + fireResourcesChange(resourceid); + return WriteResponse.success(); + case 15: + setTimezone((String) value.getValue()); + fireResourcesChange(resourceid); + return WriteResponse.success(); + default: + return super.write(identity, resourceid, value); + } + } + + private String getManufacturer() { + return "Leshan Demo Device"; + } + + private String getModelNumber() { + return "Model 500"; + } + + private String getSerialNumber() { + return "LT-500-000-0001"; + } + + private String getFirmwareVersion() { + return "1.0.0"; + } + + private long getErrorCode() { + return 0; + } + + private int getBatteryLevel() { + return 42; + } + + private long getMemoryFree() { + return Runtime.getRuntime().freeMemory() / 1024; + } + + private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime()); + + private String getUtcOffset() { + return utcOffset; + } + + private void setUtcOffset(String t) { + utcOffset = t; + } + + private String timeZone = TimeZone.getDefault().getID(); + + private String getTimezone() { + return timeZone; + } + + private void setTimezone(String t) { + timeZone = t; + } + + private String getSupportedBinding() { + return "U"; + } + + private String getDeviceType() { + return "Demo"; + } + + private String getHardwareVersion() { + return "1.0.1"; + } + + private String getSoftwareVersion() { + return "1.0.2"; + } + + private int getBatteryStatus() { + return RANDOM.nextInt(7); + } + + private long getMemoryTotal() { + return Runtime.getRuntime().totalMemory() / 1024; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + } +} diff --git a/application/src/test/resources/logback.xml b/application/src/test/resources/logback.xml index f991a40078..81d213b42d 100644 --- a/application/src/test/resources/logback.xml +++ b/application/src/test/resources/logback.xml @@ -9,13 +9,15 @@ - + + - + + diff --git a/application/src/test/resources/lwm2m/0.xml b/application/src/test/resources/lwm2m/0.xml new file mode 100644 index 0000000000..81e8523880 --- /dev/null +++ b/application/src/test/resources/lwm2m/0.xml @@ -0,0 +1,405 @@ + + + + + + + LWM2M Security + + 0 + urn:oma:lwm2m:oma:0:1.2 + 1.1 + 1.2 + Multiple + Mandatory + + + LWM2M Server URI + + Single + Mandatory + String + 0..255 + + + + + Bootstrap-Server + + Single + Mandatory + Boolean + + + + + + Security Mode + + Single + Mandatory + Integer + 0..4 + + + + + Public Key or Identity + + Single + Mandatory + Opaque + + + + + + Server Public Key + + Single + Mandatory + Opaque + + + + + + Secret Key + + Single + Mandatory + Opaque + + + + + + SMS Security Mode + + Single + Optional + Integer + 0..255 + + + + + SMS Binding Key Parameters + + Single + Optional + Opaque + 6 + + + + + SMS Binding Secret Key(s) + + Single + Optional + Opaque + 16,32,48 + + + + + LwM2M Server SMS Number + + Single + Optional + String + + + + + + Short Server ID + + Single + Optional + Integer + 1..65534 + + + + + Client Hold Off Time + + Single + Optional + Integer + + s + + + + Bootstrap-Server Account Timeout + + Single + Optional + Integer + + s + + + + Matching Type + + Single + Optional + Integer + 0..3 + + + + + SNI + + Single + Optional + String + + + + + + Certificate Usage + + Single + Optional + Integer + 0..3 + + + + + DTLS/TLS Ciphersuite + + Multiple + Optional + Integer + + + + + OSCORE Security Mode + + Single + Optional + Objlnk + + + + + + Groups To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithms Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + Signature Algorithms To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithm Certs Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + + TLS 1.3 Features To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions Supported by Server + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + Secondary LwM2M Server URI + + Multiple + Optional + String + 0..255 + + + + MQTT Server + + Single + Optional + Objlnk + + + + + LwM2M COSE Security + + Multiple + Optional + Objlnk + + + + + RDS Destination Port + + Single + Optional + Integer + 0..15 + + + + RDS Source Port + + Single + Optional + Integer + 0..15 + + + + RDS Application ID + + Single + Optional + String + + + + + + + + diff --git a/application/src/test/resources/lwm2m/1.xml b/application/src/test/resources/lwm2m/1.xml new file mode 100644 index 0000000000..f31e839c96 --- /dev/null +++ b/application/src/test/resources/lwm2m/1.xml @@ -0,0 +1,360 @@ + + + + + + + LwM2M Server + + 1 + urn:oma:lwm2m:oma:1:1.2 + 1.2 + 1.2 + Multiple + Mandatory + + + Short Server ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Lifetime + RW + Single + Mandatory + Integer + + s + + + + Default Minimum Period + RW + Single + Optional + Integer + + s + + + + Default Maximum Period + RW + Single + Optional + Integer + + s + + + + Disable + E + Single + Optional + + + + + + + Disable Timeout + RW + Single + Optional + Integer + + s + + + + Notification Storing When Disabled or Offline + RW + Single + Mandatory + Boolean + + + + + + Binding + RW + Single + Mandatory + String + + + + + + Registration Update Trigger + E + Single + Mandatory + + + + + + + Bootstrap-Request Trigger + E + Single + Optional + + + + + + + APN Link + RW + Single + Optional + Objlnk + + + + + + TLS-DTLS Alert Code + R + Single + Optional + Integer + 0..255 + + + + + Last Bootstrapped + R + Single + Optional + Time + + + + + + Registration Priority Order + R + Single + Optional + Integer + + + + + + Initial Registration Delay Timer + RW + Single + Optional + Integer + + s + + + + Registration Failure Block + R + Single + Optional + Boolean + + + + + + Bootstrap on Registration Failure + R + Single + Optional + Boolean + + + + + + Communication Retry Count + RW + Single + Optional + Integer + + + + + + Communication Retry Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Delay Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Retry Count + RW + Single + Optional + Integer + + + + + + Trigger + RW + Single + Optional + Boolean + + + + + + Preferred Transport + RW + Single + Optional + String + The possible values are those listed in the LwM2M Core Specification + + + + Mute Send + RW + Single + Optional + Boolean + + + + + + Alternate APN Links + RW + Multiple + Optional + Objlnk + + + + + + Supported Server Versions + RW + Multiple + Optional + String + + + + + + Default Notification Mode + RW + Single + Optional + Integer + 0..1 + + + + + Profile ID Hash Algorithm + RW + Single + Optional + Integer + 0..255 + + + + + + + diff --git a/application/src/test/resources/lwm2m/2.xml b/application/src/test/resources/lwm2m/2.xml new file mode 100644 index 0000000000..4ea5805b36 --- /dev/null +++ b/application/src/test/resources/lwm2m/2.xml @@ -0,0 +1,123 @@ + + + + + + + LwM2M Access Control + + 2 + urn:oma:lwm2m:oma:2:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Object ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Object Instance ID + R + Single + Mandatory + Integer + 0..65535 + + + + + ACL + RW + Multiple + Optional + Integer + 0..31 + + + + + Access Control Owner + RW + Single + Mandatory + Integer + 0..65535 + + + + + + + diff --git a/application/src/test/resources/lwm2m/3.xml b/application/src/test/resources/lwm2m/3.xml new file mode 100644 index 0000000000..724fc4cb33 --- /dev/null +++ b/application/src/test/resources/lwm2m/3.xml @@ -0,0 +1,331 @@ + + + + + + + Device + + 3 + urn:oma:lwm2m:oma:3:1.0 + 1.1 + 1.0 + Single + Mandatory + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0..7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + + + + + Power Source Current + R + Multiple + Optional + Integer + + + + + + Battery Level + R + Single + Optional + Integer + 0..100 + /100 + + + + Memory Free + R + Single + Optional + Integer + + + + + + Error Code + R + Multiple + Mandatory + Integer + 0..32 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0..6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + + + + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java index 7c75589c4c..65be16bfd6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java @@ -1,3 +1,18 @@ +/** + * 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.credentials; import org.eclipse.leshan.core.util.Hex; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java index 33a297c91a..65f027a849 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.credentials; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java index ebb1fc138b..09c27f0e42 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java @@ -1,3 +1,18 @@ +/** + * 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.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java index bcac7174aa..03933972c3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.credentials; import org.eclipse.leshan.core.SecurityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java index d79083647f..8de85ce72d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java index 280e8492f8..025c8b3b10 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.credentials; import org.eclipse.leshan.core.SecurityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java index 0aff74d189..0a2df6852e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -1,3 +1,18 @@ +/** + * 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.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index b9de7bcea2..e93984e87c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -646,8 +646,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config - .getModelProvider())) { + if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && lwM2MClient.getFrUpdate().getCurrentFwVersion() != null && !lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion()) From 92719c2ac2f5aad06836915718ad0b18e33995b5 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 11 May 2021 14:14:00 +0300 Subject: [PATCH 06/13] added lwm2m x509 test --- .../transport/TransportSqlTestSuite.java | 14 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 133 +++++++++++- .../lwm2m/NoSecLwM2MIntegrationTest.java | 9 +- .../lwm2m/X509LwM2MIntegrationTest.java | 205 ++++++++++++++++++ .../lwm2m/client/LwM2MTestClient.java | 11 +- .../resources/application-test.properties | 2 + .../lwm2m/credentials/clientKeyStore.jks | Bin 0 -> 3180 bytes .../lwm2m/credentials/serverKeyStore.jks | Bin 0 -> 3120 bytes .../lwm2m/secure/TbLwM2MAuthorizer.java | 2 +- .../X509ClientCredentialsConfig.java | 5 +- .../DefaultLwM2MTransportMsgHandler.java | 6 +- .../server/client/LwM2mClientContextImpl.java | 1 + .../TbL2M2MDtlsSessionInMemoryStore.java | 5 + .../server/store/TbLwM2MDtlsSessionStore.java | 3 + .../device/DeviceCredentialsServiceImpl.java | 19 +- 15 files changed, 391 insertions(+), 24 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java create mode 100644 application/src/test/resources/application-test.properties create mode 100644 application/src/test/resources/lwm2m/credentials/clientKeyStore.jks create mode 100644 application/src/test/resources/lwm2m/credentials/serverKeyStore.jks diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index 25df3bee00..d059ea1449 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.transport.*.rpc.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", - "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", - "org.thingsboard.server.transport.*.attributes.request.sql.*Test", - "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test", +// "org.thingsboard.server.transport.*.rpc.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", +// "org.thingsboard.server.transport.*.claim.sql.*Test", +// "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 97f7bfbc3c..91b2afc8c6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.IOUtils; +import org.eclipse.leshan.core.util.Hex; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -35,6 +36,23 @@ import org.thingsboard.server.controller.AbstractWebsocketTest; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.service.DaoSqlTest; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.KeySpec; import java.util.Base64; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -46,6 +64,114 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { protected ScheduledExecutorService executor; protected TbTestWebSocketClient wsClient; + protected final PublicKey clientPublicKey; // client public key used for RPK + protected final PrivateKey clientPrivateKey; // client private key used for RPK + protected final PublicKey serverPublicKey; // server public key used for RPK + protected final PrivateKey serverPrivateKey; // server private key used for RPK + + // client private key used for X509 + protected final PrivateKey clientPrivateKeyFromCert; + // server private key used for X509 + protected final PrivateKey serverPrivateKeyFromCert; + // client certificate signed by rootCA with a good CN (CN start by leshan_integration_test) + protected final X509Certificate clientX509Cert; + // client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test) + protected final X509Certificate clientX509CertWithBadCN; + // client certificate self-signed with a good CN (CN start by leshan_integration_test) + protected final X509Certificate clientX509CertSelfSigned; + // client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test) + protected final X509Certificate clientX509CertNotTrusted; + // server certificate signed by rootCA + protected final X509Certificate serverX509Cert; + // self-signed server certificate + protected final X509Certificate serverX509CertSelfSigned; + // rootCA used by the server + protected final X509Certificate rootCAX509Cert; + // certificates trustedby the server (should contain rootCA) + protected final Certificate[] trustedCertificates = new Certificate[1]; + + public AbstractLwM2MIntegrationTest() { +// create client credentials + try { + // Get point values + byte[] publicX = Hex + .decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray()); + byte[] publicY = Hex + .decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray()); + byte[] privateS = Hex + .decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray()); + + // Get Elliptic Curve Parameter spec for secp256r1 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + + // Create key specs + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + + // Get keys + clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + + // Get certificates from key store + char[] clientKeyStorePwd = "client".toCharArray(); + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) { + clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd); + } + + clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd); + clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client"); + clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn"); + clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed"); + clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted"); + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + + // create server credentials + try { + // Get point values + byte[] publicX = Hex + .decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray()); + byte[] publicY = Hex + .decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray()); + byte[] privateS = Hex + .decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray()); + + // Get Elliptic Curve Parameter spec for secp256r1 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + + // Create key specs + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); + +// // Get keys + serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + + // Get certificates from key store + char[] serverKeyStorePwd = "server".toCharArray(); + KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) { + serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd); + } + + serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd); + rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA"); + serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server"); + serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed"); + trustedCertificates[0] = rootCAX509Cert; + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + } + @Before public void beforeTest() throws Exception { executor = Executors.newScheduledThreadPool(10); @@ -60,7 +186,8 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { lwModel.setTenantId(tenantId); byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName)); lwModel.setData(Base64.getEncoder().encodeToString(bytes)); - lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>(){}); + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>() { + }); Assert.assertNotNull(lwModel); } wsClient = buildAndConnectWebSocketClient(); @@ -69,7 +196,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { protected void createDeviceProfile(String transportConfiguration) throws Exception { deviceProfile = new DeviceProfile(); - deviceProfile.setName("LwM2M No Security"); + deviceProfile.setName("LwM2M"); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTenantId(tenantId); deviceProfile.setTransportType(DeviceTransportType.LWM2M); @@ -87,7 +214,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { } @After - public void after() { + public void after() throws InterruptedException { executor.shutdownNow(); wsClient.close(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 809f1f609b..f4a7f7f9e2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.client.object.Security; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Test; @@ -39,6 +41,7 @@ import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCred import java.util.Collections; import java.util.List; +import static org.eclipse.leshan.client.object.Security.noSec; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -91,6 +94,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { " }\n" + "}"; + private final int port = 5685; + private final Security security = noSec("coap://localhost:" + port, 123); + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); + @NotNull private Device createDevice(String deviceAEndpoint) throws Exception { Device device = new Device(); @@ -138,7 +145,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { wsClient.registerWaitForUpdate(); LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); - client.init(); + client.init(security, coapConfig); String msg = wsClient.waitForUpdate(); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java new file mode 100644 index 0000000000..06541e5c56 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -0,0 +1,205 @@ +/** + * 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; + +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.client.object.Security; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.SingleEntityFilter; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.transport.util.SslUtil; +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; + +import java.util.Collections; +import java.util.List; + +import static org.eclipse.leshan.client.object.Security.x509; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected final String TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.0/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrap\": {\n" + + " \"servers\": {\n" + + " \"binding\": \"UQ\",\n" + + " \"shortId\": 123,\n" + + " \"lifetime\": 300,\n" + + " \"notifIfDisabled\": true,\n" + + " \"defaultMinPeriod\": 1\n" + + " },\n" + + " \"lwm2mServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5686,\n" + + " \"serverId\": 123,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " },\n" + + " \"bootstrapServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5687,\n" + + " \"serverId\": 111,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " }\n" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + + + private final int port = 5686; + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port)); + private final String endpoint = "deviceAEndpoint"; + private final String serverUri = "coaps://localhost:" + port; + + @NotNull + private Device createDevice(String credentialsId, X509ClientCredentialsConfig credentialsConfig) throws Exception { + Device device = new Device(); + device.setName("Device A"); + device.setDeviceProfileId(deviceProfile.getId()); + device.setTenantId(tenantId); + device = doPost("/api/device", device, Device.class); + Assert.assertNotNull(device); + + DeviceCredentials deviceCredentials = + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + + deviceCredentials.setCredentialsId(credentialsId); + + LwM2MCredentials X509Credentials = new LwM2MCredentials(); + + X509Credentials.setClient(credentialsConfig); + + deviceCredentials.setCredentialsValue(JacksonUtil.toString(X509Credentials)); + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + return device; + } + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + + Device device = createDevice(endpoint, new X509ClientCredentialsConfig(null, null)); + + SingleEntityFilter sef = new SingleEntityFilter(); + sef.setSingleEntity(device.getId()); + LatestValueCmd latestCmd = new LatestValueCmd(); + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + + wsClient.send(mapper.writeValueAsString(wrapper)); + wsClient.waitForReply(); + + wsClient.registerWaitForUpdate(); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); + Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); + client.init(security, coapConfig); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); + client.destroy(); + } + + @Test + public void testConnectWithCertAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + Device device = createDevice(null, new X509ClientCredentialsConfig(SslUtil.getCertificateString(clientX509CertNotTrusted), endpoint)); + + SingleEntityFilter sef = new SingleEntityFilter(); + sef.setSingleEntity(device.getId()); + LatestValueCmd latestCmd = new LatestValueCmd(); + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + + wsClient.send(mapper.writeValueAsString(wrapper)); + wsClient.waitForReply(); + + wsClient.registerWaitForUpdate(); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); + + Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); + + client.init(security, coapConfig); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); + client.destroy(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 6061813bcf..8a17b6e3c9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -32,6 +32,7 @@ import org.eclipse.californium.scandium.dtls.SessionAdapter; import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; +import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.ObjectsInitializer; @@ -54,7 +55,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; -import static org.eclipse.leshan.client.object.Security.noSec; import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; @@ -67,7 +67,7 @@ public class LwM2MTestClient { private final String endpoint; private LeshanClient client; - public void init() { + public void init(Security security, NetworkConfig coapConfig) { String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; List models = new ArrayList<>(); for (String resourceName : resources) { @@ -75,13 +75,10 @@ public class LwM2MTestClient { } LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); - initializer.setInstancesForObject(SECURITY, noSec("coap://localhost:5685", 123)); + initializer.setInstancesForObject(SECURITY, security); initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); - NetworkConfig coapConfig = new NetworkConfig(); - coapConfig.setString("COAP_PORT", Integer.toString(5685)); - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setRecommendedCipherSuitesOnly(true); @@ -256,7 +253,7 @@ public class LwM2MTestClient { } public void destroy() { - client.stop(false); + client.destroy(true); } } diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties new file mode 100644 index 0000000000..6638504b2f --- /dev/null +++ b/application/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks +transport.lwm2m.security.key_store_password=server \ No newline at end of file diff --git a/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks b/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks new file mode 100644 index 0000000000000000000000000000000000000000..7cc58589b72517c57a1086144275218ad04b75de GIT binary patch literal 3180 zcmY+EcQhLgyT>I~#h#@`&C&{j+M^=W-m|s$Dlyu|jM=CdX-UmeTS=(BDJ7^?tEm3$ z-D;v$6|}s)_uTuw_x|;q^ZB0Vd!Elf4E~`y z_c5=z7aiXiMP0SIp3X%Zap%Z$u5o#n@rt3Ni{ZVz8sNQ!BJU_!EeP+w52WLeWfEYZ z@Su1=;YZ;`5k&D2@*gicPFW5rHfz5?FF{#FIf#;+qO6jVoPshWfnN0A4YV}H3G|$Q z5i1Zt@ppj!lK}poAI*RH1%Rg>D$|YFIKb2gpEW5N9}71w{I_3R0%+=L4gR(-Ux$}= zrm0RO$19#zUzA_G3G8i7vuD>K*KK(PwRHrBK`AvmJJmkd!pe&uC*)(P;5ld71zZ3nuS1haTTmybS^XcAe)So$acDI| zmC*^Gf!EzPXY8x~_>;H9?m{U=9jZWPu%xf2q14AY93aolJz4Lu0+{AFNooT?9dtWz`(x=LG7zcaw5@tiW6 zmN7Em+7$q-do3HSnjr~K7i$OMF~&oJ-78kIbuYA|-W$8}+aB?CWmX~#($Ss!|I7&8 zfzpcY|8#*-X9$@bd@~1Kxj!&SvaHrl<<~TWt0F&f=gH`1dGss};YfXU%L1>@5(5?8 zo|Yz-=nPBV4!`{UW?-x&DSt)i`o@H4&UT_N$3Al?kw~xEz@uIOX>_CvZqsY7E>`~< z)u*Ig9OBuoh^RM|RpcGI}(s>QSJNWeWpNW|N4DYAP|kGi@> z0_}UVlN$JJX%%~{e?gb|ER3<*#P5>xqxchlz0x zdf-!mQz6b>!Ofl~woyu43-;@;oczXcNkA>gnT4T8349H4O)Nz&(3#G2!9F=|f%zMi z;B;)}v&n`X#el-~waY$)-IZ8RPQ|dIyR)_(Xg8IaeF2p=NivE{WrI6#_Kk0qdC83) zt8Kw1lF=*v%8?z=+X<|ezsSc*XZx2~c{srB~%&tf5yj*pxL10uj$iFwb_=xkXmfAQVWy$)OTCIO2^!(%q zxwYh2rLAb*+ZHn{l6_)MkB&qfn!mdBU^9A&<25(d37M%`4oO9I?`tX-Kv?q&8RveWJfTj|C*(~?a#XX* zHc(Kk*0x$06q2$&-h*yOv-F2@<*;(v=PEq<>?(zw>`knBgne}3G$IN&q>UzqPAomA z9%$1-D5=Hok(GkAn+y0IKFm#1<*%U2ZvFBl*);k|4D|2lUJQ>MmxEpV`gh;ld1rEv zflhw7)RFQ@=6A4DN0QHvgW~bihUx8!_HXhwlU#K>8k06ja}hpatemUXh5rQ9swEiGJ1d><-1Yn+7nJy9>Sx7w z;=~!rnUdx1%RGUqAEL*(bui)Z!Hac=&(Y4ZX^#p$BrXT=!v^E!aA0no{1@99M z@y$C;m9h3otJ2vBDLjtAJ&*`3n+x`5Hr@!H72_`-KU`^z$Wr9V?F!ZXesRi~(m5uQ zrmL29!G383?}8oj3MWbIfioD)ui2eS1+{gO^4K;^_WBh@)M#hmK4#>>E&iT~IKQ0G zd%9t~Td|-nRlbA$XuS5cHYnQcIqBVru2A2 zz@*?0t6R8i+L=2C$of411@kr ziwo=QI@w;(No~GjiNPc%ecPX{8As|KFW$P%K4ssybl4l$x=H19epGEbbN?VozQPWZ zHRRz|Lz62iPT6j2wv2CWiYn)pVgetR(62!%nlN^8-#O+@W6xAVK51xO=MQ=% zM%#nP*jvSr*%2y1%2dydZL}mRwgaE_Dj3n-ym8Uj&W-)7{l+dkAo2tp81>JS(9L7Q z#TWlFvY_oJ4F54dCarark`7_Xp!ebjl3e{|tNov$sp&b)=MwaNAhs?zf*AMPWQ?E~ zKCiO$_k~+-F2Oamgl}kIs>nVceYQNeiwL8#Y>HCiTZQ7=<7xT7)9oXtoDcy;`^Lsd zMjLBJhOqg$oE>PMEx1(<|O)1HA9sm+Nu(eMq@G_by%kOo}7V{ryp4 z@T<~pv9?Z^(H7Rrr5_enmuER~)Y$8;a%>PWsXloG)WNPvuDS!;2wg{nK>bMV3V4{6eZAHBpRy z1nc~^$CBLtM4#X5r9>|`8y+fhF%rd*24!pd$azQaxq3e7rN$q}80e9h1U;hMNWJ4G zZ7TBeD>JH2%L@W5ZWuP4Zma~P+mc^9=lNx-1L?zEJ}Mp9%=v8kH_@a=S|n#(U7lj) zU=^aS+<`VRu{|vtOq4KOZMdVb?1`G0?OJf}_;DrHU4C(UqO!%RYzBoQ7ENywB1Pr7 zv(vcn<7sU!l>>UP%5OhboL<#IVF&FLuf;ofQkXbndq)P`62!6=Sh5L5N{?G=KLu7T z#Zw}ME)jjpF|E zmXHTaZWr$LoRLcT8<(k`=(C~#DS~R+23BZE0Ag+YU2(9KjC*qWk>3u;C05Q8of!d- zUFw!jFxDi!>2s$)cAcYs-Y{T!emm@^e}VWkxA;5tRrvWyvm^qzONEI}46wyN>B MoMV8L095h60D(LA`2YX_ literal 0 HcmV?d00001 diff --git a/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks b/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks new file mode 100644 index 0000000000000000000000000000000000000000..f1f03005e129985fd17b09e8dd6e0e2caa2d366f GIT binary patch literal 3120 zcmb7?2{aUH8^>pc!7vD8SJuc-1`|edMaB}L#3hEIn2uW2`P}c^@7{CI`Of+7ciwZ}=bZolyzhB_&;LOXp|T(#9fHWR%m9ItbV+-x zKt^B&k!6&g$TD;y_aTU&h`(7NJUtQQdm?)wf;$;l{&)ms27)qt5f2d349x$LPW~07 zhj9?PR?blS-^PeIRe7>qzlKfC76Q=$T8-&-wd!1sA1ALX%mpGE>qRQyYQ@=L@jGH9 zwlzgWqguGkP2JL?Z!!ypYphpOvh!i;{et~-hS;E6h914$VaZ5!8ONS3_b=Wn@)`)i zKYN70Z;pqc=Ef9=BZ6&-7Mc9C||ERjTud|b7$z(dsId?92!yS5B)mT1E>WH));h6 zscGKlO-5>NW;KK!m=^Sm-{@5b=e{u4cc}hG#Aiyp_Pl1WDF(ielD~mC|5vv#d66?` z01p7JCm^0D)$63*1>E^R;1m^6Cvg8HCvV$h9O&?oB+R10Azjn)&E@fSqd(w4>ZEr+ z1IBT6>@K7U4KZq-^O9MPREcl;nx!Y#$n(X0Okj3TnT~m@3x}r@y^@w;WXzyC*0NR;wL1QDY4=Xe+yGKi4#Cz3P>2skNX ze>uRv?IFYOdzd0DuJ8H^e7LqzBSE~Fx3Lne^5oxq1R~R_e=}h*Tk!b@kZ_RAR;?l{ z{J^+uvHZ+;TWQu)0!|P65R+2I)-KBL-AhLu#Xs`-`u|F;9yeY!&*3cccvly^p1PMz zyMxEHw66MVhv)98S|B(H3j0?YI?D!(*#8}zC+ zG!ze)K6)xg$;W8hbE$7LnbCFK&4hh4T?=Oj>{}3gaUEkwOO-HSjF-hh`M-9KCL8FF zx<$&+g{3}q7=uCkTa@F3W*C}%9J9mCa;8t2WU)uFSJSD6*5hIUr8=*2SN3rCas z4x)TN?m?Z64X|Cv-H3++S5Lpfc5%k>H$WHo%^59yadG?RoBQ?m1>f54-AXh~P~(>R zqC^XOZtLnW@yWvVC5KaGa}jx%^;6F_Tbei~JJCLQt8O5_;aq_c)E@Z>B>!!;R3Yv>a7DsSefN9@{g5~bOq&uvm=C&ZGW_PJWzVL!{m z+V&Zd1lb$Qfpai(d|L0C+~?aPCZ)UTIF-jM*`9d; zHzS?%D(y)s)GGbj?UZa>K!DWq$lJ3hbzjmjaYRrACpu5@Q((*X;X}J{luqW90{G+` zNkJfC{dMjy7Ji1Z0Xev!&0(^?WR&fr2NF5|K(g|;@8;NN zWAdCdufb47p$JuDWwi*on_}Bq@mpN|QR_p>@WPUe>Drved6RL>seLL2RZ+kO1YeUt zE_J!GdFQ&wF-wPr{*tvR31(b5<_l`37f+1783!MaTI!mb?tgCBFm zCj}NS3Sk*Cp(aRaP=;DWk(+*7r_DRKZ%gEDal=g)_P@%?Vje2?bxqH2gztp=6a0;) zhDKc%Jb7KJI0u9&AAdD=<*Pr$H}{f@s4u-BJK@B-8a%qCKhoe@SR6NY(oYFlR+}$w zct3j0iKKxnC14)p1YgW~FRs5oI*4=Jl)r>rvQ;cAf>)V6P^Gqq;7Cw&4nkKQgIXq__R(T-&~r`Kr=kc<#4!o>-H%`Wuz8 zs(}?jTs-g9LG>B(ykbMLQ+Tq%Y@fH>nu(5&RR-o;;LVZ7h?S+xzE_EMFEqkOD(^XX zIqlf;=YQF6cyrW;E>D*?!>`YjIKVqLLU?Zv3b&hhwU&32v;;PQEvoAsri#F*ka7<& zc_;1q*ZPT~(BhZH&hB#GQ*AW>8{4i9?_$d|N*y2HJx406k(b?Ar1<1#_v&1#e#Li&t1P`~}OT}+!`r>=7 z*a}U}ha~P$MZMkXmV4JjtQFTM9bOZPvMB?7OYx`1ta`;SlF{WXrlC?AfeH)lvRjui z!?dnCtlmcR6=7OhyTshguRD@~R`Zujn6Pptx;s9x{NlNJc2j%y9~r1JT97t(|4o9j zYf0H_wHE$+qo3>w%ZGV+EL|(KIY352RtdVzbDY$h3h7)~g0z0ygEt5F{qj;EmjY(d z0T~&WsKR97y(@-B#Wn0Ttx2W|BV%)9OE7-U^8VGpv)<63&H9;y>(BR2@h+afz{{_8 zgGUCE@+m8`NLv5wo%ve^47(q2^;JKD8@!R7?K6(Ak;g%mRRH@hV~)=snhOUarRy75 zx)iA%8O4MjRgDYs$*MMBNqol1JF>CL5SoaFr zI4I$}{w)@M=v7w=C@)e^yFuvI^p?z50d1`xeBHOqcPT}v!Ii;XXUAdD6y?tA-2TUA zdc-adXpE1Ydo61DYOsyXi&+R}eb)WfJ#jPIf~UP8YIXDv1e!M%$dLm{eSY*_FJb%ZDa!T^?GqXP+m0raq|ZrxS|MbSSj qL;D3%%-d?jak~%bVSeI3r^Om(r)};qh1!z#p-K Date: Tue, 11 May 2021 14:48:32 +0300 Subject: [PATCH 07/13] refactored --- .../server/transport/TransportSqlTestSuite.java | 14 +++++++------- .../lwm2m/AbstractLwM2MIntegrationTest.java | 2 +- .../transport/lwm2m/NoSecLwM2MIntegrationTest.java | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index d059ea1449..25df3bee00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ -// "org.thingsboard.server.transport.*.rpc.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", -// "org.thingsboard.server.transport.*.claim.sql.*Test", -// "org.thingsboard.server.transport.*.provision.sql.*Test", + "org.thingsboard.server.transport.*.rpc.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", + "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", + "org.thingsboard.server.transport.*.attributes.request.sql.*Test", + "org.thingsboard.server.transport.*.claim.sql.*Test", + "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 91b2afc8c6..46ad3c1c34 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -214,7 +214,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { } @After - public void after() throws InterruptedException { + public void after() { executor.shutdownNow(); wsClient.close(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index f4a7f7f9e2..72f4d041f3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -95,7 +95,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { "}"; private final int port = 5685; - private final Security security = noSec("coap://localhost:" + port, 123); + private final Security security = noSec("coap://localhost:" + port, 123); private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); @NotNull From b59c846885764bded676deab7949365728ad74c4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 12 May 2021 18:38:12 +0300 Subject: [PATCH 08/13] Improvements to data converter --- .../transport/adaptor/JsonConverter.java | 32 +++++++++++++------ .../src/test/java/JsonConverterTest.java | 8 +++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index a4e8c60841..4db4aa9bd8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -226,19 +226,29 @@ public class JsonConverter { } private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { - if (value.getAsString().contains(".")) { - return KeyValueProto.newBuilder() - .setKey(key) - .setType(KeyValueType.DOUBLE_V) - .setDoubleV(value.getAsDouble()) - .build(); + String valueAsString = value.getAsString(); + KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key); + if (valueAsString.contains("e") || valueAsString.contains("E")) { + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + var bd = new BigDecimal(valueAsString); + if (bd.stripTrailingZeros().scale() <= 0) { + try { + return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build(); + } catch (ArithmeticException e) { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + } + } else { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); + } + } else if (valueAsString.contains(".")) { + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(value.getAsDouble()).build(); } else { try { long longValue = Long.parseLong(value.getAsString()); - return KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V) - .setLongV(longValue).build(); + return builder.setType(KeyValueType.LONG_V).setLongV(longValue).build(); } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(new BigDecimal(valueAsString).doubleValue()).build(); } } } @@ -252,6 +262,7 @@ public class JsonConverter { String valueAsString = value.getAsString(); String key = valueEntry.getKey(); if (valueAsString.contains("e") || valueAsString.contains("E")) { + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String var bd = new BigDecimal(valueAsString); if (bd.stripTrailingZeros().scale() <= 0) { try { @@ -269,7 +280,8 @@ public class JsonConverter { long longValue = Long.parseLong(value.getAsString()); result.add(new LongDataEntry(key, longValue)); } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String + result.add(new DoubleDataEntry(key, new BigDecimal(valueAsString).doubleValue())); } } } diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/transport/transport-api/src/test/java/JsonConverterTest.java index cedbef50c9..dc28b268f5 100644 --- a/common/transport/transport-api/src/test/java/JsonConverterTest.java +++ b/common/transport/transport-api/src/test/java/JsonConverterTest.java @@ -21,6 +21,8 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import java.util.ArrayList; + @RunWith(MockitoJUnitRunner.class) public class JsonConverterTest { @@ -38,6 +40,12 @@ public class JsonConverterTest { Assert.assertEquals(10.1, result.get(0L).get(0).getDoubleValue().get(), 0.0); } + @Test + public void testParseAttributesBigDecimalAsLong() { + var result = new ArrayList<>(JsonConverter.convertToAttributes(JSON_PARSER.parse("{\"meterReadingDelta\": 1E1}"))); + Assert.assertEquals(10L, result.get(0).getLongValue().get().longValue()); + } + @Test public void testParseAsDouble() { var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L); From 53c51953c48be65352a9bdd34fe68f9d27fc2172 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 13 May 2021 16:20:39 +0300 Subject: [PATCH 09/13] UI: Refactoring lwm2m security config and add X509 certificate in lwm2m security model --- .../device/device-credentials.component.html | 29 +- .../device/device-credentials.component.ts | 90 +----- .../security-config-lwm2m-server.component.ts | 5 + .../security-config-lwm2m.component.html | 232 +++++++--------- .../device/security-config-lwm2m.component.ts | 262 +++++++----------- .../models/lwm2m-security-config.models.ts | 99 ++----- .../assets/locale/locale.constant-en_US.json | 12 +- 7 files changed, 242 insertions(+), 487 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html index db28ff0453..0cf349a074 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -75,32 +75,7 @@ ('device.client-id-or-user-name-necessary' | translate) : ''">
- - device.lwm2m-key - - - {{ 'device.lwm2m-key-required' | translate }} - - - - device.lwm2m-value - - - {{ 'device.lwm2m-value-required' | translate }} - - - {{ 'device.lwm2m-value-format-error' | translate }} - -
- -
-
+ +
diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts index 17ad828b48..19e9419829 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -34,19 +34,7 @@ import { DeviceCredentialsType } from '@shared/models/device.models'; import { Subject } from 'rxjs'; -import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; -import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; -import { - ClientSecurityConfig, - DEFAULT_END_POINT, - DeviceCredentialsDialogLwm2mData, - END_POINT, - getDefaultSecurityConfig, - JSON_ALL_CONFIG, - validateSecurityConfig -} from '@shared/models/lwm2m-security-config.models'; -import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material/dialog'; +import { takeUntil } from 'rxjs/operators'; import { isDefinedAndNotNull } from '@core/utils'; @Component({ @@ -84,9 +72,7 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, private propagateChange = (v: any) => {}; - constructor(public fb: FormBuilder, - private translate: TranslateService, - private dialog: MatDialog) { + constructor(public fb: FormBuilder) { this.deviceCredentialsFormGroup = this.fb.group({ credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], credentialsId: [null], @@ -99,15 +85,14 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, }); this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); this.deviceCredentialsFormGroup.valueChanges.pipe( - distinctUntilChanged(), takeUntil(this.destroy$) ).subscribe(() => { this.updateView(); }); this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.pipe( takeUntil(this.destroy$) - ).subscribe((type) => { - this.credentialsTypeChanged(type); + ).subscribe(() => { + this.credentialsTypeChanged(); }); } @@ -128,8 +113,6 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, let credentialsValue = null; if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) { credentialsBasic = JSON.parse(value.credentialsValue) as DeviceCredentialMQTTBasic; - } else if (value.credentialsType === DeviceCredentialsType.LWM2M_CREDENTIALS) { - credentialsValue = JSON.parse(JSON.stringify(value.credentialsValue)) as ClientSecurityConfig; } else { credentialsValue = value.credentialsValue; } @@ -176,11 +159,10 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, }; } - credentialsTypeChanged(credentialsType: DeviceCredentialsType): void { - const credentialsValue = credentialsType === DeviceCredentialsType.LWM2M_CREDENTIALS ? this.lwm2mDefaultConfig : null; + credentialsTypeChanged(): void { this.deviceCredentialsFormGroup.patchValue({ credentialsId: null, - credentialsValue, + credentialsValue: null, credentialsBasic: {clientId: '', userName: '', password: ''} }); this.updateValidators(); @@ -198,14 +180,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); break; case DeviceCredentialsType.X509_CERTIFICATE: - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); - this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); - break; case DeviceCredentialsType.LWM2M_CREDENTIALS: - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required, this.lwm2mConfigJsonValidator]); + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); @@ -245,56 +221,4 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, onlySelf: true }); } - - openSecurityInfoLwM2mDialog($event: Event): void { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - let credentialsValue = this.deviceCredentialsFormGroup.get('credentialsValue').value; - if (credentialsValue === null || credentialsValue.length === 0) { - credentialsValue = getDefaultSecurityConfig(); - } else { - try { - credentialsValue = JSON.parse(credentialsValue); - } catch (e) { - credentialsValue = getDefaultSecurityConfig(); - } - } - const credentialsId = this.deviceCredentialsFormGroup.get('credentialsId').value || DEFAULT_END_POINT; - this.dialog.open(SecurityConfigLwm2mComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - jsonAllConfig: credentialsValue, - endPoint: credentialsId - } - }).afterClosed().subscribe( - (res) => { - if (res) { - this.deviceCredentialsFormGroup.patchValue({ - credentialsValue: this.isDefaultLw2mResponse(res[JSON_ALL_CONFIG]) ? null : JSON.stringify(res[JSON_ALL_CONFIG]), - credentialsId: this.isDefaultLw2mResponse(res[END_POINT]) ? null : JSON.stringify(res[END_POINT]).split('\"').join('') - }); - this.deviceCredentialsFormGroup.get('credentialsValue').markAsDirty(); - } - } - ); - } - - private isDefaultLw2mResponse(response: object): boolean { - return Object.keys(response).length === 0 || JSON.stringify(response) === '[{}]'; - } - - private lwm2mConfigJsonValidator(control: FormControl) { - return validateSecurityConfig(control.value) ? null : {jsonError: {parsedJson: 'error'}}; - } - - private get lwm2mDefaultConfig(): string { - return JSON.stringify(getDefaultSecurityConfig(), null, 2); - } - - lwm2mCredentialsValueTooltip(flag: boolean): string { - return !flag ? '' : 'Example (mode=\"NoSec\"):\n\r ' + this.lwm2mDefaultConfig; - } } diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts index b8e1b42ef4..d599927834 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts @@ -130,6 +130,8 @@ export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlVal case Lwm2mSecurityType.NO_SEC: this.serverFormGroup.get('clientPublicKeyOrId').clearValidators(); this.serverFormGroup.get('clientSecretKey').clearValidators(); + this.serverFormGroup.get('clientPublicKeyOrId').disable({emitEvent: false}); + this.serverFormGroup.get('clientSecretKey').disable(); break; case Lwm2mSecurityType.PSK: this.lenMinClientPublicKeyOrId = 0; @@ -172,5 +174,8 @@ export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlVal Validators.minLength(this.lengthClientSecretKey), Validators.maxLength(this.lengthClientSecretKey) ]); + + this.serverFormGroup.get('clientPublicKeyOrId').enable({emitEvent: false}); + this.serverFormGroup.get('clientSecretKey').enable(); } } diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html index bd83cc817b..77b7601ed5 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html @@ -15,129 +15,109 @@ limitations under the License. --> -
- -

{{ title }}

- - -
-
- - device.lwm2m-security-config.endpoint - - - {{ 'device.lwm2m-security-config.endpoint-required' | translate }} - - - - - - device.lwm2m-security-config.mode - - - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} - - - -
- - {{ 'device.lwm2m-security-config.identity' | translate }} - - - {{ 'device.lwm2m-security-config.identity-required' | translate }} - - -
- - {{ 'device.lwm2m-security-config.client-key' | translate }} - - {{key.value?.length || 0}}/{{lenMaxKeyClient}} - - {{ 'device.lwm2m-security-config.client-key-required' | translate }} - - - {{ 'device.lwm2m-security-config.client-key-pattern' | translate }} - - - {{ 'device.lwm2m-security-config.client-key-length' | translate: { - count: lenMaxKeyClient - } }} - - - - {{ 'device.lwm2m-security-config.client-certificate' | translate }} - -
- -
- - - - - {{ 'device.lwm2m-security-config.bootstrap-server' | translate }} - - - - - - - - - - - {{ 'device.lwm2m-security-config.lwm2m-server' | translate }} - - - - - - - - -
-
- - - - - - -
-
-
- - -
-
+ + + + + device.lwm2m-security-config.endpoint + + + {{ 'device.lwm2m-security-config.endpoint-required' | translate }} + + + + device.lwm2m-security-config.mode + + + {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} + + + + + {{ 'device.lwm2m-security-config.identity' | translate }} + + + {{ 'device.lwm2m-security-config.identity-required' | translate }} + + + + {{ 'device.lwm2m-security-config.client-key' | translate }} + + {{key.value?.length || 0}}/{{lenMaxKeyClient}} + + {{ 'device.lwm2m-security-config.client-key-required' | translate }} + + + {{ 'device.lwm2m-security-config.client-key-pattern' | translate }} + + + {{ 'device.lwm2m-security-config.client-key-length' | translate: { + count: lenMaxKeyClient + } }} + + + + device.lwm2m-security-config.client-public-key + + device.lwm2m-security-config.client-public-key-hint + + + + +
+ + + + + {{ 'device.lwm2m-security-config.bootstrap-server' | translate }} + + + + + + + + + + + {{ 'device.lwm2m-security-config.lwm2m-server' | translate }} + + + + + + + + +
+
+ + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts index 419576753c..eb920d6190 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts @@ -14,19 +14,20 @@ /// limitations under the License. /// - -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { DialogComponent } from '@shared/components/dialog.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { Router } from '@angular/router'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { TranslateService } from '@ngx-translate/core'; +import { Component, forwardRef, OnDestroy } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; import { - DeviceCredentialsDialogLwm2mData, - getClientSecurityConfig, - JSON_ALL_CONFIG, + getDefaultClientSecurityConfig, + getDefaultServerSecurityConfig, KEY_REGEXP_HEX_DEC, LEN_MAX_PSK, LEN_MAX_PUBLIC_KEY_RPK, @@ -34,48 +35,67 @@ import { Lwm2mSecurityType, Lwm2mSecurityTypeTranslationMap } from '@shared/models/lwm2m-security-config.models'; -import { MatTabChangeEvent } from '@angular/material/tabs'; -import { MatTab } from '@angular/material/tabs/tab'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-security-config-lwm2m', templateUrl: './security-config-lwm2m.component.html', - styleUrls: ['./security-config-lwm2m.component.scss'] + styleUrls: ['./security-config-lwm2m.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SecurityConfigLwm2mComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SecurityConfigLwm2mComponent), + multi: true + } + ] }) -export class SecurityConfigLwm2mComponent extends DialogComponent implements OnInit, OnDestroy { - - private destroy$ = new Subject(); +export class SecurityConfigLwm2mComponent implements ControlValueAccessor, Validator, OnDestroy { lwm2mConfigFormGroup: FormGroup; - title: string; securityConfigLwM2MType = Lwm2mSecurityType; securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType); credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap; - formControlNameJsonAllConfig = JSON_ALL_CONFIG; - jsonAllConfig: Lwm2mSecurityConfigModels; lenMaxKeyClient = LEN_MAX_PSK; - tabPrevious: MatTab; - tabIndexPrevious = 0; - constructor(protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, - public dialogRef: MatDialogRef, - public fb: FormBuilder, - public translate: TranslateService) { - super(store, router, dialogRef); + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(private fb: FormBuilder) { + this.lwm2mConfigFormGroup = this.initLwm2mConfigForm(); } - ngOnInit() { - this.jsonAllConfig = JSON.parse(JSON.stringify(this.data.jsonAllConfig)); - this.lwm2mConfigFormGroup = this.initLwm2mConfigFormGroup(); - this.title = this.translate.instant('device.lwm2m-security-info') + ': ' + this.data.endPoint; - this.lwm2mConfigFormGroup.get('x509').disable(); - this.initClientSecurityConfig(this.lwm2mConfigFormGroup.get('jsonAllConfig').value); - this.registerDisableOnLoadFormControl(this.lwm2mConfigFormGroup.get('securityConfigClientMode')); + writeValue(obj: string) { + if (isDefinedAndNotNull(obj)) { + this.initClientSecurityConfig(JSON.parse(obj)); + } + } + + registerOnChange(fn: any) { + this.propagateChange = fn; + } + + registerOnTouched(fn: any) {} + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.lwm2mConfigFormGroup.disable({emitEvent: false}); + } else { + this.lwm2mConfigFormGroup.enable({emitEvent: false}); + } + } + + validate(): ValidationErrors | null { + return this.lwm2mConfigFormGroup.valid ? null : { + securityConfigLWm2m: false + }; } ngOnDestroy() { @@ -83,175 +103,97 @@ export class SecurityConfigLwm2mComponent extends DialogComponent { - if (jsonAllConfig.client.securityConfigClientMode !== Lwm2mSecurityType.NO_SEC) { - this.lwm2mConfigFormGroup.patchValue(jsonAllConfig.client, {emitEvent: false}); - } - this.securityConfigClientUpdateValidators(jsonAllConfig.client.securityConfigClientMode); + private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void { + this.lwm2mConfigFormGroup.patchValue(config, {emitEvent: false}); + this.securityConfigClientUpdateValidators(config.client.securityConfigClientMode); } private securityConfigClientModeChanged(type: Lwm2mSecurityType): void { - const config = getClientSecurityConfig(type, this.lwm2mConfigFormGroup.get('endPoint').value); + const config = getDefaultClientSecurityConfig(type, this.lwm2mConfigFormGroup.get('client.endpoint').value); switch (type) { case Lwm2mSecurityType.PSK: - config.identity = this.data.endPoint; - config.key = this.lwm2mConfigFormGroup.get('key').value; + config.key = this.lwm2mConfigFormGroup.get('client.key').value; break; case Lwm2mSecurityType.RPK: - config.key = this.lwm2mConfigFormGroup.get('key').value; + config.key = this.lwm2mConfigFormGroup.get('client.key').value; break; } - this.jsonAllConfig.client = config; - this.lwm2mConfigFormGroup.patchValue({ - ...config, - jsonAllConfig: this.jsonAllConfig - }, {emitEvent: false}); + this.lwm2mConfigFormGroup.get('client').patchValue(config, {emitEvent: false}); this.securityConfigClientUpdateValidators(type); } private securityConfigClientUpdateValidators = (mode: Lwm2mSecurityType): void => { switch (mode) { case Lwm2mSecurityType.NO_SEC: + this.setValidatorsNoSecX509(); + this.lwm2mConfigFormGroup.get('client.cert').disable(); + break; case Lwm2mSecurityType.X509: this.setValidatorsNoSecX509(); + this.lwm2mConfigFormGroup.get('client.cert').enable(); break; case Lwm2mSecurityType.PSK: this.lenMaxKeyClient = LEN_MAX_PSK; this.setValidatorsPskRpk(mode); + this.lwm2mConfigFormGroup.get('client.identity').enable(); break; case Lwm2mSecurityType.RPK: this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; this.setValidatorsPskRpk(mode); + this.lwm2mConfigFormGroup.get('client.identity').disable(); break; } - this.lwm2mConfigFormGroup.get('identity').updateValueAndValidity({emitEvent: false}); - this.lwm2mConfigFormGroup.get('key').updateValueAndValidity({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.identity').updateValueAndValidity({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.key').updateValueAndValidity({emitEvent: false}); } private setValidatorsNoSecX509 = (): void => { - this.lwm2mConfigFormGroup.get('identity').setValidators([]); - this.lwm2mConfigFormGroup.get('key').setValidators([]); + this.lwm2mConfigFormGroup.get('client.identity').clearValidators(); + this.lwm2mConfigFormGroup.get('client.key').clearValidators(); + this.lwm2mConfigFormGroup.get('client.identity').disable({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.key').disable({emitEvent: false}); } private setValidatorsPskRpk = (mode: Lwm2mSecurityType): void => { if (mode === Lwm2mSecurityType.PSK) { - this.lwm2mConfigFormGroup.get('identity').setValidators([Validators.required]); + this.lwm2mConfigFormGroup.get('client.identity').setValidators([Validators.required]); } else { - this.lwm2mConfigFormGroup.get('identity').setValidators([]); - } - this.lwm2mConfigFormGroup.get('key').setValidators([Validators.required, - Validators.pattern(KEY_REGEXP_HEX_DEC), - Validators.maxLength(this.lenMaxKeyClient), Validators.minLength(this.lenMaxKeyClient)]); - } - - tabChanged = (tabChangeEvent: MatTabChangeEvent): void => { - if (this.tabIndexPrevious !== tabChangeEvent.index) { - this.upDateValueToJson(); + this.lwm2mConfigFormGroup.get('client.identity').clearValidators(); } - this.tabIndexPrevious = tabChangeEvent.index; + this.lwm2mConfigFormGroup.get('client.key').setValidators([ + Validators.required, + Validators.pattern(KEY_REGEXP_HEX_DEC), + Validators.maxLength(this.lenMaxKeyClient), + Validators.minLength(this.lenMaxKeyClient) + ]); + this.lwm2mConfigFormGroup.get('client.key').enable({emitEvent: false}); + this.lwm2mConfigFormGroup.get('client.cert').disable({emitEvent: false}); } - private upDateValueToJson(): void { - switch (this.tabIndexPrevious) { - case 0: - this.upDateValueToJsonTab0(); - break; - case 1: - this.upDateValueToJsonTab1(); - break; - } - } - - private upDateValueToJsonTab0 = (): void => { - if (this.lwm2mConfigFormGroup.get('identity').dirty && this.lwm2mConfigFormGroup.get('identity').valid || - this.lwm2mConfigFormGroup.get('key').dirty && this.lwm2mConfigFormGroup.get('key').valid) { - this.updateBootstrapSettings(); - this.upDateJsonAllConfig(); - } - } - - private upDateValueToJsonTab1 = (): void => { - const bootstrap = this.lwm2mConfigFormGroup.get('bootstrapServer').value; - if (bootstrap !== null - && this.lwm2mConfigFormGroup.get('bootstrapServer').dirty - && this.lwm2mConfigFormGroup.get('bootstrapServer').valid) { - this.jsonAllConfig.bootstrap.bootstrapServer = bootstrap; - this.upDateJsonAllConfig(); - } - const serverConfig = this.lwm2mConfigFormGroup.get('lwm2mServer').value; - if (serverConfig !== null - && this.lwm2mConfigFormGroup.get('lwm2mServer').dirty - && this.lwm2mConfigFormGroup.get('lwm2mServer').valid) { - this.jsonAllConfig.bootstrap.lwm2mServer = serverConfig; - this.upDateJsonAllConfig(); - } - } - - private updateBootstrapSettings() { - const securityMode = 'securityMode'; - this.jsonAllConfig.client.identity = this.lwm2mConfigFormGroup.get('identity').value; - this.jsonAllConfig.client.key = this.lwm2mConfigFormGroup.get('key').value; - if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === Lwm2mSecurityType.PSK) { - this.jsonAllConfig.bootstrap.bootstrapServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity; - this.jsonAllConfig.bootstrap.bootstrapServer.clientSecretKey = this.jsonAllConfig.client.key; - this.lwm2mConfigFormGroup.get('bootstrapServer').patchValue(this.jsonAllConfig.bootstrap.bootstrapServer, {emitEvent: false}); - } - if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === Lwm2mSecurityType.PSK) { - this.jsonAllConfig.bootstrap.lwm2mServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity; - this.jsonAllConfig.bootstrap.lwm2mServer.clientSecretKey = this.jsonAllConfig.client.key; - this.lwm2mConfigFormGroup.get('lwm2mServer').patchValue(this.jsonAllConfig.bootstrap.lwm2mServer, {emitEvent: false}); - } - } - - private upDateJsonAllConfig = (): void => { - this.lwm2mConfigFormGroup.patchValue({ - jsonAllConfig: this.jsonAllConfig - }, {emitEvent: false}); - } - - private initLwm2mConfigFormGroup = (): FormGroup => { - if (this.jsonAllConfig.client.securityConfigClientMode === Lwm2mSecurityType.PSK) { - this.data.endPoint = this.jsonAllConfig.client.endpoint; - } + private initLwm2mConfigForm = (): FormGroup => { const formGroup = this.fb.group({ - securityConfigClientMode: [this.jsonAllConfig.client.securityConfigClientMode], - identity: [''], - key: [''], - x509: [false], - bootstrapServer: [this.jsonAllConfig.bootstrap.bootstrapServer], - lwm2mServer: [this.jsonAllConfig.bootstrap.lwm2mServer], - endPoint: [this.data.endPoint], - jsonAllConfig: [this.jsonAllConfig] + client: this.fb.group({ + endpoint: ['', Validators.required], + securityConfigClientMode: [Lwm2mSecurityType.NO_SEC], + identity: [{value: '', disabled: true}], + key: [{value: '', disabled: true}], + cert: [{value: '', disabled: true}] + }), + bootstrap: this.fb.group({ + bootstrapServer: [getDefaultServerSecurityConfig()], + lwm2mServer: [getDefaultServerSecurityConfig()] + }) }); - formGroup.get('securityConfigClientMode').valueChanges.pipe( + formGroup.get('client.securityConfigClientMode').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((type) => { this.securityConfigClientModeChanged(type); }); - formGroup.get('endPoint').valueChanges.pipe( + formGroup.valueChanges.pipe( takeUntil(this.destroy$) - ).subscribe((endpoint) => { - if (formGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) { - this.jsonAllConfig.client.endpoint = endpoint; - this.upDateJsonAllConfig(); - } + ).subscribe((value) => { + this.propagateChange(JSON.stringify(value)); }); return formGroup; } - - save(): void { - this.upDateValueToJson(); - this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value.split('\'').join(''); - this.data.jsonAllConfig = this.jsonAllConfig; - if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) { - this.data.endPoint = this.data.jsonAllConfig.client.identity; - } - this.dialogRef.close(this.data); - } - - cancel(): void { - this.dialogRef.close(undefined); - } } - - diff --git a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts index de0f2dc8a5..d3dd6d3ff9 100644 --- a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts +++ b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts @@ -14,21 +14,12 @@ /// limitations under the License. /// -export const JSON_ALL_CONFIG = 'jsonAllConfig'; -export const END_POINT = 'endPoint'; -export const DEFAULT_END_POINT = 'default_client_lwm2m_end_point_no_sec'; export const LEN_MAX_PSK = 64; export const LEN_MAX_PRIVATE_KEY = 134; export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_X509 = 3000; export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; - -export interface DeviceCredentialsDialogLwm2mData { - jsonAllConfig?: Lwm2mSecurityConfigModels; - endPoint?: string; -} - export enum Lwm2mSecurityType { PSK = 'PSK', RPK = 'RPK', @@ -48,9 +39,9 @@ export const Lwm2mSecurityTypeTranslationMap = new Map - p.hasOwnProperty('client') && - isClientSecurityConfigType(p.client) && - p.hasOwnProperty('bootstrap') && - isBootstrapSecurityConfig(p.bootstrap); - -const isClientSecurityConfigType = (p: any): boolean => - p.hasOwnProperty('securityConfigClientMode') && - p.hasOwnProperty('endpoint') && - p.hasOwnProperty('identity') && - p.hasOwnProperty('key') && - p.hasOwnProperty('x509'); - -const isBootstrapSecurityConfig = (p: any): boolean => - p.hasOwnProperty('bootstrapServer') && - isServerSecurityConfig(p.bootstrapServer) && - p.hasOwnProperty('lwm2mServer') && - isServerSecurityConfig(p.lwm2mServer); - -const isServerSecurityConfig = (p: any): boolean => - p.hasOwnProperty('securityMode') && - p.hasOwnProperty('clientPublicKeyOrId') && - p.hasOwnProperty('clientSecretKey'); - -export function validateSecurityConfig(config: string): boolean { - try { - const securityConfig = JSON.parse(config); - return isSecurityConfigModels(securityConfig); - } catch (e) { - return false; - } -} - - diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f664dc27a7..d8a21107bc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -917,15 +917,7 @@ "access-token-invalid": "Access token length must be from 1 to 20 characters.", "rsa-key": "RSA public key", "rsa-key-required": "RSA public key is required.", - "lwm2m-key": "LwM2M Security config key", - "lwm2m-key-required": "LwM2M Security config key is required.", "lwm2m-value": "LwM2M Security config", - "lwm2m-value-required": "LwM2M Security config value is required.", - "lwm2m-value-format-error": "Security config value must be in LwM2M Security config format.", - "lwm2m-endpoint": "Client endpoint/identity", - "lwm2m-security-info": "Security Config Info", - "lwm2m-value-edit": "Edit Security config", - "lwm2m-credentials-value-tip": "Edit security config json editor", "lwm2m-security-config": { "identity": "Client Identity", "identity-required": "Client Identity is required.", @@ -949,7 +941,9 @@ "client-secret-key-required": "Client Secret Key is required.", "client-secret-key-pattern": "Client Secret Key must be hexadecimal format.", "client-secret-key-length": "Client Secret Key must be {{ count }} characters.", - "config-json-tab": "Json Client Security Config" + "config-json-tab": "Json Client Security Config", + "client-public-key": "Client public key", + "client-public-key-hint": "If client public key is empty, the trusted certificate will be used" }, "client-id": "Client ID", "client-id-pattern": "Contains invalid character.", From 4cd59674ee3ae844754e38398a5ba048d43b612e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 18 May 2021 17:30:01 +0300 Subject: [PATCH 10/13] refactored LwM2M client credentials for the new UI --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 2 +- .../lwm2m/NoSecLwM2MIntegrationTest.java | 8 +-- .../lwm2m/X509LwM2MIntegrationTest.java | 22 ++++---- .../AbstractLwM2MClientCredentials.java} | 21 +++---- .../device/credentials/lwm2m}/HasKey.java | 8 ++- .../lwm2m/LwM2MClientCredentials.java} | 19 +++---- .../credentials/lwm2m/LwM2MSecurityMode.java | 20 +++++++ .../lwm2m/NoSecClientCredentials.java | 24 ++++++++ .../lwm2m/PSKClientCredentials.java} | 16 +++--- .../lwm2m/RPKClientCredentials.java | 24 ++++++++ .../lwm2m/X509ClientCredentials.java} | 16 +++--- ...LwM2mCredentialsSecurityInfoValidator.java | 22 ++++---- .../TbLwM2MDtlsCertificateVerifier.java | 9 ++- .../secure/credentials/LwM2MCredentials.java | 3 +- .../X509ClientCredentialsConfig.java | 36 ------------ .../device/DeviceCredentialsServiceImpl.java | 55 +++++++++++++++---- 16 files changed, 185 insertions(+), 120 deletions(-) rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java} (60%) rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m}/HasKey.java (78%) rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java} (58%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java} (65%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java rename common/{transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java => data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java} (65%) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 46ad3c1c34..624a1d6a72 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -151,7 +151,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { parameterSpec); KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); -// // Get keys + // Get keys serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 72f4d041f3..c82845f20c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -36,7 +36,7 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; import java.util.Collections; import java.util.List; @@ -112,10 +112,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); - deviceCredentials.setCredentialsId(deviceAEndpoint); - LwM2MCredentials noSecCredentials = new LwM2MCredentials(); - noSecCredentials.setClient(new NoSecClientCredentialsConfig()); + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); + clientCredentials.setEndpoint(deviceAEndpoint); + noSecCredentials.setClient(clientCredentials); deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); return device; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java index 06541e5c56..18749cfee5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -37,7 +38,6 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; import java.util.Collections; import java.util.List; @@ -101,7 +101,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { private final String serverUri = "coaps://localhost:" + port; @NotNull - private Device createDevice(String credentialsId, X509ClientCredentialsConfig credentialsConfig) throws Exception { + private Device createDevice(X509ClientCredentials clientCredentials) throws Exception { Device device = new Device(); device.setName("Device A"); device.setDeviceProfileId(deviceProfile.getId()); @@ -114,13 +114,11 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); - deviceCredentials.setCredentialsId(credentialsId); + LwM2MCredentials credentials = new LwM2MCredentials(); - LwM2MCredentials X509Credentials = new LwM2MCredentials(); + credentials.setClient(clientCredentials); - X509Credentials.setClient(credentialsConfig); - - deviceCredentials.setCredentialsValue(JacksonUtil.toString(X509Credentials)); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); return device; } @@ -128,8 +126,9 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @Test public void testConnectAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); - - Device device = createDevice(endpoint, new X509ClientCredentialsConfig(null, null)); + X509ClientCredentials credentials = new X509ClientCredentials(); + credentials.setEndpoint(endpoint); + Device device = createDevice(credentials); SingleEntityFilter sef = new SingleEntityFilter(); sef.setSingleEntity(device.getId()); @@ -166,7 +165,10 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @Test public void testConnectWithCertAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); - Device device = createDevice(null, new X509ClientCredentialsConfig(SslUtil.getCertificateString(clientX509CertNotTrusted), endpoint)); + X509ClientCredentials credentials = new X509ClientCredentials(); + credentials.setEndpoint(endpoint); + credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); + Device device = createDevice(credentials); SingleEntityFilter sef = new SingleEntityFilter(); sef.setSingleEntity(device.getId()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java similarity index 60% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java index 8de85ce72d..66eb523209 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentials.java @@ -13,20 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import lombok.Data; -import org.eclipse.leshan.core.SecurityMode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; -import static org.eclipse.leshan.core.SecurityMode.PSK; - -@Data -public class PSKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { - private String identity; +@Getter +@Setter +@NoArgsConstructor +public abstract class AbstractLwM2MClientCredentials implements LwM2MClientCredentials { private String endpoint; - - @Override - public SecurityMode getSecurityConfigClientMode() { - return PSK; - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java similarity index 78% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java index 65be16bfd6..ec62765298 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import org.eclipse.leshan.core.util.Hex; +import lombok.SneakyThrows; +import org.apache.commons.codec.binary.Hex; -public class HasKey { +public abstract class HasKey extends AbstractLwM2MClientCredentials { private byte[] key; + @SneakyThrows public void setKey(String key) { if (key != null) { this.key = Hex.decodeHex(key.toLowerCase().toCharArray()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java similarity index 58% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java index 65f027a849..adf0c2ae62 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MClientCredentials.java @@ -13,25 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.eclipse.leshan.core.SecurityMode; -@JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "securityConfigClientMode") @JsonSubTypes({ - @JsonSubTypes.Type(value = NoSecClientCredentialsConfig.class, name = "NO_SEC"), - @JsonSubTypes.Type(value = PSKClientCredentialsConfig.class, name = "PSK"), - @JsonSubTypes.Type(value = RPKClientCredentialsConfig.class, name = "RPK"), - @JsonSubTypes.Type(value = X509ClientCredentialsConfig.class, name = "X509")}) -public interface LwM2MClientCredentialsConfig { + @JsonSubTypes.Type(value = NoSecClientCredentials.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKClientCredentials.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKClientCredentials.class, name = "RPK"), + @JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509")}) +public interface LwM2MClientCredentials { @JsonIgnore - SecurityMode getSecurityConfigClientMode(); + LwM2MSecurityMode getSecurityConfigClientMode(); + + String getEndpoint(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java new file mode 100644 index 0000000000..802fcd7efe --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/LwM2MSecurityMode.java @@ -0,0 +1,20 @@ +/** + * 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.common.data.device.credentials.lwm2m; + +public enum LwM2MSecurityMode { + PSK, RPK, X509, NO_SEC; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java new file mode 100644 index 0000000000..7e54a9b63d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/NoSecClientCredentials.java @@ -0,0 +1,24 @@ +/** + * 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.common.data.device.credentials.lwm2m; + +public class NoSecClientCredentials extends AbstractLwM2MClientCredentials { + + @Override + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.NO_SEC; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java similarity index 65% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java index 03933972c3..2566af7da8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import org.eclipse.leshan.core.SecurityMode; +import lombok.Getter; +import lombok.Setter; -import static org.eclipse.leshan.core.SecurityMode.NO_SEC; - -public class NoSecClientCredentialsConfig implements LwM2MClientCredentialsConfig { +@Getter +@Setter +public class PSKClientCredentials extends HasKey { + private String identity; @Override - public SecurityMode getSecurityConfigClientMode() { - return NO_SEC; + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.PSK; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java new file mode 100644 index 0000000000..fe329558f8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java @@ -0,0 +1,24 @@ +/** + * 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.common.data.device.credentials.lwm2m; + +public class RPKClientCredentials extends HasKey { + + @Override + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.RPK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java similarity index 65% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java index 025c8b3b10..712dcab5eb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/X509ClientCredentials.java @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.secure.credentials; +package org.thingsboard.server.common.data.device.credentials.lwm2m; -import org.eclipse.leshan.core.SecurityMode; +import lombok.Getter; +import lombok.Setter; -import static org.eclipse.leshan.core.SecurityMode.RPK; - -public class RPKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { +@Getter +@Setter +public class X509ClientCredentials extends AbstractLwM2MClientCredentials { + private String cert; @Override - public SecurityMode getSecurityConfigClientMode() { - return RPK; + public LwM2MSecurityMode getSecurityConfigClientMode() { + return LwM2MSecurityMode.X509; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index cd78cd4c1e..8d90b2a86b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -17,21 +17,21 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.secure.credentials.PSKClientCredentialsConfig; -import org.thingsboard.server.transport.lwm2m.secure.credentials.RPKClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -97,8 +97,8 @@ public class LwM2mCredentialsSecurityInfoValidator { if (credentials != null) { if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { result.setBootstrapCredentialConfig(credentials.getBootstrap()); - if (SecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { - PSKClientCredentialsConfig pskClientConfig = (PSKClientCredentialsConfig) credentials.getClient(); + if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { + PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient(); endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint; } result.setEndpoint(endpoint); @@ -130,8 +130,8 @@ public class LwM2mCredentialsSecurityInfoValidator { result.setSecurityMode(NO_SEC); } - private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { - PSKClientCredentialsConfig pskConfig = (PSKClientCredentialsConfig) clientCredentialsConfig; + private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig; if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { try { if (pskConfig.getKey() != null && pskConfig.getKey().length > 0) { @@ -149,8 +149,8 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { - RPKClientCredentialsConfig rpkConfig = (RPKClientCredentialsConfig) clientCredentialsConfig; + private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig; try { if (rpkConfig.getKey() != null) { PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey()); @@ -164,7 +164,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityMode(X509); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index d2542192bf..e83532d2cc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.secure; -import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.CertPathUtil; @@ -30,12 +29,12 @@ 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.eclipse.leshan.core.SecurityMode; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -44,7 +43,7 @@ import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; @@ -140,10 +139,10 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); - if(!credentials.getClient().getSecurityConfigClientMode().equals(SecurityMode.X509)){ + if(!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)){ continue; } - X509ClientCredentialsConfig config = (X509ClientCredentialsConfig) credentials.getClient(); + X509ClientCredentials config = (X509ClientCredentials) credentials.getClient(); String certBody = config.getCert(); String endpoint = config.getEndpoint(); if (strCert.equals(certBody)) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java index 09c27f0e42..bbc733b40b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java @@ -16,10 +16,11 @@ package org.thingsboard.server.transport.lwm2m.secure.credentials; import lombok.Data; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @Data public class LwM2MCredentials { - private LwM2MClientCredentialsConfig client; + private LwM2MClientCredentials client; private LwM2MBootstrapConfig bootstrap; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java deleted file mode 100644 index 563224fed2..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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.credentials; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.eclipse.leshan.core.SecurityMode; - -import static org.eclipse.leshan.core.SecurityMode.X509; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { - private String cert; - private String endpoint; - - @Override - public SecurityMode getSecurityConfigClientMode() { - return X509; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 426e7025c0..5349df1fc7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -16,7 +16,6 @@ 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.hibernate.exception.ConstraintViolationException; @@ -28,6 +27,9 @@ import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -133,7 +135,6 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); } - private void formatCertData(DeviceCredentials deviceCredentials) { String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); String sha3Hash = EncryptionUtil.getSha3Hash(cert); @@ -142,18 +143,48 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen } private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { - ObjectNode json = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), ObjectNode.class); - JsonNode client = json.get("client"); - if (client != null && client.get("securityConfigClientMode").asText().equals("X509") && client.has("cert")) { - JsonNode certJson = client.get("cert"); - if (!certJson.isNull()) { - String cert = EncryptionUtil.trimNewLines(certJson.asText()); - String sha3Hash = EncryptionUtil.getSha3Hash(cert); - deviceCredentials.setCredentialsId(sha3Hash); - ((ObjectNode) client).put("cert", cert); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(json)); + LwM2MClientCredentials clientCredentials; + ObjectNode json; + try { + json = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), ObjectNode.class); + if (json == null) { + throw new IllegalArgumentException(); } + clientCredentials = JacksonUtil.convertValue(json.get("client"), LwM2MClientCredentials.class); + if (clientCredentials == null) { + throw new IllegalArgumentException(); + } + } catch (IllegalArgumentException e) { + throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); + } + + String credentialsId; + + switch (clientCredentials.getSecurityConfigClientMode()) { + case NO_SEC: + case RPK: + credentialsId = clientCredentials.getEndpoint(); + break; + case PSK: + credentialsId = ((PSKClientCredentials) clientCredentials).getIdentity(); + break; + case X509: + X509ClientCredentials x509Config = (X509ClientCredentials) clientCredentials; + if (x509Config.getCert() != null) { + String cert = EncryptionUtil.trimNewLines(x509Config.getCert()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + x509Config.setCert(cert); + ((ObjectNode) json.get("client")).put("cert", cert); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(json)); + credentialsId = sha3Hash; + } else { + credentialsId = x509Config.getEndpoint(); + } + break; + default: + throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); } + deviceCredentials.setCredentialsId(credentialsId); } @Override From ab5c803a68f50c669a42221d3558ec3476d61e1a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 18 May 2021 17:40:00 +0300 Subject: [PATCH 11/13] refactored --- .../server/dao/device/DeviceCredentialsServiceImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 5349df1fc7..3cce5e1506 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -158,7 +158,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); } - String credentialsId; + String credentialsId = null; switch (clientCredentials.getSecurityConfigClientMode()) { case NO_SEC: @@ -181,8 +181,9 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen credentialsId = x509Config.getEndpoint(); } break; - default: - throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); + } + if (credentialsId == null) { + throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); } deviceCredentials.setCredentialsId(credentialsId); } From 9d8a1f0dc5b231e6e879352c612c29ef1c2e2d1c Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 26 May 2021 19:01:21 +0300 Subject: [PATCH 12/13] Merge with master --- application/src/test/resources/application-test.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 6638504b2f..cd9a981aed 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,2 +1,3 @@ transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks -transport.lwm2m.security.key_store_password=server \ No newline at end of file +transport.lwm2m.security.key_store_password=server +edges.enabled=true \ No newline at end of file From e3fa441d6bc874b8471431327698f247c2952499 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 26 May 2021 10:58:47 +0300 Subject: [PATCH 13/13] Added redis session store --- .../TbL2M2MDtlsSessionInMemoryStore.java | 4 -- .../store/TbLwM2MDtlsSessionRedisStore.java | 66 +++++++++++++++++++ .../server/store/TbLwM2MDtlsSessionStore.java | 3 - .../server/store/TbLwM2mStoreFactory.java | 6 ++ 4 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java index af9d9ee89a..f5aa7d2e5c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java @@ -15,14 +15,10 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; import java.util.concurrent.ConcurrentHashMap; -@Component -@TbLwM2mTransportComponent public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore { private final ConcurrentHashMap store = new ConcurrentHashMap<>(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java new file mode 100644 index 0000000000..b1c4b85e2a --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java @@ -0,0 +1,66 @@ +/** + * 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.server.store; + +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { + + private static final String SESSION_EP = "SESSION#EP#"; + RedisConnectionFactory connectionFactory; + + public TbLwM2MDtlsSessionRedisStore(RedisConnectionFactory redisConnectionFactory) { + this.connectionFactory = redisConnectionFactory; + } + + @Override + public void put(String endpoint, TbX509DtlsSessionInfo msg) { + try (var c = connectionFactory.getConnection()) { + var msgJson = JacksonUtil.convertValue(msg, JsonNode.class); + if (msgJson != null) { + c.set(getKey(endpoint), msgJson.toString().getBytes()); + } else { + throw new RuntimeException("Problem with serialization of message: " + msg.toString()); + } + } + } + + @Override + public TbX509DtlsSessionInfo get(String endpoint) { + try (var c = connectionFactory.getConnection()) { + var data = c.get(getKey(endpoint)); + if (data != null) { + return JacksonUtil.fromString(new String(data), TbX509DtlsSessionInfo.class); + } else { + return null; + } + } + } + + @Override + public void remove(String endpoint) { + try (var c = connectionFactory.getConnection()) { + c.del(getKey(endpoint)); + } + } + + private byte[] getKey(String endpoint) { + return (SESSION_EP + endpoint).getBytes(); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java index 0c543d1025..3d5181232f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java @@ -24,9 +24,6 @@ public interface TbLwM2MDtlsSessionStore { TbX509DtlsSessionInfo get(String endpoint); - void remove(String endpoint); - //TODO: add way to delete the session by endpoint. - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java index 164a78b0a3..2c0c96212f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java @@ -60,4 +60,10 @@ public class TbLwM2mStoreFactory { new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new InMemorySecurityStore()); } + @Bean + private TbLwM2MDtlsSessionStore sessionStore() { + return redisConfiguration.isPresent() && useRedis ? + new TbLwM2MDtlsSessionRedisStore(redisConfiguration.get().redisConnectionFactory()) : new TbL2M2MDtlsSessionInMemoryStore(); + } + }