为 MQTTnet 客户端消息的 TLS/SSL 加密导入的 PFX 证书适用于服务,但适用于 Xamarin UWP 应用程序失败

Posted

技术标签:

【中文标题】为 MQTTnet 客户端消息的 TLS/SSL 加密导入的 PFX 证书适用于服务,但适用于 Xamarin UWP 应用程序失败【英文标题】:PFX Certificate Imported for TLS/SSL Encryption of MQTTnet Client Messages Works with Service but Fails with Xamarin UWP App 【发布时间】:2020-12-01 16:13:59 【问题描述】:

我们正在努力做的事情

我们正在加密我们的 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 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)

【问题讨论】:

只是在“我们在寻找什么”中添加了一点内容,因为“答案”有点模糊。 从代码中删除了与问题无关的一行。 我修正了一些错别字并将“服务”更改为“控制台应用程序”,将 .NET 核心 DLL 更改为 .NET 标准 DLL。我不确定这些细节是否相关,但它们可能是相关的。 您能否提供显示错误“提供的客户端证书缺少所需的私钥信息”的堆栈跟踪? 如果您运行 Wireshark 跟踪,您将看到 TLS 握手和随后的 RST 或 Fin,问题是哪一边是第一? 【参考方案1】:

我遇到了几乎完全相同的问题,对我来说,解决方案是创建并实现一个自定义 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&lt;IEnumerable&lt;bytes&gt;&gt; 而不是 IEnumerable&lt;X509Certificate&gt;

*注意:MQTTnet v3.0.13

【讨论】:

以上是关于为 MQTTnet 客户端消息的 TLS/SSL 加密导入的 PFX 证书适用于服务,但适用于 Xamarin UWP 应用程序失败的主要内容,如果未能解决你的问题,请参考以下文章

C# - 使用 feck 的 TLS / SSL Websockets

邮件TLS/SSL加密通信

openssl

NodeJS 为签名和验证消息生成有效的 PEM 密钥

HTTPS协议详解:TLS/SSL握手过程

TLS(SSL)