Commit 552a1efb233a8b79d16d71d69361854c80a1f648

Authored by Vladyslav_Prykhodko
2 parents 53c51953 d628624a

Merge branch 'feature/lwm2m-certificate-verifier' of github.com:thingsboard/thin…

…gsboard into feature/lwm2m-certificate-verifier
Showing 16 changed files with 413 additions and 26 deletions
@@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m; @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m;
17 17
18 import com.fasterxml.jackson.core.type.TypeReference; 18 import com.fasterxml.jackson.core.type.TypeReference;
19 import org.apache.commons.io.IOUtils; 19 import org.apache.commons.io.IOUtils;
  20 +import org.eclipse.leshan.core.util.Hex;
20 import org.junit.After; 21 import org.junit.After;
21 import org.junit.Assert; 22 import org.junit.Assert;
22 import org.junit.Before; 23 import org.junit.Before;
@@ -35,6 +36,23 @@ import org.thingsboard.server.controller.AbstractWebsocketTest; @@ -35,6 +36,23 @@ import org.thingsboard.server.controller.AbstractWebsocketTest;
35 import org.thingsboard.server.controller.TbTestWebSocketClient; 36 import org.thingsboard.server.controller.TbTestWebSocketClient;
36 import org.thingsboard.server.dao.service.DaoSqlTest; 37 import org.thingsboard.server.dao.service.DaoSqlTest;
37 38
  39 +import java.io.IOException;
  40 +import java.io.InputStream;
  41 +import java.math.BigInteger;
  42 +import java.security.AlgorithmParameters;
  43 +import java.security.GeneralSecurityException;
  44 +import java.security.KeyFactory;
  45 +import java.security.KeyStore;
  46 +import java.security.PrivateKey;
  47 +import java.security.PublicKey;
  48 +import java.security.cert.Certificate;
  49 +import java.security.cert.X509Certificate;
  50 +import java.security.spec.ECGenParameterSpec;
  51 +import java.security.spec.ECParameterSpec;
  52 +import java.security.spec.ECPoint;
  53 +import java.security.spec.ECPrivateKeySpec;
  54 +import java.security.spec.ECPublicKeySpec;
  55 +import java.security.spec.KeySpec;
38 import java.util.Base64; 56 import java.util.Base64;
39 import java.util.concurrent.Executors; 57 import java.util.concurrent.Executors;
40 import java.util.concurrent.ScheduledExecutorService; 58 import java.util.concurrent.ScheduledExecutorService;
@@ -46,6 +64,114 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { @@ -46,6 +64,114 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
46 protected ScheduledExecutorService executor; 64 protected ScheduledExecutorService executor;
47 protected TbTestWebSocketClient wsClient; 65 protected TbTestWebSocketClient wsClient;
48 66
  67 + protected final PublicKey clientPublicKey; // client public key used for RPK
  68 + protected final PrivateKey clientPrivateKey; // client private key used for RPK
  69 + protected final PublicKey serverPublicKey; // server public key used for RPK
  70 + protected final PrivateKey serverPrivateKey; // server private key used for RPK
  71 +
  72 + // client private key used for X509
  73 + protected final PrivateKey clientPrivateKeyFromCert;
  74 + // server private key used for X509
  75 + protected final PrivateKey serverPrivateKeyFromCert;
  76 + // client certificate signed by rootCA with a good CN (CN start by leshan_integration_test)
  77 + protected final X509Certificate clientX509Cert;
  78 + // client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test)
  79 + protected final X509Certificate clientX509CertWithBadCN;
  80 + // client certificate self-signed with a good CN (CN start by leshan_integration_test)
  81 + protected final X509Certificate clientX509CertSelfSigned;
  82 + // client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test)
  83 + protected final X509Certificate clientX509CertNotTrusted;
  84 + // server certificate signed by rootCA
  85 + protected final X509Certificate serverX509Cert;
  86 + // self-signed server certificate
  87 + protected final X509Certificate serverX509CertSelfSigned;
  88 + // rootCA used by the server
  89 + protected final X509Certificate rootCAX509Cert;
  90 + // certificates trustedby the server (should contain rootCA)
  91 + protected final Certificate[] trustedCertificates = new Certificate[1];
  92 +
  93 + public AbstractLwM2MIntegrationTest() {
  94 +// create client credentials
  95 + try {
  96 + // Get point values
  97 + byte[] publicX = Hex
  98 + .decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray());
  99 + byte[] publicY = Hex
  100 + .decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray());
  101 + byte[] privateS = Hex
  102 + .decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray());
  103 +
  104 + // Get Elliptic Curve Parameter spec for secp256r1
  105 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  106 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  107 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  108 +
  109 + // Create key specs
  110 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  111 + parameterSpec);
  112 + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
  113 +
  114 + // Get keys
  115 + clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  116 + clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
  117 +
  118 + // Get certificates from key store
  119 + char[] clientKeyStorePwd = "client".toCharArray();
  120 + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
  121 + try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) {
  122 + clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd);
  123 + }
  124 +
  125 + clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd);
  126 + clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client");
  127 + clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn");
  128 + clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed");
  129 + clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted");
  130 + } catch (GeneralSecurityException | IOException e) {
  131 + throw new RuntimeException(e);
  132 + }
  133 +
  134 + // create server credentials
  135 + try {
  136 + // Get point values
  137 + byte[] publicX = Hex
  138 + .decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray());
  139 + byte[] publicY = Hex
  140 + .decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray());
  141 + byte[] privateS = Hex
  142 + .decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray());
  143 +
  144 + // Get Elliptic Curve Parameter spec for secp256r1
  145 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  146 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  147 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  148 +
  149 + // Create key specs
  150 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  151 + parameterSpec);
  152 + KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);
  153 +
  154 +// // Get keys
  155 + serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  156 + serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
  157 +
  158 + // Get certificates from key store
  159 + char[] serverKeyStorePwd = "server".toCharArray();
  160 + KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
  161 + try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) {
  162 + serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd);
  163 + }
  164 +
  165 + serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd);
  166 + rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA");
  167 + serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server");
  168 + serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed");
  169 + trustedCertificates[0] = rootCAX509Cert;
  170 + } catch (GeneralSecurityException | IOException e) {
  171 + throw new RuntimeException(e);
  172 + }
  173 + }
  174 +
49 @Before 175 @Before
50 public void beforeTest() throws Exception { 176 public void beforeTest() throws Exception {
51 executor = Executors.newScheduledThreadPool(10); 177 executor = Executors.newScheduledThreadPool(10);
@@ -60,7 +186,8 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { @@ -60,7 +186,8 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
60 lwModel.setTenantId(tenantId); 186 lwModel.setTenantId(tenantId);
61 byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName)); 187 byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName));
62 lwModel.setData(Base64.getEncoder().encodeToString(bytes)); 188 lwModel.setData(Base64.getEncoder().encodeToString(bytes));
63 - lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>(){}); 189 + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>() {
  190 + });
64 Assert.assertNotNull(lwModel); 191 Assert.assertNotNull(lwModel);
65 } 192 }
66 wsClient = buildAndConnectWebSocketClient(); 193 wsClient = buildAndConnectWebSocketClient();
@@ -69,7 +196,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { @@ -69,7 +196,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
69 protected void createDeviceProfile(String transportConfiguration) throws Exception { 196 protected void createDeviceProfile(String transportConfiguration) throws Exception {
70 deviceProfile = new DeviceProfile(); 197 deviceProfile = new DeviceProfile();
71 198
72 - deviceProfile.setName("LwM2M No Security"); 199 + deviceProfile.setName("LwM2M");
73 deviceProfile.setType(DeviceProfileType.DEFAULT); 200 deviceProfile.setType(DeviceProfileType.DEFAULT);
74 deviceProfile.setTenantId(tenantId); 201 deviceProfile.setTenantId(tenantId);
75 deviceProfile.setTransportType(DeviceTransportType.LWM2M); 202 deviceProfile.setTransportType(DeviceTransportType.LWM2M);
@@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
15 */ 15 */
16 package org.thingsboard.server.transport.lwm2m; 16 package org.thingsboard.server.transport.lwm2m;
17 17
  18 +import org.eclipse.californium.core.network.config.NetworkConfig;
  19 +import org.eclipse.leshan.client.object.Security;
18 import org.jetbrains.annotations.NotNull; 20 import org.jetbrains.annotations.NotNull;
19 import org.junit.Assert; 21 import org.junit.Assert;
20 import org.junit.Test; 22 import org.junit.Test;
@@ -39,6 +41,7 @@ import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCred @@ -39,6 +41,7 @@ import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCred
39 import java.util.Collections; 41 import java.util.Collections;
40 import java.util.List; 42 import java.util.List;
41 43
  44 +import static org.eclipse.leshan.client.object.Security.noSec;
42 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 45 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
43 46
44 public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { 47 public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
@@ -91,6 +94,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -91,6 +94,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
91 " }\n" + 94 " }\n" +
92 "}"; 95 "}";
93 96
  97 + private final int port = 5685;
  98 + private final Security security = noSec("coap://localhost:" + port, 123);
  99 + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port));
  100 +
94 @NotNull 101 @NotNull
95 private Device createDevice(String deviceAEndpoint) throws Exception { 102 private Device createDevice(String deviceAEndpoint) throws Exception {
96 Device device = new Device(); 103 Device device = new Device();
@@ -138,7 +145,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -138,7 +145,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
138 145
139 wsClient.registerWaitForUpdate(); 146 wsClient.registerWaitForUpdate();
140 LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); 147 LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint);
141 - client.init(); 148 + client.init(security, coapConfig);
142 String msg = wsClient.waitForUpdate(); 149 String msg = wsClient.waitForUpdate();
143 150
144 EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); 151 EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m;
  17 +
  18 +import org.eclipse.californium.core.network.config.NetworkConfig;
  19 +import org.eclipse.leshan.client.object.Security;
  20 +import org.jetbrains.annotations.NotNull;
  21 +import org.junit.Assert;
  22 +import org.junit.Test;
  23 +import org.thingsboard.common.util.JacksonUtil;
  24 +import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.query.EntityData;
  26 +import org.thingsboard.server.common.data.query.EntityDataPageLink;
  27 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  28 +import org.thingsboard.server.common.data.query.EntityKey;
  29 +import org.thingsboard.server.common.data.query.EntityKeyType;
  30 +import org.thingsboard.server.common.data.query.SingleEntityFilter;
  31 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  32 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  33 +import org.thingsboard.server.common.transport.util.SslUtil;
  34 +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
  35 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  36 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
  37 +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
  38 +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
  39 +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
  40 +import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig;
  41 +
  42 +import java.util.Collections;
  43 +import java.util.List;
  44 +
  45 +import static org.eclipse.leshan.client.object.Security.x509;
  46 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  47 +
  48 +public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
  49 +
  50 + protected final String TRANSPORT_CONFIGURATION = "{\n" +
  51 + " \"type\": \"LWM2M\",\n" +
  52 + " \"observeAttr\": {\n" +
  53 + " \"keyName\": {\n" +
  54 + " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
  55 + " },\n" +
  56 + " \"observe\": [],\n" +
  57 + " \"attribute\": [\n" +
  58 + " ],\n" +
  59 + " \"telemetry\": [\n" +
  60 + " \"/3_1.0/0/9\"\n" +
  61 + " ],\n" +
  62 + " \"attributeLwm2m\": {}\n" +
  63 + " },\n" +
  64 + " \"bootstrap\": {\n" +
  65 + " \"servers\": {\n" +
  66 + " \"binding\": \"UQ\",\n" +
  67 + " \"shortId\": 123,\n" +
  68 + " \"lifetime\": 300,\n" +
  69 + " \"notifIfDisabled\": true,\n" +
  70 + " \"defaultMinPeriod\": 1\n" +
  71 + " },\n" +
  72 + " \"lwm2mServer\": {\n" +
  73 + " \"host\": \"localhost\",\n" +
  74 + " \"port\": 5686,\n" +
  75 + " \"serverId\": 123,\n" +
  76 + " \"serverPublicKey\": \"\",\n" +
  77 + " \"bootstrapServerIs\": false,\n" +
  78 + " \"clientHoldOffTime\": 1,\n" +
  79 + " \"bootstrapServerAccountTimeout\": 0\n" +
  80 + " },\n" +
  81 + " \"bootstrapServer\": {\n" +
  82 + " \"host\": \"localhost\",\n" +
  83 + " \"port\": 5687,\n" +
  84 + " \"serverId\": 111,\n" +
  85 + " \"securityMode\": \"NO_SEC\",\n" +
  86 + " \"serverPublicKey\": \"\",\n" +
  87 + " \"bootstrapServerIs\": true,\n" +
  88 + " \"clientHoldOffTime\": 1,\n" +
  89 + " \"bootstrapServerAccountTimeout\": 0\n" +
  90 + " }\n" +
  91 + " },\n" +
  92 + " \"clientLwM2mSettings\": {\n" +
  93 + " \"clientOnlyObserveAfterConnect\": 1\n" +
  94 + " }\n" +
  95 + "}";
  96 +
  97 +
  98 + private final int port = 5686;
  99 + private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port));
  100 + private final String endpoint = "deviceAEndpoint";
  101 + private final String serverUri = "coaps://localhost:" + port;
  102 +
  103 + @NotNull
  104 + private Device createDevice(String credentialsId, X509ClientCredentialsConfig credentialsConfig) throws Exception {
  105 + Device device = new Device();
  106 + device.setName("Device A");
  107 + device.setDeviceProfileId(deviceProfile.getId());
  108 + device.setTenantId(tenantId);
  109 + device = doPost("/api/device", device, Device.class);
  110 + Assert.assertNotNull(device);
  111 +
  112 + DeviceCredentials deviceCredentials =
  113 + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  114 + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId());
  115 + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
  116 +
  117 + deviceCredentials.setCredentialsId(credentialsId);
  118 +
  119 + LwM2MCredentials X509Credentials = new LwM2MCredentials();
  120 +
  121 + X509Credentials.setClient(credentialsConfig);
  122 +
  123 + deviceCredentials.setCredentialsValue(JacksonUtil.toString(X509Credentials));
  124 + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
  125 + return device;
  126 + }
  127 +
  128 + @Test
  129 + public void testConnectAndObserveTelemetry() throws Exception {
  130 + createDeviceProfile(TRANSPORT_CONFIGURATION);
  131 +
  132 + Device device = createDevice(endpoint, new X509ClientCredentialsConfig(null, null));
  133 +
  134 + SingleEntityFilter sef = new SingleEntityFilter();
  135 + sef.setSingleEntity(device.getId());
  136 + LatestValueCmd latestCmd = new LatestValueCmd();
  137 + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
  138 + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
  139 + Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
  140 +
  141 + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
  142 + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
  143 + wrapper.setEntityDataCmds(Collections.singletonList(cmd));
  144 +
  145 + wsClient.send(mapper.writeValueAsString(wrapper));
  146 + wsClient.waitForReply();
  147 +
  148 + wsClient.registerWaitForUpdate();
  149 + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
  150 + Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
  151 + client.init(security, coapConfig);
  152 + String msg = wsClient.waitForUpdate();
  153 +
  154 + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
  155 + Assert.assertEquals(1, update.getCmdId());
  156 + List<EntityData> eData = update.getUpdate();
  157 + Assert.assertNotNull(eData);
  158 + Assert.assertEquals(1, eData.size());
  159 + Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
  160 + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
  161 + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
  162 + Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
  163 + client.destroy();
  164 + }
  165 +
  166 + @Test
  167 + public void testConnectWithCertAndObserveTelemetry() throws Exception {
  168 + createDeviceProfile(TRANSPORT_CONFIGURATION);
  169 + Device device = createDevice(null, new X509ClientCredentialsConfig(SslUtil.getCertificateString(clientX509CertNotTrusted), endpoint));
  170 +
  171 + SingleEntityFilter sef = new SingleEntityFilter();
  172 + sef.setSingleEntity(device.getId());
  173 + LatestValueCmd latestCmd = new LatestValueCmd();
  174 + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel")));
  175 + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null),
  176 + Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
  177 +
  178 + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
  179 + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
  180 + wrapper.setEntityDataCmds(Collections.singletonList(cmd));
  181 +
  182 + wsClient.send(mapper.writeValueAsString(wrapper));
  183 + wsClient.waitForReply();
  184 +
  185 + wsClient.registerWaitForUpdate();
  186 + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
  187 +
  188 + Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
  189 +
  190 + client.init(security, coapConfig);
  191 + String msg = wsClient.waitForUpdate();
  192 +
  193 + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
  194 + Assert.assertEquals(1, update.getCmdId());
  195 + List<EntityData> eData = update.getUpdate();
  196 + Assert.assertNotNull(eData);
  197 + Assert.assertEquals(1, eData.size());
  198 + Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
  199 + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
  200 + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
  201 + Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
  202 + client.destroy();
  203 + }
  204 +
  205 +}
@@ -32,6 +32,7 @@ import org.eclipse.californium.scandium.dtls.SessionAdapter; @@ -32,6 +32,7 @@ import org.eclipse.californium.scandium.dtls.SessionAdapter;
32 import org.eclipse.leshan.client.californium.LeshanClient; 32 import org.eclipse.leshan.client.californium.LeshanClient;
33 import org.eclipse.leshan.client.californium.LeshanClientBuilder; 33 import org.eclipse.leshan.client.californium.LeshanClientBuilder;
34 import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; 34 import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
  35 +import org.eclipse.leshan.client.object.Security;
35 import org.eclipse.leshan.client.object.Server; 36 import org.eclipse.leshan.client.object.Server;
36 import org.eclipse.leshan.client.observer.LwM2mClientObserver; 37 import org.eclipse.leshan.client.observer.LwM2mClientObserver;
37 import org.eclipse.leshan.client.resource.ObjectsInitializer; 38 import org.eclipse.leshan.client.resource.ObjectsInitializer;
@@ -54,7 +55,6 @@ import java.util.ArrayList; @@ -54,7 +55,6 @@ import java.util.ArrayList;
54 import java.util.List; 55 import java.util.List;
55 import java.util.concurrent.ScheduledExecutorService; 56 import java.util.concurrent.ScheduledExecutorService;
56 57
57 -import static org.eclipse.leshan.client.object.Security.noSec;  
58 import static org.eclipse.leshan.core.LwM2mId.DEVICE; 58 import static org.eclipse.leshan.core.LwM2mId.DEVICE;
59 import static org.eclipse.leshan.core.LwM2mId.SECURITY; 59 import static org.eclipse.leshan.core.LwM2mId.SECURITY;
60 import static org.eclipse.leshan.core.LwM2mId.SERVER; 60 import static org.eclipse.leshan.core.LwM2mId.SERVER;
@@ -67,7 +67,7 @@ public class LwM2MTestClient { @@ -67,7 +67,7 @@ public class LwM2MTestClient {
67 private final String endpoint; 67 private final String endpoint;
68 private LeshanClient client; 68 private LeshanClient client;
69 69
70 - public void init() { 70 + public void init(Security security, NetworkConfig coapConfig) {
71 String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; 71 String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"};
72 List<ObjectModel> models = new ArrayList<>(); 72 List<ObjectModel> models = new ArrayList<>();
73 for (String resourceName : resources) { 73 for (String resourceName : resources) {
@@ -75,13 +75,10 @@ public class LwM2MTestClient { @@ -75,13 +75,10 @@ public class LwM2MTestClient {
75 } 75 }
76 LwM2mModel model = new StaticModel(models); 76 LwM2mModel model = new StaticModel(models);
77 ObjectsInitializer initializer = new ObjectsInitializer(model); 77 ObjectsInitializer initializer = new ObjectsInitializer(model);
78 - initializer.setInstancesForObject(SECURITY, noSec("coap://localhost:5685", 123)); 78 + initializer.setInstancesForObject(SECURITY, security);
79 initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); 79 initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false));
80 initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); 80 initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice());
81 81
82 - NetworkConfig coapConfig = new NetworkConfig();  
83 - coapConfig.setString("COAP_PORT", Integer.toString(5685));  
84 -  
85 DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); 82 DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
86 dtlsConfig.setRecommendedCipherSuitesOnly(true); 83 dtlsConfig.setRecommendedCipherSuitesOnly(true);
87 84
@@ -256,7 +253,7 @@ public class LwM2MTestClient { @@ -256,7 +253,7 @@ public class LwM2MTestClient {
256 } 253 }
257 254
258 public void destroy() { 255 public void destroy() {
259 - client.stop(false); 256 + client.destroy(true);
260 } 257 }
261 258
262 } 259 }
  1 +transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks
  2 +transport.lwm2m.security.key_store_password=server
@@ -43,7 +43,7 @@ public class TbLwM2MAuthorizer implements Authorizer { @@ -43,7 +43,7 @@ public class TbLwM2MAuthorizer implements Authorizer {
43 if (senderIdentity.isX509()) { 43 if (senderIdentity.isX509()) {
44 TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); 44 TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint());
45 if (sessionInfo != null) { 45 if (sessionInfo != null) {
46 - if (senderIdentity.getX509CommonName().equals(sessionInfo.getX509CommonName())) { 46 + if (sessionInfo.getX509CommonName().endsWith(senderIdentity.getX509CommonName())) {
47 clientContext.registerClient(registration, sessionInfo.getCredentials()); 47 clientContext.registerClient(registration, sessionInfo.getCredentials());
48 // X509 certificate is valid and matches endpoint. 48 // X509 certificate is valid and matches endpoint.
49 return registration; 49 return registration;
@@ -15,14 +15,17 @@ @@ -15,14 +15,17 @@
15 */ 15 */
16 package org.thingsboard.server.transport.lwm2m.secure.credentials; 16 package org.thingsboard.server.transport.lwm2m.secure.credentials;
17 17
  18 +import lombok.AllArgsConstructor;
18 import lombok.Data; 19 import lombok.Data;
  20 +import lombok.NoArgsConstructor;
19 import org.eclipse.leshan.core.SecurityMode; 21 import org.eclipse.leshan.core.SecurityMode;
20 22
21 import static org.eclipse.leshan.core.SecurityMode.X509; 23 import static org.eclipse.leshan.core.SecurityMode.X509;
22 24
23 @Data 25 @Data
  26 +@NoArgsConstructor
  27 +@AllArgsConstructor
24 public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { 28 public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig {
25 - private boolean allowTrustedOnly;  
26 private String cert; 29 private String cert;
27 private String endpoint; 30 private String endpoint;
28 31
@@ -63,6 +63,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; @@ -63,6 +63,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
63 import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; 63 import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest;
64 import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; 64 import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto;
65 import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; 65 import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters;
  66 +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
66 import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; 67 import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
67 68
68 import javax.annotation.PostConstruct; 69 import javax.annotation.PostConstruct;
@@ -129,12 +130,13 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @@ -129,12 +130,13 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
129 private final LwM2MJsonAdaptor adaptor; 130 private final LwM2MJsonAdaptor adaptor;
130 private final LwM2mClientContext clientContext; 131 private final LwM2mClientContext clientContext;
131 private final LwM2mTransportRequest lwM2mTransportRequest; 132 private final LwM2mTransportRequest lwM2mTransportRequest;
  133 + private final TbLwM2MDtlsSessionStore sessionStore;
132 134
133 public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, 135 public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper,
134 LwM2mClientContext clientContext, 136 LwM2mClientContext clientContext,
135 @Lazy LwM2mTransportRequest lwM2mTransportRequest, 137 @Lazy LwM2mTransportRequest lwM2mTransportRequest,
136 FirmwareDataCache firmwareDataCache, 138 FirmwareDataCache firmwareDataCache,
137 - LwM2mTransportContext context, LwM2MJsonAdaptor adaptor) { 139 + LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) {
138 this.transportService = transportService; 140 this.transportService = transportService;
139 this.config = config; 141 this.config = config;
140 this.helper = helper; 142 this.helper = helper;
@@ -143,6 +145,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @@ -143,6 +145,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
143 this.firmwareDataCache = firmwareDataCache; 145 this.firmwareDataCache = firmwareDataCache;
144 this.context = context; 146 this.context = context;
145 this.adaptor = adaptor; 147 this.adaptor = adaptor;
  148 + this.sessionStore = sessionStore;
146 } 149 }
147 150
148 @PostConstruct 151 @PostConstruct
@@ -243,6 +246,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @@ -243,6 +246,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
243 SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); 246 SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration);
244 if (sessionInfo != null) { 247 if (sessionInfo != null) {
245 transportService.deregisterSession(sessionInfo); 248 transportService.deregisterSession(sessionInfo);
  249 + sessionStore.remove(registration.getEndpoint());
246 this.doCloseSession(sessionInfo); 250 this.doCloseSession(sessionInfo);
247 clientContext.removeClientByRegistrationId(registration.getId()); 251 clientContext.removeClientByRegistrationId(registration.getId());
248 log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); 252 log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
@@ -141,6 +141,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @@ -141,6 +141,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
141 LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); 141 LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID());
142 lwM2mClientsByEndpoint.put(registration.getEndpoint(), client); 142 lwM2mClientsByEndpoint.put(registration.getEndpoint(), client);
143 lwM2mClientsByRegistrationId.put(registration.getId(), client); 143 lwM2mClientsByRegistrationId.put(registration.getId(), client);
  144 + toClientProfile(credentials.getDeviceProfile());
144 } 145 }
145 146
146 @Override 147 @Override
@@ -36,4 +36,9 @@ public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore @@ -36,4 +36,9 @@ public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore
36 public TbX509DtlsSessionInfo get(String endpoint) { 36 public TbX509DtlsSessionInfo get(String endpoint) {
37 return store.get(endpoint); 37 return store.get(endpoint);
38 } 38 }
  39 +
  40 + @Override
  41 + public void remove(String endpoint) {
  42 + store.remove(endpoint);
  43 + }
39 } 44 }
@@ -24,6 +24,9 @@ public interface TbLwM2MDtlsSessionStore { @@ -24,6 +24,9 @@ public interface TbLwM2MDtlsSessionStore {
24 24
25 TbX509DtlsSessionInfo get(String endpoint); 25 TbX509DtlsSessionInfo get(String endpoint);
26 26
  27 +
  28 + void remove(String endpoint);
  29 +
27 //TODO: add way to delete the session by endpoint. 30 //TODO: add way to delete the session by endpoint.
28 31
29 } 32 }
@@ -226,19 +226,29 @@ public class JsonConverter { @@ -226,19 +226,29 @@ public class JsonConverter {
226 } 226 }
227 227
228 private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { 228 private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) {
229 - if (value.getAsString().contains(".")) {  
230 - return KeyValueProto.newBuilder()  
231 - .setKey(key)  
232 - .setType(KeyValueType.DOUBLE_V)  
233 - .setDoubleV(value.getAsDouble())  
234 - .build(); 229 + String valueAsString = value.getAsString();
  230 + KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key);
  231 + if (valueAsString.contains("e") || valueAsString.contains("E")) {
  232 + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String
  233 + var bd = new BigDecimal(valueAsString);
  234 + if (bd.stripTrailingZeros().scale() <= 0) {
  235 + try {
  236 + return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build();
  237 + } catch (ArithmeticException e) {
  238 + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build();
  239 + }
  240 + } else {
  241 + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build();
  242 + }
  243 + } else if (valueAsString.contains(".")) {
  244 + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(value.getAsDouble()).build();
235 } else { 245 } else {
236 try { 246 try {
237 long longValue = Long.parseLong(value.getAsString()); 247 long longValue = Long.parseLong(value.getAsString());
238 - return KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V)  
239 - .setLongV(longValue).build(); 248 + return builder.setType(KeyValueType.LONG_V).setLongV(longValue).build();
240 } catch (NumberFormatException e) { 249 } catch (NumberFormatException e) {
241 - throw new JsonSyntaxException("Big integer values are not supported!"); 250 + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String
  251 + return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(new BigDecimal(valueAsString).doubleValue()).build();
242 } 252 }
243 } 253 }
244 } 254 }
@@ -252,6 +262,7 @@ public class JsonConverter { @@ -252,6 +262,7 @@ public class JsonConverter {
252 String valueAsString = value.getAsString(); 262 String valueAsString = value.getAsString();
253 String key = valueEntry.getKey(); 263 String key = valueEntry.getKey();
254 if (valueAsString.contains("e") || valueAsString.contains("E")) { 264 if (valueAsString.contains("e") || valueAsString.contains("E")) {
  265 + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String
255 var bd = new BigDecimal(valueAsString); 266 var bd = new BigDecimal(valueAsString);
256 if (bd.stripTrailingZeros().scale() <= 0) { 267 if (bd.stripTrailingZeros().scale() <= 0) {
257 try { 268 try {
@@ -269,7 +280,8 @@ public class JsonConverter { @@ -269,7 +280,8 @@ public class JsonConverter {
269 long longValue = Long.parseLong(value.getAsString()); 280 long longValue = Long.parseLong(value.getAsString());
270 result.add(new LongDataEntry(key, longValue)); 281 result.add(new LongDataEntry(key, longValue));
271 } catch (NumberFormatException e) { 282 } catch (NumberFormatException e) {
272 - throw new JsonSyntaxException("Big integer values are not supported!"); 283 + //TODO: correct value conversion. We should make sure that if the value can't fit into Long or Double, we should send String
  284 + result.add(new DoubleDataEntry(key, new BigDecimal(valueAsString).doubleValue()));
273 } 285 }
274 } 286 }
275 } 287 }
@@ -21,6 +21,8 @@ import org.junit.runner.RunWith; @@ -21,6 +21,8 @@ import org.junit.runner.RunWith;
21 import org.mockito.junit.MockitoJUnitRunner; 21 import org.mockito.junit.MockitoJUnitRunner;
22 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 22 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
23 23
  24 +import java.util.ArrayList;
  25 +
24 @RunWith(MockitoJUnitRunner.class) 26 @RunWith(MockitoJUnitRunner.class)
25 public class JsonConverterTest { 27 public class JsonConverterTest {
26 28
@@ -39,6 +41,12 @@ public class JsonConverterTest { @@ -39,6 +41,12 @@ public class JsonConverterTest {
39 } 41 }
40 42
41 @Test 43 @Test
  44 + public void testParseAttributesBigDecimalAsLong() {
  45 + var result = new ArrayList<>(JsonConverter.convertToAttributes(JSON_PARSER.parse("{\"meterReadingDelta\": 1E1}")));
  46 + Assert.assertEquals(10L, result.get(0).getLongValue().get().longValue());
  47 + }
  48 +
  49 + @Test
42 public void testParseAsDouble() { 50 public void testParseAsDouble() {
43 var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L); 51 var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L);
44 Assert.assertEquals(1.1, result.get(0L).get(0).getDoubleValue().get(), 0.0); 52 Assert.assertEquals(1.1, result.get(0L).get(0).getDoubleValue().get(), 0.0);
@@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
16 package org.thingsboard.server.dao.device; 16 package org.thingsboard.server.dao.device;
17 17
18 18
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
19 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
20 import org.hibernate.exception.ConstraintViolationException; 22 import org.hibernate.exception.ConstraintViolationException;
21 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
@@ -23,6 +25,7 @@ import org.springframework.cache.annotation.CacheEvict; @@ -23,6 +25,7 @@ import org.springframework.cache.annotation.CacheEvict;
23 import org.springframework.cache.annotation.Cacheable; 25 import org.springframework.cache.annotation.Cacheable;
24 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
25 import org.springframework.util.StringUtils; 27 import org.springframework.util.StringUtils;
  28 +import org.thingsboard.common.util.JacksonUtil;
26 import org.thingsboard.server.common.data.Device; 29 import org.thingsboard.server.common.data.Device;
27 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; 30 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
28 import org.thingsboard.server.common.data.id.DeviceId; 31 import org.thingsboard.server.common.data.id.DeviceId;
@@ -33,7 +36,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil; @@ -33,7 +36,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil;
33 import org.thingsboard.server.dao.entity.AbstractEntityService; 36 import org.thingsboard.server.dao.entity.AbstractEntityService;
34 import org.thingsboard.server.dao.exception.DataValidationException; 37 import org.thingsboard.server.dao.exception.DataValidationException;
35 import org.thingsboard.server.dao.service.DataValidator; 38 import org.thingsboard.server.dao.service.DataValidator;
36 -import org.thingsboard.common.util.JacksonUtil;  
37 39
38 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; 40 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE;
39 import static org.thingsboard.server.dao.service.Validator.validateId; 41 import static org.thingsboard.server.dao.service.Validator.validateId;
@@ -76,7 +78,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen @@ -76,7 +78,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
76 } 78 }
77 79
78 private DeviceCredentials saveOrUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) { 80 private DeviceCredentials saveOrUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) {
79 - if(deviceCredentials.getCredentialsType() == null){ 81 + if (deviceCredentials.getCredentialsType() == null) {
80 throw new DataValidationException("Device credentials type should be specified"); 82 throw new DataValidationException("Device credentials type should be specified");
81 } 83 }
82 switch (deviceCredentials.getCredentialsType()) { 84 switch (deviceCredentials.getCredentialsType()) {
@@ -140,7 +142,18 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen @@ -140,7 +142,18 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
140 } 142 }
141 143
142 private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { 144 private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) {
143 - 145 + ObjectNode json = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), ObjectNode.class);
  146 + JsonNode client = json.get("client");
  147 + if (client != null && client.get("securityConfigClientMode").asText().equals("X509") && client.has("cert")) {
  148 + JsonNode certJson = client.get("cert");
  149 + if (!certJson.isNull()) {
  150 + String cert = EncryptionUtil.trimNewLines(certJson.asText());
  151 + String sha3Hash = EncryptionUtil.getSha3Hash(cert);
  152 + deviceCredentials.setCredentialsId(sha3Hash);
  153 + ((ObjectNode) client).put("cert", cert);
  154 + deviceCredentials.setCredentialsValue(JacksonUtil.toString(json));
  155 + }
  156 + }
144 } 157 }
145 158
146 @Override 159 @Override