From be5ec4fce93e3138d28c25ff069d47f45e72af4c Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Mon, 9 Aug 2021 15:10:04 +0300 Subject: [PATCH] Lwm2m: Device validate securityKey --- .../server/controller/DeviceController.java | 1 + .../LwM2MServerSecurityInfoRepository.java | 106 ++++++++++++++++-- .../data/security/DeviceCredentials.java | 8 ++ 3 files changed, 103 insertions(+), 12 deletions(-) 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 05385ee948..f1a58e7022 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -291,6 +291,7 @@ 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/service/lwm2m/LwM2MServerSecurityInfoRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java index b4ac3d30fd..f8ff9a687a 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 @@ -16,33 +16,29 @@ package org.thingsboard.server.service.lwm2m; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; +import org.thingsboard.server.common.data.security.DeviceCredentials; 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 java.math.BigInteger; -import java.security.AlgorithmParameters; +import java.io.IOException; import java.security.GeneralSecurityException; -import java.security.KeyFactory; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateEncodingException; +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.ECPublicKeySpec; -import java.security.spec.KeySpec; @Slf4j @Service @@ -84,5 +80,91 @@ public class LwM2MServerSecurityInfoRepository { return ""; } + public void verifySecurityKeyDevice(DeviceCredentials result) throws InvalidConfigurationException, JsonProcessingException { + ObjectNode node = result.getNodeCredentialsValue(); + checkClientKey ((ObjectNode) node.get("client")); + checkServerKey ((ObjectNode) node.get("bootstrap").get("bootstrapServer"), "Client`s by bootstrapServer"); + checkServerKey ((ObjectNode) node.get("bootstrap").get("lwm2mServer"), "Client`s by lwm2mServer"); + } + + private void checkClientKey (ObjectNode node) throws InvalidConfigurationException { + String modeName = node.get("securityConfigClientMode").asText(); + // checks security config + String value = node.get("key").textValue(); + if (SecurityMode.RPK.name().equals(modeName)) { + assertIf(decodeRfc7250PublicKey(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)) { + assertIf(decodeCertificate(Hex.decodeHex(((String) value).toCharArray())) == null, + "x509 mode, Client`s public key must be DER encoded X.509 certificate"); + } + + } + + private void checkServerKey (ObjectNode node, String serverType) throws InvalidConfigurationException { + 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); + } + + } + + protected void checkRPKServer(ObjectNode node, String serverType) throws InvalidConfigurationException { + String value = node.get("clientSecretKey").textValue(); + assertIf(decodeRfc5958PrivateKey(Hex.decodeHex(((String) value).toCharArray())) == null, + "raw-public-key mode, " + serverType + " secret key must be RFC5958 encoded private key"); + value = node.get("clientPublicKeyOrId").textValue(); + assertIf(decodeRfc7250PublicKey(Hex.decodeHex(((String) value).toCharArray())) == null, + "raw-public-key mode, " + serverType + " public key or id must be RFC7250 encoded public key"); + } + + protected void checkX509Server(ObjectNode node) throws InvalidConfigurationException { + String value = node.get("clientSecretKey").textValue();; + assertIf(decodeRfc5958PrivateKey(Hex.decodeHex(((String) value).toCharArray())) == null, + "x509 mode, secret key must be RFC5958 encoded private key"); + value = node.get("clientPublicKeyOrId").textValue(); + assertIf(decodeCertificate(Hex.decodeHex(((String) value).toCharArray())) == null, + "x509 mode, server public key must be DER encoded X.509 certificate"); + + } + + protected PrivateKey decodeRfc5958PrivateKey(byte[] encodedKey) throws InvalidConfigurationException { + try { + return SecurityUtil.privateKey.decode(encodedKey); + } catch (IOException | GeneralSecurityException e) { + return null; + } + } + + protected PublicKey decodeRfc7250PublicKey(byte[] encodedKey) throws InvalidConfigurationException { + try { + return SecurityUtil.publicKey.decode(encodedKey); + } catch (IOException | GeneralSecurityException e) { + return null; + } + } + + protected Certificate decodeCertificate(byte[] encodedCert) throws InvalidConfigurationException { + try { + return SecurityUtil.certificate.decode(encodedCert); + } catch (IOException | GeneralSecurityException e) { + return null; + } + } + + protected static void assertIf(boolean condition, String message) throws InvalidConfigurationException { + if (condition) { + throw new InvalidConfigurationException(message); + } + } + + protected static boolean isEmpty(byte[] array) { + return array == null || array.length == 0; + } + + } 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 337e6cf349..e7c5d7d19b 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 @@ -15,6 +15,9 @@ */ package org.thingsboard.server.common.data.security; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.DeviceCredentialsId; @@ -87,4 +90,9 @@ public class DeviceCredentials extends BaseData implements + id + "]"; } + public ObjectNode getNodeCredentialsValue () throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + return (ObjectNode) mapper.readTree(this.credentialsValue); + } + }