首页 > 解决方案 > 客户端证书在 xamarin.android 中不起作用

问题描述

我正在尝试在我的 xamarin.android 项目中使用https://github.com/chkr1011/MQTTnet库建立 mtls 连接。在此过程中,我在调用 conResult.Wait(TimeSpan.FromSeconds(60)); 的 Init 方法中遇到了这样的异常;

{MQTTnet.Exceptions.MqttCommunicationException:身份验证失败,请参阅内部异常。---> System.Security.Authentication.AuthenticationException:身份验证失败,请参阅内部异常。---> System.Security.Cryptography.CryptographicException:捕获...} {System.Security.Cryptography.CryptographicException:捕获未处理的异常MonoBtlsSslCtx.ProcessHandshake。---> System.NullReferenceException:对象引用未设置为对象的实例。在 Mono.Btls.MonoBtlsSsl.SetPrivateKey (Mono.Btls....}

这是我的代码:

public void Init()
    {        
        ILoadCertificate certificateService = DependencyService.Get<ILoadCertificate>();
        var cert = certificateService.LoadPemCertificate("certificate", "private_key");
        Console.WriteLine(cert.GetRSAPrivateKey());
        string clientId = Guid.NewGuid().ToString();
        string mqttURI = "";
        int mqttPort = 8883;

        var factory = new MqttFactory();
        var mqttClient = factory.CreateMqttClient();

        bool disableServerValidation = true;
        var tlsParameters = new MqttClientOptionsBuilderTlsParameters
        {
            UseTls = true,
            Certificates = new[] { new X509Certificate(cert.Export(X509ContentType.Cert)) },
            IgnoreCertificateChainErrors = disableServerValidation,
            AllowUntrustedCertificates = disableServerValidation,
            SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
            CertificateValidationHandler = (o) =>
            {
                return true;
            },
        };

        var connectOptions = new MqttClientOptionsBuilder()
            .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
            .WithClientId(clientId)
            .WithTcpServer(mqttURI, mqttPort)
            .WithCommunicationTimeout(new TimeSpan(0, 2, 30))
            .WithCleanSession()
            .WithTls(tlsParameters)
            .Build();

        var conResult = mqttClient.ConnectAsync(connectOptions);
        conResult.ContinueWith(r =>
        {
            Console.WriteLine(r.Result.ResultCode);
            Console.WriteLine(r.Exception.StackTrace);
        });
        conResult.Wait(TimeSpan.FromSeconds(60));
        var t = mqttClient.PublishAsync("events/test", "test");
        t.ContinueWith(r =>
        {
            Console.WriteLine(r.Result.PacketIdentifier);
            Console.WriteLine(r.Exception.StackTrace);
        });
        t.Wait();
    }

//This methods is used to construct certificate:

public X509Certificate2 GetCertificate(string pemCert, string pemKey)
    {
        string fileNameCert = Path.Combine(Environment
        .GetFolderPath(Environment.SpecialFolder.LocalApplicationData), pemCert);
        var pem = File.ReadAllText(fileNameCert);

        string fileNameKey = Path.Combine(Environment
        .GetFolderPath(Environment.SpecialFolder.LocalApplicationData), pemKey);
        var key = File.ReadAllText(fileNameKey);

        var keyPair = (AsymmetricCipherKeyPair)new PemReader(new StringReader(key))
       .ReadObject();
        var cert = (Org.BouncyCastle.X509.X509Certificate)new PemReader(new 
        StringReader(pem)).ReadObject();

        var builder = new Pkcs12StoreBuilder();
        builder.SetUseDerEncoding(true);
        var store = builder.Build();

        var certEntry = new X509CertificateEntry(cert);
        store.SetCertificateEntry("", certEntry);
        store.SetKeyEntry("", new AsymmetricKeyEntry(keyPair.Private), new[] { certEntry });

        byte[] data;
        using (var ms = new MemoryStream())
        {
            store.Save(ms, Array.Empty<char>(), new SecureRandom());
            data = ms.ToArray();
        }

        return new X509Certificate2(data);
    }

public byte[] GetBytesFromPEM(string pemString, string type)
    {
        string header; string footer;
        switch (type)
        {
            case "cert":
                header = "-----BEGIN CERTIFICATE-----";
                footer = "-----END CERTIFICATE-----";
                break;
            case "key":
                header = "-----BEGIN RSA PRIVATE KEY-----";
                footer = "-----END RSA PRIVATE KEY-----";
                break;
            default:
                return null;
        }

        int start = pemString.IndexOf(header) + header.Length;
        int end = pemString.IndexOf(footer, start) - start;
        return Convert.FromBase64String(pemString.Substring(start, end));
    }

我有几个选择:

  1. 在 GetCertificate 方法中构造证书可能存在一些问题;
  2. MqttNet 库本身存在问题。我怀疑该库不适用于 xamarin.android 中的证书,因为我发现了这样的主题:https://github.com/xamarin/xamarin-android/issues/4481https://github.com/chkr1011/MQTTnet /问题/883

我尝试以这种方式构造证书,但 xamarin 不支持 rsa.ImportRSAPrivateKey。我得到:System.PlatformNotSupportedException:此平台不支持操作。

string fileNameCert = Path.Combine(Environment
    .GetFolderPath(Environment
    .SpecialFolder.LocalApplicationData), certificatePath);

using var publicKey = new X509Certificate2(fileNameCert);

string fileNameKey = Path.Combine(Environment
     .GetFolderPath(Environment
     .SpecialFolder.LocalApplicationData), privateKeyPath);

 using var rsa = RSA.Create();
 byte[] keyBuffer = 
 GetBytesFromPEM(File.ReadAllText(fileNameKey), "key");

 int o;
 rsa.ImportRSAPrivateKey(keyBuffer, out o);

 var keyPair = publicKey.CopyWithPrivateKey(rsa);
 return new X509Certificate2(keyPair.Export(X509ContentType.Pkcs12));

标签: c#xamarinxamarin.androidmtlsmqttnet

解决方案


推荐阅读