我们正在尝试做什么
我们正在加密我们的 MQTT 消息。我们一直在使用 MQTTnet。这支持 TLS 加密,因此这是一个为现有解决方案启用此功能的问题。我们的 UWP Xamarin 应用程序和 .NET 控制台应用程序都对所有 MQTT 使用相同的 .NET 标准 DLL。对于代理,我们使用的是 Mosquitto,但我们计划切换到使用 MQTTnet 服务器的简单应用程序。
问题来了
我们已经在代理端以及其中一个控制台应用程序中成功添加了 TLS 加密,但是我们的 UWP Xamarin 应用程序遇到了问题。在其当前状态下,它会抛出异常并显示消息“提供的客户端证书缺少所需的私钥信息。”
我们在寻找什么
- 确认这是可能的
- 基本了解为什么这不起作用(即确认我们的假设或其他)
- 这个问题的解决方案
- 要检查的事情的想法。
因此,如果有人知道“答案”,那就太好了。否则,想法、建议、专业知识、共享经验等。
我们的假设
- 我们假设这是可能的
- 这似乎不是 MQTTnet 错误或限制
- 我们假设在使用 Xamarin UWP 应用程序时出现此错误的原因是磁盘访问或注册表访问受到某些限制
- 我们一直假设,因为 PFX 文件适用于控制台应用程序,所以它也应该适用于 Xamarin UWP 应用程序(但我们已经研究过它位于哪个存储区,因为 Xamarin UWP 应用程序只能在用户 keystore )
我们尝试过的事情
- 我们已经阅读了 MSDN 上的规范文档
- 我们已阅读博客文章
- 我们已阅读并遵循各种 Stack Overflow 帖子的建议
- 我们已阅读并遵循各种 MQTTnet 支持帖子的建议
- 我们尝试了两种不同的代理(Mosquitto 和使用 MQTTnet 服务器的示例应用程序)。
- 我们已尝试使用 OpenSSL 和通过操作系统 (Windows 10) 手动创建证书;为了获得更可控/确定性/可重复的结果,我们将切换到对后者使用 PS 脚本
- 我们已经尝试为用户存储和机器存储创建证书
- 我们已尝试将证书导入商店(参见代码)
- 我们为 X509KeyStorage 标志尝试了许多不同的组合(即可导出、持久、用户 key 集等)
- 我们试过以管理员身份运行 visual studio
- 我们已经使用 SysInternals ProcMon 并尝试确定失败的地方(即 HD 访问或注册表访问)
- 我们已尝试为 Xamarin 应用程序启用不同的功能
有用的链接
- https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/working-with-certificates
- https://learn.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 client can't connect server certificate
- Extract private key from pfx file or certificate store WITHOUT using OpenSSL on Windows
我们的一些代码
/// <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)
最佳答案
我遇到了几乎完全相同的问题,对我来说,解决方案是创建并实现自定义 TLS 参数类,该类扩展基本 MqttClientOptionsBuilderTlsParameters 类并覆盖 Certificates 属性。
public class CustomTLSParameters : MqttClientOptionsBuilderTlsParameters
{
public new IEnumerable<X509Certificate> Certificates { get; set; }
}
所以客户最终是:
var MQTTClient = new MqttFactory().CreateManagedMqttClient();
var url = "myIP";
var port = 8883;
var certs = new List<X509Certificate> {
new X509Certificate2("myCert.pfx")
};
var tlsParams = new CustomTLSParameters () {
AllowUntrustedCertificates = true,
UseTls = true,
Certificates = certs,
IgnoreCertificateChainErrors = true,
IgnoreCertificateRevocationErrors = true
};
var options = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(new MqttClientOptionsBuilder()
.WithClientId(Guid.NewGuid().ToString())
.WithTcpServer(url, port)
.WithTls(tlsParams)
.WithCleanSession()
.Build())
.Build();
await MQTTClient.StartAsync(options);
我认为这与 MqttClientOptionsBuilderTlsParameters
有关UWP 的类定义,我注意到它定义了 IEnumerable<IEnumerable<bytes>>
而不是 IEnumerable<X509Certificate>
对于 Certificates 属性。
*注:MQTTnet v3.0.13
关于c# - 为 MQTTnet 客户端消息的 TLS/SSL 加密导入的 PFX 证书适用于服务,但在 Xamarin UWP 应用程序中失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63367614/