我们正在尝试做的事情
我们正在加密我们的 MQTT 消息。我们一直在使用 MQTTnet。这支持 TLS 加密,因此需要为现有解决方案启用此功能。我们的 UWP Xamarin 应用程序和 .NET 控制台应用程序都使用相同的 .NET 标准 DLL 来处理所有 MQTT。对于代理,我们使用的是 Mosquitto,但我们计划切换到使用 MQTTnet 服务器的简单应用程序。
这是问题所在
我们已经成功地在代理端以及控制台应用程序之一添加了 TLS 加密,但我们的 UWP Xamarin 应用程序遇到了问题。在其当前状态下,它会引发异常,并显示“提供的客户端证书缺少所需的私钥信息”的消息。
我们在寻找什么
- 确认这是可能的
- 对为什么这不起作用的基本理解(即确认我们的假设或其他)
- 这个问题的解决方案
- 要检查的事情的想法。
因此,如果有人知道“答案”,那就太好了。否则,想法,建议,专业知识,共享经验等。
我们的假设
- 我们假设这是可能的
- 这似乎不是 MQTTnet 错误或限制
- 我们假设在使用 Xamarin UWP 应用程序时出现此错误的原因是对磁盘访问或注册表访问的某些限制
- 我们一直假设因为 PFX 文件适用于控制台应用程序,它也应该适用于 Xamarin UWP 应用程序(但我们已经玩弄了它所在的存储,因为 Xamarin UWP 应用程序只能在用户密钥中访问它店铺)
我们尝试过的事情
- 我们已经阅读了 MSDN 上的规范文档
- 我们已经阅读了博客文章
- 我们已阅读并遵循各种 Stack Overflow 帖子的建议
- 我们已阅读并遵循各种 MQTTnet 支持帖子的建议
- 我们尝试了两种不同的代理(Mosquitto 和一个使用 MQTTnet 服务器的示例应用程序)。
- 我们尝试使用 OpenSSL 并通过操作系统(Windows 10)手动创建证书;为了获得更可控/确定性/可重复的结果,我们将切换到使用 PS 脚本来处理后者
- 我们已经尝试为用户存储和机器存储创建证书
- 我们已经尝试将证书导入商店(见代码)
- 我们已经为 X509KeyStorage 标志尝试了许多不同的组合(即可导出、持久、用户密钥集等)
- 我们尝试以管理员身份运行 Visual Studio
- 我们使用了 SysInternals ProcMon 并试图确定失败的地方(即 HD 访问或注册表访问)
- 我们尝试为 Xamarin 应用程序启用不同的功能
有用的链接
- https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/working-with-certificates
- https://docs.microsoft.com/en-us/troubleshoot/iis/cannot-import-ssl-pfx-local-certificate
- https://github.com/chkr1011/MQTTnet/wiki/Server-and-client-method-documentation
- https://github.com/chkr1011/MQTTnet/issues/115
- https://github.com/chkr1011/MQTTnet/issues/124
- https://paulstovell.com/x509certificate2/
- MQTTnet 客户端无法连接服务器证书
- 在 Windows 上不使用 OpenSSL 从 pfx 文件或证书存储中提取私钥
我们的一些准则
/// <summary>
/// Connect to the MQTT broker using the defined options
/// </summary>
private async Task ConnectAsync()
{
IMqttClientOptions options = CreateMqttClientOptions();
try
{
await m_mqttClient.ConnectAsync(options);
}
catch (Exception ex)
{
m_logger?.LogCritical(ex, "Failed to reconnect - service unavailable");
}
}
/// <summary>
/// Helper function used to create the MQTT client options object. This includes the certificate.
/// </summary>
private IMqttClientOptions CreateMqttClientOptions()
{
string filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MobileTools.2.pfx");
X509Certificate2 certificate = new X509Certificate2(
filepath,
"notactuallymypassword",
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet);
//InstallCertificate(certificate);
// Set-up options.
return new MqttClientOptionsBuilder()
.WithCleanSession(true)
.WithClientId(m_clientID)
.WithTcpServer("NotActuallyDnsName", m_configuration.Port)
.WithTls(new MqttClientOptionsBuilderTlsParameters
{
Certificates = new List<byte[]>
{
certificate.Export(X509ContentType.Cert)
},
CertificateValidationCallback = (X509Certificate xCertificate, X509Chain xChain, SslPolicyErrors sslPolicyErrors, IMqttClientOptions clientOptions) =>
{
return true;
},
UseTls = true
})
.Build();
}
/// <summary>
/// Helper function used to create the MQTT client options object. This includes the certificate.
/// </summary>
private void InstallCertificate(X509Certificate2 certificate)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();
}
堆栈跟踪
The client certificate provided is missing the required private key information. ---> System.ArgumentException: The parameter is incorrect.
The client certificate provided is missing the required private key information.
at Windows.Networking.Sockets.StreamSocketControl.put_ClientCertificate(Certificate value)
at MQTTnet.Implementations.MqttTcpChannel.ConnectAsync(CancellationToken cancellationToken)
at MQTTnet.Internal.MqttTaskTimeout.WaitAsync(Func`2 action, TimeSpan timeout, CancellationToken cancellationToken)
at MQTTnet.Adapter.MqttChannelAdapter.ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at MQTTnet.Adapter.MqttChannelAdapter.WrapException(Exception exception)
at MQTTnet.Adapter.MqttChannelAdapter.ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
at MQTTnet.Client.MqttClient.ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken)
at MQTTnet.Client.MqttClient.ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken)
at TS.Orbit.MQTTLib.Client.MqttNetClient.ConnectAsync(IMQTTClientConfiguration configuration)