From c5ca395844256dde207f2c1ec22139ecc339f06f Mon Sep 17 00:00:00 2001 From: Kulikov <44275303+nickAS21@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:17:30 +0200 Subject: [PATCH] fix_bug_coaps_x509_docker_many_devices (#12327) * coaps: x509 - tests * coaps: x509 - tests add sever cert.pem * coaps: x509 - tests add sever cert.pem -2 * coaps: x509 - tests add sever cert.pem -3 * coaps: x509 - tests add sever cert.pem -4 * fix bug: coaps x509 - ddocker many devices with re-port * fix bug: coaps - add test, connect client with X509 * fix bug: coaps - add two test devices with the same port * fix bug: coaps - add two test devices with the same port (FeatureType.ATTRIBUTES) * fix bug: coaps - add two test devices with the same port (FeatureType.ATTRIBUTES) - 1 * fix bug: coap comments 1 --- .../coap/AbstractCoapIntegrationTest.java | 8 + .../client/CoapClientIntegrationTest.java | 2 - .../AbstractCoapSecurityIntegrationTest.java | 291 ++++++++++++++++++ ...pClientX509SecurityJksIntegrationTest.java | 44 +++ ...pClientX509SecurityPemIntegrationTest.java | 44 +++ .../CoapAttributesIntegrationTest.java | 3 - ...AbstractCoapTimeseriesIntegrationTest.java | 3 - .../transport/coap/x509/CertPrivateKey.java | 82 +++++ .../coap/x509/CoapClientX509Test.java | 239 ++++++++++++++ .../x509/TbAdvancedCertificateVerifier.java | 129 ++++++++ .../coap/credentials/client/cert.pem | 13 + .../coap/credentials/client/cert_01.pem | 14 + .../resources/coap/credentials/client/key.pem | 4 + .../coap/credentials/client/key_01.pem | 8 + .../coap/credentials/coapclientTest.jks | Bin 0 -> 20462 bytes .../coap/credentials/coapserverTest.jks | Bin 0 -> 1985 bytes .../coap/credentials/server/cert.pem | 35 +++ .../server/coapserver/CoapServerService.java | 3 +- .../coapserver/DefaultCoapServerService.java | 2 +- .../TbCoapDtlsCertificateVerifier.java | 5 +- .../TbCoapDtlsSessionInMemoryStorage.java | 9 +- .../coapserver/TbCoapDtlsSessionKey.java | 32 ++ .../transport/coap/CoapTransportResource.java | 44 ++- 23 files changed, 988 insertions(+), 26 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityJksIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityPemIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/coap/x509/CertPrivateKey.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/coap/x509/TbAdvancedCertificateVerifier.java create mode 100644 application/src/test/resources/coap/credentials/client/cert.pem create mode 100644 application/src/test/resources/coap/credentials/client/cert_01.pem create mode 100644 application/src/test/resources/coap/credentials/client/key.pem create mode 100644 application/src/test/resources/coap/credentials/client/key_01.pem create mode 100644 application/src/test/resources/coap/credentials/coapclientTest.jks create mode 100644 application/src/test/resources/coap/credentials/coapserverTest.jks create mode 100644 application/src/test/resources/coap/credentials/server/cert.pem create mode 100644 common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionKey.java diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java index 33c83b5623..2b0d87527b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java @@ -54,6 +54,14 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg protected final byte[] EMPTY_PAYLOAD = new byte[0]; protected CoapTestClient client; + protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; + protected static final String PAYLOAD_VALUES_STR_01 = "{\"key2\":\"value2\", \"key3\":false, \"key4\": 4.0, \"key5\": 5," + + " \"key6\": {\"someNumber_02\": 52, \"someArray_02\": [1,2,3,4], \"someNestedObject_02\": {\"key_02\": \"value_02\"}}}"; + + protected void processBeforeTest() throws Exception { + loginTenantAdmin(); + } protected void processAfterTest() throws Exception { if (client != null) { diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java index 112d3e6aa5..534c83bd40 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java @@ -63,8 +63,6 @@ import static org.thingsboard.server.common.data.query.EntityKeyType.SHARED_ATTR @DaoSqlTest public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest { - private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + - " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; private static final List EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5"); private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java new file mode 100644 index 0000000000..8413c6f32b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java @@ -0,0 +1,291 @@ +/** + * Copyright © 2016-2024 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.coap.security; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.junit.Assert; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.transport.coap.x509.CertPrivateKey; +import org.thingsboard.server.transport.coap.x509.CoapClientX509Test; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; + +import java.io.IOException; +import java.io.InputStream; +import java.net.ServerSocket; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +@TestPropertySource(properties = { + "coap.enabled=true", + "coap.dtls.enabled=true", + "coap.dtls.credentials.pem.cert_file=coap/credentials/server/cert.pem", + "device.connectivity.coaps.enabled=true", + "service.integrations.supported=ALL", + "transport.coap.enabled=true", +}) +public abstract class AbstractCoapSecurityIntegrationTest extends AbstractCoapIntegrationTest { + private static final String COAPS_BASE_URL = "coaps://localhost:5684/api/v1/"; + protected final String CREDENTIALS_PATH = "coap/credentials/"; + protected final String CREDENTIALS_PATH_CLIENT = CREDENTIALS_PATH + "client/"; + protected final String CREDENTIALS_PATH_CLIENT_CERT_PEM = CREDENTIALS_PATH_CLIENT + "cert.pem"; + protected final String CREDENTIALS_PATH_CLIENT_KEY_PEM = CREDENTIALS_PATH_CLIENT + "key.pem"; + protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name") + protected final PrivateKey clientPrivateKeyFromCertTrustNo; + + protected static final String CLIENT_JKS_FOR_TEST = "coapclientTest"; + protected static final String CLIENT_STORE_PWD = "client_ks_password"; + protected static final String CLIENT_ALIAS_CERT_TRUST_NO = "client_alias_trust_no"; + + protected AbstractCoapSecurityIntegrationTest() { + + try { + // Get certificates from key store + char[] clientKeyStorePwd = CLIENT_STORE_PWD.toCharArray(); + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream clientKeyStoreFile = + this.getClass().getClassLoader(). + getResourceAsStream(CREDENTIALS_PATH + CLIENT_JKS_FOR_TEST + ".jks")) { + clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd); + } + // No trust + clientPrivateKeyFromCertTrustNo = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST_NO, clientKeyStorePwd); + clientX509CertTrustNo = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST_NO); + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + } + + protected Device createDeviceWithX509(String deviceName, DeviceProfileId deviceProfileId, X509Certificate clientX509Cert) throws Exception { + Device device = new Device(); + device.setName(deviceName); + device.setType(deviceName); + device.setDeviceProfileId(deviceProfileId); + + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + String pemFormatCert = CertPrivateKey.convertCertToPEM(clientX509Cert); + deviceCredentials.setCredentialsValue(pemFormatCert); + + SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, deviceCredentials); + Device deviceX509 = readResponse(doPost("/api/device-with-credentials", saveRequest) + .andExpect(status().isOk()), Device.class); + DeviceCredentials savedDeviceCredentials = + doGet("/api/device/" + deviceX509.getId().getId() + "/credentials", DeviceCredentials.class); + Assert.assertNotNull(savedDeviceCredentials); + Assert.assertNotNull(savedDeviceCredentials.getId()); + Assert.assertEquals(deviceX509.getId(), savedDeviceCredentials.getDeviceId()); + Assert.assertEquals(DeviceCredentialsType.X509_CERTIFICATE, savedDeviceCredentials.getCredentialsType()); + accessToken = savedDeviceCredentials.getCredentialsId(); + assertNotNull(accessToken); + return deviceX509; + } + + protected void clientX509FromJksUpdateAttributesTest() throws Exception { + CertPrivateKey certPrivateKey = new CertPrivateKey(clientX509CertTrustNo, clientPrivateKeyFromCertTrustNo); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties); + assertNotNull(deviceProfile); + CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey, + "CoapX509TrustNo_" + FeatureType.ATTRIBUTES.name(), deviceProfile.getId(), null); + clientX509.disconnect(); + } + + protected void clientX509FromPathUpdateFeatureTypeTest(FeatureType featureType) throws Exception { + CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties); + assertNotNull(deviceProfile); + CoapClientX509Test clientX509 = clientX509UpdateTest(featureType, certPrivateKey, + "CoapX509TrustNo_" + featureType.name(), deviceProfile.getId(), null); + clientX509.disconnect(); + } + protected void twoClientWithSamePortX509FromPathConnectTest() throws Exception { + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties); + CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM); + CertPrivateKey certPrivateKey_01 = new CertPrivateKey(CREDENTIALS_PATH_CLIENT + "cert_01.pem", + CREDENTIALS_PATH_CLIENT + "key_01.pem"); + Integer fixedPort = getFreePort(); + CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey, + "CoapX509TrustNo_" + FeatureType.TELEMETRY.name(), deviceProfile.getId(), fixedPort); + clientX509.disconnect(); + await("Need to make port " + fixedPort + " free") + .atMost(40, TimeUnit.SECONDS) + .until(() -> isPortAvailable(fixedPort)); + CoapClientX509Test clientX509_01 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey_01, + "CoapX509TrustNo_" + FeatureType.TELEMETRY.name() + "_01", deviceProfile.getId(), + fixedPort, PAYLOAD_VALUES_STR_01); + clientX509_01.disconnect(); + } + + private CoapClientX509Test clientX509UpdateTest(FeatureType featureType, CertPrivateKey certPrivateKey, + String deviceName, DeviceProfileId deviceProfileId, Integer fixedPort) throws Exception { + return clientX509UpdateTest(featureType, certPrivateKey, deviceName, deviceProfileId, fixedPort, null); + } + + private CoapClientX509Test clientX509UpdateTest(FeatureType featureType, CertPrivateKey certPrivateKey, + String deviceName, DeviceProfileId deviceProfileId, Integer fixedPort, String payload) throws Exception { + String payloadValuesStr = payload == null ? PAYLOAD_VALUES_STR : payload; + Device deviceX509 = createDeviceWithX509(deviceName, deviceProfileId, certPrivateKey.getCert()); + CoapClientX509Test clientX509 = new CoapClientX509Test(certPrivateKey, featureType, COAPS_BASE_URL, fixedPort); + CoapResponse coapResponseX509 = clientX509.postMethod(payloadValuesStr); + assertNotNull(coapResponseX509); + assertEquals(CoAP.ResponseCode.CREATED, coapResponseX509.getCode()); + + if (FeatureType.ATTRIBUTES.equals(featureType)) { + DeviceId deviceId = deviceX509.getId(); + JsonNode expectedNode = JacksonUtil.toJsonNode(payloadValuesStr); + List expectedKeys = getKeysFromNode(expectedNode); + List actualKeys = getActualKeysList(deviceId, expectedKeys, "attributes/CLIENT_SCOPE"); + assertNotNull(actualKeys); + + Set actualKeySet = new HashSet<>(actualKeys); + Set expectedKeySet = new HashSet<>(expectedKeys); + assertEquals(expectedKeySet, actualKeySet); + + String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet, "attributes/CLIENT_SCOPE"); + List> actualValues = doGetAsyncTyped(getAttributesValuesUrl, new TypeReference<>() { + }); + assertValuesList(actualValues, expectedNode); + } + return clientX509; + } + + private List getActualKeysList(DeviceId deviceId, List expectedKeys, String apiSuffix) throws Exception { + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis() + 5000; + + List actualKeys = null; + while (start <= end) { + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/" + apiSuffix, new TypeReference<>() { + }); + if (actualKeys.size() == expectedKeys.size()) { + break; + } + Thread.sleep(100); + start += 100; + } + return actualKeys; + } + + private String getAttributesValuesUrl(DeviceId deviceId, Set actualKeySet, String apiSuffix) { + return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/" + apiSuffix + "?keys=" + String.join(",", actualKeySet); + } + + private List getKeysFromNode(JsonNode jNode) { + List jKeys = new ArrayList<>(); + Iterator fieldNames = jNode.fieldNames(); + while (fieldNames.hasNext()) { + jKeys.add(fieldNames.next()); + } + return jKeys; + } + + protected void assertValuesList(List> actualValues, JsonNode expectedValues) { + assertTrue(actualValues.size() > 0); + assertEquals(expectedValues.size(), actualValues.size()); + for (Map map : actualValues) { + String key = (String) map.get("key"); + Object actualValue = map.get("value"); + assertTrue(expectedValues.has(key)); + JsonNode expectedValue = expectedValues.get(key); + assertExpectedActualValue(expectedValue, actualValue); + } + } + + protected void assertExpectedActualValue(JsonNode expectedValue, Object actualValue) { + switch (expectedValue.getNodeType()) { + case STRING: + assertEquals(expectedValue.asText(), actualValue); + break; + case NUMBER: + if (expectedValue.isInt()) { + assertEquals(expectedValue.asInt(), actualValue); + } else if (expectedValue.isLong()) { + assertEquals(expectedValue.asLong(), actualValue); + } else if (expectedValue.isFloat() || expectedValue.isDouble()) { + assertEquals(expectedValue.asDouble(), actualValue); + } + break; + case BOOLEAN: + assertEquals(expectedValue.asBoolean(), actualValue); + break; + case ARRAY: + case OBJECT: + expectedValue.toString().equals(JacksonUtil.toString(actualValue)); + break; + default: + break; + } + } + + private static int getFreePort() throws IOException { + try (ServerSocket socket = new ServerSocket(0)) { + return socket.getLocalPort(); + } + } + + private static boolean isPortAvailable(int port) { + try (ServerSocket serverSocket = new ServerSocket(port)) { + serverSocket.setReuseAddress(true); + return true; + } catch (IOException e) { + return false; + } + } +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityJksIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityJksIntegrationTest.java new file mode 100644 index 0000000000..12f373948b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityJksIntegrationTest.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2024 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.coap.security.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.security.AbstractCoapSecurityIntegrationTest; + +@Slf4j +@DaoSqlTest +@TestPropertySource(properties = { + "coap.dtls.credentials.type=KEYSTORE", + "coap.dtls.credentials.keystore.store_file=coap/credentials/coapserverTest.jks", + "coap.dtls.credentials.keystore.key_password=server_ks_password", + "coap.dtls.credentials.keystore.key_alias=server", +}) +public class CoapClientX509SecurityJksIntegrationTest extends AbstractCoapSecurityIntegrationTest { + + @Before + public void beforeTest() throws Exception { + processBeforeTest(); + } + + @Test + public void testX509NoTrustFromJksConnectCoapSuccessUpdateAttributesSuccess() throws Exception { + clientX509FromJksUpdateAttributesTest(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityPemIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityPemIntegrationTest.java new file mode 100644 index 0000000000..9e65943622 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityPemIntegrationTest.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2024 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.coap.security.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.security.AbstractCoapSecurityIntegrationTest; + +@Slf4j +@DaoSqlTest +public class CoapClientX509SecurityPemIntegrationTest extends AbstractCoapSecurityIntegrationTest { + @Before + public void beforeTest() throws Exception { + processBeforeTest(); + } + + @Test + public void testX509NoTrustFromPathConnectCoapSuccessUpdateAttributesSuccess() throws Exception { + clientX509FromPathUpdateFeatureTypeTest(FeatureType.ATTRIBUTES); + } + @Test + public void testX509NoTrustFromPathConnectCoapSuccessUpdateTelemetrySuccess() throws Exception { + clientX509FromPathUpdateFeatureTypeTest(FeatureType.TELEMETRY); + } @Test + public void testTwoDevicesWithSamePortX509NoTrustFromPathConnectCoapSuccess() throws Exception { + twoClientWithSamePortX509FromPathConnectTest(); + } +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java index 292fae3456..4c8b9094f0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java @@ -44,9 +44,6 @@ import static org.junit.Assert.assertTrue; @DaoSqlTest public class CoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { - private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + - " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; - @Before public void beforeTest() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java index b02a97bbfa..8cbff6c105 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java @@ -40,9 +40,6 @@ import static org.junit.Assert.assertNotNull; @Slf4j public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoapIntegrationTest { - private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + - " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; - @Before public void beforeTest() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/x509/CertPrivateKey.java b/application/src/test/java/org/thingsboard/server/transport/coap/x509/CertPrivateKey.java new file mode 100644 index 0000000000..8fce5b7e79 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/x509/CertPrivateKey.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2024 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.coap.x509; + +import org.apache.commons.io.FileUtils; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.thingsboard.common.util.SslUtil; +import java.io.File; +import java.io.IOException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.List; + +public class CertPrivateKey { + private final X509Certificate cert; + private PrivateKey privateKey; + + public CertPrivateKey(String certFilePathPem, String keyFilePathPem) throws Exception { + List certs = SslUtil.readCertFile(fileRead(certFilePathPem)); + this.cert = certs.get(0); + this.privateKey = SslUtil.readPrivateKey(fileRead(keyFilePathPem), null); + if (this.privateKey instanceof BCECPrivateKey) { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(this.privateKey.getEncoded()); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + this.privateKey = keyFactory.generatePrivate(keySpec); + } + if (!(this.privateKey instanceof ECPrivateKey)) { + throw new RuntimeException("Private key generation must be of type java.security.interfaces.ECPrivateKey, which is used in the standard Java API!"); + } + } + + public CertPrivateKey(X509Certificate cert, PrivateKey privateKey) { + this.cert = cert; + this.privateKey = privateKey; + } + + public X509Certificate getCert() { + return this.cert; + } + + public PrivateKey getPrivateKey() { + return this.privateKey; + } + + private String fileRead(String fileName) throws IOException { + ClassLoader classLoader = getClass().getClassLoader(); + File file = new File(classLoader.getResource(fileName).getFile()); + return FileUtils.readFileToString(file, "UTF-8"); + } + + public static String convertCertToPEM(X509Certificate certificate) throws Exception { + StringBuilder pemBuilder = new StringBuilder(); + pemBuilder.append("-----BEGIN CERTIFICATE-----\n"); + // Copy cert to Base64 + String base64EncodedCert = Base64.getEncoder().encodeToString(certificate.getEncoded()); + int index = 0; + while (index < base64EncodedCert.length()) { + pemBuilder.append(base64EncodedCert, index, Math.min(index + 64, base64EncodedCert.length())); + pemBuilder.append("\n"); + index += 64; + } + pemBuilder.append("-----END CERTIFICATE-----\n"); + return pemBuilder.toString(); + } +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java b/application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java new file mode 100644 index 0000000000..d13380b19c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java @@ -0,0 +1,239 @@ +/** + * Copyright © 2016-2024 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.coap.x509; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapObserveRelation; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.config.CoapConfig; +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.config.ValueException; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.eclipse.californium.scandium.DTLSConnector; +import org.eclipse.californium.scandium.config.DtlsConfig.SignatureAndHashAlgorithmsDefinition; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.CertificateType; +import org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm; +import org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.HashAlgorithm; +import org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SignatureAlgorithm; +import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; +import org.eclipse.californium.scandium.dtls.x509.CertificateProvider; +import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.transport.coap.CoapTestCallback; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.eclipse.californium.core.config.CoapConfig.DEFAULT_BLOCKWISE_STATUS_LIFETIME_IN_SECONDS; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_AUTO_HANDSHAKE_TIMEOUT; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CIPHER_SUITES; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_FRAGMENTED_HANDSHAKE_MESSAGE_LENGTH; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_PENDING_HANDSHAKE_RESULT_JOBS; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_RETRANSMISSIONS; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_RETRANSMISSION_TIMEOUT; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECEIVE_BUFFER_SIZE; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_SIGNATURE_AND_HASH_ALGORITHMS; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_USE_HELLO_VERIFY_REQUEST; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_USE_MULTI_HANDSHAKE_MESSAGE_RECORDS; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT; +import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.CLIENT_ONLY; +import static org.eclipse.californium.scandium.config.DtlsConfig.MODULE; +import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_ECDSA; +import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_RSA; +import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA384_WITH_ECDSA; + +@Slf4j +public class CoapClientX509Test { + + private static final long CLIENT_REQUEST_TIMEOUT = 60000L; + + private final CoapClient clientX509; + private final DTLSConnector dtlsConnector; + private final Configuration config; + private final CertPrivateKey certPrivateKey; + private final String coapsBaseUrl; + + @Getter + private CoAP.Type type = CoAP.Type.CON; + + public CoapClientX509Test(CertPrivateKey certPrivateKey, FeatureType featureType, String coapsBaseUrl, Integer fixedPort) { + this.certPrivateKey = certPrivateKey; + this.coapsBaseUrl = coapsBaseUrl; + this.config = createConfiguration(); + this.dtlsConnector = createDTLSConnector(fixedPort); + this.clientX509 = createClient(getFeatureTokenUrl(featureType)); + } + public void disconnect() { + if (clientX509 != null) { + clientX509.shutdown(); + } + } + + public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException { + return this.postMethod(requestBody.getBytes()); + } + + public CoapResponse postMethod(byte[] requestBodyBytes) throws ConnectorException, IOException { + return clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(requestBodyBytes, MediaTypeRegistry.APPLICATION_JSON); + } + + public void postMethod(CoapHandler handler, String payload, int format) { + clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(handler, payload, format); + } + + public void postMethod(CoapHandler handler, byte[] payload) { + clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(handler, payload, MediaTypeRegistry.APPLICATION_JSON); + } + public void postMethod(CoapHandler handler, byte[] payload, int format) { + clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(handler, payload, format); + } + + public CoapResponse getMethod() throws ConnectorException, IOException { + return clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).get(); + } + + public CoapObserveRelation getObserveRelation(CoapTestCallback callback) { + return getObserveRelation(callback, true); + } + + public CoapObserveRelation getObserveRelation(CoapTestCallback callback, boolean confirmable) { + Request request = Request.newGet().setObserve(); + request.setType(confirmable ? CoAP.Type.CON : CoAP.Type.NON); + return clientX509.observe(request, callback); + } + + public void setURI(String featureTokenUrl) { + if (clientX509 == null) { + throw new RuntimeException("Failed to connect! CoapClient is not initialized!"); + } + clientX509.setURI(featureTokenUrl); + } + + public void setURI(String accessToken, FeatureType featureType) { + if (featureType == null) { + featureType = FeatureType.ATTRIBUTES; + } + setURI(getFeatureTokenUrl(accessToken, featureType)); + } + + public void useCONs() { + if (clientX509 == null) { + throw new RuntimeException("Failed to connect! CoapClient is not initialized!"); + } + type = CoAP.Type.CON; + clientX509.useCONs(); + } + + public void useNONs() { + if (clientX509 == null) { + throw new RuntimeException("Failed to connect! CoapClient is not initialized!"); + } + type = CoAP.Type.NON; + clientX509.useNONs(); + } + + private Configuration createConfiguration() { + Configuration clientCoapConfig = new Configuration(); + clientCoapConfig.set(CoapConfig.BLOCKWISE_STRICT_BLOCK2_OPTION, true); + clientCoapConfig.set(CoapConfig.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); + clientCoapConfig.set(CoapConfig.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME_IN_SECONDS, TimeUnit.SECONDS); + clientCoapConfig.set(CoapConfig.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); + clientCoapConfig.set(CoapConfig.RESPONSE_MATCHING, CoapConfig.MatcherMode.RELAXED); + clientCoapConfig.set(CoapConfig.PREFERRED_BLOCK_SIZE, 1024); + clientCoapConfig.set(CoapConfig.MAX_MESSAGE_SIZE, 1024); + clientCoapConfig.set(DTLS_ROLE, CLIENT_ONLY); + clientCoapConfig.set(DTLS_MAX_RETRANSMISSIONS, 2); + clientCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, 5000, MILLISECONDS); + clientCoapConfig.set(DTLS_MAX_RETRANSMISSION_TIMEOUT, 60000, TimeUnit.MILLISECONDS); + clientCoapConfig.set(DTLS_USE_HELLO_VERIFY_REQUEST, false); + clientCoapConfig.set(DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT, false); + clientCoapConfig.set(DTLS_MAX_FRAGMENTED_HANDSHAKE_MESSAGE_LENGTH, 22490); + clientCoapConfig.set(DTLS_AUTO_HANDSHAKE_TIMEOUT, 100000, TimeUnit.MILLISECONDS); + clientCoapConfig.set(DTLS_MAX_PENDING_HANDSHAKE_RESULT_JOBS, 64); + clientCoapConfig.set(DTLS_USE_MULTI_HANDSHAKE_MESSAGE_RECORDS, false); + clientCoapConfig.set(DTLS_RECEIVE_BUFFER_SIZE, 8192); + clientCoapConfig.setTransient(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + SignatureAndHashAlgorithmsDefinition algorithmsDefinition = new SignatureAndHashAlgorithmsDefinition(MODULE + "SIGNATURE_AND_HASH_ALGORITHMS", "List of DTLS signature- and hash-algorithms.\nValues e.g SHA256withECDSA or ED25519."); + SignatureAndHashAlgorithm SHA384_WITH_RSA = new SignatureAndHashAlgorithm(HashAlgorithm.SHA384, + SignatureAlgorithm.RSA); + List algorithms = null; + try { + algorithms = algorithmsDefinition.checkValue(Arrays.asList(SHA256_WITH_ECDSA, SHA256_WITH_RSA, SHA384_WITH_ECDSA, SHA384_WITH_RSA)); + } catch (ValueException e) { + throw new RuntimeException(e); + } + clientCoapConfig.setTransient(DTLS_SIGNATURE_AND_HASH_ALGORITHMS); + clientCoapConfig.set(DTLS_SIGNATURE_AND_HASH_ALGORITHMS, algorithms); + clientCoapConfig.setTransient(DTLS_CIPHER_SUITES); + clientCoapConfig.set(DTLS_CIPHER_SUITES, Arrays.asList(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)); + clientCoapConfig.setTransient(DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT); + clientCoapConfig.set(DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT, false); + return clientCoapConfig; + } + + private DTLSConnector createDTLSConnector(Integer fixedPort) { + try { + // Create DTLS config client + DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(this.config); + configBuilder.setAdvancedCertificateVerifier(new TbAdvancedCertificateVerifier()); + X509Certificate[] certificateChainClient = new X509Certificate[]{this.certPrivateKey.getCert()}; + CertificateProvider certificateProvider = new SingleCertificateProvider(this.certPrivateKey.getPrivateKey(), certificateChainClient, Collections.singletonList(CertificateType.X_509)); + configBuilder.setCertificateIdentityProvider(certificateProvider); + if (fixedPort != null) { + InetSocketAddress localAddress = new InetSocketAddress("0.0.0.0", fixedPort); + configBuilder.setAddress(localAddress); + configBuilder.setReuseAddress(true); + } + return new DTLSConnector(configBuilder.build()); + } catch (Exception e) { + throw new RuntimeException("", e); + } + } + + private CoapClient createClient(String featureTokenUrl) { + CoapClient client = new CoapClient(featureTokenUrl); + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + builder.setConnector(dtlsConnector); + client.setEndpoint(builder.build()); + return client; + } + + public String getFeatureTokenUrl(FeatureType featureType) { + return this.coapsBaseUrl + featureType.name().toLowerCase(); + } + + public String getFeatureTokenUrl(String token, FeatureType featureType) { + return this.coapsBaseUrl + token + "/" + featureType.name().toLowerCase(); + } +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/x509/TbAdvancedCertificateVerifier.java b/application/src/test/java/org/thingsboard/server/transport/coap/x509/TbAdvancedCertificateVerifier.java new file mode 100644 index 0000000000..a08746eefa --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/x509/TbAdvancedCertificateVerifier.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016-2024 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.coap.x509; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel; +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.HandshakeException; +import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; +import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; +import org.eclipse.californium.scandium.util.ServerNames; + +import javax.security.auth.x500.X500Principal; +import java.net.InetSocketAddress; +import java.security.PublicKey; +import java.security.cert.CertPath; +import java.util.Arrays; +import java.util.List; + +@Slf4j +public class TbAdvancedCertificateVerifier implements NewAdvancedCertificateVerifier { + + private HandshakeResultHandler resultHandler; + /** + * Get the list of supported certificate types in order of preference. + * + * @return the list of supported certificate types. + * @since 3.0 (renamed from getSupportedCertificateType) + */ + @Override + public List getSupportedCertificateTypes() { + return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY); + } + + /** + * Validates the certificate provided by the the peer as part of the + * certificate message. + *

+ * If a x509 certificate chain is provided in the certificate message, + * validate the chain and key usage. If a RawPublicKey certificate is + * provided, check, if this public key is trusted. + * + * @param cid connection ID + * @param serverName indicated server names. May be {@code null}, if not + * available or SNI is not enabled. + * @param remotePeer socket address of remote peer + * @param clientUsage indicator to check certificate usage. {@code true}, + * check key usage for client, {@code false} for server. + * @param verifySubject {@code true} to verify the certificate's subjects, + * {@code false}, if not. + * @param truncateCertificatePath {@code true} truncate certificate path at + * a trusted certificate before validation. + * @param message certificate message to be validated + * @return certificate verification result, or {@code null}, if result is + * provided asynchronous. + * @since 3.0 (removed DTLSSession session, added remotePeer and + * verifySubject) + */ + @Override + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, InetSocketAddress remotePeer, + boolean clientUsage, boolean verifySubject, boolean truncateCertificatePath, + CertificateMessage message) { + CertPath certChain = message.getCertificateChain(); + CertificateVerificationResult result; + + if (certChain == null) { + PublicKey publicKey = message.getPublicKey(); + result = new CertificateVerificationResult(cid, publicKey, null); + } else { + if (message.getCertificateChain().getCertificates().isEmpty()) { + result = new CertificateVerificationResult(cid, new HandshakeException("Empty certificate chain", + new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE)), null); + } else { + result = new CertificateVerificationResult(cid, certChain, null); + } + } + + return result; + } + + /** + * Return an list of certificate authorities which are trusted + * for authenticating peers. + * + * @return a non-null (possibly empty) list of accepted CA issuers. + */ + @Override + public List getAcceptedIssuers() { + log.trace("getAcceptedIssuers: return null"); + return null; + } + + /** + * Set the handler for asynchronous handshake results. + *

+ * Called during initialization of the {link DTLSConnector}. Synchronous + * implementations may just ignore this using an empty implementation. + * + * @param resultHandler handler for asynchronous master secret results. This + * handler MUST NOT be called from the thread calling + * {@link #verifyCertificate(ConnectionId, ServerNames, InetSocketAddress, boolean, boolean, boolean, CertificateMessage)}, + * instead just return the result there. + */ + @Override + public void setResultHandler(HandshakeResultHandler resultHandler) { + if (this.resultHandler != null && resultHandler != null && this.resultHandler != resultHandler) { + throw new IllegalStateException("handshake result handler already set!"); + } + this.resultHandler = resultHandler; + } +} diff --git a/application/src/test/resources/coap/credentials/client/cert.pem b/application/src/test/resources/coap/credentials/client/cert.pem new file mode 100644 index 0000000000..4d385a588f --- /dev/null +++ b/application/src/test/resources/coap/credentials/client/cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB/TCCAaOgAwIBAgIIVNrVgKT9OE8wCgYIKoZIzj0EAwIwWjEOMAwGA1UEAxMF +Y2YtY2ExFDASBgNVBAsTC0NhbGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElv +VDEPMA0GA1UEBxMGT3R0YXdhMQswCQYDVQQGEwJDQTAeFw0yMzEwMjYwODA4MjJa +Fw0yNTEwMjUwODA4MjJaMF4xEjAQBgNVBAMTCWNmLWNsaWVudDEUMBIGA1UECxML +Q2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZPdHRh +d2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQxYO5/M5 +ie6+3QPOaAy5MD6CkFILZwIb2rOBCX/EWPaocX1H+eynUnaEEbmqxeN6rnI/pH19 +j4PtsegfHLrzzaNPME0wHQYDVR0OBBYEFKwEDLTJ+5cQoZfbjWN1vJ2ssgK+MAsG +A1UdDwQEAwIHgDAfBgNVHSMEGDAWgBSxVzoI1TL87++hsUb9vQwqODzgUTAKBggq +hkjOPQQDAgNIADBFAiA2KCOw3n2AK9Vm8u2u1bQREIEs3tKAU7eFjpNFn929NwIh +AInhBGoEwS2Xlu5bdZSfWnujoRrEQiIiQpStmLxVcIsH +-----END CERTIFICATE----- diff --git a/application/src/test/resources/coap/credentials/client/cert_01.pem b/application/src/test/resources/coap/credentials/client/cert_01.pem new file mode 100644 index 0000000000..3b97ab4aad --- /dev/null +++ b/application/src/test/resources/coap/credentials/client/cert_01.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICIzCCAcmgAwIBAgIUZZCGYm65c9vU0Xfvd/pAnLVDouUwCgYIKoZIzj0EAwIw +ZzELMAkGA1UEBhMCVUExDTALBgNVBAgMBEtpeXYxDTALBgNVBAcMBEtpeXYxFDAS +BgNVBAoMC1RoaW5nc2JvYXJkMRIwEAYDVQQLDAlkZXZlbG9wZXIxEDAOBgNVBAMM +B2NlcnRfMDEwHhcNMjQxMjE4MTU1NjE1WhcNMjUxMjE4MTU1NjE1WjBnMQswCQYD +VQQGEwJVQTENMAsGA1UECAwES2l5djENMAsGA1UEBwwES2l5djEUMBIGA1UECgwL +VGhpbmdzYm9hcmQxEjAQBgNVBAsMCWRldmVsb3BlcjEQMA4GA1UEAwwHY2VydF8w +MTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNU1tE6o/QpqJJqpy+m+UoPuQe5g +eTgS4M3x0iQS6pzNEJBhzbnOp/BysGMB4wKiAWTRuKdH/gcRXDBTjLd/d7ijUzBR +MB0GA1UdDgQWBBSiao1iNWYzlsrSbxYqbda116HG1jAfBgNVHSMEGDAWgBSiao1i +NWYzlsrSbxYqbda116HG1jAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA +MEUCIB2aCM/nvDqic9NkoSX/71GwksLiAKiFNkt2BZQykrcHAiEAr2h5IMdkyurN +Jy/idx2y44CP0tMq/3QV0QLCQFJIi6s= +-----END CERTIFICATE----- diff --git a/application/src/test/resources/coap/credentials/client/key.pem b/application/src/test/resources/coap/credentials/client/key.pem new file mode 100644 index 0000000000..02ca740c93 --- /dev/null +++ b/application/src/test/resources/coap/credentials/client/key.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDn0+4CuLeX7xwBs0ts +UUEDB3+HRwRKdIPeJlIbKuvvEQ== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/application/src/test/resources/coap/credentials/client/key_01.pem b/application/src/test/resources/coap/credentials/client/key_01.pem new file mode 100644 index 0000000000..d5918e8181 --- /dev/null +++ b/application/src/test/resources/coap/credentials/client/key_01.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJldU1MBuJUJnNHa9Ob5NGlXc/Os6put9eh1TlIbuScnoAoGCCqGSM49 +AwEHoUQDQgAE1TW0Tqj9CmokmqnL6b5Sg+5B7mB5OBLgzfHSJBLqnM0QkGHNuc6n +8HKwYwHjAqIBZNG4p0f+BxFcMFOMt393uA== +-----END EC PRIVATE KEY----- diff --git a/application/src/test/resources/coap/credentials/coapclientTest.jks b/application/src/test/resources/coap/credentials/coapclientTest.jks new file mode 100644 index 0000000000000000000000000000000000000000..ca8c8ed1d77f31bab0711a31eeb63ae952c933ef GIT binary patch literal 20462 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 z z8Ycse&LxPB6tj(~84FM=Ymk`uyhaOc1nPhjqtTVUuEPve(TnAH*8{gmfzk(e9|^nC zO&8dv0X@^?xRbtoX#HlQM3P%yl%fT-|Xshij+iQ&3{+cYb}op*0%%%FFVG`WXAnuzfs3vuZVyiwg+qhQ&#YfQmnOev3YR&Fs|`_w zvd6=6<22})n8JA-Pc`K5Xu|ITWkSh0{loqpH9TxHe5Q8#ZkA@M7}rRDd97vcr{(aK(_NO*AuoQXip%VO3*IND{R*i`{< z)R%+f)}YwwP^@Jsa4xv|@u4q5q%|6rsl5{(;mplDe5J}dYhn8@VG>ix_j5M!h~27f zp~PsH79uu#tr|%pj?%+;SmeA&?Y8eB7>_Z$TMI+WpRz@Sk*W}x4H_${CqBv#a zf>W*e-W}7yPptkfa~ov-1>zP|_UoGnmkUKZ6ujCTAd-1Z3pzcPP$`|&2h8Ag@9<+&Z*!iX)(*Q`Mjx{K\hylOan*;kqm$M}?1Cu_<6QnGg~ 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 BIWPbK literal 0 HcmV?d00001 diff --git a/application/src/test/resources/coap/credentials/coapserverTest.jks b/application/src/test/resources/coap/credentials/coapserverTest.jks new file mode 100644 index 0000000000000000000000000000000000000000..4adf1f4f8942be609d1716a5fd1518fcaff4b354 GIT binary patch literal 1985 zcmezO_TO6u1_mY|W&zXO#i>PQsYUV0sYN9W42+ZSn@V{wutw;a8dw6kIR;IP89>at zfSHMriAjXBf58F)zP295%^Tb||C(OH`X?sBfQyYotIgw_EekV~fuJG30WTYKC<`+W zTTp&}iL;}DoH(zMiJ_69rJ=E@nW;sTIIl5?YYye2XbA>8#vsx_n2jB5CKDsnaArn! zW+w)ge=FwI7cHJ|%eu@(G-FAY+S(^_39D9}-*m-shK^e#)1ssMCD*@~;(nFg_Ta>y z?N&?l;ww~UZ~nf+?Mg&T+Ns^vi=7P|4Y+|0l;vjux|ekU&?X>9m4yf74Q)0?R#tXq zMgv)pARmhui^!?J&Tq_}%!Ic3D{SpD6s_`AZ6;@q>4`zm{)JXZ1vbOGJ|Iycz7{BU%%JhG0S_A&&~Yq`T197 zY4hTPLOi~II-IP((d1XcyzP55uO77GyOzUW$KZZ1mR&Q#$Zd&Y;^IYfvw8Q6xD~d~ zn9kb0%o#>SBA zo4GzRv~S|fxie4jH`m@Jvdgxp$zPmf3wg7^w!(`&fwHGny>~WVZno49+;P<4H^$IiP(QW=J06N6QA`P zTF*B9{%dn%-gZT#bj@raV8{ndb>N)L8WI#5970k?hUR_RWn^TxGJ7x>xB@fSH{RYW zyk(y=8obsWSi4AQsnE;gUGu#ss?VrZYMT3V+dU=)<^wFMx0f%K?4P7r*sQl9Rq3?L ztL7yM&)aI&f4nXD@i;h_6XYDM|+S9Vlm7UkNXN{my2-6?r_%u*9R5Xx>q&*2oM@I!`VC9sVmYJMb zlBxizA~KUxiwP!2%mhr|)Y!utuT$5cU7InT(XmYD%JxZ)hu@u${vPrA_Nxqm3Pv_2 zh1mb06|>5}T>3Qg)&D{HoqqqE>CvxZi%*S$k9~V_GNpPsWV=Aw25)@iqu2Q8X I`>3=P0KnLuSpWb4 literal 0 HcmV?d00001 diff --git a/application/src/test/resources/coap/credentials/server/cert.pem b/application/src/test/resources/coap/credentials/server/cert.pem new file mode 100644 index 0000000000..03eb9e372e --- /dev/null +++ b/application/src/test/resources/coap/credentials/server/cert.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIBaDCCAQ2gAwIBAgIUCY+goBAOhowBs7BHs/qXdAX8XFgwCgYIKoZIzj0EAwIw +ETEPMA0GA1UEAwwGUm9vdENBMB4XDTI0MTIxOTEzNTY1OFoXDTM0MTIxNzEzNTY1 +OFowETEPMA0GA1UEAwwGU2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +/qief3Kjnz0FpkQVaKRqJq3kHmCqqs+y1EGYLEZZAqLFvxmv7xoL6muG4Mj8tzqk +Ll94JJuz97hG1FiEZsq7O6NDMEEwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsG +AQUFBwMBMB0GA1UdDgQWBBTK/UPsN0I2ErVPILWKMRV6TSeAmTAKBggqhkjOPQQD +AgNJADBGAiEA8EhlOwvTbwGlxo55UIOJp9LBbCp0BEIWojlu8PzOVSsCIQDlV24S +3BUJVCuMRujO5lTfJLxaSKkOEIgRANwIGi88WA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBGzCBwgIUP/PGQOKa5EyvsIXNgvv9PNietyEwCgYIKoZIzj0EAwMwEDEOMAwG +A1UEAwwFVFJVU1QwHhcNMjQxMjE5MTM1NjU4WhcNMzQxMjE3MTM1NjU4WjARMQ8w +DQYDVQQDDAZSb290Q0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT+qJ5/cqOf +PQWmRBVopGomreQeYKqqz7LUQZgsRlkCosW/Ga/vGgvqa4bgyPy3OqQuX3gkm7P3 +uEbUWIRmyrs7MAoGCCqGSM49BAMDA0gAMEUCIQD2DY3UDXbzaIBKrsCtohKlEunH +ip9LkSeYfSKCnfm23gIgA8AEJdunpRmPkilxgy6wZSLLROqDpGDnhnyv8dsR8cc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBLTCB1AIUcsuauXAqvIS2RQcNPYysETJUAvwwCgYIKoZIzj0EAwMwIzEhMB8G +A1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTI0MTIxOTEzNTY1OFoX +DTM0MTIxNzEzNTY1OFowEDEOMAwGA1UEAwwFVFJVU1QwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAAT+qJ5/cqOfPQWmRBVopGomreQeYKqqz7LUQZgsRlkCosW/Ga/v +Ggvqa4bgyPy3OqQuX3gkm7P3uEbUWIRmyrs7MAoGCCqGSM49BAMDA0gAMEUCIQCM +DV8sfoArfWiXAUF2LNS3kkHD7sgb91jr2+poEHgBBgIgXf9VeJp3K5jHX6lJwtE8 +nd+jW7T9nhTc/5njHg7xons= +-----END CERTIFICATE----- +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIB+Z69so6HqCCWo5VOFxGsLXOlTWIYijOtzt+SeNGrgPoAoGCCqGSM49 +AwEHoUQDQgAE/qief3Kjnz0FpkQVaKRqJq3kHmCqqs+y1EGYLEZZAqLFvxmv7xoL +6muG4Mj8tzqkLl94JJuz97hG1FiEZsq7Ow== +-----END EC PRIVATE KEY----- diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java index 0b1ba35709..5f4f1d152b 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java @@ -17,7 +17,6 @@ package org.thingsboard.server.coapserver; import org.eclipse.californium.core.CoapServer; -import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.concurrent.ConcurrentMap; @@ -25,5 +24,5 @@ public interface CoapServerService { CoapServer getCoapServer() throws UnknownHostException; - ConcurrentMap getDtlsSessionsMap(); + ConcurrentMap getDtlsSessionsMap(); } diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java index f3d30bfea4..41b10b31a3 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java @@ -78,7 +78,7 @@ public class DefaultCoapServerService implements CoapServerService { } @Override - public ConcurrentMap getDtlsSessionsMap() { + public ConcurrentMap getDtlsSessionsMap() { return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionsMap() : null; } 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 cc7dcad77c..d61d7f279c 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 @@ -109,7 +109,8 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri if (msg != null && strCert.equals(msg.getCredentials())) { DeviceProfile deviceProfile = msg.getDeviceProfile(); if (msg.hasDeviceInfo() && deviceProfile != null) { - tbCoapDtlsSessionInMemoryStorage.put(remotePeer, new TbCoapDtlsSessionInfo(msg, deviceProfile)); + TbCoapDtlsSessionKey tbCoapDtlsSessionKey = new TbCoapDtlsSessionKey(remotePeer, msg.getCredentials()); + tbCoapDtlsSessionInMemoryStorage.put(tbCoapDtlsSessionKey, new TbCoapDtlsSessionInfo(msg, deviceProfile)); } break; } @@ -138,7 +139,7 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri public void setResultHandler(HandshakeResultHandler resultHandler) { } - public ConcurrentMap getTbCoapDtlsSessionsMap() { + public ConcurrentMap getTbCoapDtlsSessionsMap() { return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionsMap(); } diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java index b4101f1763..5ff44561d8 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java @@ -18,7 +18,6 @@ package org.thingsboard.server.coapserver; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import java.net.InetSocketAddress; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -26,7 +25,7 @@ import java.util.concurrent.ConcurrentMap; @Data public class TbCoapDtlsSessionInMemoryStorage { - private final ConcurrentMap dtlsSessionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap dtlsSessionsMap = new ConcurrentHashMap<>(); private long dtlsSessionInactivityTimeout; private long dtlsSessionReportTimeout; @@ -36,9 +35,9 @@ public class TbCoapDtlsSessionInMemoryStorage { this.dtlsSessionReportTimeout = dtlsSessionReportTimeout; } - public void put(InetSocketAddress remotePeer, TbCoapDtlsSessionInfo dtlsSessionInfo) { - log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", remotePeer, dtlsSessionInfo.getLastActivityTime()); - dtlsSessionsMap.putIfAbsent(remotePeer, dtlsSessionInfo); + public void put(TbCoapDtlsSessionKey tbCoapDtlsSessionKey, TbCoapDtlsSessionInfo dtlsSessionInfo) { + log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", tbCoapDtlsSessionKey, dtlsSessionInfo.getLastActivityTime()); + dtlsSessionsMap.putIfAbsent(tbCoapDtlsSessionKey, dtlsSessionInfo); } public void evictTimeoutSessions() { diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionKey.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionKey.java new file mode 100644 index 0000000000..cf3e0b4fec --- /dev/null +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionKey.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2024 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.coapserver; + +import java.net.InetSocketAddress; +import java.util.Objects; + +public record TbCoapDtlsSessionKey(InetSocketAddress peerAddress, String credentials) { + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TbCoapDtlsSessionKey that = (TbCoapDtlsSessionKey) o; + return Objects.equals(peerAddress, that.peerAddress) && + Objects.equals(credentials, that.credentials); + } +} + diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 67ae1b9ae7..f151283d37 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -24,7 +24,10 @@ import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.ResourceObserver; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.californium.elements.auth.X509CertPath; import org.thingsboard.server.coapserver.CoapServerService; +import org.thingsboard.server.coapserver.TbCoapDtlsSessionKey; import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.adaptor.JsonConverter; @@ -47,12 +50,14 @@ import org.thingsboard.server.transport.coap.client.CoapClientContext; import org.thingsboard.server.transport.coap.client.TbCoapClientState; import java.net.InetSocketAddress; +import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; +import java.security.cert.X509Certificate; import static org.eclipse.californium.elements.DtlsEndpointContext.KEY_SESSION_ID; @@ -65,7 +70,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private static final int FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST = 3; private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4; - private final ConcurrentMap dtlsSessionsMap; + private final ConcurrentMap dtlsSessionsMap; private final long timeout; private final long piggybackTimeout; private final CoapClientContext clients; @@ -177,11 +182,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { var dtlsSessionId = request.getSourceContext().get(KEY_SESSION_ID); if (dtlsSessionsMap != null && dtlsSessionId != null && !dtlsSessionId.isEmpty()) { - TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionsMap - .computeIfPresent(request.getSourceContext().getPeerAddress(), (dtlsSessionIdStr, dtlsSessionInfo) -> { - dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis()); - return dtlsSessionInfo; - }); + TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = this.getCoapDtlsSessionInfo(request.getSourceContext()); if (tbCoapDtlsSessionInfo != null) { processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getMsg(), tbCoapDtlsSessionInfo.getDeviceProfile()); } else { @@ -251,7 +252,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { TransportProtos.SessionInfoProto sessionInfo = clients.getNewSyncSession(clientState); UUID sessionId = toSessionId(sessionInfo); transportService.process(sessionInfo, clientState.getAdaptor().convertToPostAttributes(sessionId, request, - clientState.getConfiguration().getAttributesMsgDescriptor()), + clientState.getConfiguration().getAttributesMsgDescriptor()), new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); } @@ -259,7 +260,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { TransportProtos.SessionInfoProto sessionInfo = clients.getNewSyncSession(clientState); UUID sessionId = toSessionId(sessionInfo); transportService.process(sessionInfo, clientState.getAdaptor().convertToPostTelemetry(sessionId, request, - clientState.getConfiguration().getTelemetryMsgDescriptor()), + clientState.getConfiguration().getTelemetryMsgDescriptor()), new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); } @@ -458,5 +459,32 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } + private TbCoapDtlsSessionInfo getCoapDtlsSessionInfo(EndpointContext endpointContext) { + InetSocketAddress peerAddress = endpointContext.getPeerAddress(); + String certPemStr = getCertPem(endpointContext); + TbCoapDtlsSessionKey tbCoapDtlsSessionKey = StringUtils.isNotBlank(certPemStr) ? new TbCoapDtlsSessionKey(peerAddress, certPemStr) : null; + TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo; + if (tbCoapDtlsSessionKey != null) { + tbCoapDtlsSessionInfo = dtlsSessionsMap + .computeIfPresent(tbCoapDtlsSessionKey, (dtlsSessionIdStr, dtlsSessionInfo) -> { + dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis()); + return dtlsSessionInfo; + }); + } else { + tbCoapDtlsSessionInfo = null; + } + return tbCoapDtlsSessionInfo; + } + private String getCertPem(EndpointContext endpointContext) { + try { + X509CertPath certPath = (X509CertPath) endpointContext.getPeerIdentity(); + X509Certificate x509Certificate = (X509Certificate) certPath.getPath().getCertificates().get(0); + return Base64.getEncoder().encodeToString(x509Certificate.getEncoded()); + } catch (Exception e) { + log.error("Failed to get cert PEM: [{}]", endpointContext.getPeerAddress(), e); + return null; + } + } } +