mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-19 11:03:07 +08:00
Add support for certificate validation callback for Web Socket connections.
This commit is contained in:
@@ -61,7 +61,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isConnected || Interlocked.Read(ref _isDisconnectPending) != 0;
|
||||
return _isConnected && Interlocked.Read(ref _isDisconnectPending) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace MQTTnet.Client.Options
|
||||
{
|
||||
public class MqttClientCertificateValidationCallbackContext
|
||||
{
|
||||
public X509Certificate Certificate { get; set; }
|
||||
|
||||
public X509Chain Chain { get; set; }
|
||||
|
||||
public SslPolicyErrors SslPolicyErrors { get; set; }
|
||||
|
||||
public IMqttClientChannelOptions ClientOptions { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -262,6 +262,7 @@ namespace MQTTnet.Client.Options
|
||||
Certificates = _tlsParameters.Certificates?.ToList(),
|
||||
#endif
|
||||
CertificateValidationCallback = _tlsParameters.CertificateValidationCallback,
|
||||
CertificateValidationHandler = _tlsParameters.CertificateValidationHandler,
|
||||
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
|
||||
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors
|
||||
};
|
||||
|
||||
@@ -10,12 +10,15 @@ namespace MQTTnet.Client.Options
|
||||
{
|
||||
public bool UseTls { get; set; }
|
||||
|
||||
[Obsolete("This property will be removed soon. Use CertificateValidationHandler instead.")]
|
||||
public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Func<MqttClientCertificateValidationCallbackContext, bool> CertificateValidationHandler { get; set; }
|
||||
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
|
||||
|
||||
#if WINDOWS_UWP
|
||||
@@ -23,7 +26,6 @@ namespace MQTTnet.Client.Options
|
||||
#else
|
||||
public IEnumerable<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace MQTTnet.Client.Options
|
||||
public bool IgnoreCertificateChainErrors { get; set; }
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
#if WINDOWS_UWP
|
||||
public List<byte[]> Certificates { get; set; }
|
||||
#else
|
||||
@@ -23,6 +24,9 @@ namespace MQTTnet.Client.Options
|
||||
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
|
||||
|
||||
[Obsolete("This property will be removed soon. Use CertificateValidationHandler instead.")]
|
||||
public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback { get; set; }
|
||||
|
||||
public Func<MqttClientCertificateValidationCallbackContext, bool> CertificateValidationHandler { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
return "netstandard1.3";
|
||||
#elif NETSTANDARD2_0
|
||||
return "netstandard2.0";
|
||||
#elif NETSTANDARD2_1
|
||||
return "netstandard2.1";
|
||||
#elif WINDOWS_UWP
|
||||
return "uap10.0";
|
||||
#endif
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
var networkStream = socket.GetStream();
|
||||
|
||||
if (_options.TlsOptions.UseTls)
|
||||
if (_options.TlsOptions?.UseTls == true)
|
||||
{
|
||||
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
||||
try
|
||||
@@ -181,9 +181,28 @@ namespace MQTTnet.Implementations
|
||||
|
||||
bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (_options.TlsOptions.CertificateValidationCallback != null)
|
||||
#region OBSOLETE
|
||||
|
||||
var certificateValidationCallback = _options?.TlsOptions?.CertificateValidationCallback;
|
||||
if (certificateValidationCallback != null)
|
||||
{
|
||||
return _options.TlsOptions.CertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _clientOptions);
|
||||
return certificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _clientOptions);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
var certificateValidationHandler = _options?.TlsOptions?.CertificateValidationHandler;
|
||||
if (certificateValidationHandler != null)
|
||||
{
|
||||
var context = new MqttClientCertificateValidationCallbackContext
|
||||
{
|
||||
Certificate = x509Certificate,
|
||||
Chain = chain,
|
||||
SslPolicyErrors = sslPolicyErrors,
|
||||
ClientOptions = _options
|
||||
};
|
||||
|
||||
return certificateValidationHandler(context);
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
using System;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Client.Options;
|
||||
using MQTTnet.Internal;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Client.Options;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttWebSocketChannel : Disposable, IMqttChannel
|
||||
public sealed class MqttWebSocketChannel : IMqttChannel
|
||||
{
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
readonly MqttClientWebSocketOptions _options;
|
||||
|
||||
private SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
||||
private WebSocket _webSocket;
|
||||
AsyncLock _sendLock = new AsyncLock();
|
||||
WebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
@@ -53,6 +53,67 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
|
||||
var clientWebSocket = new ClientWebSocket();
|
||||
try
|
||||
{
|
||||
SetupClientWebSocket(clientWebSocket);
|
||||
|
||||
await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Prevent a memory leak when always creating new instance which will fail while connecting.
|
||||
clientWebSocket.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
_webSocket = clientWebSocket;
|
||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
|
||||
return response.Count;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
// The lock is required because the client will throw an exception if _SendAsync_ is
|
||||
// called from multiple threads at the same time. But this issue only happens with several
|
||||
// framework versions.
|
||||
if (_sendLock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
|
||||
{
|
||||
|
||||
if (_options.ProxyOptions != null)
|
||||
{
|
||||
@@ -90,68 +151,37 @@ namespace MQTTnet.Implementations
|
||||
#else
|
||||
clientWebSocket.Options.ClientCertificates.Add(certificate);
|
||||
#endif
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
|
||||
_webSocket = clientWebSocket;
|
||||
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
|
||||
#if NETSTANDARD2_1
|
||||
if (certificateValidationHandler != null)
|
||||
{
|
||||
clientWebSocket.Options.RemoteCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) =>
|
||||
{
|
||||
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
|
||||
var context = new MqttClientCertificateValidationCallbackContext
|
||||
{
|
||||
Certificate = certificate,
|
||||
Chain = chain,
|
||||
SslPolicyErrors = sslPolicyErrors,
|
||||
ClientOptions = _options
|
||||
};
|
||||
|
||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||
return certificateValidationHandler(context);
|
||||
});
|
||||
}
|
||||
#else
|
||||
if (certificateValidationHandler != null)
|
||||
{
|
||||
throw new NotSupportedException("The remote certificate validation callback for Web Sockets is only supported for netstandard 2.1+");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
|
||||
return response.Count;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
// The lock is required because the client will throw an exception if _SendAsync_ is
|
||||
// called from multiple threads at the same time. But this issue only happens with several
|
||||
// framework versions.
|
||||
if (_sendLock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock?.Release();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
void Cleanup()
|
||||
{
|
||||
_sendLock?.Dispose();
|
||||
_sendLock = null;
|
||||
@@ -169,7 +199,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
private IWebProxy CreateProxy()
|
||||
IWebProxy CreateProxy()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_options.ProxyOptions?.Address))
|
||||
{
|
||||
|
||||
@@ -4,11 +4,12 @@ using System.Threading;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public class BlockingQueue<TItem> : Disposable
|
||||
public sealed class BlockingQueue<TItem> : IDisposable
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly LinkedList<TItem> _items = new LinkedList<TItem>();
|
||||
private readonly ManualResetEventSlim _gate = new ManualResetEventSlim(false);
|
||||
readonly object _syncRoot = new object();
|
||||
readonly LinkedList<TItem> _items = new LinkedList<TItem>();
|
||||
|
||||
ManualResetEventSlim _gate = new ManualResetEventSlim(false);
|
||||
|
||||
public int Count
|
||||
{
|
||||
@@ -28,13 +29,13 @@ namespace MQTTnet.Internal
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_items.AddLast(item);
|
||||
_gate.Set();
|
||||
_gate?.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public TItem Dequeue(CancellationToken cancellationToken = default(CancellationToken))
|
||||
public TItem Dequeue(CancellationToken cancellationToken = default)
|
||||
{
|
||||
while (true)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
@@ -48,17 +49,19 @@ namespace MQTTnet.Internal
|
||||
|
||||
if (_items.Count == 0)
|
||||
{
|
||||
_gate.Reset();
|
||||
_gate?.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
_gate.Wait(cancellationToken);
|
||||
_gate?.Wait(cancellationToken);
|
||||
}
|
||||
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
public TItem PeekAndWait(CancellationToken cancellationToken = default(CancellationToken))
|
||||
|
||||
public TItem PeekAndWait(CancellationToken cancellationToken = default)
|
||||
{
|
||||
while (true)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
@@ -69,12 +72,14 @@ namespace MQTTnet.Internal
|
||||
|
||||
if (_items.Count == 0)
|
||||
{
|
||||
_gate.Reset();
|
||||
_gate?.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
_gate.Wait(cancellationToken);
|
||||
_gate?.Wait(cancellationToken);
|
||||
}
|
||||
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
public void RemoveFirst(Predicate<TItem> match)
|
||||
@@ -109,13 +114,10 @@ namespace MQTTnet.Internal
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_gate.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
_gate?.Dispose();
|
||||
_gate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' AND '$(SolutionName)' != 'MQTTnet.noUWP' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
<AssemblyName>MQTTnet</AssemblyName>
|
||||
|
||||
Reference in New Issue
Block a user