Chromium Code Reviews| 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..dc56b9579a129eb74dd94569411391c8b4e2e0b3 |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java |
| @@ -0,0 +1,178 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
|
bulach
2013/02/28 16:34:18
nit: 13 :)
digit1
2013/03/04 19:03:20
Done.
|
| +// 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::android") |
| +public class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void> |
|
bulach
2013/02/28 16:34:18
question (apologies if stupid :)
is it correct to
digit
2013/02/28 16:49:00
Currently yes, but I plan to have a helper unit te
digit1
2013/03/04 19:03:20
ok, the unit test didn't make it, so I've changed
|
| + 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. |
|
bulach
2013/02/28 16:34:18
nit: native stubs tend to go last
digit
2013/02/28 16:49:00
ok, I'll move it
|
| + private native void nativeOnRequestCompletion(int nativeSSLClientCertificateRequest, |
| + 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) { |
| + 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, null); |
| + return true; |
| + } |
| + |
| +} |