Skip to content

Instantly share code, notes, and snippets.

@jkoplo
Last active February 6, 2023 05:26
Show Gist options
  • Save jkoplo/bd60cfe1a02c6e13b0a2d753289ae00f to your computer and use it in GitHub Desktop.
Save jkoplo/bd60cfe1a02c6e13b0a2d753289ae00f to your computer and use it in GitHub Desktop.
MQTTNet to AWS IoT - Core
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;
}
}
}
@benlongo
Copy link

I believe AWS does support retained messages now

@jkoplo
Copy link
Author

jkoplo commented Feb 6, 2023

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