mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-29 09:52:55 +08:00
Expose certificate selection in MQTTnet options
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttClientCertificateSelectionEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public MqttClientCertificateSelectionEventArgs(
|
||||||
|
string targetHost,
|
||||||
|
X509CertificateCollection localCertificates,
|
||||||
|
X509Certificate remoteCertificate,
|
||||||
|
string[] acceptableIssuers,
|
||||||
|
MqttClientTcpOptions tcpOptions)
|
||||||
|
{
|
||||||
|
TargetHost = targetHost;
|
||||||
|
LocalCertificates = localCertificates;
|
||||||
|
RemoveCertificate = remoteCertificate;
|
||||||
|
AcceptableIssuers = acceptableIssuers;
|
||||||
|
TcpOptions = tcpOptions ?? throw new ArgumentNullException(nameof(tcpOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] AcceptableIssuers { get; }
|
||||||
|
|
||||||
|
public X509CertificateCollection LocalCertificates { get; }
|
||||||
|
|
||||||
|
public X509Certificate RemoveCertificate { get; }
|
||||||
|
|
||||||
|
public string TargetHost { get; }
|
||||||
|
|
||||||
|
public MqttClientTcpOptions TcpOptions { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,11 +10,20 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public sealed class MqttClientCertificateValidationEventArgs : EventArgs
|
public sealed class MqttClientCertificateValidationEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public X509Certificate Certificate { get; set; }
|
public MqttClientCertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors, IMqttClientChannelOptions clientOptions)
|
||||||
|
{
|
||||||
|
Certificate = certificate;
|
||||||
|
Chain = chain;
|
||||||
|
ClientOptions = clientOptions;
|
||||||
|
SslPolicyErrors = sslPolicyErrors;
|
||||||
|
}
|
||||||
|
|
||||||
public X509Chain Chain { get; set; }
|
public X509Certificate Certificate { get; }
|
||||||
|
|
||||||
|
public X509Chain Chain { get; }
|
||||||
|
|
||||||
|
public IMqttClientChannelOptions ClientOptions { get; }
|
||||||
|
|
||||||
public IMqttClientChannelOptions ClientOptions { get; set; }
|
|
||||||
#if NET452 || NET461 || NET48
|
#if NET452 || NET461 || NET48
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can be a host string name or an object derived from WebRequest.
|
/// Can be a host string name or an object derived from WebRequest.
|
||||||
@@ -22,6 +31,6 @@ namespace MQTTnet.Client
|
|||||||
public object Sender { get; set; }
|
public object Sender { get; set; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public SslPolicyErrors SslPolicyErrors { get; set; }
|
public SslPolicyErrors SslPolicyErrors { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,8 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
||||||
|
|
||||||
|
public Func<MqttClientCertificateSelectionEventArgs, X509Certificate> CertificateSelectionHandler { get; set; }
|
||||||
|
|
||||||
public bool UseTls { get; set; }
|
public bool UseTls { get; set; }
|
||||||
|
|
||||||
public bool IgnoreCertificateRevocationErrors { get; set; }
|
public bool IgnoreCertificateRevocationErrors { get; set; }
|
||||||
|
|||||||
@@ -49,6 +49,17 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithCertificateSelectionHandler(Func<MqttClientCertificateSelectionEventArgs, X509Certificate> certificateSelectionHandler)
|
||||||
|
{
|
||||||
|
if (certificateSelectionHandler == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(certificateSelectionHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
_tlsOptions.CertificateSelectionHandler = certificateSelectionHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientTlsOptionsBuilder WithClientCertificates(IEnumerable<X509Certificate2> certificates)
|
public MqttClientTlsOptionsBuilder WithClientCertificates(IEnumerable<X509Certificate2> certificates)
|
||||||
{
|
{
|
||||||
if (certificates == null)
|
if (certificates == null)
|
||||||
|
|||||||
@@ -106,10 +106,29 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
if (_tcpOptions.RemoteEndpoint is DnsEndPoint dns)
|
if (_tcpOptions.RemoteEndpoint is DnsEndPoint dns)
|
||||||
{
|
{
|
||||||
targetHost = dns.Host;
|
targetHost = dns.Host;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
|
||||||
|
SslStream sslStream;
|
||||||
|
if (_tcpOptions.TlsOptions.CertificateSelectionHandler != null)
|
||||||
|
{
|
||||||
|
sslStream = new SslStream(
|
||||||
|
networkStream,
|
||||||
|
false,
|
||||||
|
InternalUserCertificateValidationCallback,
|
||||||
|
InternalUserCertificateSelectionCallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use a different constructor depending on the options for MQTTnet so that we do not have
|
||||||
|
// to copy the exact same behavior of the selection handler.
|
||||||
|
sslStream = new SslStream(
|
||||||
|
networkStream,
|
||||||
|
false,
|
||||||
|
InternalUserCertificateValidationCallback);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NETCOREAPP3_1_OR_GREATER
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
@@ -134,9 +153,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
TrustMode = X509ChainTrustMode.CustomRootTrust,
|
TrustMode = X509ChainTrustMode.CustomRootTrust,
|
||||||
VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown,
|
VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown,
|
||||||
RevocationMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors
|
RevocationMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode
|
||||||
? X509RevocationMode.NoCheck
|
|
||||||
: _tcpOptions.TlsOptions.RevocationMode
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(_tcpOptions.TlsOptions.TrustChain);
|
sslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(_tcpOptions.TlsOptions.TrustChain);
|
||||||
@@ -292,19 +309,34 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
X509Certificate InternalUserCertificateSelectionCallback(
|
||||||
|
object sender,
|
||||||
|
string targetHost,
|
||||||
|
X509CertificateCollection localCertificates,
|
||||||
|
X509Certificate remoteCertificate,
|
||||||
|
string[] acceptableIssuers)
|
||||||
|
{
|
||||||
|
var certificateSelectionHandler = _tcpOptions?.TlsOptions?.CertificateSelectionHandler;
|
||||||
|
if (certificateSelectionHandler != null)
|
||||||
|
{
|
||||||
|
var eventArgs = new MqttClientCertificateSelectionEventArgs(targetHost, localCertificates, remoteCertificate, acceptableIssuers, _tcpOptions);
|
||||||
|
return certificateSelectionHandler(eventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localCertificates?.Count > 0)
|
||||||
|
{
|
||||||
|
return localCertificates[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
{
|
{
|
||||||
var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler;
|
var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler;
|
||||||
if (certificateValidationHandler != null)
|
if (certificateValidationHandler != null)
|
||||||
{
|
{
|
||||||
var eventArgs = new MqttClientCertificateValidationEventArgs
|
var eventArgs = new MqttClientCertificateValidationEventArgs(x509Certificate, chain, sslPolicyErrors, _tcpOptions);
|
||||||
{
|
|
||||||
Certificate = x509Certificate,
|
|
||||||
Chain = chain,
|
|
||||||
SslPolicyErrors = sslPolicyErrors,
|
|
||||||
ClientOptions = _tcpOptions
|
|
||||||
};
|
|
||||||
|
|
||||||
return certificateValidationHandler(eventArgs);
|
return certificateValidationHandler(eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ namespace MQTTnet.Implementations
|
|||||||
// A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2].
|
// A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2].
|
||||||
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
|
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
|
||||||
#else
|
#else
|
||||||
// The lock is required because the client will throw an exception if _SendAsync_ is
|
// 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
|
// called from multiple threads at the same time. But this issue only happens with several
|
||||||
// framework versions.
|
// framework versions.
|
||||||
if (_sendLock == null)
|
if (_sendLock == null)
|
||||||
@@ -156,7 +156,7 @@ namespace MQTTnet.Implementations
|
|||||||
#else
|
#else
|
||||||
var proxyUri = new Uri(_options.ProxyOptions.Address);
|
var proxyUri = new Uri(_options.ProxyOptions.Address);
|
||||||
WebProxy webProxy;
|
WebProxy webProxy;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
|
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
|
||||||
{
|
{
|
||||||
var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
|
var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
|
||||||
@@ -164,9 +164,9 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
|
webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_options.ProxyOptions.UseDefaultCredentials)
|
if (_options.ProxyOptions.UseDefaultCredentials)
|
||||||
{
|
{
|
||||||
// Only update the property if required because setting it to false will alter
|
// Only update the property if required because setting it to false will alter
|
||||||
@@ -234,7 +234,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
clientWebSocket.Options.Credentials = _options.Credentials;
|
clientWebSocket.Options.Credentials = _options.Credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
|
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
|
||||||
if (certificateValidationHandler != null)
|
if (certificateValidationHandler != null)
|
||||||
{
|
{
|
||||||
@@ -245,7 +245,7 @@ namespace MQTTnet.Implementations
|
|||||||
#elif WINDOWS_UWP
|
#elif WINDOWS_UWP
|
||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
|
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
|
||||||
#elif NET452 || NET461 || NET48
|
#elif NET452 || NET461 || NET48
|
||||||
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
|
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
{
|
{
|
||||||
var context = new MqttClientCertificateValidationEventArgs
|
var context = new MqttClientCertificateValidationEventArgs
|
||||||
{
|
{
|
||||||
@@ -262,17 +262,16 @@ namespace MQTTnet.Implementations
|
|||||||
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
{
|
{
|
||||||
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
|
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
|
||||||
var context = new MqttClientCertificateValidationEventArgs
|
var context = new MqttClientCertificateValidationEventArgs(certificate, chain, sslPolicyErrors, _options);
|
||||||
{
|
|
||||||
Certificate = certificate,
|
|
||||||
Chain = chain,
|
|
||||||
SslPolicyErrors = sslPolicyErrors,
|
|
||||||
ClientOptions = _options
|
|
||||||
};
|
|
||||||
|
|
||||||
return certificateValidationHandler(context);
|
return certificateValidationHandler(context);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
var certificateSelectionHandler = _options.TlsOptions?.CertificateSelectionHandler;
|
||||||
|
if (certificateSelectionHandler != null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Remote certificate selection callback is not supported for WebSocket connections.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user