From 01c90badf9a0cbf6a5649b76f0e1992c3ad09d98 Mon Sep 17 00:00:00 2001 From: Rido Date: Wed, 1 Nov 2023 01:22:13 -0700 Subject: [PATCH] Support custom CA chain validation (#1851) * Add CaFile based on SslStream CustomTrust * change to cert collection * do not require to disable revocation checks --- Samples/Client/Client_Connection_Samples.cs | 51 +++++++++++++++++++ Samples/MQTTnet.Samples.csproj | 2 +- .../Client/Options/MqttClientTlsOptions.cs | 5 ++ .../Options/MqttClientTlsOptionsBuilder.cs | 9 ++++ .../MQTTnet/Implementations/MqttTcpChannel.cs | 15 ++++-- 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/Samples/Client/Client_Connection_Samples.cs b/Samples/Client/Client_Connection_Samples.cs index 5c1f6511..65c3c652 100644 --- a/Samples/Client/Client_Connection_Samples.cs +++ b/Samples/Client/Client_Connection_Samples.cs @@ -7,6 +7,7 @@ // ReSharper disable InconsistentNaming using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; using MQTTnet.Client; using MQTTnet.Extensions.WebSocket4Net; using MQTTnet.Formatter; @@ -435,4 +436,54 @@ public static class Client_Connection_Samples Console.ReadLine(); } } + + public static async Task ConnectTls_WithCaFile() + { + var mqttFactory = new MqttFactory(); + + X509Certificate2Collection caChain = new X509Certificate2Collection(); + caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt + + using (var mqttClient = mqttFactory.CreateMqttClient()) + { + var mqttClientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("test.mosquitto.org", 8883) + .WithTlsOptions(new MqttClientTlsOptionsBuilder() + .WithTrustChain(caChain) + .Build()) + .Build(); + + var connAck = await mqttClient.ConnectAsync(mqttClientOptions); + Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode); + } + + + } + const string mosquitto_org = @" +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL +BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG +A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU +BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv +by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE +BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES +MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp +dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg +UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW +Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA +s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH +3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo +E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT +MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV +6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC +6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf ++pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK +sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 +LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE +m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= +-----END CERTIFICATE----- +"; + } \ No newline at end of file diff --git a/Samples/MQTTnet.Samples.csproj b/Samples/MQTTnet.Samples.csproj index dcc1ad06..b45acc68 100644 --- a/Samples/MQTTnet.Samples.csproj +++ b/Samples/MQTTnet.Samples.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 enable enable false diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index 75bd0347..d50b2aa0 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -50,8 +50,13 @@ namespace MQTTnet.Client #if NET48 || NETCOREAPP3_1_OR_GREATER public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; + #else public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/; #endif + +#if NET7_0_OR_GREATER + public X509Certificate2Collection TrustChain { get; set; } +#endif } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs index a07a1111..72b0b24e 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs @@ -131,6 +131,15 @@ namespace MQTTnet.Client _tlsOptions.EncryptionPolicy = encryptionPolicy; return this; } +#endif + +#if NET7_0_OR_GREATER + public MqttClientTlsOptionsBuilder WithTrustChain(X509Certificate2Collection chain) + { + _tlsOptions.TrustChain = chain; + return this; + } + #endif } } \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 8e4de584..11861177 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -115,15 +115,24 @@ namespace MQTTnet.Implementations ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols, ClientCertificates = LoadCertificates(), EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol, - CertificateRevocationCheckMode = - _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode, + CertificateRevocationCheckMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode, TargetHost = targetHost, CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy, EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy, AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation }; +#if NET7_0_OR_GREATER + if (_tcpOptions.TlsOptions.TrustChain?.Count > 0) + { + sslOptions.CertificateChainPolicy = new X509ChainPolicy(); + sslOptions.CertificateChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + sslOptions.CertificateChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown; + sslOptions.CertificateChainPolicy.RevocationMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode; + sslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(_tcpOptions.TlsOptions.TrustChain); + } +#endif - await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); + await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); #else await sslStream.AuthenticateAsClientAsync( targetHost,