1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.chrome.browser;
7 import android.app.Activity;
8 import android.content.Context;
9 import android.os.AsyncTask;
10 import android.security.KeyChain;
11 import android.security.KeyChainAliasCallback;
12 import android.security.KeyChainException;
13 import android.util.Log;
15 import org.chromium.base.ActivityStatus;
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.base.ThreadUtils;
20 import java.security.Principal;
21 import java.security.PrivateKey;
22 import java.security.cert.CertificateEncodingException;
23 import java.security.cert.X509Certificate;
25 import javax.security.auth.x500.X500Principal;
27 @JNINamespace("chrome::android")
28 class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void>
29 implements KeyChainAliasCallback {
31 static final String TAG = "SSLClientCertificateRequest";
33 // ClientCertRequest models an asynchronous client certificate request on the Java side. Use
34 // selectClientCertificate() on the UI thread to start/create a new request, this will launch a
35 // system activity through KeyChain.choosePrivateKeyAlias() to let the user select a client
38 // The selected certificate will be sent back as a string alias, which is used to call
39 // KeyChain.getCertificateChain() and KeyChain.getPrivateKey(). Unfortunately, these APIs are
40 // blocking, thus can't be called from the UI thread.
42 // To solve this, start an AsyncTask when the alias is received. It will retrieve the
43 // certificate chain and private key in the background, then later send the result back to the
46 private final int mNativePtr;
47 private String mAlias;
48 private byte[][] mEncodedChain;
49 private PrivateKey mPrivateKey;
51 private SSLClientCertificateRequest(int nativePtr) {
52 mNativePtr = nativePtr;
58 // KeyChainAliasCallback implementation
60 public void alias(final String alias) {
61 // This is called by KeyChainActivity in a background thread. Post task to handle the
62 // certificate selection on the UI thread.
63 ThreadUtils.runOnUiThread(new Runnable() {
67 // No certificate was selected.
71 // Launch background thread.
79 protected Void doInBackground(Void... params) {
80 // Executed in a background thread, can call blocking APIs.
81 X509Certificate[] chain = null;
82 PrivateKey key = null;
83 Context context = ActivityStatus.getActivity().getApplicationContext();
85 key = KeyChain.getPrivateKey(context, mAlias);
86 chain = KeyChain.getCertificateChain(context, mAlias);
87 } catch (KeyChainException e) {
88 Log.w(TAG, "KeyChainException when looking for '" + mAlias + "' certificate");
90 } catch (InterruptedException e) {
91 Log.w(TAG, "InterruptedException when looking for '" + mAlias + "'certificate");
95 if (key == null || chain == null || chain.length == 0) {
96 Log.w(TAG, "Empty client certificate chain?");
100 // Get the encoded certificate chain.
101 byte[][] encodedChain = new byte[chain.length][];
103 for (int i = 0; i < chain.length; ++i) {
104 encodedChain[i] = chain[i].getEncoded();
106 } catch (CertificateEncodingException e) {
107 Log.w(TAG, "Could not retrieve encoded certificate chain: " + e);
111 mEncodedChain = encodedChain;
117 protected void onPostExecute(Void result) {
118 // Back to the UI thread.
119 nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mPrivateKey);
124 * Create a new asynchronous request to select a client certificate.
126 * @param nativePtr The native object responsible for this request.
127 * @param keyTypes The list of supported key exchange types.
128 * @param encodedPrincipals The list of CA DistinguishedNames.
129 * @param hostName The server host name is available (empty otherwise).
130 * @param port The server port if available (0 otherwise).
131 * @return true on success.
132 * Note that nativeOnSystemRequestComplete will be called iff this method returns true.
135 private static boolean selectClientCertificate(int nativePtr, String[] keyTypes,
136 byte[][] encodedPrincipals, String hostName, int port) {
137 ThreadUtils.assertOnUiThread();
139 Activity activity = ActivityStatus.getActivity();
140 if (activity == null) {
141 Log.w(TAG, "No active Chromium main activity!?");
145 // Build the list of principals from encoded versions.
146 Principal[] principals = null;
147 if (encodedPrincipals.length > 0) {
148 principals = new X500Principal[encodedPrincipals.length];
150 for (int n = 0; n < encodedPrincipals.length; n++) {
151 principals[n] = new X500Principal(encodedPrincipals[n]);
153 } catch (Exception e) {
155 Log.w(TAG, "Exception while decoding issuers list: " + e);
160 // All good, create new request, add it to our list and launch the certificate selection
162 SSLClientCertificateRequest request = new SSLClientCertificateRequest(nativePtr);
164 KeyChain.choosePrivateKeyAlias(
165 activity, request, keyTypes, principals, hostName, port, null);
169 // Called to pass request results to native side.
170 private static native void nativeOnSystemRequestCompletion(
171 int requestPtr, byte[][] certChain, PrivateKey privateKey);