Use hybrid client authentication for DTLS CoAP transport
This commit is contained in:
parent
a230e5838b
commit
422d44433a
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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}'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user