Skip to content

Android: Installed X509 certificates can't be used with SSL authentication #99874

@dotMorten

Description

@dotMorten

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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions