Created
April 27, 2021 23:37
-
-
Save jkoplo/16f25875f7aca9503a7520fcda99005f to your computer and use it in GitHub Desktop.
MQTTNet to AWS IoT - Framework
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 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