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}"
# CoAP DTLS bind port
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
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store

View File

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

View File

@ -78,7 +78,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
@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) {
@ -110,7 +109,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
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());
@ -123,15 +121,12 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
CertificateExpiredException |
CertificateNotYetValidException e) {
log.error(e.getMessage(), e);
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE,
session.getPeer());
throw new HandshakeException("Certificate chain could not be validated", alert);
}
}
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);
}
return new CertificateVerificationResult(cid, certpath, null);
} catch (HandshakeException e) {
log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null);
@ -158,4 +153,4 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
public long getDtlsSessionReportTimeout() {
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
}
}
}

View File

@ -15,15 +15,12 @@
*/
package org.thingsboard.server.coapserver;
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.data.ResourceUtils;
@ -35,9 +32,7 @@ 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)
@ -50,9 +45,6 @@ public class TbCoapDtlsSettings {
@Value("${transport.coap.dtls.bind_port}")
private Integer port;
@Value("${transport.coap.dtls.mode:NO_AUTH}")
private String mode;
@Value("${transport.coap.dtls.key_store}")
private String keyStoreFile;
@ -81,34 +73,25 @@ public class TbCoapDtlsSettings {
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 = ResourceUtils.getUri(this, keyStoreFile);
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();
}
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
configBuilder.setAddress(getInetSocketAddress());
String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile);
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
configBuilder.setServerOnly(true);
configBuilder.setClientAuthenticationRequired(false);
configBuilder.setClientAuthenticationWanted(true);
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) {
@ -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 {
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

@ -223,18 +223,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
Request request = advanced.getRequest();
String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
if (StringUtils.isNotEmpty(dtlsSessionIdStr)) {
if (dtlsSessionIdMap != null) {
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);
}
if (dtlsSessionIdMap != null && StringUtils.isNotEmpty(dtlsSessionIdStr)) {
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 {
processAccessTokenRequest(exchange, type, request);
}

View File

@ -96,8 +96,6 @@ transport:
bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}"
# CoAP DTLS bind port
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
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store
@ -299,4 +297,4 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: '${METRICS_ENDPOINTS_EXPOSE:info}'