Merge branch 'master' into fix_bug_lwm2m_profile_update_dynamic
This commit is contained in:
		
						commit
						80e93d8a40
					
				@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5");
 | 
			
		||||
    private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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<String> expectedKeys = getKeysFromNode(expectedNode);
 | 
			
		||||
            List<String> actualKeys = getActualKeysList(deviceId, expectedKeys, "attributes/CLIENT_SCOPE");
 | 
			
		||||
            assertNotNull(actualKeys);
 | 
			
		||||
 | 
			
		||||
            Set<String> actualKeySet = new HashSet<>(actualKeys);
 | 
			
		||||
            Set<String> expectedKeySet = new HashSet<>(expectedKeys);
 | 
			
		||||
            assertEquals(expectedKeySet, actualKeySet);
 | 
			
		||||
 | 
			
		||||
            String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet, "attributes/CLIENT_SCOPE");
 | 
			
		||||
            List<Map<String, Object>> actualValues = doGetAsyncTyped(getAttributesValuesUrl, new TypeReference<>() {
 | 
			
		||||
            });
 | 
			
		||||
            assertValuesList(actualValues, expectedNode);
 | 
			
		||||
        }
 | 
			
		||||
        return clientX509;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private List<String> getActualKeysList(DeviceId deviceId, List<String> expectedKeys, String apiSuffix) throws Exception {
 | 
			
		||||
        long start = System.currentTimeMillis();
 | 
			
		||||
        long end = System.currentTimeMillis() + 5000;
 | 
			
		||||
 | 
			
		||||
        List<String> 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<String> actualKeySet, String apiSuffix) {
 | 
			
		||||
        return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/" + apiSuffix + "?keys=" + String.join(",", actualKeySet);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private List getKeysFromNode(JsonNode jNode) {
 | 
			
		||||
        List<String> jKeys = new ArrayList<>();
 | 
			
		||||
        Iterator<String> fieldNames = jNode.fieldNames();
 | 
			
		||||
        while (fieldNames.hasNext()) {
 | 
			
		||||
            jKeys.add(fieldNames.next());
 | 
			
		||||
        }
 | 
			
		||||
        return jKeys;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void assertValuesList(List<Map<String, Object>> actualValues, JsonNode expectedValues) {
 | 
			
		||||
        assertTrue(actualValues.size() > 0);
 | 
			
		||||
        assertEquals(expectedValues.size(), actualValues.size());
 | 
			
		||||
        for (Map<String, Object> 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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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<X509Certificate> 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<SignatureAndHashAlgorithm> 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<CertificateType> 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.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 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<X500Principal> getAcceptedIssuers() {
 | 
			
		||||
        log.trace("getAcceptedIssuers: return null");
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the handler for asynchronous handshake results.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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-----
 | 
			
		||||
@ -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-----
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
-----BEGIN PRIVATE KEY-----
 | 
			
		||||
MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDn0+4CuLeX7xwBs0ts
 | 
			
		||||
UUEDB3+HRwRKdIPeJlIbKuvvEQ==
 | 
			
		||||
-----END PRIVATE KEY-----
 | 
			
		||||
@ -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-----
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@ -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-----
 | 
			
		||||
@ -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<InetSocketAddress, TbCoapDtlsSessionInfo> getDtlsSessionsMap();
 | 
			
		||||
    ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> getDtlsSessionsMap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@ public class DefaultCoapServerService implements CoapServerService {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ConcurrentMap<InetSocketAddress, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
 | 
			
		||||
    public ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
 | 
			
		||||
        return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionsMap() : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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<InetSocketAddress, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionsMap() {
 | 
			
		||||
    public ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionsMap() {
 | 
			
		||||
        return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionsMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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<InetSocketAddress, TbCoapDtlsSessionInfo> dtlsSessionsMap = new ConcurrentHashMap<>();
 | 
			
		||||
    private final ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> 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() {
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<InetSocketAddress, TbCoapDtlsSessionInfo> dtlsSessionsMap;
 | 
			
		||||
    private final ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> 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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user