Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds WithCaFile TlsOption to validate the connection with X509ChainPolicy.CustomRootTrust #1848

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Samples/Client/Client_Connection_Samples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,4 +435,21 @@ public static void Reconnect_Using_Timer()
Console.ReadLine();
}
}

public static async Task ConnectTls_WithCaFile()
{
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions(new MqttClientTlsOptionsBuilder()
.WithCaFile("mosquitto.org.crt") // from https://test.mosquitto.org/ssl/mosquitto.org.crt
.Build())
.Build();

var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
}
}
}
6 changes: 6 additions & 0 deletions Samples/MQTTnet.Samples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="mosquitto.org.crt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
24 changes: 24 additions & 0 deletions Samples/mosquitto.org.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-----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-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;

namespace MQTTnet.Client.Options
{
internal class MqttClientCaFileCertificateValidationHandler
{
internal static bool Handle(MqttClientCertificateValidationEventArgs cvArgs)
{
if (cvArgs.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
return true;
}
else
{
// only configure the chain if RemoteChainErrors is the only error
if (cvArgs.SslPolicyErrors == System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors)
{
string caFile = cvArgs.ClientOptions.TlsOptions.CaFile;
if (!string.IsNullOrEmpty(caFile))
{
#if NET6_0_OR_GREATER
X509Certificate2Collection caCerts = new X509Certificate2Collection();
caCerts.ImportFromPemFile(caFile);
cvArgs.Chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
cvArgs.Chain.ChainPolicy.CustomTrustStore.AddRange(caCerts);
cvArgs.Chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private CAs are very likely to do not offer revocation endpoints.

X509Certificate2 cert = new X509Certificate2(cvArgs.Certificate);
var res = cvArgs.Chain.Build(cert);
if (!res)
{
cvArgs.Chain.ChainStatus.ToList().ForEach(s => Trace.TraceWarning(s.StatusInformation));
}
return res;
#else
throw new InvalidOperationException("WithCaFile requires .NET6 or greater");
#endif
}
else
{
return false;
}
}
return false;
}
}
}
}
1 change: 1 addition & 0 deletions Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ public sealed class MqttClientTlsOptions
#else
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
#endif
public string CaFile { get; set; }
}
}
17 changes: 17 additions & 0 deletions Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Client.Options;
#if NETCOREAPP3_1_OR_GREATER
using System.Net.Security;
#endif
Expand Down Expand Up @@ -45,6 +46,11 @@ public MqttClientTlsOptionsBuilder WithCertificateValidationHandler(Func<MqttCli
throw new ArgumentNullException(nameof(certificateValidationHandler));
}

if (!string.IsNullOrEmpty(_tlsOptions.CaFile))
{
throw new InvalidOperationException("WithCaFile cannot be used with a custom Certificate Validation Handler");
}

_tlsOptions.CertificateValidationHandler = certificateValidationHandler;
return this;
}
Expand Down Expand Up @@ -131,6 +137,17 @@ public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(EncryptionPolicy encry
_tlsOptions.EncryptionPolicy = encryptionPolicy;
return this;
}

public MqttClientTlsOptionsBuilder WithCaFile(string caFile)
{
if (_tlsOptions.CertificateValidationHandler != null)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithCaFile needs to configure the CertificateValidationHandler, if it was manually assigned this throws an exception.

{
throw new InvalidOperationException("WithCaFile cannot be used with a custom Certificate Validation Handler");
}
WithCertificateValidationHandler(MqttClientCaFileCertificateValidationHandler.Handle);
_tlsOptions.CaFile = caFile;
return this;
}
#endif
}
}