From 60bc4ebcee895c4cbf4970a8aa6a0e3c37be3c81 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 26 Jul 2018 10:35:52 +0200 Subject: [PATCH] Refactor proxy usage and support. Refactor TLS parameter usage. --- .../Client/MqttClientOptionsBuilder.cs | 93 +++++++------------ .../MqttClientOptionsBuilderTlsParameters.cs | 29 ++++++ .../Client/MqttClientWebSocketOptions.cs | 4 +- .../Client/MqttClientWebSocketProxyOptions.cs | 8 +- .../Implementations/MqttWebSocketChannel.cs | 50 +++++----- 5 files changed, 96 insertions(+), 88 deletions(-) create mode 100644 Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs index ed8ae03d..c03e53ae 100644 --- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs @@ -1,8 +1,5 @@ using System; using System.Linq; -using System.Net.Security; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; using MQTTnet.Serializer; namespace MQTTnet.Client @@ -10,10 +7,10 @@ namespace MQTTnet.Client public class MqttClientOptionsBuilder { private readonly MqttClientOptions _options = new MqttClientOptions(); + private MqttClientTcpOptions _tcpOptions; private MqttClientWebSocketOptions _webSocketOptions; - - private MqttClientTlsOptions _tlsOptions; + private MqttClientOptionsBuilderTlsParameters _tlsParameters; public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value) { @@ -88,7 +85,7 @@ namespace MQTTnet.Client throw new InvalidOperationException("A WebSocket channel must be set if MqttClientWebSocketProxy is configured."); } - _webSocketOptions.MqttClientWebSocketProxy = new MqttClientWebSocketProxyOptions + _webSocketOptions.ProxyOptions = new MqttClientWebSocketProxyOptions { Address = address, Username = username, @@ -112,86 +109,66 @@ namespace MQTTnet.Client return this; } + public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters) + { + _tlsParameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); + return this; + } public MqttClientOptionsBuilder WithTls() { - return WithTls(null); - } - - public MqttClientOptionsBuilder WithTls(Func certificateValidationCallback) - { - return WithTls(SslProtocols.None, certificateValidationCallback); - } - - public MqttClientOptionsBuilder WithTls(SslProtocols sslProtocol, - Func certificateValidationCallback = null) - { - return WithTls(new byte[][] { }, sslProtocol, certificateValidationCallback); - } - - public MqttClientOptionsBuilder WithTls(byte[][] certificates, - SslProtocols sslProtocol = SslProtocols.Tls12, - Func certificateValidationCallback = null) - { - return WithTls(false, certificates, sslProtocol, certificateValidationCallback); - } - - public MqttClientOptionsBuilder WithTls(bool ignoreCertificateRevocationErrors, - byte[][] certificates = null, - SslProtocols sslProtocol = SslProtocols.Tls12, - Func certificateValidationCallback = null) - { - return WithTls(false, ignoreCertificateRevocationErrors, certificates, sslProtocol, certificateValidationCallback); - } - - public MqttClientOptionsBuilder WithTls(bool ignoreCertificateChainErrors, - bool ignoreCertificateRevocationErrors = false, - byte[][] certificates = null, - SslProtocols sslProtocol = SslProtocols.Tls12, - Func certificateValidationCallback = null) - { - return WithTls(false, ignoreCertificateChainErrors, ignoreCertificateRevocationErrors, certificates, sslProtocol, certificateValidationCallback); + return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true }); } + [Obsolete("Use method _WithTlps_ which accepts the _MqttClientOptionsBuilderTlsParameters_.")] public MqttClientOptionsBuilder WithTls( - bool allowUntrustedCertificates, + bool allowUntrustedCertificates = false, bool ignoreCertificateChainErrors = false, bool ignoreCertificateRevocationErrors = false, - byte[][] certificates = null, - SslProtocols sslProtocol = SslProtocols.Tls12, - Func certificateValidationCallback = null) + params byte[][] certificates) { - _tlsOptions = new MqttClientTlsOptions + _tlsParameters = new MqttClientOptionsBuilderTlsParameters { UseTls = true, AllowUntrustedCertificates = allowUntrustedCertificates, IgnoreCertificateChainErrors = ignoreCertificateChainErrors, IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors, - Certificates = certificates?.ToList(), - SslProtocol = sslProtocol, - CertificateValidationCallback = certificateValidationCallback + Certificates = certificates?.ToList() }; return this; } - public IMqttClientOptions Build() { - if (_tlsOptions != null) + if (_tlsParameters != null) { if (_tcpOptions == null && _webSocketOptions == null) { throw new InvalidOperationException("A channel (TCP or WebSocket) must be set if TLS is configured."); } - if (_tcpOptions != null) + if (_tlsParameters?.UseTls == true) { - _tcpOptions.TlsOptions = _tlsOptions; - } - else if (_webSocketOptions != null) - { - _webSocketOptions.TlsOptions = _tlsOptions; + var tlsOptions = new MqttClientTlsOptions + { + UseTls = true, + SslProtocol = _tlsParameters.SslProtocol, + AllowUntrustedCertificates = _tlsParameters.AllowUntrustedCertificates, + Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(), + CertificateValidationCallback = _tlsParameters.CertificateValidationCallback, + IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors, + IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors + }; + + if (_tcpOptions != null) + { + _tcpOptions.TlsOptions = tlsOptions; + } + else if (_webSocketOptions != null) + { + _webSocketOptions.TlsOptions = tlsOptions; + } } } diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs new file mode 100644 index 00000000..5764c797 --- /dev/null +++ b/Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace MQTTnet.Client +{ + public class MqttClientOptionsBuilderTlsParameters + { + public bool UseTls { get; set; } + + public Func CertificateValidationCallback + { + get; + set; + } + + public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; + + public IEnumerable> Certificates { get; set; } + + public bool AllowUntrustedCertificates { get; set; } + + public bool IgnoreCertificateChainErrors { get; set; } + + public bool IgnoreCertificateRevocationErrors { get; set; } + } +} diff --git a/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs b/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs index 2cce4310..fe8f1d27 100644 --- a/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs +++ b/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs @@ -13,9 +13,7 @@ namespace MQTTnet.Client public CookieContainer CookieContainer { get; set; } -#if NET452 || NET461 - public MqttClientWebSocketProxyOptions MqttClientWebSocketProxy { get; set; } = new MqttClientWebSocketProxyOptions(); -#endif + public MqttClientWebSocketProxyOptions ProxyOptions { get; set; } public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); diff --git a/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs b/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs index 81f48da8..0ac9df1f 100644 --- a/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs +++ b/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs @@ -1,6 +1,4 @@ -#if NET452 || NET461 - -namespace MQTTnet.Client +namespace MQTTnet.Client { public class MqttClientWebSocketProxyOptions { @@ -16,6 +14,4 @@ namespace MQTTnet.Client public string[] BypassList { get; set; } } -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index cf8c183a..53173d27 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -6,10 +7,6 @@ using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Client; -#if NET452 || NET461 - using System.Net; -#endif - namespace MQTTnet.Implementations { public class MqttWebSocketChannel : IMqttChannel @@ -49,25 +46,10 @@ namespace MQTTnet.Implementations var clientWebSocket = new ClientWebSocket(); -#if NET452 || NET461 - // use proxy if we have an address - if (string.IsNullOrEmpty(_options.MqttClientWebSocketProxy.Address) == false) + if (_options.ProxyOptions != null) { - var proxyUri = new Uri(_options.MqttClientWebSocketProxy.Address); - - // use proxy credentials if we have them - if (string.IsNullOrEmpty(_options.MqttClientWebSocketProxy.Username) == false && string.IsNullOrEmpty(_options.MqttClientWebSocketProxy.Password) == false) - { - var credentials = new NetworkCredential(_options.MqttClientWebSocketProxy.Username, _options.MqttClientWebSocketProxy.Password, _options.MqttClientWebSocketProxy.Domain); - clientWebSocket.Options.Proxy = new WebProxy(proxyUri, _options.MqttClientWebSocketProxy.BypassOnLocal, _options.MqttClientWebSocketProxy.BypassList, credentials); - } - else - { - // use proxy without credentials - clientWebSocket.Options.Proxy = new WebProxy(proxyUri, _options.MqttClientWebSocketProxy.BypassOnLocal, _options.MqttClientWebSocketProxy.BypassList); - } + clientWebSocket.Options.Proxy = CreateProxy(); } -#endif if (_options.RequestHeaders != null) { @@ -156,5 +138,31 @@ namespace MQTTnet.Implementations _webSocket = null; } } + + private IWebProxy CreateProxy() + { + if (string.IsNullOrEmpty(_options.ProxyOptions?.Address)) + { + return null; + } + +#if WINDOWS_UWP + throw new NotSupportedException("Proxies are not supported in UWP."); +#elif NETSTANDARD1_3 + throw new NotSupportedException("Proxies are not supported in netstandard 1.3."); +#else + var proxyUri = new Uri(_options.ProxyOptions.Address); + + if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password)) + { + var credentials = + new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain); + + return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials); + } + + return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList); +#endif + } } } \ No newline at end of file