ssl - 访问 XamarinForms 中的自签名 X509 证书,用于与 Mosquitto 代理的 mqtt TLS 连接

标签 ssl xamarin xamarin.forms mqtt mosquitto

我希望使用自签名 x509 证书 TLS 保护许多现有的 XamarinForms 应用程序,这些应用程序使用 M2MqttDotnetCore 连接到 mosquitto mqtt 代理客户。

为此,我创建了一个简单的示例 XamarinForms pub/sub 聊天应用程序,以了解如何保护 XamarinForms mqtt 客户端应用程序,该应用程序可以在此 GitHub 存储库中正常运行。 jhalbrecht/XamarinFormsMqttSample

我在 Mosquitto_pub、python 和 .net 控制台应用程序中有示例,它们实现了使用 TLS 和自签名证书通过端口 8883 成功连接到 mosquitto 代理的目标。 XamarinForms UWP 应用程序也可以在不安全和安全的情况下运行。 我无法让 Android 应用程序在端口 8883 上使用 TLS,Android 应用程序确实在端口 1883 上运行不安全。这是来自 Visual Studio 2017 的运行时日志

[0:] M2Mqtt.Exceptions.MqttConnectionException: Exception connecting to the broker ---> System.AggregateException: One or more errors occurred. ---> System.Security.Authentication.AuthenticationException: A call to SSPI failed, see inner exception. ---> Mono.Btls.MonoBtlsException: Ssl error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED
  at /Users/builder/jenkins/workspace/xamarin-android-d15-9/xamarin-android/external/mono/external/boringssl/ssl/handshake_client.c:1132
  at Mono.Btls.MonoBtlsContext.ProcessHandshake () [0x00038] in <fb6d78e506844b3b96d5b35aa047fbbd>:0 
  at Mono.Net.Security.MobileAuthenticatedStream.ProcessHandshake (Mono.Net.Security.AsyncOperationStatus status) [0x0003e] in <fb6d78e506844b3b96d5b35aa047fbbd>:0 
  at (wrapper remoting-invoke-with-check) Mono.Net.Security.MobileAuthenticatedStream.ProcessHandshake(Mono.Net.Security.AsyncOperationStatus)
  at Mono.Net.Security.AsyncHandshakeRequest.Run (Mono.Net.Security.AsyncOperationStatus status) [0x00006] in <fb6d78e506844b3b96d5b35aa047fbbd>:0 
  at Mono.Net.Security.AsyncProtocolRequest+<ProcessOperation>d__24.MoveNext () [0x000ff] in <fb6d78e506844b3b96d5b35aa047fbbd>:0 
--- End of stack trace from previous location where exception was thrown ---
  at Mono.Net.Security.AsyncProtocolRequest+<StartOperation>d__23.MoveNext () [0x0008b] in <fb6d78e506844b3b96d5b35aa047fbbd>:0 
   --- End of inner exception stack trace ---
  at Mono.Net.Security.MobileAuthenticatedStream+<ProcessAuthentication>d__47.MoveNext () [0x00254] in <fb6d78e506844b3b96d5b35aa047fbbd>:0 
   --- End of inner exception stack trace ---
  at System.Threading.Tasks.Task.ThrowIfExceptional (System.Boolean includeTaskCanceledExceptions) [0x00011] in <d4a23bbd2f544c30a48c44dd622ce09f>:0 
  at System.Threading.Tasks.Task.Wait (System.Int32 millisecondsTimeout, System.Threading.CancellationToken cancellationToken) [0x00043] in <d4a23bbd2f544c30a48c44dd622ce09f>:0 
  at System.Threading.Tasks.Task.Wait () [0x00000] in <d4a23bbd2f544c30a48c44dd622ce09f>:0 
  at M2Mqtt.Net.MqttNetworkChannel.Connect () [0x000a8] in <72fbe921f857483bafbb8b397ec98dd1>:0 
  at M2Mqtt.MqttClient.Connect (System.String clientId, System.String username, System.String password, System.Boolean willRetain, System.Byte willQosLevel, System.Boolean willFlag, System.String willTopic, System.String willMessage, System.Boolean cleanSession, System.UInt16 keepAlivePeriod) [0x0001e] in <72fbe921f857483bafbb8b397ec98dd1>:0 
   --- End of inner exception stack trace ---
  at M2Mqtt.MqttClient.Connect (System.String clientId, System.String username, System.String password, System.Boolean willRetain, System.Byte willQosLevel, System.Boolean willFlag, System.String willTopic, System.String willMessage, System.Boolean cleanSession, System.UInt16 keepAlivePeriod) [0x00037] in <72fbe921f857483bafbb8b397ec98dd1>:0 
  at M2Mqtt.MqttClient.Connect (System.String clientId) [0x00000] in <72fbe921f857483bafbb8b397ec98dd1>:0 
  at MqttDataServices.Services.MqttDataService+<Initialize>d__5.MoveNext () [0x00266] in C:\jstuff\MqttSample\MqttDataServices\Services\MqttDataService.cs:183 

我目前加载和访问 X509 证书的方式不安全,也不是最佳做法。有用。我希望最终能够了解如何访问每个移动平台的设备 ca keystore 。我用 cross-platform plug-in FilePicker加载证书,对其进行 base64 编码并保存。

 FileData fileData = await Plugin.FilePicker.CrossFilePicker.Current.PickFile();
 if (fileData == null)
 return; // user canceled file picking

 string fileName = fileData.FileName;
 string content = Convert.ToBase64String(fileData.DataArray, 0, fileData.DataArray.Length,
 Base64FormattingOptions.None);

 string deviceFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), fileName);
 File.WriteAllText(deviceFileName, content);      

我已经通过 Twitter 联系了一些 Xamarin 人员。我有一个 open issue在我上面提到的存储库中讨论了来自 Microsoft 的@baulig 的问题我相信给了我答案但是我目前不知道如何实现它。

I just looked at the certificate validation code and what it does is essentially

var certStore = KeyStore.GetInstance ("AndroidCAStore"); certStore.Load(null);

This is the entry point: https://github.com/mono/mono/blob/master/mcs/class/System/Mono.Btls/MonoBtlsX509LookupAndroid.cs, it calls this code https://github.com/mono/mono/blob/master/mcs/class/System/System/AndroidPlatform.cs#L101 which then calls into xamarin-android code here: https://github.com/xamarin/xamarin-android/blob/master/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs

The KeyStore should be this class: https://developer.xamarin.com/api/type/Java.Security.KeyStore/.

So you should be able to do this via Java.Security.KeyStore.

  • 需要在 AndroidManifest.xml 中授予哪些权限?
  • 我需要研究哪些条款才能正确访问平台 ca keystore ?

初始发布后的添加

  • 2019 年 2 月 27 日(美国标准时间)下午 2:51
    MqttDataService.cs 添加证书和 mqtt 客户端创建
 X509Certificate caCert = X509Certificate.CreateFromCertFile(Path.Combine(filesDirectoryBasePath, "ca.crt"));
 string thePfxPathOnDevice = Path.Combine(filesDirectoryBasePath, "xamarinclient.pfx");
 string theBase64EncodedPfx = File.ReadAllText(thePfxPathOnDevice);

 byte[] certificate = Convert.FromBase64String(theBase64EncodedPfx);
 X509Certificate2 clientCert = new X509Certificate2(certificate, "xamarin");
 _client = new MqttClient(
     GetHostName(_xpdSetting.MqttBrokerAddress),
     Int32.Parse(_xpdSetting.MqttBrokerTlsPort),
     _xpdSetting.UseTls,
     caCert,
     clientCert,
     MqttSslProtocols.TLSv1_2
     //MyRemoteCertificateValidationCallback
     );

最佳答案

由于您使用的是 .Net/Mono Socket(通过 M2MqttDotnetCore),只需使用证书固定,您只需处理 RemoteCertificateValidationCallback。因此不会干扰 Android 的受信任商店等......

Android 上的 SslStream 用法:

注意:Android 上的 SslStream 存在问题,对象分配可能会变得疯狂...我相信(?)有一个关于这个的公开问题。 (我不得不多次使用 Java 的 SSLSocket 来解决这个问题)

启用 native TLS 1.2+

enter image description here

  • 通过 Android 项目构建选项使用 BoringSSL

将您的证书添加到 Android 的 Assets 目录:

├── Assets
│   └── sushihangover.cert
  • 这是您的 cert/.pem 文件(不是您的 key !!)

  • 确保这是一个没有 unicode BOM header 的 ascii 文件

  • 通过 openssl 示例(只需将其更改为您的主机和安全端口)

    echo -n | openssl s_client -connect 10.1.10.250:5001 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
    

RemoteCertificateValidationCallback 实现

注意:以下代码可以在NetStd2.0或Xamarin.Android中使用

X509Certificate sushihangoverCert; // Class level var

bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors certificateErrors)
{
    if (sushihangoverCert == null)
    {
        // There is no non-async version of OpenAppPackageFileAsync (via Xamarin.Essential) 😡 Why!!!
        using (var waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset))
        {
            Task.Run(async () =>
            {
                using (var assetStream = await Xamarin.Essentials.FileSystem.OpenAppPackageFileAsync("sushihangover.cert"))
                using (var memStream = new MemoryStream())
                {
                    assetStream.CopyTo(memStream);
                    sushihangoverCert = new X509Certificate(memStream.ToArray());
                    waitHandle.Set();
                }
            });
            waitHandle.WaitOne();
        }
    }
    return sushihangoverCert.Equals(certificate) ? true : false;
}

SSLStream 使用示例:

注意:这是使用自签名证书连接到 NetCore Web API 端口

using (var tcpClient = new TcpClient("10.1.10.250", 5001))
using (var ssl = new SslStream(tcpClient.GetStream(), false, new RemoteCertificateValidationCallback(CertificateValidation)))
{
    ssl.AuthenticateAsClient("10.1.10.250", null, System.Security.Authentication.SslProtocols.Tls12, false);
    if (ssl.CanWrite)
    {
        var send = Encoding.ASCII.GetBytes("GET /api/item HTTP/1.1\r\nhost: 10.1.10.250\r\n\r\n");
        await ssl.WriteAsync(send, 0, send.Length);
        var buffer = new byte[4096];
        var count = await ssl.ReadAsync(buffer, 0, buffer.Length);
        Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, count));
    }
    else
        throw new SocketException();
}

服务器证书不匹配错误:

如果您的服务器证书(自签名或非自签名)与您固定到的证书不匹配,您将收到:

{Mono.Btls.MonoBtlsException: Ssl error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED

关于ssl - 访问 XamarinForms 中的自签名 X509 证书,用于与 Mosquitto 代理的 mqtt TLS 连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54914621/

相关文章:

linux - 用于 SSL 的 Arangod.conf

ios - 如何最好地将原始像素数据转换为 PNG 文件?

debugging - Xamarin 调试在新创建的项目上损坏导致终止读取 : Bad file descriptor

visual-studio - 无法在调试 Xamarin Android 和 iOS 的设备上部署

php - 如何使用 php 和 ssl 连接到 mysql 数据库?

ssl - 如何为独立的 Mule 应用程序启用 TLSv1.1

c# - 如何使用 C# 在 Android 中将复选框文本的位置更改为左侧

xamarin.forms - MVVMCross 如何在 View 代码隐藏中获取 ViewModel 实例

xamarin.forms - 如何实现Xamarin.Forms自定义RadioButton渲染器? (转发)

java - 通过 SSL 连接到站点时出错