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..5943ab8cea4e280cb1733a0c44bc9bc4c6d530eb |
| --- /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 request_id The unique numerical id for the request. |
|
bulach
2013/03/11 11:20:09
nit: nativePtr: the native object responsible for
digit1
2013/03/11 21:41:22
Done.
|
| + * @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. |
| + * Note that nativeOnSystemRequestComplete will be called iff this |
| + * method returns true. |
| + */ |
| + @CalledByNative |
| + static private boolean selectClientCertificate( |
| + int nativePtr, String[] key_types, byte[][] encoded_principals, |
|
bulach
2013/03/11 11:20:09
nit: javaCaseStyle: keyTypes, encodedPrincipals, h
digit1
2013/03/11 21:41:22
Done.
|
| + 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; |
| + } |
| + |
| + // Called to pass request results to native side. |
| + private static native void nativeOnSystemRequestCompletion( |
| + int requestPtr, byte[][] certChain, PrivateKey privateKey); |
| +} |