-
-
Save jkoplo/bd60cfe1a02c6e13b0a2d753289ae00f to your computer and use it in GitHub Desktop.
using MQTTnet; | |
using MQTTnet.Client.Options; | |
using Oocx.ReadX509CertificateFromPem; | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Security.Cryptography.X509Certificates; | |
using System.Net.Security; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace MQTTNet_AWS | |
{ | |
class Program | |
{ | |
private static RootCertificateTrust rootCertificateTrust; | |
private static string certificateAuthorityCertPEMString; | |
private static string deviceCertPEMString; | |
private static string devicePrivateCertPEMString; | |
static async Task Main(string[] args) | |
{ | |
// Create a new MQTT client. | |
var factory = new MqttFactory(); | |
var mqttClient = factory.CreateMqttClient(); | |
var broker = "<AWS-IoT-Endpoint>"; | |
var port = 8883; | |
deviceCertPEMString = File.ReadAllText(@"C:\xxxx-certificate.pem.crt"); | |
devicePrivateCertPEMString = File.ReadAllText(@"C:\xxxx-private.pem.key"); | |
certificateAuthorityCertPEMString = File.ReadAllText(@"C:\AmazonRootCA1.pem"); | |
//Converting from PEM to X509 certs in C# is hard | |
//Load the CA certificate | |
//https://gist.github.com/ChrisTowles/f8a5358a29aebcc23316605dd869e839 | |
var certBytes = Encoding.UTF8.GetBytes(certificateAuthorityCertPEMString); | |
var signingcert = new X509Certificate2(certBytes); | |
//Load the device certificate | |
//Use Oocx.ReadX509CertificateFromPem to load cert from pem | |
var reader = new CertificateFromPemReader(); | |
X509Certificate2 deviceCertificate = reader.LoadCertificateWithPrivateKeyFromStrings(deviceCertPEMString, devicePrivateCertPEMString); | |
//This is a helper class to allow verifying a root CA separately from the Windows root store | |
rootCertificateTrust = new RootCertificateTrust(); | |
rootCertificateTrust.AddCert(signingcert); | |
// Certificate based authentication | |
List<X509Certificate> certs = new List<X509Certificate> | |
{ | |
signingcert, | |
deviceCertificate | |
}; | |
//Set things up for our MQTTNet client | |
//NOTE: AWS does NOT support will topics or retained messages | |
//If you attempt to use either, it will disconnect with little explanation | |
MqttClientOptionsBuilderTlsParameters tlsOptions = new MqttClientOptionsBuilderTlsParameters(); | |
tlsOptions.Certificates = certs; | |
tlsOptions.SslProtocol = System.Security.Authentication.SslProtocols.Tls12; | |
tlsOptions.UseTls = true; | |
tlsOptions.AllowUntrustedCertificates = true; | |
tlsOptions.CertificateValidationHandler += rootCertificateTrust.VerifyServerCertificate; | |
var options = new MqttClientOptionsBuilder() | |
.WithTcpServer(broker, port) | |
.WithClientId("mqttnet-ID") | |
.WithTls(tlsOptions) | |
.Build(); | |
await mqttClient.ConnectAsync(options, CancellationToken.None); | |
var message = new MqttApplicationMessageBuilder() | |
.WithTopic("test") | |
.WithPayload("Hello World") | |
.Build(); | |
await mqttClient.PublishAsync(message, CancellationToken.None); | |
} | |
} | |
/// <summary> | |
/// Verifies certificates against a list of manually trusted certs. | |
/// If a certificate is not in the Windows cert store, this will check that it's valid per our internal code. | |
/// </summary> | |
internal class RootCertificateTrust | |
{ | |
X509Certificate2Collection certificates; | |
internal RootCertificateTrust() | |
{ | |
certificates = new X509Certificate2Collection(); | |
} | |
/// <summary> | |
/// Add a trusted certificate | |
/// </summary> | |
/// <param name="x509Certificate2"></param> | |
internal void AddCert(X509Certificate2 x509Certificate2) | |
{ | |
certificates.Add(x509Certificate2); | |
} | |
/// <summary> | |
/// This matches the delegate signature expected for certificate verification for MQTTNet | |
/// </summary> | |
/// <param name="arg"></param> | |
/// <returns></returns> | |
internal bool VerifyServerCertificate(MqttClientCertificateValidationCallbackContext arg) => VerifyServerCertificate(new object(), arg.Certificate, arg.Chain, arg.SslPolicyErrors); | |
/// <summary> | |
/// This matches the delegate signature expected for certificate verification for M2MQTT | |
/// </summary> | |
/// <param name="sender"></param> | |
/// <param name="certificate"></param> | |
/// <param name="chain"></param> | |
/// <param name="sslPolicyErrors"></param> | |
/// <returns></returns> | |
internal bool VerifyServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) | |
{ | |
if (sslPolicyErrors == SslPolicyErrors.None) return true; | |
X509Chain chainNew = new X509Chain(); | |
var chainTest = chain; | |
chainTest.ChainPolicy.ExtraStore.AddRange(certificates); | |
// Check all properties | |
chainTest.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; | |
// This setup does not have revocation information | |
chainTest.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; | |
// Build the chain | |
var buildResult = chainTest.Build(new X509Certificate2(certificate)); | |
//Just in case it built with trust | |
if (buildResult) return true; | |
//If the error is something other than UntrustedRoot, fail | |
foreach (var status in chainTest.ChainStatus) | |
{ | |
if (status.Status != X509ChainStatusFlags.UntrustedRoot) | |
{ | |
return false; | |
} | |
} | |
//If the UntrustedRoot is on something OTHER than the GreenGrass CA, fail | |
foreach (var chainElement in chainTest.ChainElements) | |
{ | |
foreach (var chainStatus in chainElement.ChainElementStatus) | |
{ | |
if (chainStatus.Status == X509ChainStatusFlags.UntrustedRoot) | |
{ | |
var found = certificates.Find(X509FindType.FindByThumbprint, chainElement.Certificate.Thumbprint, false); | |
if (found.Count == 0) return false; | |
} | |
} | |
} | |
return true; | |
} | |
} | |
} |
Hmm, getting a timeout exception is a bit odd. One downside of MQTT is that there's not much built into the 3.1 version that allows a broker to give a client an error message. Because of this, a lot of security issues manifest as the server just dropping the client's connection and the client getting a timeout or a disconnect error message.
The code above is definitely known-good. That said AWS IoT Core security can be really complicated. Depending on how you have your Things and their policies set up you could accidentally not allow the Thing to connect at all. Or you might have your policy set to only allow connections from that Thing when using a specific clientID or you aren't allowing publish on that topic or a bunch of other things that end up just giving you a timeout error.
You might try first testing the connection with OpenSSL and your certs - you can find plenty of guides on that online. That will help confirm that the TLS and mutual auth is all correct before even trying to establish the MQTT protocol communications.
Thanks for the sample code! The code works with MQTTnet v3.0.13
For MQTTnet v4.1.0.247
Change:
internal bool VerifyServerCertificate(MqttClientCertificateValidationCallbackContext arg) => VerifyServerCertificate(new object(), arg.Certificate, arg.Chain, arg.SslPolicyErrors);
to:
internal bool VerifyServerCertificate(MqttClientCertificateValidationEventArgs arg) => VerifyServerCertificate(new object(), arg.Certificate, arg.Chain, arg.SslPolicyErrors);
MQTTnet.Client.Options namespace is no longer available.
I believe AWS does support retained messages now
I believe AWS does support retained messages now
Looks like it!
https://aws.amazon.com/about-aws/whats-new/2021/08/aws-iot-core-supports-mqtt-retained-messages/
When I was first setting things up between .NET and AWS IoT they were unsupported. And since they're an option on subscribe, and subscribing is often the first thing you do after connect, it caused me a lot of heartache to just get an immediate disconnect - I assumed it was some topic permission or other connection issue.
Hi I am using the same code however I am getting below error.
MQTTnet.Adapter.MqttConnectingFailedException
d__4.MoveNext() in C:\Users\Fawad\source\repos\POC_Console_MQTT\POC_Console_MQTT\Program.cs:line 75HResult=0x80131500
Message=Error while authenticating. Exception of type 'MQTTnet.Exceptions.MqttCommunicationTimedOutException' was thrown.
Source=MQTTnet
StackTrace:
at MQTTnet.Client.MqttClient.d__43.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable
1.ConfiguredTaskAwaiter.GetResult() at MQTTnet.Client.MqttClient.<ConnectAsync>d__34.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at MQTTnet.Client.MqttClient.<ConnectAsync>d__34.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter
1.GetResult()at POC_Console_MQTT.Program.
Inner Exception 1:
MqttCommunicationTimedOutException: Exception of type 'MQTTnet.Exceptions.MqttCommunicationTimedOutException' was thrown.