mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Moved the usage of certificate provider into the server listener (#1652)
* Moved the usage of certificate provider into the server listener so that each connection has the posibility of receiving a new SSL cert. * Removed warning from unused field. * Reduced changeds on original codebase. --------- Co-authored-by: Jeff Scherrer <Jeff.Scherrer@vw.com>
This commit is contained in:
co-authored by
Jeff Scherrer
parent
0234918f30
commit
73f118fe1c
@@ -0,0 +1,198 @@
|
|||||||
|
|
||||||
|
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Certificates;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Tests.Mockups;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
|
||||||
|
public class Tls_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
|
||||||
|
public X509Certificate2 CreateCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
var rsa = RSA.Create();
|
||||||
|
|
||||||
|
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509EnhancedKeyUsageExtension(
|
||||||
|
new OidCollection { new Oid(oid) }, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||||
|
|
||||||
|
var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10));
|
||||||
|
var pfxCertificate = new X509Certificate2(certificate.Export(X509ContentType.Pfx), (string)null, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
|
||||||
|
{
|
||||||
|
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
|
||||||
|
clientOptionsBuilder
|
||||||
|
.WithClientId(Guid.NewGuid().ToString())
|
||||||
|
.WithTcpServer("localhost", 8883)
|
||||||
|
.WithTls(tls =>
|
||||||
|
{
|
||||||
|
tls.UseTls = true;
|
||||||
|
tls.SslProtocol = SslProtocols.Tls12;
|
||||||
|
tls.CertificateValidationHandler = certValidator;
|
||||||
|
});
|
||||||
|
|
||||||
|
var clientOptions = clientOptionsBuilder.Build();
|
||||||
|
return await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Tls_Swap_Test()
|
||||||
|
{
|
||||||
|
var testEnvironment = CreateTestEnvironment(MQTTnet.Formatter.MqttProtocolVersion.V500);
|
||||||
|
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
|
||||||
|
|
||||||
|
var firstOid = "1.3.6.1.5.5.7.3.1";
|
||||||
|
var secondOid = "1.3.6.1.5.5.7.3.2";
|
||||||
|
|
||||||
|
var certificateProvider = new CertificateProvider
|
||||||
|
{
|
||||||
|
CurrentCertificate = CreateCertificate(firstOid)
|
||||||
|
};
|
||||||
|
|
||||||
|
serverOptionsBuilder
|
||||||
|
.WithoutDefaultEndpoint()
|
||||||
|
.WithEncryptedEndpoint()
|
||||||
|
.WithEncryptionSslProtocol(SslProtocols.Tls12)
|
||||||
|
.WithEncryptionCertificate(certificateProvider);
|
||||||
|
|
||||||
|
var serverOptions = serverOptionsBuilder.Build();
|
||||||
|
|
||||||
|
var server = testEnvironment.CreateServer(serverOptions);
|
||||||
|
|
||||||
|
var publishedCount = 0;
|
||||||
|
server.InterceptingPublishAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref publishedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await server.StartAsync();
|
||||||
|
|
||||||
|
var firstClient = await ConnectClientAsync(testEnvironment, args =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var firstClientReceivedCount = 0;
|
||||||
|
firstClient.ApplicationMessageReceivedAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref firstClientReceivedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await firstClient.SubscribeAsync("TestTopic1");
|
||||||
|
|
||||||
|
await firstClient.PublishAsync(new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4}
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
|
||||||
|
|
||||||
|
// Validate that the certificate was switched
|
||||||
|
var secondClient = await ConnectClientAsync(testEnvironment, args =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var secondClientReceivedCount = 0;
|
||||||
|
secondClient.ApplicationMessageReceivedAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref secondClientReceivedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await secondClient.SubscribeAsync("TestTopic2");
|
||||||
|
|
||||||
|
await firstClient.PublishAsync(new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic2",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic2",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Ensure first client still works
|
||||||
|
await firstClient.PublishAsync(new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(6, publishedCount);
|
||||||
|
Assert.AreEqual(4, firstClientReceivedCount);
|
||||||
|
Assert.AreEqual(2, secondClientReceivedCount);
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class CertificateProvider : ICertificateProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
public X509Certificate2 CurrentCertificate { get; set; }
|
||||||
|
|
||||||
|
public X509Certificate2 GetCertificate()
|
||||||
|
=> CurrentCertificate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -14,6 +14,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Certificates;
|
||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
{
|
{
|
||||||
@@ -38,7 +39,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
if (options.DefaultEndpointOptions.IsEnabled)
|
if (options.DefaultEndpointOptions.IsEnabled)
|
||||||
{
|
{
|
||||||
RegisterListeners(options.DefaultEndpointOptions, null, logger, _cancellationTokenSource.Token);
|
RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.TlsEndpointOptions?.IsEnabled == true)
|
if (options.TlsEndpointOptions?.IsEnabled == true)
|
||||||
@@ -48,13 +49,7 @@ namespace MQTTnet.Implementations
|
|||||||
throw new ArgumentException("TLS certificate is not set.");
|
throw new ArgumentException("TLS certificate is not set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsCertificate = options.TlsEndpointOptions.CertificateProvider.GetCertificate();
|
RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||||
if (!tlsCertificate.HasPrivateKey)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
@@ -91,11 +86,12 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, X509Certificate2 tlsCertificate, IMqttNetLogger logger, CancellationToken cancellationToken)
|
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
|
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
|
||||||
{
|
{
|
||||||
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, tlsCertificate, logger)
|
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger)
|
||||||
{
|
{
|
||||||
ClientHandler = OnClientAcceptedAsync
|
ClientHandler = OnClientAcceptedAsync
|
||||||
};
|
};
|
||||||
@@ -108,7 +104,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
|
if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
|
||||||
{
|
{
|
||||||
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, tlsCertificate, logger)
|
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger)
|
||||||
{
|
{
|
||||||
ClientHandler = OnClientAcceptedAsync
|
ClientHandler = OnClientAcceptedAsync
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#if !WINDOWS_UWP
|
#if !WINDOWS_UWP
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Certificates;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
@@ -27,7 +28,7 @@ namespace MQTTnet.Implementations
|
|||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
readonly MqttServerTcpEndpointBaseOptions _options;
|
readonly MqttServerTcpEndpointBaseOptions _options;
|
||||||
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
|
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
|
||||||
readonly X509Certificate2 _tlsCertificate;
|
X509Certificate2 _tlsCertificate;
|
||||||
|
|
||||||
CrossPlatformSocket _socket;
|
CrossPlatformSocket _socket;
|
||||||
IPEndPoint _localEndPoint;
|
IPEndPoint _localEndPoint;
|
||||||
@@ -36,19 +37,18 @@ namespace MQTTnet.Implementations
|
|||||||
AddressFamily addressFamily,
|
AddressFamily addressFamily,
|
||||||
MqttServerOptions serverOptions,
|
MqttServerOptions serverOptions,
|
||||||
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
|
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
|
||||||
X509Certificate2 tlsCertificate,
|
|
||||||
IMqttNetLogger logger)
|
IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_addressFamily = addressFamily;
|
_addressFamily = addressFamily;
|
||||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||||
_options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
|
_options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
|
||||||
_tlsCertificate = tlsCertificate;
|
|
||||||
_rootLogger = logger;
|
_rootLogger = logger;
|
||||||
_logger = logger.WithSource(nameof(MqttTcpServerListener));
|
_logger = logger.WithSource(nameof(MqttTcpServerListener));
|
||||||
|
|
||||||
if (_options is MqttServerTlsTcpEndpointOptions tlsOptions)
|
if (_options is MqttServerTlsTcpEndpointOptions tlsOptions)
|
||||||
{
|
{
|
||||||
_tlsOptions = tlsOptions;
|
_tlsOptions = tlsOptions;
|
||||||
|
_tlsCertificate = _tlsOptions.CertificateProvider.GetCertificate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,8 +194,26 @@ namespace MQTTnet.Implementations
|
|||||||
stream = clientSocket.GetStream();
|
stream = clientSocket.GetStream();
|
||||||
X509Certificate2 clientCertificate = null;
|
X509Certificate2 clientCertificate = null;
|
||||||
|
|
||||||
|
if (_tlsOptions.CertificateProvider != null)
|
||||||
|
{
|
||||||
|
var tlsCertificate = _tlsOptions.CertificateProvider.GetCertificate();
|
||||||
|
if (tlsCertificate != _tlsCertificate)
|
||||||
|
{
|
||||||
|
var oldTlsCertificate = _tlsCertificate;
|
||||||
|
_tlsCertificate = tlsCertificate;
|
||||||
|
#if !NET452
|
||||||
|
oldTlsCertificate.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_tlsCertificate != null)
|
if (_tlsCertificate != null)
|
||||||
{
|
{
|
||||||
|
if (!_tlsCertificate.HasPrivateKey)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||||
|
}
|
||||||
|
|
||||||
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
|
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
|
||||||
|
|
||||||
#if NETCOREAPP3_1_OR_GREATER
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
|
|||||||
@@ -198,6 +198,18 @@ namespace MQTTnet.Server
|
|||||||
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
|
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider)
|
||||||
|
{
|
||||||
|
if (certificateProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(certificateProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
_options.TlsEndpointOptions.CertificateProvider = certificateProvider;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user