From 1dde8dea8b1dc7a8f8225007736bc15565f05bf4 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Tue, 9 Jan 2024 14:14:48 +0100 Subject: [PATCH] Add support for X.509 Client Certificates (#114) --- .editorconfig | 5 + .../docs/how-to/client-certificates.md | 193 ++++++++++++++++++ Documentation/docs/how-to/publish.md | 10 +- Documentation/docs/how-to/subscribe.md | 8 +- Documentation/docusaurus.config.js | 2 +- Source/HiveMQtt/Client/HiveMQClient.cs | 6 +- .../Client/HiveMQClientOptionsBuilder.cs | 67 ++++++ Source/HiveMQtt/Client/HiveMQClientSocket.cs | 16 +- .../Client/HiveMQClientTrafficProcessor.cs | 2 +- .../Client/Options/HiveMQClientOptions.cs | 8 +- .../HiveMQClient/ClientOptionsBuilderTest.cs | 31 ++- .../HiveMQClient/PublishBuilderTest.cs | 1 - .../TestFiles/hivemq-server-cert.pem | 21 ++ Tests/HiveMQtt.Test/HiveMQtt.Test.csproj | 3 + 14 files changed, 341 insertions(+), 32 deletions(-) create mode 100644 Documentation/docs/how-to/client-certificates.md create mode 100644 Tests/HiveMQtt.Test/HiveMQClient/TestFiles/hivemq-server-cert.pem diff --git a/.editorconfig b/.editorconfig index bf2e42f8..ef1bbd10 100644 --- a/.editorconfig +++ b/.editorconfig @@ -193,9 +193,14 @@ dotnet_diagnostic.IDE0028.severity = none dotnet_diagnostic.IDE0049.severity = none dotnet_diagnostic.IDE0053.severity = none dotnet_diagnostic.IDE0090.severity = none +dotnet_diagnostic.IDE0300.severity = none dotnet_diagnostic.IDE0290.severity = none dotnet_diagnostic.SA1508.severity = none +# FIXME: CLS Compliance +dotnet_diagnostic.CS3001.severity = none +dotnet_diagnostic.CS3002.severity = none + ########################################## # Formatting Rules # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules diff --git a/Documentation/docs/how-to/client-certificates.md b/Documentation/docs/how-to/client-certificates.md new file mode 100644 index 00000000..f8ee89dd --- /dev/null +++ b/Documentation/docs/how-to/client-certificates.md @@ -0,0 +1,193 @@ +# Custom Client Certificates + +The HiveMQtt client has the ability to use custom client certificates to identify itself to the MQTT broker that it connect to. + +For more information on X.509 client certificates, see the following: + + * [X509 Client Certificate Authentication - MQTT Security Fundamentals](https://www.hivemq.com/blog/mqtt-security-fundamentals-x509-client-certificate-authentication/) + * [How to Generate a PEM client certificate](https://docs.hivemq.com/hivemq/latest/user-guide/howtos.html#_generate_a_pem_client_certificate_e_g_mosquitto_pub_sub) + +You can add one or more client certificates to the HiveMQtt client through the `HiveMQClientOptionsBuilder` class. + +Adding certificates will cause the client to present these certificates to the broker upon TLS connection negotiation. + +## Using X509Certificate2 + +```csharp +using HiveMQtt.Client.Options; +using System.Security.Cryptography.X509Certificates; + +// Can pre-create a X509Certificate2 or alternatively pass a string path +// to the certificate (see below) +var clientCertificate = new X509Certificate2( + 'path/to/certificate-file-1.pem'); + +var options = new HiveMQClientOptionsBuilder() + .WithClientCertificate(clientCertificate); + .WithClientCertificate('path/to/certificate-file-2.pem'); + +var client = new HiveMQttClient(options); +``` + +## Using Certificates with a Passwords + +If your certificate and protected with a password, you can either instantiate the +`X509Certificate2` object manually and pass it to the HiveMQtt client with +`WithClientCertificate`: + +```csharp +using HiveMQtt.Client.Options; +using System.Security.Cryptography.X509Certificates; + +var clientCertificate = new X509Certificate2( + 'path/to/certificate-with-password.pem', + 'certificate-password'); + +var options = new HiveMQClientOptionsBuilder() + .WithClientCertificate(clientCertificate); + +var client = new HiveMQttClient(options); +``` + +...or alternatively, just pass the string path to the certificate with the password: + +```csharp +using HiveMQtt.Client.Options; +using System.Security.Cryptography.X509Certificates; + + +var options = new HiveMQClientOptionsBuilder() + .WithClientCertificate( + 'path/to/certificate-with-password.pem', + 'certificate-password' + ); + +var client = new HiveMQttClient(options); +``` + +## Security Tips + +When using `X509Certificate2` in C# with TLS client certificates that require a password, it's important to handle and protect the certificate passwords securely. Here are some tips to manage certificate passwords safely: + +1. **Avoid Hardcoding Passwords:** Never hardcode the certificate password directly in the source code. This can lead to security vulnerabilities, as the source code (or compiled binaries) could be accessed by unauthorized parties. + +2. **Use Configuration Files:** Store the password in a configuration file separate from the codebase. Ensure this file is not checked into source control (like Git) and is only accessible by the application and authorized team members. + +3. **Environment Variables:** Consider using environment variables to store certificate passwords. This is useful in cloud or containerized environments. Environment variables can be set at the operating system level or within the deployment environment, keeping sensitive data out of the code. + +4. **Secure Secrets Management:** When appropriate, utilize a secrets management tool (like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault) to store and access secrets like certificate passwords. These tools provide a secure and centralized way to manage sensitive data, with features like access control, audit logs, and automatic rotation of secrets. + +5. **Regular Updates and Rotation:** Regularly update and rotate certificates and passwords. This practice can limit the damage if a certificate or its password is compromised. + +## Using an Environment Variable for the Certificate Password + +Instead of hard-coding a password, you can use an environment variable to hold the certificate password as follows: + +```csharp +using System; +using HiveMQtt.Client.Options; +using System.Security.Cryptography.X509Certificates; + +var certPassword = Environment.GetEnvironmentVariable("CERT_PASSWORD"); + +if (string.IsNullOrEmpty(certPassword)) +{ + throw new InvalidOperationException( + "Certificate password not found in environment variables"); +} + +var options = new HiveMQClientOptionsBuilder() + .WithClientCertificate( + "path/to/certificate-with-password.pem", + certPassword + ); + +var client = new HiveMQttClient(options); +``` + +## Using a Configuration File for the Certificate Password + +You can use a configuration file to store the password instead of hardcoding it into your source code. In .NET applications, this is commonly done using appsettings.json or a similar configuration file. Here's a step-by-step guide on how to implement this: + + +To enhance security when handling sensitive information such as a certificate password, you can use a configuration file to store the password instead of hardcoding it into your source code. In .NET applications, this is commonly done using appsettings.json or a similar configuration file. Here's a step-by-step guide on how to implement this: + +### Step 1: Modify appsettings.json + +Add the certificate password to your `appsettings.json` file. It's important to ensure that this file is properly secured and not included in source control (e.g., Git). + +```json +{ + // Other configuration settings + "CertificateSettings": { + "CertificatePath": "path/to/certificate-with-password.pem", + "CertificatePassword": "YourSecurePassword" + } +} +``` + +### Step 2: Create a Configuration Model + +Create a simple model to represent the settings. + +```csharp +public class CertificateSettings +{ + public string CertificatePath { get; set; } + public string CertificatePassword { get; set; } +} +``` + +### Step 3: Load Configuration in Your Application + +In the part of your application where you configure services, set up code to load the settings from `appsettings.json`. + +```csharp +using Microsoft.Extensions.Configuration; +using System.IO; + +// Assuming you are in the Startup.cs or a similar setup file +public class Startup +{ + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + // Other configurations... +} +``` + +### Step 4: Use the Configuration in Your Code + +Now, use the configuration settings when creating the HiveMQtt client options. + +```csharp +// Load certificate settings +var certSettings = new CertificateSettings(); +Configuration.GetSection("CertificateSettings").Bind(certSettings); + +// Use settings to initialize HiveMQtt client options +var options = new HiveMQClientOptionsBuilder() + .WithClientCertificate( + certSettings.CertificatePath, + certSettings.CertificatePassword + ); + +var client = new HiveMQttClient(options); +``` + +### Notes + +A couple tips on the above example: + +* Secure `appsettings.json`: Ensure this file is not exposed or checked into source control. Use file permissions to restrict access. + +* Environment-Specific Settings: For different environments (development, staging, production), use environment-specific appsettings files like `appsettings.Production.json`. + +## Extended Options + +TLS negotiation with client certificates is based on the `X509Certificate2` class. See the [official +.NET documentation](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2?view=net-8.0) for more options and information. diff --git a/Documentation/docs/how-to/publish.md b/Documentation/docs/how-to/publish.md index 7a1df60a..0ae62a4c 100644 --- a/Documentation/docs/how-to/publish.md +++ b/Documentation/docs/how-to/publish.md @@ -1,6 +1,6 @@ # Publish -# Simple +## Simple The simple way to publish a message is to use the following API: @@ -23,7 +23,7 @@ For the Quality of Service, see the [QualityOfService enum](https://github.com/h But if you want more control and extended options for a publish, see the next section. -# MQTT5PublishMessage +## MQTT5PublishMessage The [MQTT5PublishMessage](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/Source/HiveMQtt/MQTT5/Types/MQTT5PublishMessage.cs) class represents the entirety of a publish message in MQTT. If you construct this class directly, you can access all of the MQTT publish options such as `Retain`, `PayloadFormatIndicator`, `UserProperties` and so forth. @@ -43,7 +43,7 @@ var result = await client.PublishAsync(message); For the full details, see the source code on [MQTT5PublishMessage](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/Source/HiveMQtt/MQTT5/Types/MQTT5PublishMessage.cs). -# PublishMessageBuilder +## PublishMessageBuilder The `PublishMessageBuilder` class provides a convenient way to construct MQTT publish messages with various options and properties. It allows you to customize the topic, payload, quality of service (QoS) level, retain flag, and other attributes of the message. @@ -60,7 +60,7 @@ await client.PublishAsync(publishMessage).ConfigureAwait(false); By using `PublishMessageBuilder`, you can easily construct MQTT publish messages with the desired properties and options. It provides a fluent and intuitive way to customize the topic, payload, QoS level, retain flag, and other attributes of the message. -# Publish Return Value: `PublishResult` +## Publish Return Value: `PublishResult` The `PublishAsync` method returns a `PublishResult` object. @@ -70,7 +70,7 @@ For `QualityOfService.AtLeastOnceDelivery` (QoS level 1) and `QualityOfService.E For ease of use, you can call `PublishResult.ReasonCode()` to retrieve the appropriate result code automatically. -# See Also +## See Also * [MQTT5PublishMessage](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/Source/HiveMQtt/MQTT5/Types/MQTT5PublishMessage.cs) * [QualityOfService](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/Source/HiveMQtt/MQTT5/Types/QualityOfService.cs) diff --git a/Documentation/docs/how-to/subscribe.md b/Documentation/docs/how-to/subscribe.md index f7008c40..61dcdfc7 100644 --- a/Documentation/docs/how-to/subscribe.md +++ b/Documentation/docs/how-to/subscribe.md @@ -1,6 +1,6 @@ # Subscribe -# Set Your Message Handlers First +## Set Your Message Handlers First You can subscribe to one or many topics in MQTT. But to do so, you must first set a message handler. @@ -27,7 +27,7 @@ client.OnMessageReceived += MessageHandler; * See: [OnMessageReceivedEventArgs](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/Source/HiveMQtt/Client/Events/OnMessageReceivedEventArgs.cs) -# Basic +## Basic To subscribe to a single topic with a Quality of Service level, use `SubscribeAsync` as follows. @@ -39,7 +39,7 @@ assert subscribeResult.Subscriptions.Length() == 1 assert subscribeResult.Subscriptions[0].SubscribeReasonCode == SubAckReasonCode.GrantedQoS1 ``` -# Using `SubscribeOptions` +## Using `SubscribeOptions` To utilize the complete set of options for `SubscribeAsync`, create a `SubscribeOptions` object. @@ -61,7 +61,7 @@ subscribeOptions.UserProperties.Add("Client-Geo", "-33.8688, 151.2093"); var result = await client.SubscribeAsync(subscribeOptions).ConfigureAwait(false); ``` -# See Also +## See Also * [TopicFilter](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/Source/HiveMQtt/MQTT5/Types/TopicFilter.cs) * [SubscribeOptions](https://github.com/hivemq/hivemq-mqtt-client-dotnet/blob/main/Source/HiveMQtt/Client/Options/SubscribeOptions.cs) diff --git a/Documentation/docusaurus.config.js b/Documentation/docusaurus.config.js index 98caaacb..2e9e389b 100644 --- a/Documentation/docusaurus.config.js +++ b/Documentation/docusaurus.config.js @@ -145,7 +145,7 @@ const config = { ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} HiveMQ, GmbH. Built with Docusaurus.`, + copyright: `Copyright © ${new Date().getFullYear()} HiveMQ, GmbH.`, }, prism: { additionalLanguages: ['csharp'], diff --git a/Source/HiveMQtt/Client/HiveMQClient.cs b/Source/HiveMQtt/Client/HiveMQClient.cs index bf092fe5..c00615a8 100644 --- a/Source/HiveMQtt/Client/HiveMQClient.cs +++ b/Source/HiveMQtt/Client/HiveMQClient.cs @@ -307,10 +307,10 @@ public async Task SubscribeAsync(SubscribeOptions options) { subAck = await taskCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(120)).ConfigureAwait(false); } - catch (TimeoutException ex) + catch (TimeoutException) { - // log.Error(string.Format("Connect timeout. No response received in time.", ex); - throw ex; + Logger.Error("Subscribe timeout. No response received in time."); + throw; } finally { diff --git a/Source/HiveMQtt/Client/HiveMQClientOptionsBuilder.cs b/Source/HiveMQtt/Client/HiveMQClientOptionsBuilder.cs index 62694686..f0064ce1 100644 --- a/Source/HiveMQtt/Client/HiveMQClientOptionsBuilder.cs +++ b/Source/HiveMQtt/Client/HiveMQClientOptionsBuilder.cs @@ -15,6 +15,7 @@ */ namespace HiveMQtt.Client; +using System.Security.Cryptography.X509Certificates; using HiveMQtt.Client.Options; /// @@ -58,6 +59,7 @@ namespace HiveMQtt.Client; public class HiveMQClientOptionsBuilder { private readonly HiveMQClientOptions options = new HiveMQClientOptions(); + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); /// /// Sets the address of the broker to connect to. @@ -130,6 +132,71 @@ public HiveMQClientOptionsBuilder WithUseTls(bool useTls) return this; } + /// + /// Adds an X.509 certificate to be used for client authentication. This can be called + /// multiple times to add multiple certificates. + /// + /// The client X.509 certificate to be used for client authentication. + /// The HiveMQClientOptionsBuilder instance. + public HiveMQClientOptionsBuilder WithClientCertificate(X509Certificate2 clientCertificate) + { + this.options.ClientCertificates.Add(clientCertificate); + return this; + } + + /// + /// Adds a list of X.509 certificates to be used for client authentication. + /// + /// The list of client X.509 certificates to be used for client authentication. + /// The HiveMQClientOptionsBuilder instance. + public HiveMQClientOptionsBuilder WithClientCertificates(List clientCertificates) + { + foreach (var certificate in clientCertificates) + { + this.options.ClientCertificates.Add(certificate); + } + + return this; + } + + /// + /// Adds an X.509 certificate to be used for client authentication. + /// + /// The path to the client X.509 certificate to be used for client authentication. + /// The optional password for the client X.509 certificate. + /// The HiveMQClientOptionsBuilder instance. + public HiveMQClientOptionsBuilder WithClientCertificate(string clientCertificatePath, string? password = null) + { + if (File.Exists(clientCertificatePath)) + { + // Check if the file is readable + try + { + using (var fileStream = File.OpenRead(clientCertificatePath)) + { + // File exists and is readable. + this.options.ClientCertificates.Add(new X509Certificate2(clientCertificatePath, password)); + return this; + } + } + catch (UnauthorizedAccessException) + { + Logger.Error("File exists but is not readable due to access permissions."); + throw; + } + catch (IOException) + { + Logger.Error("An I/O error occurred while trying to read the file."); + throw; + } + } + else + { + Logger.Error("File does not exist."); + throw new FileNotFoundException(); + } + } + /// /// Sets whether to use a clean start. /// diff --git a/Source/HiveMQtt/Client/HiveMQClientSocket.cs b/Source/HiveMQtt/Client/HiveMQClientSocket.cs index 070f12d1..c6dffdfe 100644 --- a/Source/HiveMQtt/Client/HiveMQClientSocket.cs +++ b/Source/HiveMQtt/Client/HiveMQClientSocket.cs @@ -198,6 +198,7 @@ private async Task CreateTLSConnectionAsync(Stream stream) { TargetHost = this.Options.Host, EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12, + ClientCertificates = this.Options.ClientCertificates, }; if (this.Options.AllowInvalidBrokerCertificates) @@ -220,7 +221,7 @@ private async Task CreateTLSConnectionAsync(Stream stream) await ((SslStream)this.stream).AuthenticateAsClientAsync(tlsOptions).ConfigureAwait(false); return true; } - catch (SocketException e) + catch (Exception e) { Logger.Error(e.Message); if (e.InnerException != null) @@ -228,18 +229,7 @@ private async Task CreateTLSConnectionAsync(Stream stream) Logger.Error(e.InnerException.Message); } - Console.WriteLine("Socket error while establishing TLS connection - closing the connection."); - return false; - } - catch (AuthenticationException e) - { - Logger.Error(e.Message); - if (e.InnerException != null) - { - Logger.Error(e.InnerException.Message); - } - - Console.WriteLine("TLS Authentication failed - closing the connection."); + Logger.Error("Error while establishing TLS connection - closing the connection."); return false; } } diff --git a/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs b/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs index 37c3b201..40eb86ca 100644 --- a/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs +++ b/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs @@ -338,7 +338,7 @@ private Task ReceivedPacketsProcessorAsync(CancellationToken cancellationT break; default: Logger.Trace("<-- Unknown"); - Console.WriteLine($"Unknown packet received: {packet}"); + Logger.Error($"Unknown packet received: {packet}"); break; } // switch (packet) } diff --git a/Source/HiveMQtt/Client/Options/HiveMQClientOptions.cs b/Source/HiveMQtt/Client/Options/HiveMQClientOptions.cs index 5bec723e..23283087 100644 --- a/Source/HiveMQtt/Client/Options/HiveMQClientOptions.cs +++ b/Source/HiveMQtt/Client/Options/HiveMQClientOptions.cs @@ -17,7 +17,7 @@ namespace HiveMQtt.Client.Options; using System; using System.Linq; - +using System.Security.Cryptography.X509Certificates; using HiveMQtt.Client; using HiveMQtt.Client.Exceptions; @@ -43,6 +43,7 @@ public HiveMQClientOptions() this.UserProperties = new Dictionary(); this.UseTLS = false; this.AllowInvalidBrokerCertificates = false; + this.ClientCertificates = new X509CertificateCollection(); } // Client Identifier to be used in the Client. Will be set automatically if not specified. @@ -156,6 +157,11 @@ public HiveMQClientOptions() /// public bool UseTLS { get; set; } + /// + /// Gets or sets the collection of client X509 certificates. + /// + public X509CertificateCollection ClientCertificates { get; set; } + /// /// Gets or sets a value indicating whether the MQTT client should allow invalid broker TLS certificates. /// diff --git a/Tests/HiveMQtt.Test/HiveMQClient/ClientOptionsBuilderTest.cs b/Tests/HiveMQtt.Test/HiveMQClient/ClientOptionsBuilderTest.cs index 3272a34f..27001e96 100644 --- a/Tests/HiveMQtt.Test/HiveMQClient/ClientOptionsBuilderTest.cs +++ b/Tests/HiveMQtt.Test/HiveMQClient/ClientOptionsBuilderTest.cs @@ -10,7 +10,7 @@ public class ClientOptionsBuilderTest { [Theory] #pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - [InlineData("mqtt.example.com", 1883, "myClientId", true, true, true, 60, "UsernamePassword", "authData", "myUserName", "myPassword", true, 10, true, true)] + [InlineData("mqtt.example.com", 1883, "myClientId", true, true, true, 60, "UsernamePassword", "authData", "myUserName", "myPassword", true, 10, true, true, "HiveMQClient/TestFiles/hivemq-server-cert.pem")] #pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant public void Build_WithValidParameters_ReturnsValidOptions( string broker, @@ -27,7 +27,8 @@ public void Build_WithValidParameters_ReturnsValidOptions( bool preferIPv6, int topicAliasMaximum, bool requestResponseInfo, - bool requestProblemInfo) + bool requestProblemInfo, + string clientCertificatePath) { // Arrange var builder = new HiveMQClientOptionsBuilder() @@ -50,7 +51,8 @@ public void Build_WithValidParameters_ReturnsValidOptions( .WithPreferIPv6(preferIPv6) .WithTopicAliasMaximum(topicAliasMaximum) .WithRequestResponseInformation(requestResponseInfo) - .WithRequestProblemInformation(requestProblemInfo); + .WithRequestProblemInformation(requestProblemInfo) + .WithClientCertificate(clientCertificatePath); // Act var options = builder.Build(); @@ -75,5 +77,28 @@ public void Build_WithValidParameters_ReturnsValidOptions( Assert.NotNull(options.LastWillAndTestament); Assert.Equal(2, options.UserProperties.Count); Assert.Equal("LWT message", options.LastWillAndTestament.PayloadAsString); + Assert.Single(options.ClientCertificates); + } + + [Fact] + public void WithClientCertificate_NonExistentFile_ShouldThrowFileNotFoundException() + { + // Arrange + var builder = new HiveMQClientOptionsBuilder(); + var nonExistentFilePath = "nonexistent-file.pem"; + + // Act & Assert + Assert.Throws(() => builder.WithClientCertificate(nonExistentFilePath)); + } + + [Fact] + public void WithClientCertificate_NonExistentDirectory_ShouldThrowFileNotFoundException() + { + // Arrange + var builder = new HiveMQClientOptionsBuilder(); + var nonExistentDirectoryPath = "/this/nonexistent/file.pem"; + + // Act & Assert + Assert.Throws(() => builder.WithClientCertificate(nonExistentDirectoryPath)); } } diff --git a/Tests/HiveMQtt.Test/HiveMQClient/PublishBuilderTest.cs b/Tests/HiveMQtt.Test/HiveMQClient/PublishBuilderTest.cs index 9432875b..e2f344a6 100644 --- a/Tests/HiveMQtt.Test/HiveMQClient/PublishBuilderTest.cs +++ b/Tests/HiveMQtt.Test/HiveMQClient/PublishBuilderTest.cs @@ -208,4 +208,3 @@ public async Task PublishPayloadFormatIndicatorAsync() Assert.True(disconnectResult); } } - diff --git a/Tests/HiveMQtt.Test/HiveMQClient/TestFiles/hivemq-server-cert.pem b/Tests/HiveMQtt.Test/HiveMQClient/TestFiles/hivemq-server-cert.pem new file mode 100644 index 00000000..97bbc86e --- /dev/null +++ b/Tests/HiveMQtt.Test/HiveMQClient/TestFiles/hivemq-server-cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIEFrmqYzANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJJ +VDELMAkGA1UECBMCUEExEDAOBgNVBAcTB1BhbGVybW8xDzANBgNVBAoTBkhpdmVN +UTEXMBUGA1UECxMOSGl2ZU1RIENsaWVudHMxDjAMBgNVBAMTBVBldGVyMB4XDTI0 +MDEwMjExMTkwMloXDTI0MTIyNzExMTkwMlowZjELMAkGA1UEBhMCSVQxCzAJBgNV +BAgTAlBBMRAwDgYDVQQHEwdQYWxlcm1vMQ8wDQYDVQQKEwZIaXZlTVExFzAVBgNV +BAsTDkhpdmVNUSBDbGllbnRzMQ4wDAYDVQQDEwVQZXRlcjCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJCLG9ORnoZi+khkE//RqBPGYKqt2oJybHa+rxMR +w2krhXrm5GmulTeEMQBMLj5iPkPHdebC/6jtFp0Z6jX+jGyVo2hvtP3NkXi04xUT +jTnYTL7w9+0VEqAs+VldD/foi8JHDIouhHiPqMtZjIQBTNOCMbJA3ijc3V7v1k/S +cVn9tny7R4vTNegdsVpmTg3dDl05IXSmM4RvHAFaTnfKhxDLovJZMFmw7W6UdAtw +zaPaN6LyVbYv7S8AgCsQRFOc2jowep5noPuyKXwuep9zNZ5gY296UkhCubSHfM8o +BTTiBV0Adjv9EiIy/LmZq9J1IW9l0JYxQCw4DzirzVdtqAMCAwEAAaMhMB8wHQYD +VR0OBBYEFP5mNw74q4svHqU9jS8xzSgQkctNMA0GCSqGSIb3DQEBCwUAA4IBAQAr +g+yIt3WVzib/XOHM+D0HWyb0OBmP8ZaTkIr3MhgaStq26xon0udyr9gImIuFNVOR +h2tdQuI39ZMSTzHcRexCpJq/iuVjcjlSTfhGXrACmElywcEgRsUz0iETNcNOLtkT +7X6VJOCEraYK+iuXx48KZGisFpPz/rz8aQExiS8gxycARgT7N7IYba4fP9c87JjZ +5hr3hmBUMUmVH2KmDjVQDlKIZXEQrGmwOEUbpII0jlleLUuixGZykTnUEjwQ+htT +tHDITkA1fR705wH2oMaz0eI6lukwNoNigX8j6H+O+O+3CgwUTOrtNxXZk+6y0fN6 +IafTOzh5YhNP9Ve1WHjd +-----END CERTIFICATE----- diff --git a/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj b/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj index f43896c3..9601b084 100644 --- a/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj +++ b/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj @@ -20,5 +20,8 @@ PreserveNewest + + PreserveNewest +