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..0f12ea9b259bfafa8d136a455416cfca7bda6feb |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java |
@@ -0,0 +1,180 @@ |
+// Copyright (c) 2012 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("browser") |
+public class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void> |
+ implements KeyChainAliasCallback { |
+ |
+ static final String TAG = "SSLClientCertificateRequest"; |
+ |
+ // ClientCertRequest models an asynchronous client certificate request |
+ // on the Dalvik side. There is a matching C++ ClientCertRequest |
+ // class that has _slightly_ different lifecycles (e.g. if a Tab |
+ // is closed while an asynchronous request is still pending, the native |
+ // C++ class will be deleted, but the Java one must persist until the |
+ // system sends its answer to the activity). |
+ // |
+ // In theory, it is possible to only have one Java class to model |
+ // each request, but this makes the native interface more complicated |
+ // to use. |
+ // |
+ // Each request must be started from the UI thread, and the system |
+ // will answer the KeyChain.choosePrivateKeyAlias() call with a |
+ // private key alias string, which can be used to call |
+ // KeyChain.getCertificateChain() and KeyChain.getPrivateKey(), |
+ // however these functions are blocking and can't be called on the |
+ // UI thread. |
+ // |
+ // To solve this, start an AsyncTask when an 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; |
+ |
+ public 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. |
+ nativeOnRequestCompletion(mNativePtr, mAlias, mEncodedChain, mPrivateKey); |
+ } |
+ |
+ |
+ // Called to pass request results to native side. |
+ private native void nativeOnRequestCompletion(int nativeSSLClientCertRequest, |
+ String alias, |
+ byte[][] certChain, |
+ PrivateKey privateKey); |
+ |
+ /** |
+ * Create a new asynchronous request to select a client certificate. |
+ * |
+ * @param request_id The unique numerical id for the request. |
+ * @param key_types The list of supported key exchange types. |
+ * @param encoded_principals 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. |
+ */ |
+ @CalledByNative |
+ static public boolean selectClientCertificate(int nativePtr, |
+ String[] key_types, |
+ byte[][] encoded_principals, |
+ String host_name, |
+ int port, |
+ String alias_hint) { |
+ 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 (encoded_principals.length > 0) { |
+ principals = new X500Principal[encoded_principals.length]; |
+ try { |
+ for (int n = 0; n < encoded_principals.length; n++) { |
+ principals[n] = new X500Principal(encoded_principals[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, key_types, |
+ principals, host_name, port, |
+ alias_hint); |
+ return true; |
+ } |
+ |
+} |