| 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..2f36d37758d248ff7e23a8c6468dc51390154021
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java
|
| @@ -0,0 +1,172 @@
|
| +// 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("browser::android")
|
| +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).
|
| + //
|
| + // 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;
|
| +
|
| + 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.
|
| + nativeOnRequestCompletion(mNativePtr, mEncodedChain, mPrivateKey);
|
| + }
|
| +
|
| +
|
| + /**
|
| + * 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 private 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;
|
| + }
|
| +
|
| + // Called to pass request results to native side.
|
| + private static native void nativeOnRequestCompletion(int nativeSSLClientCertificateRequest,
|
| + byte[][] certChain,
|
| + PrivateKey privateKey);
|
| +}
|
|
|