From c779b394f1a38be5bd8b686003f01f5e5d1a0c45 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 26 May 2020 23:46:43 +0200 Subject: [PATCH] Add abstraction layer for server certificate. --- Build/MQTTnet.AspNetCore.nuspec | 2 +- Build/MQTTnet.Extensions.ManagedClient.nuspec | 2 +- Build/MQTTnet.Extensions.Rpc.nuspec | 2 +- Build/MQTTnet.Extensions.WebSocket4Net.nuspec | 2 +- Build/MQTTnet.nuspec | 3 +- .../Certificates/BlobCertificateProvider.cs | 28 ++++++++++++ .../Certificates/ICertificateProvider.cs | 9 ++++ .../Certificates/X509CertificateProvider.cs | 22 +++++++++ .../Implementations/MqttTcpServerAdapter.cs | 28 +++--------- .../Server/MqttServerOptionsBuilder.cs | 19 +++++--- .../Server/MqttServerTlsTcpEndpointOptions.cs | 45 ++++++++++++++----- 11 files changed, 118 insertions(+), 44 deletions(-) create mode 100644 Source/MQTTnet/Certificates/BlobCertificateProvider.cs create mode 100644 Source/MQTTnet/Certificates/ICertificateProvider.cs create mode 100644 Source/MQTTnet/Certificates/X509CertificateProvider.cs diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index f423f51d..53fb07ec 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -13,7 +13,7 @@ This is a support library to integrate MQTTnet into AspNetCore. For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/). Copyright Christian Kratky 2016-2020 - MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin + MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index d02464b7..d7de8f47 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -13,7 +13,7 @@ This is an extension library which provides a managed MQTT client with additional features using MQTTnet. For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/). Copyright Christian Kratky 2016-2020 - MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin + MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index a5eabc5c..109665af 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -13,7 +13,7 @@ This is an extension library which allows executing synchronous device calls including a response using MQTTnet. For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/). Copyright Christian Kratky 2016-2020 - MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin + MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor diff --git a/Build/MQTTnet.Extensions.WebSocket4Net.nuspec b/Build/MQTTnet.Extensions.WebSocket4Net.nuspec index 6133179d..58a52295 100644 --- a/Build/MQTTnet.Extensions.WebSocket4Net.nuspec +++ b/Build/MQTTnet.Extensions.WebSocket4Net.nuspec @@ -13,7 +13,7 @@ This is an extension library which allows using _WebSocket4Net_ as transport for MQTTnet clients. For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/). Copyright Christian Kratky 2016-2020 - MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin + MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 8367ebb2..b329846d 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -16,9 +16,10 @@ * [AspNetCore] Adjusted some namespaces (BREAKING CHANGE!) * [Server] Adjusted some namespaces (BREAKING CHANGE!) * [Server] Added state checks (throw if not started etc.) for most server APIs. +* [Server] Exposed real X509Certificate2 (instead byte array) to TLS options (thanks to @borigas). Copyright Christian Kratky 2016-2020 - MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin + MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor diff --git a/Source/MQTTnet/Certificates/BlobCertificateProvider.cs b/Source/MQTTnet/Certificates/BlobCertificateProvider.cs new file mode 100644 index 00000000..f8daebb1 --- /dev/null +++ b/Source/MQTTnet/Certificates/BlobCertificateProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Security.Cryptography.X509Certificates; + +namespace MQTTnet.Certificates +{ + public class BlobCertificateProvider : ICertificateProvider + { + public BlobCertificateProvider(byte[] blob) + { + Blob = blob ?? throw new ArgumentNullException(nameof(blob)); + } + + public byte[] Blob { get; } + + public string Password { get; set; } + + public X509Certificate2 GetCertificate() + { + if (string.IsNullOrEmpty(Password)) + { + // Use a different overload when no password is specified. Otherwise the constructor will fail. + return new X509Certificate2(Blob); + } + + return new X509Certificate2(Blob, Password); + } + } +} diff --git a/Source/MQTTnet/Certificates/ICertificateProvider.cs b/Source/MQTTnet/Certificates/ICertificateProvider.cs new file mode 100644 index 00000000..42113d49 --- /dev/null +++ b/Source/MQTTnet/Certificates/ICertificateProvider.cs @@ -0,0 +1,9 @@ +using System.Security.Cryptography.X509Certificates; + +namespace MQTTnet.Certificates +{ + public interface ICertificateProvider + { + X509Certificate2 GetCertificate(); + } +} diff --git a/Source/MQTTnet/Certificates/X509CertificateProvider.cs b/Source/MQTTnet/Certificates/X509CertificateProvider.cs new file mode 100644 index 00000000..4bbfa4d0 --- /dev/null +++ b/Source/MQTTnet/Certificates/X509CertificateProvider.cs @@ -0,0 +1,22 @@ +#if !WINDOWS_UWP +using System; +using System.Security.Cryptography.X509Certificates; + +namespace MQTTnet.Certificates +{ + public class X509CertificateProvider : ICertificateProvider + { + readonly X509Certificate2 _certificate; + + public X509CertificateProvider(X509Certificate2 certificate) + { + _certificate = certificate ?? throw new ArgumentNullException(nameof(certificate)); + } + + public X509Certificate2 GetCertificate() + { + return _certificate; + } + } +} +#endif \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs index 57a414ff..592a3b49 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs @@ -44,30 +44,12 @@ namespace MQTTnet.Implementations if (options.TlsEndpointOptions?.IsEnabled == true) { - if (options.TlsEndpointOptions.Certificate == null && options.TlsEndpointOptions.X509Certificate == null) + if (options.TlsEndpointOptions.CertificateProvider == null) { throw new ArgumentException("TLS certificate is not set."); } - else if (options.TlsEndpointOptions.Certificate != null && options.TlsEndpointOptions.X509Certificate != null) - { - throw new ArgumentException($"{nameof(MqttServerTlsTcpEndpointOptions.Certificate)} and {nameof(MqttServerTlsTcpEndpointOptions.X509Certificate)} cannot both be set"); - } - - X509Certificate2 tlsCertificate; - if (options.TlsEndpointOptions.X509Certificate != null) - { - tlsCertificate = options.TlsEndpointOptions.X509Certificate; - } - else if (string.IsNullOrEmpty(options.TlsEndpointOptions.CertificateCredentials?.Password)) - { - // Use a different overload when no password is specified. Otherwise the constructor will fail. - tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate); - } - else - { - tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate, options.TlsEndpointOptions.CertificateCredentials.Password); - } - + + var tlsCertificate = options.TlsEndpointOptions.CertificateProvider.GetCertificate(); if (!tlsCertificate.HasPrivateKey) { throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); @@ -76,13 +58,13 @@ namespace MQTTnet.Implementations RegisterListeners(options.TlsEndpointOptions, tlsCertificate, _cancellationTokenSource.Token); } - return Task.FromResult(0); + return PlatformAbstractionLayer.CompletedTask; } public Task StopAsync() { Cleanup(); - return Task.FromResult(0); + return PlatformAbstractionLayer.CompletedTask; } protected override void Dispose(bool disposing) diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs index 9821b648..b52215f7 100644 --- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs +++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs @@ -2,6 +2,8 @@ using System.Net; using System.Net.Security; using System.Security.Authentication; +using MQTTnet.Certificates; + #if !WINDOWS_UWP using System.Security.Cryptography.X509Certificates; #endif @@ -10,7 +12,7 @@ namespace MQTTnet.Server { public class MqttServerOptionsBuilder { - private readonly MqttServerOptions _options = new MqttServerOptions(); + readonly MqttServerOptions _options = new MqttServerOptions(); public MqttServerOptionsBuilder WithConnectionBacklog(int value) { @@ -85,17 +87,24 @@ namespace MQTTnet.Server return this; } +#if !WINDOWS_UWP public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null) { - _options.TlsEndpointOptions.Certificate = value; - _options.TlsEndpointOptions.CertificateCredentials = credentials; + if (value == null) throw new ArgumentNullException(nameof(value)); + + _options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value) + { + Password = credentials?.Password + }; + return this; } -#if !WINDOWS_UWP public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate) { - _options.TlsEndpointOptions.X509Certificate = certificate; + if (certificate == null) throw new ArgumentNullException(nameof(certificate)); + + _options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate); return this; } #endif diff --git a/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs b/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs index 0c529b57..25f61bd3 100644 --- a/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs +++ b/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs @@ -1,33 +1,56 @@ -using System.Net.Security; +using System; using System.Security.Authentication; -#if !WINDOWS_UWP -using System.Security.Cryptography.X509Certificates; -#endif +using MQTTnet.Certificates; namespace MQTTnet.Server { public class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions { + ICertificateProvider _certificateProvider; + public MqttServerTlsTcpEndpointOptions() { Port = 8883; } + [Obsolete("Please use CertificateProvider with 'BlobCertificateProvider' instead.")] public byte[] Certificate { get; set; } -#if !WINDOWS_UWP - public X509Certificate2 X509Certificate { get; set; } -#endif - + [Obsolete("Please use CertificateProvider with 'BlobCertificateProvider' including password property instead.")] public IMqttServerCertificateCredentials CertificateCredentials { get; set; } +#if !WINDOWS_UWP + public System.Net.Security.RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; } +#endif + public ICertificateProvider CertificateProvider + { + get + { + // Backward compatibility only. Gets converted to auto property when + // obsolete properties are removed. + if (_certificateProvider != null) + { + return _certificateProvider; + } + + if (Certificate == null) + { + return null; + } + + return new BlobCertificateProvider(Certificate) + { + Password = CertificateCredentials?.Password + }; + } + + set => _certificateProvider = value; + } + public bool ClientCertificateRequired { get; set; } public bool CheckCertificateRevocation { get; set; } -#if !WINDOWS_UWP - public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; } -#endif public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; } }