MQTTnet icon indicating copy to clipboard operation
MQTTnet copied to clipboard

Question: missing documentation / sample for using Client Certificates with the new MqttClientOptionsBuilder / WithTlsOptions / WithClientCertificatesProvider

Open hbertsch opened this issue 1 year ago • 4 comments

Describe your question

I upgraded to 4.3.6.1152 coming from 4.2.1.781

Which project is your question related to?

I was using this code to attach client certificates for MQTT client authentication:

var mqttClientOptions = new MqttClientOptionsBuilder()
                .WithTcpServer(
                    connectionInfo.MqttConnectionInformation.MqttUri,
                    connectionInfo.MqttConnectionInformation.MqttPort)
                .WithClientId(connectionInfo.MqttConnectionInformation.ClientId)
                .WithTls(new MqttClientOptionsBuilderTlsParameters
                {
                    UseTls = true,
                    SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
                   
                    // this is one X509Certificate2
                    Certificates = new[] { myClientCertificates },
                    CertificateValidationHandler = delegate { return true; },
                })
                .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
                .Build();

This was throwing the following warnings:

warning CS0618: 'MqttClientOptionsBuilderTlsParameters' is obsolete: 'Use methods from MqttClientOptionsBuilder instead.' warning CS0618: 'MqttClientOptionsBuilderTlsParameters.Certificates' is obsolete: 'Use CertificatesProvider instead.' warning CS0618: 'MqttClientOptionsBuilder.WithTls(MqttClientOptionsBuilderTlsParameters)' is obsolete: 'Use WithTlsOptions(... configure) instead.'

I managed to resolve the ssl protocol and validation warning but I do not know how to resolve the issue with the CertificatesProvider. There is no sample or documentation (I could find) on how to do this. Can you please help me out here?

var mqttClientOptions = new MqttClientOptionsBuilder()
                .WithTcpServer(
                    connectionInfo.MqttConnectionInformation.MqttUri,
                    connectionInfo.MqttConnectionInformation.MqttPort)
                .WithClientId(connectionInfo.MqttConnectionInformation.ClientId)
                .WithTlsOptions(opt =>
                {
                    opt.WithSslProtocols(SslProtocols.Tls12);
                    opt.WithCertificateValidationHandler(_ => true);
                    opt.WithClientCertificatesProvider(...???...);
                })
                .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
                .Build();
            

hbertsch avatar Jul 10 '24 11:07 hbertsch

Hello, have you found a solution?

joaquingi avatar Sep 25 '24 12:09 joaquingi

Nope, but I also did not continue working on this feature in our project. Maybe it will become relevant somewhen in the future again...

hbertsch avatar Nov 04 '24 10:11 hbertsch

I got this to work locally using self-signed certificates. Using MQTTnet v5.0.1.1416 NuGet pkg.

MQTT client

  var clientCertificate = new X509Certificate2([path to client.pfx file]);

  var mqttFactory = new MqttClientFactory();

  using var mqttClient = mqttFactory.CreateMqttClient();

  var mqttClientOptions = new MqttClientOptionsBuilder()
      .WithTcpServer("localhost", 8883)
      .WithClientId("client1")
      .WithProtocolVersion(MqttProtocolVersion.V500)
      .WithTlsOptions(o =>
          {
              o.UseTls();
              o.WithClientCertificates(new[] { clientCertificate });
              o.WithSslProtocols(System.Security.Authentication.SslProtocols.Tls12);

              // Add a custom certificate validation handler.  Return true for self-signed certs 
              // since they are bogus...
              o.WithCertificateValidationHandler(eventArgs =>
              {
                  eventArgs.Certificate.Subject.Verbose();
                  eventArgs.Certificate.GetExpirationDateString().Verbose();
                  eventArgs.Chain.ChainPolicy.RevocationMode.Verbose();
                  eventArgs.Chain.ChainStatus.Verbose();
                  eventArgs.SslPolicyErrors.Verbose();
                  return true;
              });
          })
      .Build();

  var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

MQTT server

var mqttServerFactory = new MqttServerFactory(new MQTTSerilogLogger());

var serverCertificate = certificates.LoadCertificate([path to server.pfx file], [cert password]);

var mqttServerOptions = new MqttServerOptionsBuilder()
    .WithEncryptionCertificate(serverCertificate)
    .WithEncryptedEndpoint()
    .WithEncryptedEndpointPort(8883)
    .Build();

mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true;

// Always return true for self-signed certificates because they are bogus....
mqttServerOptions.TlsEndpointOptions.RemoteCertificateValidationCallback = (s,cert,chain,err) => true;

mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);

// Handling client connections and certificate validation
mqttServer.ClientConnectedAsync += async e =>
{
    Log.Information($"Client '{e.ClientId}' connected.");

    // Perform additional client checks here if necessary

    await Task.CompletedTask;
};

mqttServer.ClientDisconnectedAsync += async e =>
{
    Log.Information($"Client '{e.ClientId}' disconnected.");

    await Task.CompletedTask;
};

mqttServer.ValidatingConnectionAsync += async e =>
{
    // Validate the client certificate
    if (e.ClientCertificate == null)
    {
        e.ReasonCode = MQTTnet.Protocol.MqttConnectReasonCode.BadUserNameOrPassword;
        Log.Warning("No client certificate provided.");
    }
    else
    {
        // Example: Check certificate expiration (additional logic can be added)
        if (DateTime.Now > e.ClientCertificate.NotAfter || DateTime.Now < e.ClientCertificate.NotBefore)
        {
            e.ReasonCode = MQTTnet.Protocol.MqttConnectReasonCode.BadUserNameOrPassword;
            Log.Warning("Client certificate expired or not yet valid.");
        }
        else
        {
            e.ReasonCode = MQTTnet.Protocol.MqttConnectReasonCode.Success;
            Log.Warning("Client certificate validated successfully.");
        }
    }

    await Task.CompletedTask;
};

await mqttServer.StartAsync();

MQTT Server logging

[12:22:12 INF] MQTTSERVERKEEPALIVEMONITOR Starting keep alive monitor
[12:22:12 INF] MQTTTCPSERVERLISTENER Starting TCP listener (Endpoint=0.0.0.0:8883, TLS=True)
[12:22:12 VRB] MQTTTCPSERVERLISTENER TCP listener started (Endpoint=0.0.0.0:8883)
[12:22:12 INF] MQTTTCPSERVERLISTENER Starting TCP listener (Endpoint=[::]:8883, TLS=True)
[12:22:12 VRB] MQTTTCPSERVERLISTENER TCP listener started (Endpoint=[::]:8883)
[12:22:12 INF] MQTTSERVER Started
[12:22:17 VRB] MQTTTCPSERVERLISTENER TCP client '[::1]:63561' accepted (Local endpoint=[::]:8883)
[12:22:17 VRB] MQTTCHANNELADAPTER RX (22 bytes) <<< Connect: [ClientId=client1] [Username=] [Password=] [KeepAlivePeriod=15] [CleanSession=True]
[12:22:17 WRN] Client certificate validated successfully.
[12:22:17 VRB] MQTTCLIENTSESSIONSMANAGER Created new session for client 'client1'
[12:22:17 VRB] MQTTCHANNELADAPTER TX (10 bytes) >>> ConnAck: [ReturnCode=ConnectionAccepted] [ReasonCode=Success] [IsSessionPresent=False]
[12:22:23 INF] Client 'client1' connected.
[12:22:23 INF] MQTTCONNECTEDCLIENT Client 'client1': Session started

joneal avatar Jan 24 '25 17:01 joneal

@hbertsch There is an implementation of the interface as part of the project called DefaultMqttCertificatesProvider. It requires that all certificates are provided in the constructor. Later on, these certificates are forwarded to the .NET SSL APIs for WebSockets or SSL streams (SslClientAuthenticationOptions).

If you need to know which certificates and how these certificates are chosen you need to check the documentation of the .NET framework instead.

chkr1011 avatar Jan 26 '25 11:01 chkr1011