【发布时间】:2022-11-21 12:50:58
【问题描述】:
我在 Xamarin.Forms 应用程序(仅限 Android,无 IOS 项目)中遇到客户端认证问题。 我有一个 .pfx 文件,我将其作为 EmbeddedResource 包含在我的解决方案中。 我还在我的 Android 11 设备上安装了这个 pfx,因此它出现在安全设置用户证书选项卡中。 这是一个完全有效的用户证书。
我想使用此客户端证书向后端发送 Post 请求。 当我使用我的解决方案中的 .pfx 文件时,它运行良好。 问题是,当我从设备的密钥库中读取证书时,我无法做同样的事情(我必须这样做,因为在生产中,解决方案中不会有 .pfx)。
如您所见,在这两种情况下我都使用自定义的 AndroidClientHandler。
在第一个场景中,当我读取 .pfx 文件时,我在我的代码中的某处创建了 http 调用,如下所示:
var ms = new MemoryStream();
Assembly.GetExecutingAssembly().GetManifestResourceStream("CertTest.MyDeviceCert.pfx").CopyTo(ms);
var pfxByteArray = ms.ToArray();
string url = @"https://my-backend-hostname:443/api/endpoint-name";
var objectToPost = someObjectWhatIWantToPost.
var client = new AndroidHttpsClientHandler(pfxByteArray);
var httpClient = new HttpClient(client);
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = JsonContent.Create(objectToPost);
var response = await httpClient.SendAsync(request);
响应是 201 Created,所以一切正常。 魔法发生在 Android HttpClientHandler 类中。 该类的完整代码是:
public class AndroidHttpsClientHandler : AndroidClientHandler
{
private SSLContext sslContext;
private const string clientCertPassword = "123456";
public AndroidHttpsClientHandler(byte[] keystoreRaw) : base()
{
IKeyManager[] keyManagers = null;
ITrustManager[] trustManagers = null;
if (keystoreRaw != null)
{
using (MemoryStream memoryStream = new MemoryStream(keystoreRaw))
{
KeyStore keyStore = KeyStore.GetInstance("pkcs12");
keyStore.Load(memoryStream, clientCertPassword.ToCharArray());
KeyManagerFactory kmf = KeyManagerFactory.GetInstance("x509");
kmf.Init(keyStore, clientCertPassword.ToCharArray());
keyManagers = kmf.GetKeyManagers();
}
}
sslContext = SSLContext.GetInstance("TLS");
sslContext.Init(keyManagers, trustManagers, null);
}
protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection
connection)
{
SSLSocketFactory socketFactory = sslContext.SocketFactory;
if (connection != null)
{
connection.SSLSocketFactory = socketFactory;
}
return socketFactory;
}
}
场景 2:当我想使用设备安装的证书中的证书时,我使用以下代码读取它:
var keyChain = KeyChain.GetCertificateChain(Android.App.Application.Context, alias);
var clientCert = keyChain.FirstOrDefault();
var clientCertByArray = clientCert.GetEncoded();
var client = new AndroidHttpsClientHandler(clientCertByArray);
其余代码与场景 1 相同,但现在当 keyStore.Load(memoryStream, clientCertPassword.ToCharArray()) 在 AndroidHttpsClientHandler 的构造函数中运行时,我得到一个 IOException。
我怀疑 pfxByteArray 和 clientCertByArray 在这两种情况下是不一样的。
我们在 System.Security.Cryptography.X509Certificates 命名空间中有一个 X509Certificate2 类,它有一个 public X509Certificate2(byte[] rawData) 构造函数。 我将 pfxByteArray 和 clientCertByArray 传递给它以检查差异。
var workingCert = new X509Certificate2(pfxByteArray);
var notWorkingClientCert = new X509Certificate2(clientCertByArray);
我注意到一个主要区别:notWorkingClientCert 实例的 PrivateKey 属性为 null,而 HasPrivateKey 属性为 false。
所以我的问题是如何以正确的方式从 KeyStore 中读取证书,就像在读取 .pfx 文件时一样?
我想提一下,此代码向我返回 null,但证书的别名是“MyDeviceCert”:
var privateKey = KeyChain.GetPrivateKey(Android.App.Application.Context, "MyDeviceCert");
【问题讨论】:
-
docs 中有很多关于不从主线程调用、权限、上下文和异常的说明。你读过它们吗?
-
是的,但对我的具体情况没有任何用处。
-
“如果调用者无权访问它,则返回 null”——您确定不是这种情况吗?您是否尝试过备用调用来请求权限?
-
如果是这样,我需要什么许可? :O
-
现在我明白了,我必须在我的 MainActivity 中调用这段代码: string certAlias = "MyCertName"; KeyChain.ChoosePrivateKeyAlias(activity: this, response: new Callback(), keyTypes: new string[] { "RSA", "DSA" }, issuers: null, uri: null, alias: certAlias);用户选择证书后,以下代码将返回私钥: var privateKey = KeyChain.GetPrivateKey(Android.App.Application.Context, certAlias);
标签: android xamarin keystore x509certificate2