diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index ee70ac0..1941a25 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,6 +1,6 @@
---
name: Bug report
-about: Create a report to help us improve
+about: Create a report to help us improve.
title: ''
labels: ''
assignees: ''
@@ -11,11 +11,12 @@ assignees: ''
A clear and concise description of what the bug is.
### Which project is your bug related to?
-- [x] Client
-- [ ] ManagedClient
-- [ ] MQTTnet.Server standalone
-- [ ] Server
-- [ ] Generic
+
+- Client
+- ManagedClient
+- MQTTnet.Server standalone
+- Server
+- Generic
### To Reproduce
Steps to reproduce the behavior:
@@ -35,9 +36,11 @@ Add any other context about the problem here.
Include debugging or logging information here:
```batch
+\\ Put your logging output here.
```
### Code example
Please provide full code examples below where possible to make it easier for the developers to check your issues.
```csharp
+\\ Put your code here.
```
diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md
index 8ec115e..fb7bdc3 100644
--- a/.github/ISSUE_TEMPLATE/custom.md
+++ b/.github/ISSUE_TEMPLATE/custom.md
@@ -1,6 +1,6 @@
---
name: Custom issue template
-about: Describe this issue template's purpose here.
+about: Do you have a question related to the project? Use this template.
title: ''
labels: ''
assignees: ''
@@ -10,9 +10,10 @@ assignees: ''
### Describe your question
A clear and concise description of what you want to know.
-### Which project is your bug related to?
-- [x] Client
-- [ ] ManagedClient
-- [ ] MQTTnet.Server standalone
-- [ ] Server
-- [ ] Generic
+### Which project is your question related to?
+
+- Client
+- ManagedClient
+- MQTTnet.Server standalone
+- Server
+- Generic
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 23a133d..c76e79f 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,6 +1,6 @@
---
name: Feature request
-about: Suggest an idea for this project
+about: Suggest an idea for this project.
title: ''
labels: ''
assignees: ''
@@ -12,11 +12,12 @@ Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Example. I'm am trying to do [...] but [...]
### Which project is your feature request related to?
-- [x] Client
-- [ ] ManagedClient
-- [ ] MQTTnet.Server standalone
-- [ ] Server
-- [ ] Generic
+
+- Client
+- ManagedClient
+- MQTTnet.Server standalone
+- Server
+- Generic
### Describe the solution you'd like
A clear and concise description of what you want to happen.
diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec
index 8fc2673..6aafdc6 100644
--- a/Build/MQTTnet.nuspec
+++ b/Build/MQTTnet.nuspec
@@ -19,6 +19,7 @@
* [Server] Fixed: Sending Large packets with AspnetCore based connection throws System.ArgumentException.
* [Server] Fixed wrong usage of socket option _NoDelay_.
* [Server] Added remote certificate validation callback (thanks to @rudacs).
+* [Server] Add support for certificate passwords (thanks to @cslutgen).
* [MQTTnet.Server] Added REST API for publishing basic messages.
Copyright Christian Kratky 2016-2019
diff --git a/Source/MQTTnet.Server/Configuration/CertificateSettingsModel.cs b/Source/MQTTnet.Server/Configuration/CertificateSettingsModel.cs
new file mode 100644
index 0000000..89eb48b
--- /dev/null
+++ b/Source/MQTTnet.Server/Configuration/CertificateSettingsModel.cs
@@ -0,0 +1,35 @@
+using System.IO;
+
+namespace MQTTnet.Server.Configuration
+{
+ public class CertificateSettingsModel
+ {
+ ///
+ /// Path to certificate.
+ ///
+ public string Path { get; set; }
+
+ ///
+ /// Password of certificate.
+ ///
+ public string Password { get; set; }
+
+ ///
+ /// Read certificate file.
+ ///
+ public byte[] ReadCertificate()
+ {
+ if (string.IsNullOrEmpty(Path) || string.IsNullOrWhiteSpace(Path))
+ {
+ throw new FileNotFoundException("No path set");
+ }
+
+ if (!File.Exists(Path))
+ {
+ throw new FileNotFoundException($"Could not find Certificate in path: {Path}");
+ }
+
+ return File.ReadAllBytes(Path);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Configuration/TcpEndpointModel.cs b/Source/MQTTnet.Server/Configuration/TcpEndpointModel.cs
index 8221390..8693268 100644
--- a/Source/MQTTnet.Server/Configuration/TcpEndpointModel.cs
+++ b/Source/MQTTnet.Server/Configuration/TcpEndpointModel.cs
@@ -9,9 +9,9 @@ namespace MQTTnet.Server.Configuration
public class TcpEndPointModel
{
///
- /// Path to Certificate
+ /// Certificate settings.
///
- public string CertificatePath { get; set; }
+ public CertificateSettingsModel Certificate { get; set; }
///
/// Enabled / Disable
@@ -33,25 +33,6 @@ namespace MQTTnet.Server.Configuration
///
public int Port { get; set; } = 1883;
- ///
- /// Read Certificate file
- ///
- ///
- public byte[] ReadCertificate()
- {
- if (string.IsNullOrEmpty(CertificatePath) || string.IsNullOrWhiteSpace(CertificatePath))
- {
- throw new FileNotFoundException("No path set");
- }
-
- if (!File.Exists(CertificatePath))
- {
- throw new FileNotFoundException($"Could not find Certificate in path: {CertificatePath}");
- }
-
- return File.ReadAllBytes(CertificatePath);
- }
-
///
/// Read IPv4
///
diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs
index efb0010..b8c463f 100644
--- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs
+++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs
@@ -47,7 +47,7 @@ namespace MQTTnet.Server.Mqtt
MqttSubscriptionInterceptor mqttSubscriptionInterceptor,
MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor,
MqttServerStorage mqttServerStorage,
- PythonScriptHostService pythonScriptHostService,
+ PythonScriptHostService pythonScriptHostService,
ILogger logger)
{
_settings = mqttSettings ?? throw new ArgumentNullException(nameof(mqttSettings));
@@ -179,7 +179,7 @@ namespace MQTTnet.Server.Mqtt
.WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor)
.WithSubscriptionInterceptor(_mqttSubscriptionInterceptor)
.WithStorage(_mqttServerStorage);
-
+
// Configure unencrypted connections
if (_settings.TcpEndPoint.Enabled)
{
@@ -210,9 +210,23 @@ namespace MQTTnet.Server.Mqtt
{
options
.WithEncryptedEndpoint()
- .WithEncryptionSslProtocol(SslProtocols.Tls12)
- .WithEncryptionCertificate(_settings.EncryptedTcpEndPoint.ReadCertificate());
+ .WithEncryptionSslProtocol(SslProtocols.Tls12);
+
+ if (!string.IsNullOrEmpty(_settings.EncryptedTcpEndPoint?.Certificate?.Path))
+ {
+ IMqttServerCertificateCredentials certificateCredentials = null;
+ if (!string.IsNullOrEmpty(_settings.EncryptedTcpEndPoint?.Certificate?.Password))
+ {
+ certificateCredentials = new MqttServerCertificateCredentials
+ {
+ Password = _settings.EncryptedTcpEndPoint.Certificate.Password
+ };
+ }
+
+ options.WithEncryptionCertificate(_settings.EncryptedTcpEndPoint.Certificate.ReadCertificate(), certificateCredentials);
+ }
+
if (_settings.EncryptedTcpEndPoint.TryReadIPv4(out var address4))
{
options.WithEncryptedEndpointBoundIPAddress(address4);
diff --git a/Source/MQTTnet.Server/appsettings.json b/Source/MQTTnet.Server/appsettings.json
index 8ea10d6..71eaf20 100644
--- a/Source/MQTTnet.Server/appsettings.json
+++ b/Source/MQTTnet.Server/appsettings.json
@@ -27,7 +27,10 @@
"IPv4": "*",
"IPv6": "*",
"Port": 8883,
- "CertificatePath": "/absolute/path/to/pfx"
+ "Certificate": {
+ "Path": "/absolute/path/to/pfx",
+ "Password": ""
+ }
},
"WebSocketEndPoint": {
"Enabled": true,
@@ -63,4 +66,4 @@
}
},
"AllowedHosts": "*"
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
index a7aefd1..65a1ec9 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
@@ -139,6 +139,13 @@ namespace MQTTnet.Client.Options
return this;
}
+ public MqttClientOptionsBuilder WithCredentials(IMqttClientCredentials credentials)
+ {
+ _options.Credentials = credentials;
+
+ return this;
+ }
+
public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler)
{
_options.ExtendedAuthenticationExchangeHandler = handler;
diff --git a/Source/MQTTnet/Formatter/MqttPacketReader.cs b/Source/MQTTnet/Formatter/MqttPacketReader.cs
index 2589c5b..61698a1 100644
--- a/Source/MQTTnet/Formatter/MqttPacketReader.cs
+++ b/Source/MQTTnet/Formatter/MqttPacketReader.cs
@@ -21,8 +21,6 @@ namespace MQTTnet.Formatter
{
// The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length.
// So in all cases at least 2 bytes must be read for a complete MQTT packet.
- // async/await is used here because the next packet is received in a couple of minutes so the performance
- // impact is acceptable according to a useless waiting thread.
var buffer = fixedHeaderBuffer;
var totalBytesRead = 0;
@@ -55,16 +53,7 @@ namespace MQTTnet.Formatter
};
}
-#if WINDOWS_UWP
- // UWP will have a dead lock when calling this not async.
var bodyLength = await ReadBodyLengthAsync(buffer[1], cancellationToken).ConfigureAwait(false);
-#else
- // Here the async/await pattern is not used because the overhead of context switches
- // is too big for reading 1 byte in a row. We expect that the remaining data was sent
- // directly after the initial bytes. If the client disconnects just in this moment we
- // will get an exception anyway.
- var bodyLength = ReadBodyLength(buffer[1], cancellationToken);
-#endif
if (!bodyLength.HasValue)
{
@@ -81,49 +70,6 @@ namespace MQTTnet.Formatter
};
}
-#if !WINDOWS_UWP
- private int? ReadBodyLength(byte initialEncodedByte, CancellationToken cancellationToken)
- {
- var offset = 0;
- var multiplier = 128;
- var value = initialEncodedByte & 127;
- int encodedByte = initialEncodedByte;
-
- while ((encodedByte & 128) != 0)
- {
- offset++;
- if (offset > 3)
- {
- throw new MqttProtocolViolationException("Remaining length is invalid.");
- }
-
- if (cancellationToken.IsCancellationRequested)
- {
- return null;
- }
-
- var readCount = _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).GetAwaiter().GetResult();
-
- if (cancellationToken.IsCancellationRequested)
- {
- return null;
- }
-
- if (readCount == 0)
- {
- return null;
- }
-
- encodedByte = _singleByteBuffer[0];
-
- value += (encodedByte & 127) * multiplier;
- multiplier *= 128;
- }
-
- return value;
- }
-#else
-
private async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken)
{
var offset = 0;
@@ -164,6 +110,5 @@ namespace MQTTnet.Formatter
return value;
}
-#endif
}
}
diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs
index 0e28ad0..e3dcab8 100644
--- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs
+++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs
@@ -48,7 +48,7 @@ namespace MQTTnet.Implementations
throw new ArgumentException("TLS certificate is not set.");
}
- var tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate);
+ var tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate, options.TlsEndpointOptions.CertificateCredentials.Password);
if (!tlsCertificate.HasPrivateKey)
{
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj
index 5e0251d..61ca517 100644
--- a/Source/MQTTnet/MQTTnet.csproj
+++ b/Source/MQTTnet/MQTTnet.csproj
@@ -41,13 +41,13 @@
RELEASE;NETSTANDARD1_3
-
+
-
+
diff --git a/Source/MQTTnet/Server/IMqttServerCertificateCredentials.cs b/Source/MQTTnet/Server/IMqttServerCertificateCredentials.cs
new file mode 100644
index 0000000..3f5fe0e
--- /dev/null
+++ b/Source/MQTTnet/Server/IMqttServerCertificateCredentials.cs
@@ -0,0 +1,4 @@
+public interface IMqttServerCertificateCredentials
+{
+ string Password { get; }
+}
diff --git a/Source/MQTTnet/Server/MqttServerCertificateCredentials.cs b/Source/MQTTnet/Server/MqttServerCertificateCredentials.cs
new file mode 100644
index 0000000..05b6c5f
--- /dev/null
+++ b/Source/MQTTnet/Server/MqttServerCertificateCredentials.cs
@@ -0,0 +1,7 @@
+namespace MQTTnet.Server
+{
+ public class MqttServerCertificateCredentials : IMqttServerCertificateCredentials
+ {
+ public string Password { get; set; }
+ }
+}
diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs
index 1fcd981..c25af84 100644
--- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs
+++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs
@@ -82,9 +82,10 @@ namespace MQTTnet.Server
return this;
}
- public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value)
+ public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null)
{
_options.TlsEndpointOptions.Certificate = value;
+ _options.TlsEndpointOptions.CertificateCredentials = credentials;
return this;
}
@@ -94,6 +95,16 @@ namespace MQTTnet.Server
return this;
}
+#if !WINDOWS_UWP
+ public MqttServerOptionsBuilder WithClientCertificate(RemoteCertificateValidationCallback validationCallback = null, bool checkCertificateRevocation = false)
+ {
+ _options.TlsEndpointOptions.ClientCertificateRequired = true;
+ _options.TlsEndpointOptions.CheckCertificateRevocation = checkCertificateRevocation;
+ _options.TlsEndpointOptions.RemoteCertificateValidationCallback = validationCallback;
+ return this;
+ }
+#endif
+
public MqttServerOptionsBuilder WithoutEncryptedEndpoint()
{
_options.TlsEndpointOptions.IsEnabled = false;
@@ -107,7 +118,7 @@ namespace MQTTnet.Server
return this;
}
#endif
-
+
public MqttServerOptionsBuilder WithStorage(IMqttServerStorage value)
{
_options.Storage = value;
diff --git a/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs b/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs
index 282bef9..9bf325b 100644
--- a/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs
+++ b/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs
@@ -12,6 +12,8 @@ namespace MQTTnet.Server
public byte[] Certificate { get; set; }
+ public IMqttServerCertificateCredentials CertificateCredentials { get; set; }
+
public bool ClientCertificateRequired { get; set; }
public bool CheckCertificateRevocation { get; set; }
diff --git a/Tests/MQTTnet.Core.Tests/RPC_Tests.cs b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs
index b0babfa..9f03172 100644
--- a/Tests/MQTTnet.Core.Tests/RPC_Tests.cs
+++ b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs
@@ -8,6 +8,8 @@ using MQTTnet.Client.Receiving;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol;
+using MQTTnet.Client.Options;
+using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
@@ -15,26 +17,39 @@ namespace MQTTnet.Tests
public class RPC_Tests
{
[TestMethod]
- public async Task Execute_Success()
+ public Task Execute_Success_With_QoS_0()
{
- using (var testEnvironment = new TestEnvironment())
- {
- await testEnvironment.StartServerAsync();
- var responseSender = await testEnvironment.ConnectClientAsync();
- await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
+ return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
+ }
- responseSender.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(async e =>
- {
- await responseSender.PublishAsync(e.ApplicationMessage.Topic + "/response", "pong");
- });
+ [TestMethod]
+ public Task Execute_Success_With_QoS_1()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
+ }
- var requestSender = await testEnvironment.ConnectClientAsync();
+ [TestMethod]
+ public Task Execute_Success_With_QoS_2()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
+ }
- var rpcClient = new MqttRpcClient(requestSender);
- var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
+ [TestMethod]
+ public Task Execute_Success_With_QoS_0_MQTT_V5()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
+ }
- Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
- }
+ [TestMethod]
+ public Task Execute_Success_With_QoS_1_MQTT_V5()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
+ }
+
+ [TestMethod]
+ public Task Execute_Success_With_QoS_2_MQTT_V5()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
}
[TestMethod]
@@ -51,5 +66,27 @@ namespace MQTTnet.Tests
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
+
+ private async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
+ {
+ using (var testEnvironment = new TestEnvironment())
+ {
+ await testEnvironment.StartServerAsync();
+ var responseSender = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(protocolVersion));
+ await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
+
+ responseSender.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(async e =>
+ {
+ await responseSender.PublishAsync(e.ApplicationMessage.Topic + "/response", "pong");
+ });
+
+ var requestSender = await testEnvironment.ConnectClientAsync();
+
+ var rpcClient = new MqttRpcClient(requestSender);
+ var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", qosLevel);
+
+ Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
+ }
+ }
}
}