diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index c37587f546..d456d7026b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -126,7 +126,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.edge.EdgeLicenseService; import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; -import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository; +import org.thingsboard.server.service.lwm2m.LwM2MServiceImpl; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; @@ -185,6 +185,9 @@ public abstract class BaseController { @Autowired protected DeviceProfileService deviceProfileService; + @Autowired + protected LwM2MServiceImpl lwM2MService; + @Autowired protected AssetService assetService; @@ -263,9 +266,6 @@ public abstract class BaseController { @Autowired protected TbDeviceProfileCache deviceProfileCache; - @Autowired - protected LwM2MServerSecurityInfoRepository lwM2MServerSecurityInfoRepository; - @Autowired(required = false) protected EdgeService edgeService; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index f1a58e7022..05385ee948 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -291,7 +291,6 @@ public class DeviceController extends BaseController { public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { checkNotNull(deviceCredentials); try { - lwM2MServerSecurityInfoRepository.verifySecurityKeyDevice(deviceCredentials); Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials)); tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null); diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index 46d76299ec..4f74676be5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -157,7 +157,6 @@ public class DeviceProfileController extends BaseController { isSoftwareChanged = true; } } - lwM2MServerSecurityInfoRepository.verifySecurityKeyDeviceProfile(deviceProfile); DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); 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 64537d9f5f..fb71d4ea91 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -46,7 +46,7 @@ public class Lwm2mController extends BaseController { @ResponseBody public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("isBootstrapServer") boolean bootstrapServer) throws ThingsboardException { try { - return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(bootstrapServer); + return lwM2MService.getServerSecurityInfo(bootstrapServer); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java new file mode 100644 index 0000000000..7d1a69d270 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.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.service.lwm2m; + +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; + +public interface LwM2MService { + + ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer); + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java index 522abba65a..1bd9245cb2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractLwM2MIntegrationTest.java @@ -57,6 +57,9 @@ 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.bootstrap.secure.LwM2MBootstrapConfig; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapServers; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MServerBootstrap; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; @@ -83,9 +86,18 @@ import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.HOST; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.HOST_BS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.PORT; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.PORT_BS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_PORT; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_PORT_BS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID_BS; @DaoSqlTest public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { @@ -312,7 +324,13 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest LwM2MCredentials credentials = new LwM2MCredentials(); credentials.setClient(clientCredentials); - + // TODO + /** + * Without: credentials.setBootstrap(createBootstrapConfig()); + * doPost("/api/device/credentials", deviceCredentials).andExpect(status().isBadRequest()) + * .andExpect(statusReason(containsString("Device credentials are missing fields or mandatory value in these fields: bootstrap, bootstrapServer, lwm2mServer"))); + */ + credentials.setBootstrap(createBootstrapConfig()); deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); return device; @@ -351,6 +369,30 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); } + protected LwM2MBootstrapConfig createBootstrapConfig() { + LwM2MBootstrapConfig bootstrap = new LwM2MBootstrapConfig(); + LwM2MBootstrapServers servers = new LwM2MBootstrapServers(); + servers.setShortId(SHORT_SERVER_ID); + bootstrap.setServers(servers); + LwM2MServerBootstrap server = new LwM2MServerBootstrap(); + server.setHost(HOST); + server.setPort(PORT); + server.setSecurityHost(HOST); + server.setSecurityPort(SECURE_PORT); + server.setServerId(servers.getShortId()); + server.setBootstrapServerIs(false); + bootstrap.setLwm2mServer(server); + LwM2MServerBootstrap serverBS = new LwM2MServerBootstrap(); + serverBS.setHost(HOST_BS); + serverBS.setPort(PORT_BS); + serverBS.setSecurityHost(HOST_BS); + serverBS.setSecurityPort(SECURE_PORT_BS); + serverBS.setServerId(SHORT_SERVER_ID_BS); + serverBS.setBootstrapServerIs(true); + bootstrap.setBootstrapServer(serverBS); + return bootstrap; + } + protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); postRequest.file(content); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index 014ab752fe..c99f978dae 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -33,7 +33,7 @@ public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest { @Test public void testConnectWithPSKAndObserveTelemetry() throws Exception { String pskIdentity = "SOME_PSK_ID"; - String pskKey = "73656372657450534b"; + String pskKey = "0123456789ABCDEF0123456789ABCDEF"; PSKClientCredentials clientCredentials = new PSKClientCredentials(); clientCredentials.setEndpoint(ENDPOINT); clientCredentials.setKey(pskKey); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java index e131064953..9a333058a2 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.dao.device; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.security.DeviceCredentials; public interface DeviceCredentialsService { @@ -30,4 +32,6 @@ public interface DeviceCredentialsService { DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); + + void verifyLwm2mSecurityKeyDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) ; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index d49272f2c6..4ccf07f2b0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java @@ -89,10 +89,4 @@ public class DeviceCredentials extends BaseData implements + credentialsId + ", credentialsValue=" + credentialsValue + ", createdTime=" + createdTime + ", id=" + id + "]"; } - - public JsonNode getNodeCredentialsValue () throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readTree(this.credentialsValue); - } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 898a1ad8b2..edc408a78d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -40,7 +40,7 @@ public class LwM2mServerListener { public final RegistrationListener registrationListener = new RegistrationListener() { /** - * Register – запрос, представленный в виде POST /rd?… + * Register – query represented as POST /rd?… */ @Override public void registered(Registration registration, Registration previousReg, @@ -49,7 +49,7 @@ public class LwM2mServerListener { } /** - * Update – представляет из себя CoAP POST запрос на URL, полученный в ответ на Register. + * Update – query represented as CoAP POST request for the URL received in response to Register. */ @Override public void updated(RegistrationUpdate update, Registration updatedRegistration, @@ -58,7 +58,7 @@ public class LwM2mServerListener { } /** - * De-register (CoAP DELETE) – отправляется клиентом в случае инициирования процедуры выключения. + * De-register (CoAP DELETE) – Sent by the client when a shutdown procedure is initiated. */ @Override public void unregistered(Registration registration, Collection observations, boolean expired, diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index edf1d65fa0..b99fd2ac27 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -345,13 +345,10 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { try { - LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); if (resultIds.isObjectInstance() || resultIds.isResource()) { validateVersionedId(client, request); - ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - if (resourceModelWrite != null) { - WriteRequest downlink = null; + WriteRequest downlink = null; ContentFormat contentFormat = getWriteRequestContentFormat(client, request, this.config.getModelProvider()); if (resultIds.isObjectInstance()) { /* @@ -365,15 +362,21 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } else { callback.onValidationError(toString(request), "No resources to update!"); } - } else if (resultIds.isResource()) { - if (resourceModelWrite.multiple) { - if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) { - Map value = convertMultiResourceValuesFromRpcBody((LinkedHashMap) request.getValue(), resourceModelWrite.type, request.getObjectId()); - downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), - value, resourceModelWrite.type); - } else { - callback.onValidationError(toString(request), "Resource value is bad. Format: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!"); + } + else if (resultIds.isResource()) { + ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); + if (resourceModelWrite != null) { + if (resourceModelWrite.multiple) { + if (request.getValue() instanceof Map && ((Map) request.getValue()).size() > 0) { + Map value = convertMultiResourceValuesFromRpcBody((LinkedHashMap) request.getValue(), resourceModelWrite.type, request.getObjectId()); + downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), + value, resourceModelWrite.type); + } else { + callback.onValidationError(toString(request), "Resource value is bad. Format: " + request.getValue().getClass().getSimpleName() + ". Value of Multi-Instance Resource must be in Json format!"); + } } + } else { + callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); } } if (downlink != null) { @@ -381,21 +384,10 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } else { callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for ObjectInstance or Multi-Instance Resource !"); } - } - else { - callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); - } } else { callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + ". This operation can only be used for ObjectInstance or Resource (multiple)"); } - - - - - - - } catch (Exception e) { callback.onValidationError(toString(request), e.getMessage()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 2c4f911db6..84ac0277a3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -418,8 +418,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl * #1 clientOnlyObserveAfterConnect == true * - Only Observe Request to the client marked as observe from the profile configuration. * #2. clientOnlyObserveAfterConnect == false - * После регистрации отправляю запрос на read всех ресурсов, которые после регистрации есть у клиента, - * а затем запрос на observe (edited) * - Read Request to the client after registration to read all resource values for all objects * - then Observe Request to the client marked as observe from the profile configuration. * diff --git a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh index dcf38b4f10..f68ca30005 100644 --- a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh +++ b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh @@ -321,7 +321,6 @@ cert_end_point() { } if [ "$client_start" -lt "$client_finish" ]; then - echo "Файл содержит, как минимум, одно слово Bash." echo echo "==Start Client==" while [ "$CLIENT_NUMBER" -lt "$client_finish" ]; do 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 bdc03d767c..dbe42cf9f4 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 @@ -899,7 +899,6 @@ public class DefaultTransportService implements TransportService { String resourceId = msg.getResourceKey(); transportResourceCache.update(tenantId, resourceType, resourceId); sessions.forEach((id, mdRez) -> { - log.warn("ResourceUpdate - [{}] [{}]", id, mdRez); transportCallbackExecutor.submit(() -> mdRez.getListener().onResourceUpdate(Optional.ofNullable(msg))); }); diff --git a/dao/pom.xml b/dao/pom.xml index 1ef95d8358..736ba64326 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -227,6 +227,10 @@ org.elasticsearch.client rest + + org.eclipse.leshan + leshan-server-cf + 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 3cce5e1506..9d0ff83491 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 @@ -15,9 +15,14 @@ */ 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.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.util.SecurityUtil; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -30,6 +35,7 @@ import org.thingsboard.server.common.data.device.credentials.BasicMqttCredential 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.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -39,10 +45,20 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; + @Service @Slf4j public class DeviceCredentialsServiceImpl extends AbstractEntityService implements DeviceCredentialsService { @@ -92,6 +108,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen break; case LWM2M_CREDENTIALS: formatSimpleLwm2mCredentials(deviceCredentials); + verifySecurityKeyDevice(deviceCredentials); break; } log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); @@ -237,4 +254,157 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen } }; + + private void verifySecurityKeyDevice(DeviceCredentials deviceCredentials) { + try { +// LwM2MCredentialsValid credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), LwM2MCredentialsValid.class); + JsonNode nodeCredentialsValue = JacksonUtil.toJsonNode(deviceCredentials.getCredentialsValue()); + String [] fields = {"client", "bootstrap", "bootstrap:bootstrapServer", "bootstrap:lwm2mServer"}; + String validateMsg = validateNodeCredentials (nodeCredentialsValue, fields); + if (validateMsg.isEmpty()) { + checkClientKey(nodeCredentialsValue.get("client")); + checkServerKey(nodeCredentialsValue.get("bootstrap").get("bootstrapServer"), "Client`s by bootstrapServer"); + checkServerKey(nodeCredentialsValue.get("bootstrap").get("lwm2mServer"), "Client`s by lwm2mServer"); + } + else { + throw new DataValidationException(validateMsg); + } + } catch (DataValidationException | DecoderException e) { + throw new DataValidationException(e.getMessage()); + } + } + + public void verifyLwm2mSecurityKeyDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) { + try { + Map serverBs = transportConfiguration.getBootstrap().getBootstrapServer(); + checkDeviceProfileServer (serverBs, "Servers: BootstrapServer`s"); + Map serverLwm2m = transportConfiguration.getBootstrap().getLwm2mServer(); + checkDeviceProfileServer (serverLwm2m, "Servers: Lwm2mServer`s"); + } catch (DataValidationException e) { + throw new DataValidationException(e.getMessage()); + + } + } + + private String validateNodeCredentials (JsonNode nodeCredentialsValue, String [] fields) { + Set msgSet = ConcurrentHashMap.newKeySet(); + String msg = ""; + for (String field : fields) { + if (field.contains(":")) { + String [] keys = field.split(":"); + if (!nodeCredentialsValue.hasNonNull(keys[0])) { + msgSet.add(keys[1]); + } + else { + if (!nodeCredentialsValue.get(keys[0]).hasNonNull(keys[1])) msgSet.add(keys[1]); + } + } + else { + if (!nodeCredentialsValue.hasNonNull(field)) msgSet.add(field); + } + } + if (msgSet.size() > 0) msg = "Device credentials are missing fields or mandatory value in this fields: " + String.join(", ", msgSet); + return msg; + } + + private void checkClientKey (JsonNode node) throws DataValidationException, DecoderException { + String modeName = node.get("securityConfigClientMode").asText(); + // checks security config + // HexDec Len = 32,64,128 + if (SecurityMode.PSK.name().equals(modeName)) { + String key = node.get("key").textValue(); + assertIf(key == null || key.isEmpty(), + "pre-shared-key mode, Client`s private key or id must be not null or empty."); + assertIf(!key.matches("-?[0-9a-fA-F]+"), + "pre-shared-key mode, Client`s private key or id must be HexDecimal format."); + assertIf(key.length()%32 != 0 , + "pre-shared-key mode, Client`s private key or id must be a multiple of 32."); + assertIf(key.length() > 128, + "pre-shared-key mode, Client`s private key or id must be not more than 128."); + String identity = node.get("identity").textValue(); + assertIf(identity == null || identity.isEmpty(), + "pre-shared-key mode, Client`s identity key must be not null or empty."); + } else if (SecurityMode.RPK.name().equals(modeName)) { + String value = node.get("key").textValue(); + assertIf(decodeRfc7250PublicKey(org.eclipse.leshan.core.util.Hex.decodeHex(((String) value).toCharArray())) == null, + "raw-public-key mode, Client`s public key or id must be RFC7250 encoded public key"); + } else if (SecurityMode.X509.name().equals(modeName)) { + String value = node.get("cert").textValue(); + if (value != null && !value.isEmpty()) { + assertIf(decodeCertificate(Hex.decodeHex(((String) value).toCharArray())) == null, + "x509 mode, Client`s public key must be DER encoded X.509 certificate"); + } + } + } + + private void checkServerKey (JsonNode node, String serverType) throws DataValidationException { + String modeName = node.get("securityMode").asText(); + // checks security config + if (SecurityMode.RPK.name().equals(modeName)) { + checkRPKServer(node, serverType); + } else if (SecurityMode.X509.name().equals(modeName)) { + checkX509Server(node, serverType); + } + } + + protected void checkRPKServer(JsonNode node, String serverType) throws DataValidationException { + String value = node.get("clientSecretKey").textValue(); + assertIf(decodeRfc5958PrivateKey(org.eclipse.leshan.core.util.Hex.decodeHex(value.toCharArray())) == null, + "raw-public-key mode, " + serverType + " secret key must be RFC5958 encoded private key"); + value = node.get("clientPublicKeyOrId").textValue(); + assertIf(decodeRfc7250PublicKey(org.eclipse.leshan.core.util.Hex.decodeHex(value.toCharArray())) == null, + "raw-public-key mode, " + serverType + " public key or id must be RFC7250 encoded public key"); + } + + protected void checkX509Server(JsonNode node, String serverType) throws DataValidationException { + String value = node.get("clientSecretKey").textValue(); + assertIf(decodeRfc5958PrivateKey(org.eclipse.leshan.core.util.Hex.decodeHex(value.toCharArray())) == null, + "x509 mode " + serverType + " secret key must be RFC5958 encoded private key"); + value = node.get("clientPublicKeyOrId").textValue(); + assertIf(decodeCertificate(org.eclipse.leshan.core.util.Hex.decodeHex(value.toCharArray())) == null, + "x509 mode " + serverType + " public key must be DER encoded X.509 certificate"); + + } + + protected void checkDeviceProfileServer (Map server, String serverType) throws DataValidationException { + // checks security config + String value = (String) server.get("serverPublicKey"); + if (SecurityMode.RPK.name().equals(server.get("securityMode"))) { + assertIf(decodeRfc7250PublicKey(org.eclipse.leshan.core.util.Hex.decodeHex(value.toCharArray())) == null, + "raw-public-key mode, " + serverType + " public key or id must be RFC7250 encoded public key"); + } else if (SecurityMode.X509.name().equals(server.get("securityMode"))) { + assertIf(decodeCertificate(org.eclipse.leshan.core.util.Hex.decodeHex(value.toCharArray())) == null, + "x509 mode, " + serverType + " public key must be DER encoded X.509 certificate"); + } + } + + protected PrivateKey decodeRfc5958PrivateKey(byte[] encodedKey) { + try { + return SecurityUtil.privateKey.decode(encodedKey); + } catch (IOException | GeneralSecurityException e) { + return null; + } + } + + protected PublicKey decodeRfc7250PublicKey(byte[] encodedKey) { + try { + return SecurityUtil.publicKey.decode(encodedKey); + } catch (IOException | GeneralSecurityException e) { + return null; + } + } + + protected Certificate decodeCertificate(byte[] encodedCert) { + try { + return SecurityUtil.certificate.decode(encodedCert); + } catch (IOException | GeneralSecurityException e) { + return null; + } + } + + protected static void assertIf(boolean condition, String message) throws DataValidationException { + if (condition) { + throw new DataValidationException(message); + } + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 720d50ad69..aacaccc73b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; @@ -132,6 +133,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @Autowired private DashboardService dashboardService; + @Autowired + private DeviceCredentialsService deviceCredentialsService; + private final Lock findOrCreateLock = new ReentrantLock(); @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") @@ -409,6 +413,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); } } + } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) { + deviceCredentialsService.verifyLwm2mSecurityKeyDeviceProfile((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration); } List profileAlarms = deviceProfile.getProfileData().getAlarms(); diff --git a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh index dcf38b4f10..d623bfad42 100755 --- a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh +++ b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh @@ -321,7 +321,7 @@ cert_end_point() { } if [ "$client_start" -lt "$client_finish" ]; then - echo "Файл содержит, как минимум, одно слово Bash." + echo echo "==Start Client==" while [ "$CLIENT_NUMBER" -lt "$client_finish" ]; do diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts index 64a95d0513..47d7e5d35b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts @@ -185,13 +185,13 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, * If the Object field “Mandatory” is “Mandatory” and the Object field “Instances” is “Single” then - * the number of Object Instance MUST be 1. * 1. == Multiple (true), == Optional (false) - - * Object Instance ID MIN_ID=0 MAX_ID=65535 (может ни одного не быть) + * Object Instance ID MIN_ID=0 MAX_ID=65535 (there may be none) * 2. == Multiple (true), == Mandatory (true) - - * Object Instance ID MIN_ID=0 MAX_ID=65535 (min один обязательный) + * Object Instance ID MIN_ID=0 MAX_ID=65535 (min one mandatory) * 3. == Single (false), == Optional (false) - - * Object Instance ID cnt_max = 1 cnt_min = 0 (может ни одного не быть) + * Object Instance ID cnt_max = 1 cnt_min = 0 (there may be none ) * 4. == Single (false), == Mandatory (true) - - * Object Instance ID cnt_max = cnt_min = 1 (всегда есть один) + * Object Instance ID cnt_max = cnt_min = 1 (there must always be one) */ addInstances = ($event: Event, control: AbstractControl): void => {