You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Xamarin Forms Android访问用户证书存储失败的权限与操作排查

解决Xamarin Forms Android读取用户安装的X.509客户端证书问题

嘿,这个问题我之前也踩过坑!核心原因是Android的证书存储机制和UWP完全不一样,你用的.NET X509Store在Android上根本访问不到用户手动安装的客户端证书——它默认只会读取应用自己的私有存储,而不是系统级的证书KeyChain(就是你在浏览器里能用的那个存储)。下面给你一步步说怎么解决:

一、先补全必要权限

除了你已经加的USE_CREDENTIALS,还得在AndroidManifest.xml里加这些权限:

  • android.permission.READ_EXTERNAL_STORAGE:如果证书是用户从SD卡/外部存储导入的
  • 针对Android 6.0+(API 23以上),别忘了动态请求USE_CREDENTIALS权限,因为它属于危险权限,不能只在清单里声明就完事。

二、别用X509Store了,改用Android原生KeyChain API

因为.NET的X509Store在Android上不兼容系统证书存储,我们得用DependencyService写平台特定的实现:

1. 在共享项目里定义接口

先写一个跨平台的接口,方便共享代码调用:

public interface IClientCertificateProvider
{
    Task<X509Certificate2> GetClientCertificateAsync(string friendlyName);
}

2. Android项目里写具体实现

在Android项目中添加这个实现类,用KeyChain来获取证书:

[assembly: Dependency(typeof(AndroidClientCertificateProvider))]
namespace YourAppNamespace.Droid
{
    public class AndroidClientCertificateProvider : IClientCertificateProvider
    {
        private TaskCompletionSource<X509Certificate2> _certTaskSource;

        public async Task<X509Certificate2> GetClientCertificateAsync(string friendlyName)
        {
            _certTaskSource = new TaskCompletionSource<X509Certificate2>();

            // 触发系统证书选择弹窗(如果需要直接指定别名,后面说)
            var pickCertIntent = KeyChain.CreateChoosePrivateKeyIntent(null, null);
            ((MainActivity)Forms.Context).StartActivityForResult(pickCertIntent, 1001);

            var selectedCert = await _certTaskSource.Task;
            // 筛选出你要的证书
            return selectedCert?.FriendlyName == friendlyName ? selectedCert : null;
        }

        // 接收证书选择结果的方法,要在MainActivity里调用
        public void OnCertificateSelected(int requestCode, Result resultCode, Intent data)
        {
            if (requestCode != 1001 || resultCode != Result.Ok)
            {
                _certTaskSource.SetResult(null);
                return;
            }

            try
            {
                var certAlias = data.GetStringExtra(KeyChain.ExtraKeyAlias);
                if (string.IsNullOrEmpty(certAlias))
                {
                    _certTaskSource.SetResult(null);
                    return;
                }

                // 获取证书链,取第一个就是我们要的客户端证书
                var androidCerts = KeyChain.GetCertificateChain(Forms.Context, certAlias);
                if (androidCerts?.Length > 0)
                {
                    // 转换成.NET的X509Certificate2格式
                    var certBytes = androidCerts[0].Encoded;
                    _certTaskSource.SetResult(new X509Certificate2(certBytes));
                }
                else
                {
                    _certTaskSource.SetResult(null);
                }
            }
            catch (Exception ex)
            {
                _certTaskSource.SetException(ex);
            }
        }
    }
}

然后在你的MainActivity里重写OnActivityResult,把结果传给上面的实现:

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);
    var certProvider = DependencyService.Get<IClientCertificateProvider>() as AndroidClientCertificateProvider;
    certProvider?.OnCertificateSelected(requestCode, resultCode, data);
}

3. 在共享代码里调用

把原来的GetClientCertificate方法改成异步调用:

private async Task<X509Certificate2> GetClientCertificateAsync()
{
    var certProvider = DependencyService.Get<IClientCertificateProvider>();
    return await certProvider.GetClientCertificateAsync("MY_CERTNAME");
}

三、额外技巧:跳过选择弹窗直接找证书

如果你不想每次都让用户选证书,知道证书的别名的话,可以直接用KeyChain.GetCertificateChain查询,不用弹选择框:

// 替换掉弹窗的代码,直接查询指定别名
var androidCerts = KeyChain.GetCertificateChain(Forms.Context, "你的证书别名");

不过别名需要你提前知道——用户安装证书时设置的名称,或者你可以先调用一次选择弹窗,获取到别名后存起来下次用。

为啥原来的代码在UWP能用?

因为UWP的X509Store(StoreLocation.CurrentUser)直接映射到系统的用户证书存储,和浏览器用的是同一个,所以能读到。但Android的.NET实现没做这个映射,必须用原生API才行。

内容的提问来源于stack exchange,提问作者phil soady

火山引擎 最新活动