dotnet / dotnet/MQTTnet

WithClientCertificates Fails with Disposed X509Certificate2 Object (Possible Reference Retention)

Open
#2,157 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
C#
Stars
5.1k
Forks
1.2k
PR merge metrics
No merged PRs in 30d

Description

# Description

When configuring MQTTnet with mTLS using WithClientCertificates, disposing the X509Certificate2 object before MqttClientOptionsBuilder.Build() causes the TLS handshake to fail with a tlsv13 alert certificate required error. This suggests MQTTnet (or underlying .NET SslStream) might retain a reference to the X509Certificate2 object rather than copying its data immediately, leading to issues when the object is disposed early. This behavior is inconsistent with the expectation that certificate data is copied during configuration, as noted in .NET networking patterns.

# Environment:

MQTTnet Version: 5.0.1.1416
.NET Version: .NET 8.0
OS: Ubuntu 22.04,
Broker: custom MQTT broker requiring TLS 1.3 mTLS

# Steps to Reproduce:

Create an MQTT client with mTLS using a PEM certificate and key and use the keyword **using** for the X509Certificate2

```C#
var mqttClient = new MqttFactory().CreateMqttClient();
var certPem = File.ReadAllText("cert.pem");
var keyPem = File.ReadAllText("key.pem");
using var clientCertificate = X509Certificate2.CreateFromPem(certPem, keyPem);

// Case 1: Dispose before Build() - Fails
using var clientCertificatePfx = new X509Certificate2(clientCertificate.Export(X509ContentType.Pkcs12));
var optionsBuilder = new MqttClientOptionsBuilder()
.WithTcpServer("broker.example.com", 8883)
.WithTlsOptions(o => o.WithClientCertificates([clientCertificatePfx]));
var options = optionsBuilder.Build(); // clientCertificatePfx disposed here
await mqttClient.ConnectAsync(options); // Fails
```

# Compare with non-disposed version

```C#
var clientCertificatePfx = new X509Certificate2(clientCertificate.Export(X509ContentType.Pkcs12));
var optionsBuilder = new MqttClientOptionsBuilder()
.WithTcpServer("broker.example.com", 8883)
.WithTlsOptions(o => o.WithClientCertificates([clientCertificatePfx]));
var options = optionsBuilder.Build(); // clientCertificatePfx still alive
await mqttClient.ConnectAsync(options); // Succeeds
```

# Expected Behavior:

WithClientCertificates should copy the certificate data (public cert and private key) when called, allowing the X509Certificate2 object to be safely disposed before Build() or ConnectAsync(). This aligns with typical .NET networking patterns (e.g., HttpClient with client certificates).

# Actual Behavior:

Disposing the X509Certificate2 object before Build() causes the TLS handshake to fail with:

```text
MQTTnet.Adapter.MqttConnectingFailedException: Error while authenticating.
---> Interop+Crypto+OpenSslCryptographicException: error:0A00045C:SSL routines::tlsv13 alert certificate required
```

# Impact:

Forces users to keep X509Certificate2 objects alive longer than necessary, risking memory leaks or requiring manual management.
Breaks idiomatic C# patterns like using for disposable resources, leading to unexpected failures in production code.

# Suggested Fix:

Ensure WithClientCertificates deep-copies the certificate data (e.g., raw bytes of cert and key) into MqttClientOptions, avoiding reliance on the original X509Certificate2 object post-configuration.
Alternatively, document this behavior explicitly, warning users against early disposal.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.