dotnet / MQTTnet

MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.
MIT License
4.5k stars 1.07k forks source link

Certificate Authentication using PFX in .NET472 To Azure #2081

Closed NguyenTam closed 2 months ago

NguyenTam commented 2 months ago

Hello, I would like to ask question relating certificate authentication to Azure.

I tried to login to Azure using pfx-file, which I created from openssl-command: openssl.exe pkcs12 -export -in <client pem file> -inkey <client key file> -out <client pfx file>

Then I tried to connect to Azure cloud roughly this way:

clientoptionbuilder.WithConnectionUri("<Azure's Broker>");
clientoptionbuilder.WithClientId("client id");
clientoptionbuilder.WithCleanSession(true);
clientoptionbuilder.WithCredentials("<username>");
var tlsoptionsbuilder=new MQTTnet.Client.MqttClientTlsOptionsBuilder();
tlsoptionsbuilder.UseTls(true);
tlsoptionsbuilder.WithSslProtocols(System.Security.Authentication.SslProtocols.Tls12);
tlsoptionsbuilder.WithTargetHost("<Azure's Broker>");  // <= is this necessary anymore??
tlsoptionsbuilder.WithAllowUntrustedCertificates(true);
var pfx=new System.Security.Cryptography.X509Certificates.X509Certificate2("<path to pfx file just created>", "password");
tlsoptionsbuilder.WithClientCertificates(new System.Collections.Generic.List<X509Certificate2> {pfx});
tlsoptionsbuilder.WithCertificateValidationHandler((certcontext) => { return true;});
clientoptionbuilder.WithTlsOptions(tlsoptionsbuilder.Build());
myMQTTClient.StartAsync(new MQTTnet.Extensions.ManagedClient.ManagedMqttClientOptionsBuilder().WithClientOptions(clientoptionbuilder.Build()).Build());

but I got Exception:

Got disconnected from the broker, '<BROKER'S URL>': MQTTnet.Exceptions.MqttCommunicationException: Authentication failed because the remote party has closed the transport stream. ---> System.IO.IOException: Authentication failed because the remote party has closed the transport stream. at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult) at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result) at System.Threading.Tasks.TaskFactory1.FromAsyncCoreLogic(IAsyncResult iar, Func2 endFunction, Action1 endAction, Task1 promise, Boolean requiresSynchronization) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at MQTTnet.Implementations.MqttTcpChannel.d17.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at MQTTnet.Adapter.MqttChannelAdapter.<>cDisplayClass30_0.<b1>d.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at MQTTnet.Adapter.MqttChannelAdapter.d30.MoveNext() --- End of inner exception stack trace --- at MQTTnet.Adapter.MqttChannelAdapter.WrapAndThrowException(Exception exception) at MQTTnet.Adapter.MqttChannelAdapter.d30.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at MQTTnet.Client.MqttClient.d54.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at MQTTnet.Client.MqttClient.d42.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at MQTTnet.Client.MqttClient.d42.MoveNext()

Someone know where is the problem? I am still stuck at .NET 472 framework, so I can't use CreateFrom*PEM*-methods yet. The client's pem-file and key-file work on MQTT Explorer, I used them to publish messages, no CA-file is needed.

I have read quite a lot sites, mostly written by @rido-min ,but seems for .NET Core. I have asked AI for help which asked me to install BouncyCastle.Crypto.dll to create own ImportFromPem-method, but the implementation involve calling PemReader-method which is not available in .net framework.

Could someone direct me a bit, so I can publish messages to Azure's broker?

rido-min commented 2 months ago

Hi @NguyenTam

Seems to me you are not targeting the TLS port, it should be 8883, the code below works for me running in net472

IMqttClient mqttClient = new MqttFactory().CreateMqttClient();
MqttClientConnectResult connAck = await mqttClient.ConnectAsync(new MqttClientOptionsBuilder()
    .WithTcpServer("<yourbroker>.eventgrid.azure.net", 8883)
    .WithCredentials("client1")
    .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
    .WithTlsOptions(new MqttClientTlsOptionsBuilder()
        .WithClientCertificates(new X509Certificate2Collection(new X509Certificate2("client1.pfx", "<yourpassword>")))
        .Build())
    .Build());
Console.WriteLine($"Client Connected: {mqttClient.IsConnected} with CONNACK: {connAck.ResultCode}");

mqttClient.ApplicationMessageReceivedAsync += async m => await Console.Out.WriteAsync(
    $"Received message on topic: '{m.ApplicationMessage.Topic}' with content: '{m.ApplicationMessage.ConvertPayloadToString()}'\n\n");

MqttClientSubscribeResult suback = await mqttClient.SubscribeAsync("sample/+", MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);
suback.Items.ToList().ForEach(s => Console.WriteLine($"subscribed to '{s.TopicFilter.Topic}'  with '{s.ResultCode}'"));

MqttClientPublishResult puback = await mqttClient.PublishStringAsync("sample/topic1", "hello world!", MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);
Console.WriteLine(puback.ReasonString);

Console.ReadLine();
NguyenTam commented 2 months ago

Thank you @rido-min for helping,

When I simplified to your way, then certificate authentication work ✅. Thanks for your help.