Index: chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..735ab90b380291459734847aa864fe639b291d7c |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java |
@@ -0,0 +1,168 @@ |
+// Copyright (c) 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 java.security.cert.CertificateEncodingException; |
+import java.security.cert.X509Certificate; |
+import java.security.Principal; |
+import java.security.PrivateKey; |
+import javax.security.auth.x500.X500Principal; |
+ |
+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.ActivityStatus; |
+import org.chromium.base.CalledByNative; |
+import org.chromium.base.JNINamespace; |
+import org.chromium.base.ThreadUtils; |
+ |
+@JNINamespace("chrome::android") |
+class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void> |
+ implements KeyChainAliasCallback { |
+ |
+ 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; |
+ } |
+ |
+ // KeyChainAliasCallback implementation |
+ @Override |
+ public void alias(String alias) { |
+ if (alias == null) { |
+ // No certificate was selected. |
+ onPostExecute(null); |
+ } else { |
+ mAlias = alias; |
+ // Launch background thread. |
+ execute(); |
+ } |
+ } |
+ |
+ @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"); |
+ return null; |
+ } |
+ |
+ if (key == null || chain == null || chain.length == 0) { |
+ Log.w(TAG, "Empty client certificate chain?"); |
+ return null; |
+ } |
+ |
+ // Get the encoded certificate chain. |
+ byte[][] encoded_chain = new byte[chain.length][]; |
+ try { |
+ for (int i = 0; i < chain.length; ++i) { |
+ encoded_chain[i] = chain[i].getEncoded(); |
+ } |
+ } catch (CertificateEncodingException e) { |
+ Log.w(TAG, "Could not retrieve encoded certificate chain: " + e); |
+ return null; |
+ } |
+ |
+ mEncodedChain = encoded_chain; |
+ mPrivateKey = key; |
+ return null; |
+ } |
+ |
+ @Override |
+ protected void onPostExecute(Void result) { |
+ // Back to the UI thread. |
+ nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mPrivateKey); |
+ } |
+ |
+ |
+ /** |
+ * Create a new asynchronous request to select a client certificate. |
+ * |
+ * @param nativePtr The native object responsible for this request. |
+ * @param keyTypes The list of supported key exchange types. |
+ * @param encodedPrincipals The list of CA DistinguishedNames. |
+ * @param host_name 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 |
+ static private boolean selectClientCertificate( |
+ int nativePtr, String[] keyTypes, byte[][] encodedPrincipals, |
+ String hostName, int port) { |
+ ThreadUtils.assertOnUiThread(); |
+ |
+ Activity activity = ActivityStatus.getActivity(); |
+ if (activity == null) { |
+ Log.w(TAG, "No active Chromium main 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) { |
+ // 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); |
+ |
+ KeyChain.choosePrivateKeyAlias( |
+ activity, request, keyTypes, principals, hostName, port, null); |
+ return true; |
+ } |
+ |
+ // Called to pass request results to native side. |
+ private static native void nativeOnSystemRequestCompletion( |
+ int requestPtr, byte[][] certChain, PrivateKey privateKey); |
+} |