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.Tasks;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Certificates;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
@@ -38,7 +39,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
RegisterListeners(options.DefaultEndpointOptions, null, logger, _cancellationTokenSource.Token);
|
||||
RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions?.IsEnabled == true)
|
||||
@@ -48,13 +49,7 @@ namespace MQTTnet.Implementations
|
||||
throw new ArgumentException("TLS certificate is not set.");
|
||||
}
|
||||
|
||||
var tlsCertificate = options.TlsEndpointOptions.CertificateProvider.GetCertificate();
|
||||
if (!tlsCertificate.HasPrivateKey)
|
||||
{
|
||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||
}
|
||||
|
||||
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
|
||||
RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, tlsCertificate, logger)
|
||||
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger)
|
||||
{
|
||||
ClientHandler = OnClientAcceptedAsync
|
||||
};
|
||||
@@ -108,7 +104,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Certificates;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
@@ -27,7 +28,7 @@ namespace MQTTnet.Implementations
|
||||
readonly MqttServerOptions _serverOptions;
|
||||
readonly MqttServerTcpEndpointBaseOptions _options;
|
||||
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
|
||||
readonly X509Certificate2 _tlsCertificate;
|
||||
X509Certificate2 _tlsCertificate;
|
||||
|
||||
CrossPlatformSocket _socket;
|
||||
IPEndPoint _localEndPoint;
|
||||
@@ -36,19 +37,18 @@ namespace MQTTnet.Implementations
|
||||
AddressFamily addressFamily,
|
||||
MqttServerOptions serverOptions,
|
||||
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
|
||||
X509Certificate2 tlsCertificate,
|
||||
IMqttNetLogger logger)
|
||||
{
|
||||
_addressFamily = addressFamily;
|
||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||
_options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
|
||||
_tlsCertificate = tlsCertificate;
|
||||
_rootLogger = logger;
|
||||
_logger = logger.WithSource(nameof(MqttTcpServerListener));
|
||||
|
||||
if (_options is MqttServerTlsTcpEndpointOptions tlsOptions)
|
||||
{
|
||||
_tlsOptions = tlsOptions;
|
||||
_tlsCertificate = _tlsOptions.CertificateProvider.GetCertificate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,8 +194,26 @@ namespace MQTTnet.Implementations
|
||||
stream = clientSocket.GetStream();
|
||||
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.HasPrivateKey)
|
||||
{
|
||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||
}
|
||||
|
||||
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
|
||||
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
@@ -198,6 +198,18 @@ namespace MQTTnet.Server
|
||||
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider)
|
||||
{
|
||||
if (certificateProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(certificateProvider));
|
||||
}
|
||||
|
||||
_options.TlsEndpointOptions.CertificateProvider = certificateProvider;
|
||||
|
||||
return this;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user