Last active
February 3, 2025 18:06
-
-
Save jkoplo/bd60cfe1a02c6e13b0a2d753289ae00f to your computer and use it in GitHub Desktop.
MQTTNet to AWS IoT - Core
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I believe AWS does support retained messages now