Skip to content

Instantly share code, notes, and snippets.

@jkoplo
Created April 27, 2021 23:37
Show Gist options
  • Save jkoplo/16f25875f7aca9503a7520fcda99005f to your computer and use it in GitHub Desktop.
Save jkoplo/16f25875f7aca9503a7520fcda99005f to your computer and use it in GitHub Desktop.
MQTTNet to AWS IoT - Framework
using MQTTnet;
using MQTTnet.Client.Options;
using OpenSSL.X509Certificate2Provider;
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");
ICertificateProvider provider = new CertificateFromFileProvider(deviceCertPEMString, devicePrivateCertPEMString, true);
X509Certificate2 deviceCertificate = provider.Certificate;
//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);
//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
};
MqttClientOptionsBuilderTlsParameters tlsOptions = new MqttClientOptionsBuilderTlsParameters();
tlsOptions.Certificates = certs;
tlsOptions.SslProtocol = System.Security.Authentication.SslProtocols.Tls12;
tlsOptions.UseTls = true;
tlsOptions.AllowUntrustedCertificates = true;
tlsOptions.CertificateValidationHandler += rootCertificateTrust.VerifyServerCertificate;
//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
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="args"></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;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment