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,12 +117,10 @@ 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);
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
        Resource root = server.getRoot();
 | 
			
		||||
        TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
 | 
			
		||||
        server.setMessageDeliverer(messageDeliverer);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
@ -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,20 +73,13 @@ 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.setClientAuthenticationRequired(false);
 | 
			
		||||
        configBuilder.setClientAuthenticationWanted(true);
 | 
			
		||||
        configBuilder.setAdvancedCertificateVerifier(
 | 
			
		||||
                new TbCoapDtlsCertificateVerifier(
 | 
			
		||||
                        transportService,
 | 
			
		||||
@ -104,12 +89,10 @@ public class TbCoapDtlsSettings {
 | 
			
		||||
                        skipValidityCheckForClientCert
 | 
			
		||||
                )
 | 
			
		||||
        );
 | 
			
		||||
            }
 | 
			
		||||
        configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
 | 
			
		||||
                Collections.singletonList(CertificateType.X_509));
 | 
			
		||||
        return configBuilder.build();
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
 | 
			
		||||
        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 {
 | 
			
		||||
        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,8 +223,7 @@ 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) {
 | 
			
		||||
        if (dtlsSessionIdMap != null && StringUtils.isNotEmpty(dtlsSessionIdStr)) {
 | 
			
		||||
            TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
 | 
			
		||||
                    .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
 | 
			
		||||
                        dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis());
 | 
			
		||||
@ -232,9 +231,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
 | 
			
		||||
                    });
 | 
			
		||||
            if (tbCoapDtlsSessionInfo != null) {
 | 
			
		||||
                processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile());
 | 
			
		||||
                } else {
 | 
			
		||||
                    exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
 | 
			
		||||
                }
 | 
			
		||||
            } 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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user