root/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. JNINamespace
  2. getAlias
  3. getPrivateKey
  4. getCertificateChain
  5. doInBackground
  6. onPostExecute
  7. getAlias
  8. getPrivateKey
  9. getCertificateChain
  10. getAlias
  11. getPrivateKey
  12. getCertificateChain
  13. alias
  14. selectClientCertificate
  15. notifyClientCertificatesChangedOnIOThread
  16. nativeNotifyClientCertificatesChangedOnIOThread
  17. nativeOnSystemRequestCompletion

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
import android.util.Log;

import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.base.ThreadUtils;
import org.chromium.net.AndroidPrivateKey;
import org.chromium.net.DefaultAndroidKeyStore;
import org.chromium.ui.base.WindowAndroid;

import java.security.Principal;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;

import javax.security.auth.x500.X500Principal;

/**
 * Handles selection of client certificate on the Java side. This class is responsible for selection
 * of the client certificate to be used for authentication and retrieval of the private key and full
 * certificate chain.
 *
 * The entry point is selectClientCertificate() and it will be called on the UI thread. Then the
 * class will construct and run an appropriate CertAsyncTask, that will run in background, and
 * finally pass the results back to the UI thread, which will return to the native code.
 */
@JNINamespace("chrome::android")
public class SSLClientCertificateRequest {
    static final String TAG = "SSLClientCertificateRequest";

    private static final DefaultAndroidKeyStore sLocalKeyStore =
            new DefaultAndroidKeyStore();

    /**
     * Common implementation for anynchronous task of handling the certificate request. This
     * AsyncTask uses the abstract methods to retrieve the authentication material from a
     * generalized key store. The key store is accessed in background, as the APIs being exercised
     * may be blocking. The results are posted back to native on the UI thread.
     */
    abstract static class CertAsyncTask extends AsyncTask<Void, Void, Void> {
        // These fields will store the results computed in doInBackground so that they can be posted
        // back in onPostExecute.
        private byte[][] mEncodedChain;
        private AndroidPrivateKey mAndroidPrivateKey;

        // Pointer to the native certificate request needed to return the results.
        private final long mNativePtr;

        CertAsyncTask(long nativePtr) {
            mNativePtr = nativePtr;
        }

        // These overriden methods will be used to access the key store.
        abstract String getAlias();
        abstract AndroidPrivateKey getPrivateKey(String alias);
        abstract X509Certificate[] getCertificateChain(String alias);

        @Override
        protected Void doInBackground(Void... params) {
            String alias = getAlias();
            if (alias == null) return null;

            AndroidPrivateKey key = getPrivateKey(alias);
            X509Certificate[] chain = getCertificateChain(alias);

            if (key == null || chain == null || chain.length == 0) {
                Log.w(TAG, "Empty client certificate chain?");
                return null;
            }

            // Encode the certificate chain.
            byte[][] encodedChain = new byte[chain.length][];
            try {
                for (int i = 0; i < chain.length; ++i) {
                    encodedChain[i] = chain[i].getEncoded();
                }
            } catch (CertificateEncodingException e) {
                Log.w(TAG, "Could not retrieve encoded certificate chain: " + e);
                return null;
            }

            mEncodedChain = encodedChain;
            mAndroidPrivateKey = key;
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            ThreadUtils.assertOnUiThread();
            nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mAndroidPrivateKey);
        }
    }

    /** Implementation of CertAsyncTask for the system KeyChain API. */
    private static class CertAsyncTaskKeyChain extends CertAsyncTask {
        final Context mContext;
        final String mAlias;

        CertAsyncTaskKeyChain(Context context, long nativePtr, String alias) {
            super(nativePtr);
            mContext = context;
            assert alias != null;
            mAlias = alias;
        }

        @Override
        String getAlias() {
            return mAlias;
        }

        @Override
        AndroidPrivateKey getPrivateKey(String alias) {
            try {
                return sLocalKeyStore.createKey(KeyChain.getPrivateKey(mContext, alias));
            } catch (KeyChainException e) {
                Log.w(TAG, "KeyChainException when looking for '" + alias + "' certificate");
                return null;
            } catch (InterruptedException e) {
                Log.w(TAG, "InterruptedException when looking for '" + alias + "'certificate");
                return null;
            }
        }

        @Override
        X509Certificate[] getCertificateChain(String alias) {
            try {
                return KeyChain.getCertificateChain(mContext, alias);
            } catch (KeyChainException e) {
                Log.w(TAG, "KeyChainException when looking for '" + alias + "' certificate");
                return null;
            } catch (InterruptedException e) {
                Log.w(TAG, "InterruptedException when looking for '" + alias + "'certificate");
                return null;
            }
        }
    }

    /** Implementation of CertAsyncTask for use with a PKCS11-backed KeyStore. */
    private static class CertAsyncTaskPKCS11 extends CertAsyncTask {
        private final PKCS11AuthenticationManager mPKCS11AuthManager;
        private final String mHostName;
        private final int mPort;

        CertAsyncTaskPKCS11(long nativePtr, String hostName, int port,
                PKCS11AuthenticationManager pkcs11CardAuthManager) {
            super(nativePtr);
            mHostName = hostName;
            mPort = port;
            mPKCS11AuthManager = pkcs11CardAuthManager;
        }

        @Override
        String getAlias() {
            return mPKCS11AuthManager.getClientCertificateAlias(mHostName, mPort);
        }

        @Override
        AndroidPrivateKey getPrivateKey(String alias) {
            return mPKCS11AuthManager.getPrivateKey(alias);
        }

        @Override
        X509Certificate[] getCertificateChain(String alias) {
            return mPKCS11AuthManager.getCertificateChain(alias);
        }
    }

    /**
     * The system KeyChain API will call us back on the alias() method, passing the alias of the
     * certificate selected by the user.
     */
    private static class KeyChainCertSelectionCallback implements KeyChainAliasCallback {
        private final long mNativePtr;
        private final Context mContext;

        KeyChainCertSelectionCallback(Context context, long nativePtr) {
            mContext = context;
            mNativePtr = nativePtr;
        }

        @Override
        public void alias(final String alias) {
            // This is called by KeyChainActivity in a background thread. Post task to
            // handle the certificate selection on the UI thread.
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (alias == null) {
                        // No certificate was selected.
                        ThreadUtils.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                nativeOnSystemRequestCompletion(mNativePtr, null, null);
                            }
                        });
                    } else {
                        new CertAsyncTaskKeyChain(mContext, mNativePtr, alias).execute();
                    }
                }
            });
        }
    }

    /**
     * Create a new asynchronous request to select a client certificate.
     *
     * @param nativePtr         The native object responsible for this request.
     * @param window            A WindowAndroid instance.
     * @param keyTypes          The list of supported key exchange types.
     * @param encodedPrincipals The list of CA DistinguishedNames.
     * @param hostName          The server host name is available (empty otherwise).
     * @param port              The server port if available (0 otherwise).
     * @return                  true on success.
     * Note that nativeOnSystemRequestComplete will be called iff this method returns true.
     */
    @CalledByNative
    private static boolean selectClientCertificate(final long nativePtr, final WindowAndroid window,
            final String[] keyTypes, byte[][] encodedPrincipals, final String hostName,
            final int port) {
        ThreadUtils.assertOnUiThread();

        final Activity activity = window.getActivity().get();
        if (activity == null) {
            Log.w(TAG, "Certificate request on GC'd activity.");
            return false;
        }

        // Build the list of principals from encoded versions.
        Principal[] principals = null;
        if (encodedPrincipals.length > 0) {
            principals = new X500Principal[encodedPrincipals.length];
            try {
                for (int n = 0; n < encodedPrincipals.length; n++) {
                    principals[n] = new X500Principal(encodedPrincipals[n]);
                }
            } catch (Exception e) {
                Log.w(TAG, "Exception while decoding issuers list: " + e);
                return false;
            }
        }

        final Principal[] principalsForCallback = principals;
        // Certificate for client authentication can be obtained either from the system store of
        // from a smart card (if available).
        Runnable useSystemStore = new Runnable() {
            @Override
            public void run() {
                KeyChainCertSelectionCallback callback =
                        new KeyChainCertSelectionCallback(activity.getApplicationContext(),
                            nativePtr);
                KeyChain.choosePrivateKeyAlias(activity, callback, keyTypes, principalsForCallback,
                    hostName, port, null);
            }
        };

        final Context appContext = activity.getApplicationContext();
        final PKCS11AuthenticationManager smartCardAuthManager =
            ((ChromiumApplication) appContext).getPKCS11AuthenticationManager();
        if (smartCardAuthManager.isPKCS11AuthEnabled()) {
            // Smart card support is available, prompt the user whether to use it or Android system
            // store.
            Runnable useSmartCard = new Runnable() {
                @Override
                public void run() {
                    new CertAsyncTaskPKCS11(nativePtr, hostName, port,
                            smartCardAuthManager).execute();
                }
            };
            Runnable cancelRunnable = new Runnable() {
                @Override
                public void run() {
                    // We took ownership of the request, need to delete it.
                    nativeOnSystemRequestCompletion(nativePtr, null, null);
                }
            };

            KeyStoreSelectionDialog selectionDialog = new KeyStoreSelectionDialog(
                    useSystemStore, useSmartCard, cancelRunnable);
            selectionDialog.show(activity.getFragmentManager(), null);
        } else {
            // Smart card support is not available, use the system store unconditionally.
            useSystemStore.run();
        }

        // We've taken ownership of the native ssl request object.
        return true;
    }

    public static void notifyClientCertificatesChangedOnIOThread() {
        Log.d(TAG, "ClientCertificatesChanged!");
        nativeNotifyClientCertificatesChangedOnIOThread();
    }

    private static native void nativeNotifyClientCertificatesChangedOnIOThread();

    // Called to pass request results to native side.
    private static native void nativeOnSystemRequestCompletion(
            long requestPtr, byte[][] certChain, AndroidPrivateKey androidKey);
}

/* [<][>][^][v][top][bottom][index][help] */