-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Description
On Android when using the SocketsHttpHandler with X509 Client certificates to PKI authenticate with a server, you can only use certificates with a private key coming from a file. When using the more correct and secure way of only using certificates installed in the keychain authentication fails, because the SocketsHttpHandler wants direct access to the private key instead of using the Java APIs to authenticate.
Reproduction Steps
Reproduction steps are a little tricky since they require a server that requires PKI authentication, and a certificate installed on the users device. I'm going to assume you have this here.
Uri uri = new Uri("https://mypkiserver/index.html");
var handler = new SocketsHttpHandler();
var tcs = new TaskCompletionSource<X509Certificate2>();
Android.Security.KeyChain.ChoosePrivateKeyAlias(Platform.CurrentActivity!, response: new KeyChainAliasCallback(tcs),
new String[] { Android.Security.Keystore.KeyProperties.KeyAlgorithmRsa, "DSA" }, // List of acceptable key types. null for any
null, // issuer, null for any
null, // host name of server requesting the cert, null if unavailable
-1, // port of server requesting the cert, -1 if unavailable
"");
certificate = await tcs.Task;
if (certificate != null)
handler.SslOptions.ClientCertificates = new X509Certificate2Collection(certificate);
using var client = new HttpClient(handler);
var response = await client.GetAsync(infoUri); //will usually throw if certificate can't be used
response = response.EnsureSuccessStatusCode();KeyChainAliasCallback.cs responsible for letting the user pick an installed certificate:
public class KeyChainAliasCallback : Java.Lang.Object, Android.Security.IKeyChainAliasCallback
{
private readonly TaskCompletionSource<X509Certificate2?> _tcs;
public KeyChainAliasCallback(TaskCompletionSource<X509Certificate2?> tcs)
{
_tcs = tcs;
}
void Android.Security.IKeyChainAliasCallback.Alias(string? alias)
{
if (alias != null)
{
var certificateChain = Android.Security.KeyChain.GetCertificateChain(Platform.CurrentActivity!, alias!);
if (certificateChain != null && certificateChain.Length > 0)
{
X509Certificate2 certificate = new(certificateChain[0].Handle);
_tcs.TrySetResult(certificate);
}
}
_tcs.TrySetResult(null);
}
}Expected behavior
Certificates used from the keychain where the private key isn't directly accessible will work.
Actual behavior
Fails to authenticate.
Regression?
No response
Known Workarounds
None
Configuration
No response
Other information
Related issue: #45741 (comment)
This is a blocker for larger enterprises since they can't use their secured services with the level of security they require. Using file-based certificates in-app with the full exportable private key just isn't acceptable to them.