Use hybrid client authentication for DTLS CoAP transport

This commit is contained in:
Igor Kulikov 2021-07-01 14:03:03 +03:00
parent a230e5838b
commit 422d44433a
6 changed files with 37 additions and 103 deletions

View File

@ -625,8 +625,6 @@ transport:
bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}"
# CoAP DTLS bind port # CoAP DTLS bind port
bind_port: "${COAP_DTLS_BIND_PORT:5684}" bind_port: "${COAP_DTLS_BIND_PORT:5684}"
# Secure mode. Allowed values: NO_AUTH, X509
mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
# Path to the key store that holds the certificate # Path to the key store that holds the certificate
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}" key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store # Password used to access the key store

View File

@ -117,12 +117,10 @@ public class DefaultCoapServerService implements CoapServerService {
dtlsCoapEndpointBuilder.setConnector(connector); dtlsCoapEndpointBuilder.setConnector(connector);
CoapEndpoint dtlsCoapEndpoint = dtlsCoapEndpointBuilder.build(); CoapEndpoint dtlsCoapEndpoint = dtlsCoapEndpointBuilder.build();
server.addEndpoint(dtlsCoapEndpoint); server.addEndpoint(dtlsCoapEndpoint);
if (dtlsConnectorConfig.isClientAuthenticationRequired()) {
tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier(); tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier();
dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor(); dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor();
dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS); dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS);
} }
}
Resource root = server.getRoot(); Resource root = server.getRoot();
TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
server.setMessageDeliverer(messageDeliverer); server.setMessageDeliverer(messageDeliverer);

View File

@ -78,7 +78,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
@Override @Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
try { try {
String credentialsBody = null;
CertPath certpath = message.getCertificateChain(); CertPath certpath = message.getCertificateChain();
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
for (X509Certificate cert : chain) { for (X509Certificate cert : chain) {
@ -110,7 +109,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
latch.await(10, TimeUnit.SECONDS); latch.await(10, TimeUnit.SECONDS);
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
if (msg != null && strCert.equals(msg.getCredentials())) { if (msg != null && strCert.equals(msg.getCredentials())) {
credentialsBody = msg.getCredentials();
DeviceProfile deviceProfile = msg.getDeviceProfile(); DeviceProfile deviceProfile = msg.getDeviceProfile();
if (msg.hasDeviceInfo() && deviceProfile != null) { if (msg.hasDeviceInfo() && deviceProfile != null) {
TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID()); TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID());
@ -123,15 +121,12 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
CertificateExpiredException | CertificateExpiredException |
CertificateNotYetValidException e) { CertificateNotYetValidException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
}
}
if (credentialsBody == null) {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE, AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE,
session.getPeer()); session.getPeer());
throw new HandshakeException("Certificate chain could not be validated", alert); throw new HandshakeException("Certificate chain could not be validated", alert);
} else {
return new CertificateVerificationResult(cid, certpath, null);
} }
}
return new CertificateVerificationResult(cid, certpath, null);
} catch (HandshakeException e) { } catch (HandshakeException e) {
log.trace("Certificate validation failed!", e); log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null); return new CertificateVerificationResult(cid, e, null);

View File

@ -15,15 +15,12 @@
*/ */
package org.thingsboard.server.coapserver; package org.thingsboard.server.coapserver;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType; 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.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.data.ResourceUtils;
@ -35,9 +32,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.util.Collections; import java.util.Collections;
import java.util.Optional;
@Slf4j @Slf4j
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false) @ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
@ -50,9 +45,6 @@ public class TbCoapDtlsSettings {
@Value("${transport.coap.dtls.bind_port}") @Value("${transport.coap.dtls.bind_port}")
private Integer port; private Integer port;
@Value("${transport.coap.dtls.mode:NO_AUTH}")
private String mode;
@Value("${transport.coap.dtls.key_store}") @Value("${transport.coap.dtls.key_store}")
private String keyStoreFile; private String keyStoreFile;
@ -81,20 +73,13 @@ public class TbCoapDtlsSettings {
private TbServiceInfoProvider serviceInfoProvider; private TbServiceInfoProvider serviceInfoProvider;
public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException { 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(); DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
configBuilder.setAddress(getInetSocketAddress()); configBuilder.setAddress(getInetSocketAddress());
String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile); String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile);
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
SecurityMode securityMode = securityModeOpt.get();
if (securityMode.equals(SecurityMode.NO_AUTH)) {
configBuilder.setClientAuthenticationRequired(false);
configBuilder.setServerOnly(true); configBuilder.setServerOnly(true);
} else { configBuilder.setClientAuthenticationRequired(false);
configBuilder.setClientAuthenticationWanted(true);
configBuilder.setAdvancedCertificateVerifier( configBuilder.setAdvancedCertificateVerifier(
new TbCoapDtlsCertificateVerifier( new TbCoapDtlsCertificateVerifier(
transportService, transportService,
@ -104,12 +89,10 @@ public class TbCoapDtlsSettings {
skipValidityCheckForClientCert skipValidityCheckForClientCert
) )
); );
}
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
Collections.singletonList(CertificateType.X_509)); Collections.singletonList(CertificateType.X_509));
return configBuilder.build(); return configBuilder.build();
} }
}
private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) { private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
try { try {
@ -120,43 +103,9 @@ public class TbCoapDtlsSettings {
} }
} }
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 { private InetSocketAddress getInetSocketAddress() throws UnknownHostException {
InetAddress addr = InetAddress.getByName(host); InetAddress addr = InetAddress.getByName(host);
return new InetSocketAddress(addr, port); 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

@ -223,8 +223,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
Request request = advanced.getRequest(); Request request = advanced.getRequest();
String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY); String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
if (StringUtils.isNotEmpty(dtlsSessionIdStr)) { if (dtlsSessionIdMap != null && StringUtils.isNotEmpty(dtlsSessionIdStr)) {
if (dtlsSessionIdMap != null) {
TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
.computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> { .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis()); dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis());
@ -232,9 +231,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
}); });
if (tbCoapDtlsSessionInfo != null) { if (tbCoapDtlsSessionInfo != null) {
processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile()); processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile());
} else {
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
}
} else { } else {
processAccessTokenRequest(exchange, type, request); processAccessTokenRequest(exchange, type, request);
} }

View File

@ -96,8 +96,6 @@ transport:
bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}"
# CoAP DTLS bind port # CoAP DTLS bind port
bind_port: "${COAP_DTLS_BIND_PORT:5684}" bind_port: "${COAP_DTLS_BIND_PORT:5684}"
# Secure mode. Allowed values: NO_AUTH, X509
mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
# Path to the key store that holds the certificate # Path to the key store that holds the certificate
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}" key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store # Password used to access the key store