23 changed files with 988 additions and 26 deletions
@ -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(); |
|||
} |
|||
} |
|||
@ -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----- |
|||
@ -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); |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue