CoAP DTLS support (#4316)

* dtls init commit

* added fixes after review

* fix typo

* changed translation for DeviceCredentialsType.X509_CERTIFICATE
This commit is contained in:
ShvaykaD 2021-03-30 18:28:03 +03:00 committed by GitHub
parent 68a73caada
commit fd3e18f18b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1050 additions and 113 deletions

View File

@ -583,7 +583,24 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}" bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}" timeout: "${COAP_TIMEOUT:10000}"
dtls:
# Enable/disable DTLS 1.2 support
enabled: "${COAP_DTLS_ENABLED:false}"
# Secure mode. Allowed values: NO_AUTH, X509
mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
# Path to the key store that holds the certificate
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store
key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
x509:
dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
swagger: swagger:
api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}"
security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}" security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}"

View File

@ -44,6 +44,10 @@
<groupId>org.eclipse.californium</groupId> <groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId> <artifactId>californium-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>scandium</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId> <artifactId>spring-context-support</artifactId>

View File

@ -46,6 +46,10 @@ public class CoapTransportContext extends TransportContext {
@Value("${transport.coap.timeout}") @Value("${transport.coap.timeout}")
private Long timeout; private Long timeout;
@Getter
@Autowired(required = false)
private TbCoapDtlsSettings dtlsSettings;
@Getter @Getter
@Autowired @Autowired
private JsonCoapAdaptor jsonCoapAdaptor; private JsonCoapAdaptor jsonCoapAdaptor;

View File

@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation;
import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.core.server.resources.ResourceObserver; import org.eclipse.californium.core.server.resources.ResourceObserver;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private static final int FEATURE_TYPE_POSITION = 4; private static final int FEATURE_TYPE_POSITION = 4;
private static final int REQUEST_ID_POSITION = 5; private static final int REQUEST_ID_POSITION = 5;
private static final int FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST = 3;
private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4;
private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID";
private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>(); private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>();
private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet();
private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet(); private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet();
public CoapTransportResource(CoapTransportContext coapTransportContext, String name) { private ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap;
public CoapTransportResource(CoapTransportContext coapTransportContext, ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap, String name) {
super(coapTransportContext, name); super(coapTransportContext, name);
this.setObservable(true); // enable observing this.setObservable(true); // enable observing
this.addObserver(new CoapResourceObserver()); this.addObserver(new CoapResourceObserver());
this.dtlsSessionIdMap = dtlsSessionIdMap;
// this.setObservable(false); // disable observing // this.setObservable(false); // disable observing
// this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs
// this.getAttributes().setObservable(); // mark observable in the Link-Format // this.getAttributes().setObservable(); // mark observable in the Link-Format
@ -187,14 +195,40 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
Exchange advanced = exchange.advanced(); Exchange advanced = exchange.advanced();
Request request = advanced.getRequest(); Request request = advanced.getRequest();
Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
if (credentials.isEmpty()) { if (!StringUtils.isEmpty(dtlsSessionIdStr)) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST); if (dtlsSessionIdMap != null) {
return; TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
.computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis());
return dtlsSessionInfo;
});
if (tbCoapDtlsSessionInfo != null) {
processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile());
} else {
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
}
} else {
processAccessTokenRequest(exchange, type, request);
}
} else {
processAccessTokenRequest(exchange, type, request);
}
} }
private void processAccessTokenRequest(CoapExchange exchange, SessionMsgType type, Request request) {
Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
if (credentials.isEmpty()) {
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
return;
}
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(),
new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> {
processRequest(exchange, type, request, sessionInfo, deviceProfile);
}));
}
private void processRequest(CoapExchange exchange, SessionMsgType type, Request request, TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
try { try {
TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile); TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);
@ -285,7 +319,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
log.trace("[{}] Failed to decode message: ", sessionId, e); log.trace("[{}] Failed to decode message: ", sessionId, e);
exchange.respond(CoAP.ResponseCode.BAD_REQUEST); exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
} }
}));
} }
private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) {
@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {
List<String> uriPath = request.getOptions().getUriPath(); List<String> uriPath = request.getOptions().getUriPath();
if (uriPath.size() >= ACCESS_TOKEN_POSITION) { if (uriPath.size() > ACCESS_TOKEN_POSITION) {
return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)));
} else { } else {
return Optional.empty(); return Optional.empty();
@ -322,9 +355,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
try { try {
if (uriPath.size() >= FEATURE_TYPE_POSITION) { if (uriPath.size() >= FEATURE_TYPE_POSITION) {
return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase()));
} else if (uriPath.size() == 3 && uriPath.contains(DataConstants.PROVISION)) { } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) {
if (uriPath.contains(DataConstants.PROVISION)) {
return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
} }
return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST - 1).toUpperCase()));
}
} catch (RuntimeException e) { } catch (RuntimeException e) {
log.warn("Failed to decode feature type: {}", uriPath); log.warn("Failed to decode feature type: {}", uriPath);
} }
@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
try { try {
if (uriPath.size() >= REQUEST_ID_POSITION) { if (uriPath.size() >= REQUEST_ID_POSITION) {
return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1)));
} else {
return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION_CERTIFICATE_REQUEST - 1)));
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
log.warn("Failed to decode feature type: {}", uriPath); log.warn("Failed to decode feature type: {}", uriPath);

View File

@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -30,6 +33,11 @@ import javax.annotation.PreDestroy;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service("CoapTransportService") @Service("CoapTransportService")
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
@ -44,34 +52,53 @@ public class CoapTransportService {
@Autowired @Autowired
private CoapTransportContext coapTransportContext; private CoapTransportContext coapTransportContext;
private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier;
private CoapServer server; private CoapServer server;
private ScheduledExecutorService dtlsSessionsExecutor;
@PostConstruct @PostConstruct
public void init() throws UnknownHostException { public void init() throws UnknownHostException {
log.info("Starting CoAP transport..."); log.info("Starting CoAP transport...");
log.info("Starting CoAP transport server"); log.info("Starting CoAP transport server");
this.server = new CoapServer(); this.server = new CoapServer();
CoapEndpoint.Builder capEndpointBuilder = new CoapEndpoint.Builder();
if (isDtlsEnabled()) {
TbCoapDtlsSettings dtlsSettings = coapTransportContext.getDtlsSettings();
DtlsConnectorConfig dtlsConnectorConfig = dtlsSettings.dtlsConnectorConfig();
DTLSConnector connector = new DTLSConnector(dtlsConnectorConfig);
capEndpointBuilder.setConnector(connector);
if (dtlsConnectorConfig.isClientAuthenticationRequired()) {
tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier();
dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor();
dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS);
}
} else {
InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
capEndpointBuilder.setInetSocketAddress(sockAddr);
capEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard());
}
CoapEndpoint coapEndpoint = capEndpointBuilder.build();
server.addEndpoint(coapEndpoint);
createResources(); createResources();
Resource root = this.server.getRoot(); Resource root = this.server.getRoot();
TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
this.server.setMessageDeliverer(messageDeliverer); this.server.setMessageDeliverer(messageDeliverer);
InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
CoapEndpoint.Builder coapEndpoitBuilder = new CoapEndpoint.Builder();
coapEndpoitBuilder.setInetSocketAddress(sockAddr);
CoapEndpoint coapEndpoint = coapEndpoitBuilder.build();
server.addEndpoint(coapEndpoint);
server.start(); server.start();
log.info("CoAP transport started!"); log.info("CoAP transport started!");
} }
private void createResources() { private void createResources() {
CoapResource api = new CoapResource(API); CoapResource api = new CoapResource(API);
api.add(new CoapTransportResource(coapTransportContext, V1)); api.add(new CoapTransportResource(coapTransportContext, getDtlsSessionsMap(), V1));
CoapResource efento = new CoapResource(EFENTO); CoapResource efento = new CoapResource(EFENTO);
CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS); CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS);
@ -81,8 +108,27 @@ public class CoapTransportService {
server.add(efento); server.add(efento);
} }
private boolean isDtlsEnabled() {
return coapTransportContext.getDtlsSettings() != null;
}
private ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionIdsMap() : null;
}
private void evictTimeoutSessions() {
tbDtlsCertificateVerifier.evictTimeoutSessions();
}
private long getDtlsSessionReportTimeout() {
return tbDtlsCertificateVerifier.getDtlsSessionReportTimeout();
}
@PreDestroy @PreDestroy
public void shutdown() { public void shutdown() {
if (dtlsSessionsExecutor != null) {
dtlsSessionsExecutor.shutdownNow();
}
log.info("Stopping CoAP transport!"); log.info("Stopping CoAP transport!");
this.server.destroy(); this.server.destroy();
log.info("CoAP transport stopped!"); log.info("CoAP transport stopped!");

View File

@ -0,0 +1,161 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.CertPathUtil;
import org.eclipse.californium.scandium.dtls.AlertMessage;
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.DTLSSession;
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 org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import javax.security.auth.x500.X500Principal;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Slf4j
@Data
public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage;
private TransportService transportService;
private TbServiceInfoProvider serviceInfoProvider;
private boolean skipValidityCheckForClientCert;
public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) {
this.transportService = transportService;
this.serviceInfoProvider = serviceInfoProvider;
this.skipValidityCheckForClientCert = skipValidityCheckForClientCert;
this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout);
}
@Override
public List<CertificateType> getSupportedCertificateType() {
return Collections.singletonList(CertificateType.X_509);
}
@Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
try {
String credentialsBody = null;
CertPath certpath = message.getCertificateChain();
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
for (X509Certificate cert : chain) {
try {
if (!skipValidityCheckForClientCert) {
cert.checkValidity();
}
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1];
CountDownLatch latch = new CountDownLatch(1);
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (!StringUtils.isEmpty(msg.getCredentials())) {
deviceCredentialsResponse[0] = msg;
}
latch.countDown();
}
@Override
public void onError(Throwable e) {
log.error(e.getMessage(), e);
latch.countDown();
}
});
latch.await(10, TimeUnit.SECONDS);
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
if (msg != null && strCert.equals(msg.getCredentials())) {
credentialsBody = msg.getCredentials();
DeviceProfile deviceProfile = msg.getDeviceProfile();
if (msg.hasDeviceInfo() && deviceProfile != null) {
TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID());
tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile));
}
break;
}
} catch (InterruptedException |
CertificateEncodingException |
CertificateExpiredException |
CertificateNotYetValidException e) {
log.error(e.getMessage(), e);
}
}
if (credentialsBody == null) {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE,
session.getPeer());
throw new HandshakeException("Certificate chain could not be validated", alert);
} else {
return new CertificateVerificationResult(cid, certpath, null);
}
} catch (HandshakeException e) {
log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null);
}
}
@Override
public List<X500Principal> getAcceptedIssuers() {
return CertPathUtil.toSubjects(null);
}
@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
// empty implementation
}
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap();
}
public void evictTimeoutSessions() {
tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions();
}
public long getDtlsSessionReportTimeout() {
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Slf4j
@Data
public class TbCoapDtlsSessionInMemoryStorage {
private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>();
private long dtlsSessionInactivityTimeout;
private long dtlsSessionReportTimeout;
public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) {
this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout;
this.dtlsSessionReportTimeout = dtlsSessionReportTimeout;
}
public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) {
log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime());
dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo);
}
public void evictTimeoutSessions() {
long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout;
dtlsSessionIdMap.entrySet().removeIf(entry -> {
if (entry.getValue().getLastActivityTime() < expTime) {
log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey());
return true;
} else {
return false;
}
});
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos;
@Data
public class TbCoapDtlsSessionInfo {
private TransportProtos.SessionInfoProto sessionInfoProto;
private DeviceProfile deviceProfile;
private long lastActivityTime;
public TbCoapDtlsSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto, DeviceProfile deviceProfile) {
this.sessionInfoProto = sessionInfoProto;
this.deviceProfile = deviceProfile;
this.lastActivityTime = System.currentTimeMillis();
}
}

View File

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.Optional;
@Slf4j
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')")
@Component
public class TbCoapDtlsSettings {
@Value("${transport.coap.bind_address}")
private String host;
@Value("${transport.coap.bind_port}")
private Integer port;
@Value("${transport.coap.dtls.mode}")
private String mode;
@Value("${transport.coap.dtls.key_store}")
private String keyStoreFile;
@Value("${transport.coap.dtls.key_store_password}")
private String keyStorePassword;
@Value("${transport.coap.dtls.key_password}")
private String keyPassword;
@Value("${transport.coap.dtls.key_alias}")
private String keyAlias;
@Value("${transport.coap.dtls.skip_validity_check_for_client_cert}")
private boolean skipValidityCheckForClientCert;
@Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}")
private long dtlsSessionInactivityTimeout;
@Value("${transport.coap.dtls.x509.dtls_session_report_timeout}")
private long dtlsSessionReportTimeout;
@Autowired
private TransportService transportService;
@Autowired
private TbServiceInfoProvider serviceInfoProvider;
public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException {
Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode);
if (securityModeOpt.isEmpty()) {
log.warn("Incorrect configuration of securityMode {}", mode);
throw new RuntimeException("Failed to parse mode property: " + mode + "!");
} else {
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
configBuilder.setAddress(getInetSocketAddress());
String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath();
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
SecurityMode securityMode = securityModeOpt.get();
if (securityMode.equals(SecurityMode.NO_AUTH)) {
configBuilder.setClientAuthenticationRequired(false);
configBuilder.setServerOnly(true);
} else {
configBuilder.setAdvancedCertificateVerifier(
new TbCoapDtlsCertificateVerifier(
transportService,
serviceInfoProvider,
dtlsSessionInactivityTimeout,
dtlsSessionReportTimeout,
skipValidityCheckForClientCert
)
);
}
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
Collections.singletonList(CertificateType.X_509));
return configBuilder.build();
}
}
private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
try {
return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(),
keyPassword.toCharArray());
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException("Failed to load serverCredentials due to: ", e);
}
}
private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) {
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
try {
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
keyStoreFilePath, keyAlias,
keyStorePassword.toCharArray());
trustBuilder.setTrustedCertificates(trustedCertificates);
if (trustBuilder.hasTrusts()) {
config.setAdvancedCertificateVerifier(trustBuilder.build());
}
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException("Failed to load trusted certificates due to: ", e);
}
}
private InetSocketAddress getInetSocketAddress() throws UnknownHostException {
InetAddress addr = InetAddress.getByName(host);
return new InetSocketAddress(addr, port);
}
private enum SecurityMode {
X509,
NO_AUTH;
static Optional<SecurityMode> parse(String name) {
SecurityMode mode = null;
if (name != null) {
for (SecurityMode securityMode : SecurityMode.values()) {
if (securityMode.name().equalsIgnoreCase(name)) {
mode = securityMode;
break;
}
}
}
return Optional.ofNullable(mode);
}
}
}

View File

@ -0,0 +1,97 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap.client;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.elements.DtlsEndpointContext;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.exception.ConnectorException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NoSecClient {
private ExecutorService executor = Executors.newFixedThreadPool(1);
private CoapClient coapClient;
public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
this.coapClient = new CoapClient(uri);
}
public void test() {
executor.submit(() -> {
try {
while (!Thread.interrupted()) {
CoapResponse response = null;
try {
response = coapClient.get();
} catch (ConnectorException | IOException e) {
System.err.println("Error occurred while sending request: " + e);
System.exit(-1);
}
if (response != null) {
System.out.println(response.getCode() + " - " + response.getCode().name());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println();
System.out.println("ADVANCED:");
EndpointContext context = response.advanced().getSourceContext();
Principal identity = context.getPeerIdentity();
if (identity != null) {
System.out.println(context.getPeerIdentity());
} else {
System.out.println("anonymous");
}
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
System.out.println(Utils.prettyPrint(response));
} else {
System.out.println("No response received.");
}
Thread.sleep(5000);
}
} catch (Exception e) {
System.out.println("Error occurred while sending COAP requests.");
}
});
}
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
}
public static void main(String[] args) throws URISyntaxException {
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " +
"host port accessToken clientKeys sharedKeys");
String host = args[0];
int port = Integer.parseInt(args[1]);
String accessToken = args[2];
String clientKeys = args[3];
String sharedKeys = args[4];
NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys);
client.test();
}
}

View File

@ -0,0 +1,145 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap.client;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.elements.DtlsEndpointContext;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SecureClientNoAuth {
private final DTLSConnector dtlsConnector;
private ExecutorService executor = Executors.newFixedThreadPool(1);
private CoapClient coapClient;
public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
this.dtlsConnector = dtlsConnector;
this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys);
}
public void test() {
executor.submit(() -> {
try {
while (!Thread.interrupted()) {
CoapResponse response = null;
try {
response = coapClient.get();
} catch (ConnectorException | IOException e) {
System.err.println("Error occurred while sending request: " + e);
System.exit(-1);
}
if (response != null) {
System.out.println(response.getCode() + " - " + response.getCode().name());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println();
System.out.println("ADVANCED:");
EndpointContext context = response.advanced().getSourceContext();
Principal identity = context.getPeerIdentity();
if (identity != null) {
System.out.println(context.getPeerIdentity());
} else {
System.out.println("anonymous");
}
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
System.out.println(Utils.prettyPrint(response));
} else {
System.out.println("No response received.");
}
Thread.sleep(5000);
}
} catch (Exception e) {
System.out.println("Error occurred while sending COAP requests.");
}
});
}
private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
CoapClient client = new CoapClient(uri);
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setConnector(dtlsConnector);
client.setEndpoint(builder.build());
return client;
}
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
}
public static void main(String[] args) throws URISyntaxException {
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " +
"host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
String host = args[0];
int port = Integer.parseInt(args[1]);
String accessToken = args[2];
String clientKeys = args[7];
String sharedKeys = args[8];
String keyStoreUriPath = args[3];
String keyStoreAlias = args[4];
String trustedAliasPattern = args[5];
String keyStorePassword = args[6];
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys);
client.test();
}
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
try {
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
trustBuilder.setTrustedCertificates(trustedCertificates);
config.setAdvancedCertificateVerifier(trustBuilder.build());
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
} catch (GeneralSecurityException e) {
System.err.println("certificates are invalid!");
throw new IllegalArgumentException(e.getMessage());
} catch (IOException e) {
System.err.println("certificates are missing!");
throw new IllegalArgumentException(e.getMessage());
}
}
}

View File

@ -0,0 +1,144 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap.client;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.elements.DtlsEndpointContext;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SecureClientX509 {
private final DTLSConnector dtlsConnector;
private ExecutorService executor = Executors.newFixedThreadPool(1);
private CoapClient coapClient;
public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException {
this.dtlsConnector = dtlsConnector;
this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys);
}
public void test() {
executor.submit(() -> {
try {
while (!Thread.interrupted()) {
CoapResponse response = null;
try {
response = coapClient.get();
} catch (ConnectorException | IOException e) {
System.err.println("Error occurred while sending request: " + e);
System.exit(-1);
}
if (response != null) {
System.out.println(response.getCode() + " - " + response.getCode().name());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println();
System.out.println("ADVANCED:");
EndpointContext context = response.advanced().getSourceContext();
Principal identity = context.getPeerIdentity();
if (identity != null) {
System.out.println(context.getPeerIdentity());
} else {
System.out.println("anonymous");
}
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
System.out.println(Utils.prettyPrint(response));
} else {
System.out.println("No response received.");
}
Thread.sleep(5000);
}
} catch (Exception e) {
System.out.println("Error occurred while sending COAP requests.");
}
});
}
private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException {
URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys));
CoapClient client = new CoapClient(uri);
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setConnector(dtlsConnector);
client.setEndpoint(builder.build());
return client;
}
private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) {
return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
}
public static void main(String[] args) throws URISyntaxException {
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " +
"host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
String host = args[0];
int port = Integer.parseInt(args[1]);
String clientKeys = args[6];
String sharedKeys = args[7];
String keyStoreUriPath = args[2];
String keyStoreAlias = args[3];
String trustedAliasPattern = args[4];
String keyStorePassword = args[5];
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys);
client.test();
}
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
try {
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
trustBuilder.setTrustedCertificates(trustedCertificates);
config.setAdvancedCertificateVerifier(trustBuilder.build());
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
} catch (GeneralSecurityException e) {
System.err.println("certificates are invalid!");
throw new IllegalArgumentException(e.getMessage());
} catch (IOException e) {
System.err.println("certificates are missing!");
throw new IllegalArgumentException(e.getMessage());
}
}
}

View File

@ -30,7 +30,7 @@ import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.util.SslUtil; import org.thingsboard.server.common.transport.util.SslUtil;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.security.KeyStore; import java.security.KeyStore;

View File

@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
import org.thingsboard.server.transport.mqtt.util.SslUtil; import org.thingsboard.server.common.transport.util.SslUtil;
import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPeerUnverifiedException;
import java.security.cert.Certificate; import java.security.cert.Certificate;

View File

@ -25,7 +25,15 @@ import java.util.UUID;
public class SessionInfoCreator { public class SessionInfoCreator {
public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) { public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) {
return TransportProtos.SessionInfoProto.newBuilder().setNodeId(context.getNodeId()) return getSessionInfoProto(msg, context.getNodeId(), sessionId);
}
public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
return getSessionInfoProto(msg, nodeId, sessionId);
}
private static TransportProtos.SessionInfoProto getSessionInfoProto(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
return TransportProtos.SessionInfoProto.newBuilder().setNodeId(nodeId)
.setSessionIdMSB(sessionId.getMostSignificantBits()) .setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits()) .setSessionIdLSB(sessionId.getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())

View File

@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.mqtt.util; package org.thingsboard.server.common.transport.util;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils; import org.springframework.util.Base64Utils;
import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.EncryptionUtil;
import java.io.IOException;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;

View File

@ -1152,6 +1152,11 @@
<artifactId>californium-core</artifactId> <artifactId>californium-core</artifactId>
<version>${californium.version}</version> <version>${californium.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>scandium</artifactId>
<version>${californium.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>

View File

@ -46,6 +46,24 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}" bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}" timeout: "${COAP_TIMEOUT:10000}"
dtls:
# Enable/disable DTLS 1.2 support
enabled: "${COAP_DTLS_ENABLED:false}"
# Secure mode. Allowed values: NO_AUTH, X509
mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
# Path to the key store that holds the certificate
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store
key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
x509:
dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"

View File

@ -518,7 +518,7 @@ export enum DeviceCredentialsType {
export const credentialTypeNames = new Map<DeviceCredentialsType, string>( export const credentialTypeNames = new Map<DeviceCredentialsType, string>(
[ [
[DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'],
[DeviceCredentialsType.X509_CERTIFICATE, 'MQTT X.509'], [DeviceCredentialsType.X509_CERTIFICATE, 'X.509'],
[DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'] [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic']
] ]
); );