Skip to content

Commit

Permalink
Update the MQTT integration to allow using Transport Layer Security
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinchalet committed Dec 11, 2024
1 parent 376eb8a commit 3417222
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 1 deletion.
5 changes: 5 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
<PublicSign>false</PublicSign>
</PropertyGroup>

<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '9.0'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_CERTIFICATE_LOADER</DefineConstants>
</PropertyGroup>

<!--
Note: Arcade always generates .resx backing files with internal static methods/constants.
To ensure the OpenNetty resources are public, the default visibility is manually overridden.
Expand Down
77 changes: 76 additions & 1 deletion src/OpenNetty.Mqtt/OpenNettyMqttBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

using System.ComponentModel;
using System.Security.Cryptography.X509Certificates;
using System.Xml.Linq;
using Microsoft.Extensions.FileProviders;
using MQTTnet.Client;
Expand Down Expand Up @@ -136,7 +137,10 @@ public OpenNettyMqttBuilder ImportFromXmlConfiguration(XDocument document)
var element = document.Root.Element("Mqtt") ?? throw new InvalidOperationException(SR.FormatID0103("Mqtt"));
var builder = new MqttClientOptionsBuilder();

builder.WithTcpServer((string?) element.Attribute("Server") ?? throw new InvalidOperationException(SR.FormatID0104("Server")));
builder.WithTcpServer(
host: (string?) element.Attribute("Server") ?? throw new InvalidOperationException(SR.FormatID0104("Server")),
port: (int?) element.Attribute("Port"));

builder.WithProtocolVersion(MqttProtocolVersion.V500);

var username = (string?) element.Attribute("Username");
Expand All @@ -147,7 +151,78 @@ public OpenNettyMqttBuilder ImportFromXmlConfiguration(XDocument document)
builder.WithCredentials(username, password);
}

builder.WithTlsOptions(builder =>
{
var certificates = GetServerCertificates(element);
if (certificates is { Count: > 0 })
{
builder.UseTls()
.WithRevocationMode(X509RevocationMode.NoCheck)
.WithTrustChain(certificates);

var host = (string?) element.Attribute("TlsServerTargetHost");
if (!string.IsNullOrEmpty(host))
{
builder.WithTargetHost(host);
}

certificates = GetClientCertificates(element);
if (certificates is { Count: > 0 })
{
builder.WithClientCertificates(certificates);
}
}

else
{
builder.UseTls(false);
}
});

return Configure(options => options.ClientOptions = builder.Build());

static X509Certificate2Collection? GetServerCertificates(XElement element)
{
var path = (string?) element.Attribute("TlsServerCertificateAuthorityFile");
if (string.IsNullOrEmpty(path))
{
return null;
}

var certificates = new X509Certificate2Collection();
certificates.ImportFromPemFile(path);
return certificates;
}

static X509Certificate2Collection? GetClientCertificates(XElement element)
{
var paths = (
TlsClientCertificateFile: (string?) element.Attribute("TlsClientCertificateFile"),
TlsClientCertificatePrivateKeyFile: (string?) element.Attribute("TlsClientCertificatePrivateKeyFile"));

if (string.IsNullOrEmpty(paths.TlsClientCertificateFile))
{
return null;
}

if (string.IsNullOrEmpty(paths.TlsClientCertificatePrivateKeyFile))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0111));
}

var certificate = X509Certificate2.CreateFromPemFile(
paths.TlsClientCertificateFile, paths.TlsClientCertificatePrivateKeyFile);

// Note: on Windows, the client certificate is exported and re-imported to work around a limitation
// of the cryptographic stack that doesn't allow using an ephemeral key for TLS client authentication.
return OperatingSystem.IsWindows() ?
#if SUPPORTS_CERTIFICATE_LOADER
[X509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pkcs12), password: null)] :
#else
[new X509Certificate2(certificate.Export(X509ContentType.Pkcs12))] :
#endif
[certificate];
}
}

/// <inheritdoc/>
Expand Down
3 changes: 3 additions & 0 deletions src/OpenNetty/OpenNettyResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@
<data name="ID0110" xml:space="preserve">
<value>Only passwords containing ASCII digit characters (9 at most) can be used with gateways that don't support digest authentication.</value>
</data>
<data name="ID0111" xml:space="preserve">
<value>A private key file must be specified when using a TLS client certificate.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>Endpoint names cannot contain the '+' or '*' characters.</value>
</data>
Expand Down

0 comments on commit 3417222

Please sign in to comment.