Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / SSLClientCertificateRequest.java
index c22e7dc..f95cca1 100644 (file)
@@ -16,109 +16,200 @@ import org.chromium.base.ActivityStatus;
 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 java.security.Principal;
-import java.security.PrivateKey;
 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")
-class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void>
-        implements KeyChainAliasCallback {
-
+public class SSLClientCertificateRequest {
     static final String TAG = "SSLClientCertificateRequest";
 
-    // ClientCertRequest models an asynchronous client certificate request on the Java side. Use
-    // selectClientCertificate() on the UI thread to start/create a new request, this will launch a
-    // system activity through KeyChain.choosePrivateKeyAlias() to let the user select a client
-    // certificate.
-    //
-    // The selected certificate will be sent back as a string alias, which is used to call
-    // KeyChain.getCertificateChain() and KeyChain.getPrivateKey(). Unfortunately, these APIs are
-    // blocking, thus can't be called from the UI thread.
-    //
-    // To solve this, start an AsyncTask when the alias is received. It will retrieve the
-    // certificate chain and private key in the background, then later send the result back to the
-    // UI thread.
-    //
-    private final int mNativePtr;
-    private String mAlias;
-    private byte[][] mEncodedChain;
-    private PrivateKey mPrivateKey;
-
-    private SSLClientCertificateRequest(int nativePtr) {
-        mNativePtr = nativePtr;
-        mAlias = null;
-        mEncodedChain = null;
-        mPrivateKey = null;
-    }
+    private static final DefaultAndroidKeyStore sLocalKeyStore =
+            new DefaultAndroidKeyStore();
 
-    // KeyChainAliasCallback implementation
-    @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.
-                    onPostExecute(null);
-                } else {
-                    mAlias = alias;
-                    // Launch background thread.
-                    execute();
+    /**
+     * 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 int mNativePtr;
+
+        CertAsyncTask(int 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;
             }
-        });
-    }
 
-    @Override
-    protected Void doInBackground(Void... params) {
-        // Executed in a background thread, can call blocking APIs.
-        X509Certificate[] chain = null;
-        PrivateKey key = null;
-        Context context = ActivityStatus.getActivity().getApplicationContext();
-        try {
-            key = KeyChain.getPrivateKey(context, mAlias);
-            chain = KeyChain.getCertificateChain(context, mAlias);
-        } catch (KeyChainException e) {
-            Log.w(TAG, "KeyChainException when looking for '" + mAlias + "' certificate");
-            return null;
-        } catch (InterruptedException e) {
-            Log.w(TAG, "InterruptedException when looking for '" + mAlias + "'certificate");
+            mEncodedChain = encodedChain;
+            mAndroidPrivateKey = key;
             return null;
         }
 
-        if (key == null || chain == null || chain.length == 0) {
-            Log.w(TAG, "Empty client certificate chain?");
-            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, int nativePtr, String alias) {
+            super(nativePtr);
+            mContext = context;
+            assert alias != null;
+            mAlias = alias;
+        }
+
+        @Override
+        String getAlias() {
+            return mAlias;
         }
 
-        // Get the encoded certificate chain.
-        byte[][] encodedChain = new byte[chain.length][];
-        try {
-            for (int i = 0; i < chain.length; ++i) {
-                encodedChain[i] = chain[i].getEncoded();
+        @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;
             }
-        } catch (CertificateEncodingException e) {
-            Log.w(TAG, "Could not retrieve encoded certificate chain: " + e);
-            return null;
         }
 
-        mEncodedChain = encodedChain;
-        mPrivateKey = key;
-        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;
+            }
+        }
     }
 
-    @Override
-    protected void onPostExecute(Void result) {
-        // Back to the UI thread.
-        nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mPrivateKey);
+    /** 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(int 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 int mNativePtr;
+        private final Context mContext;
+
+        KeyChainCertSelectionCallback(Context context, int 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.
@@ -132,11 +223,10 @@ class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void>
      * Note that nativeOnSystemRequestComplete will be called iff this method returns true.
      */
     @CalledByNative
-    private static boolean selectClientCertificate(int nativePtr, String[] keyTypes,
-            byte[][] encodedPrincipals, String hostName, int port) {
+    private static boolean selectClientCertificate(final int nativePtr, final String[] keyTypes,
+            byte[][] encodedPrincipals, final String hostName, final int port) {
         ThreadUtils.assertOnUiThread();
-
-        Activity activity = ActivityStatus.getActivity();
+        final Activity activity = ActivityStatus.getActivity();
         if (activity == null) {
             Log.w(TAG, "No active Chromium main activity!?");
             return false;
@@ -151,22 +241,68 @@ class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void>
                     principals[n] = new X500Principal(encodedPrincipals[n]);
                 }
             } catch (Exception e) {
-                // Bail on error.
                 Log.w(TAG, "Exception while decoding issuers list: " + e);
                 return false;
             }
         }
 
-        // All good, create new request, add it to our list and launch the certificate selection
-        // activity.
-        SSLClientCertificateRequest request = new SSLClientCertificateRequest(nativePtr);
+        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();
+        }
 
-        KeyChain.choosePrivateKeyAlias(
-                activity, request, keyTypes, principals, hostName, port, null);
+        // We've taken ownership of the native ssl request object.
         return true;
     }
 
+    // TODO(yfriedman): Java code doesn't have a global for the IO thread so it's exposed here.
+    // X509Util helper function could probably move here (as it's still in net/)
+    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(
-            int requestPtr, byte[][] certChain, PrivateKey privateKey);
+            int requestPtr, byte[][] certChain, AndroidPrivateKey androidKey);
 }