问题描述
我们正在尝试做的事情
我们正在加密MQTT消息。我们一直在使用MQTTnet。这支持TLS加密,因此这是为现有解决方案启用此功能的问题。我们的UWP Xamarin应用程序和.NET控制台应用程序对所有MQTT都使用相同的.NET标准DLL。对于代理,我们使用的是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 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 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);
我认为它与UWP的MqttClientOptionsBuilderTlsParameters
类定义有关,我注意到它为Certificates属性定义了IEnumerable<IEnumerable<bytes>>
而不是IEnumerable<X509Certificate>
。
*注意:MQTTnet v3.0.13