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}"
|
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
|
||||||
|
|||||||
@ -117,11 +117,9 @@ 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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
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) {
|
return new CertificateVerificationResult(cid, certpath, 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) {
|
} 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);
|
||||||
@ -158,4 +153,4 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
|
|||||||
public long getDtlsSessionReportTimeout() {
|
public long getDtlsSessionReportTimeout() {
|
||||||
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
|
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,34 +73,25 @@ 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);
|
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
|
||||||
if (securityModeOpt.isEmpty()) {
|
configBuilder.setAddress(getInetSocketAddress());
|
||||||
log.warn("Incorrect configuration of securityMode {}", mode);
|
String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile);
|
||||||
throw new RuntimeException("Failed to parse mode property: " + mode + "!");
|
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
|
||||||
} else {
|
configBuilder.setServerOnly(true);
|
||||||
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
|
configBuilder.setClientAuthenticationRequired(false);
|
||||||
configBuilder.setAddress(getInetSocketAddress());
|
configBuilder.setClientAuthenticationWanted(true);
|
||||||
String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile);
|
configBuilder.setAdvancedCertificateVerifier(
|
||||||
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
|
new TbCoapDtlsCertificateVerifier(
|
||||||
SecurityMode securityMode = securityModeOpt.get();
|
transportService,
|
||||||
if (securityMode.equals(SecurityMode.NO_AUTH)) {
|
serviceInfoProvider,
|
||||||
configBuilder.setClientAuthenticationRequired(false);
|
dtlsSessionInactivityTimeout,
|
||||||
configBuilder.setServerOnly(true);
|
dtlsSessionReportTimeout,
|
||||||
} else {
|
skipValidityCheckForClientCert
|
||||||
configBuilder.setAdvancedCertificateVerifier(
|
)
|
||||||
new TbCoapDtlsCertificateVerifier(
|
);
|
||||||
transportService,
|
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
|
||||||
serviceInfoProvider,
|
Collections.singletonList(CertificateType.X_509));
|
||||||
dtlsSessionInactivityTimeout,
|
return configBuilder.build();
|
||||||
dtlsSessionReportTimeout,
|
|
||||||
skipValidityCheckForClientCert
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
|
|
||||||
Collections.singletonList(CertificateType.X_509));
|
|
||||||
return configBuilder.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
|
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 {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@ -223,18 +223,14 @@ 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());
|
return dtlsSessionInfo;
|
||||||
return dtlsSessionInfo;
|
});
|
||||||
});
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
@ -299,4 +297,4 @@ management:
|
|||||||
web:
|
web:
|
||||||
exposure:
|
exposure:
|
||||||
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
|
# 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