SSLClientCertificateRequest.java revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 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.
4
5package org.chromium.chrome.browser;
6
7import java.security.cert.CertificateEncodingException;
8import java.security.cert.X509Certificate;
9import java.security.Principal;
10import java.security.PrivateKey;
11import javax.security.auth.x500.X500Principal;
12
13import android.app.Activity;
14import android.content.Context;
15import android.os.AsyncTask;
16import android.security.KeyChain;
17import android.security.KeyChainAliasCallback;
18import android.security.KeyChainException;
19import android.util.Log;
20
21import org.chromium.base.ActivityStatus;
22import org.chromium.base.CalledByNative;
23import org.chromium.base.JNINamespace;
24import org.chromium.base.ThreadUtils;
25
26@JNINamespace("chrome::android")
27class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void>
28        implements KeyChainAliasCallback {
29
30    static final String TAG = "SSLClientCertificateRequest";
31
32    // ClientCertRequest models an asynchronous client certificate request
33    // on the Java side. Use selectClientCertificate() on the UI thread to
34    // start/create a new request, this will launch a system activity
35    // through KeyChain.choosePrivateKeyAlias() to let the user select
36    // a client certificate.
37    //
38    // The selected certificate will be sent back as a string alias,
39    // which is used to call KeyChain.getCertificateChain() and
40    // KeyChain.getPrivateKey(). Unfortunately, these APIs are blocking,
41    // thus can't be called from the UI thread.
42    //
43    // To solve this, start an AsyncTask when the alias is received.
44    // it will retrieve the certificate chain and private key in the
45    // background, then later send the result back to the UI thread.
46    //
47    private final int mNativePtr;
48    private String mAlias;
49    private byte[][] mEncodedChain;
50    private PrivateKey mPrivateKey;
51
52    private SSLClientCertificateRequest(int nativePtr) {
53        mNativePtr = nativePtr;
54        mAlias = null;
55        mEncodedChain = null;
56        mPrivateKey = null;
57    }
58
59    // KeyChainAliasCallback implementation
60    @Override
61    public void alias(String alias) {
62        if (alias == null) {
63            // No certificate was selected.
64            onPostExecute(null);
65        } else {
66            mAlias = alias;
67            // Launch background thread.
68            execute();
69        }
70    }
71
72    @Override
73    protected Void doInBackground(Void... params) {
74        // Executed in a background thread, can call blocking APIs.
75        X509Certificate[] chain = null;
76        PrivateKey key = null;
77        Context context = ActivityStatus.getActivity().getApplicationContext();
78        try {
79            key = KeyChain.getPrivateKey(context, mAlias);
80            chain = KeyChain.getCertificateChain(context, mAlias);
81        } catch (KeyChainException e) {
82            Log.w(TAG, "KeyChainException when looking for '" + mAlias + "' certificate");
83            return null;
84        } catch (InterruptedException e) {
85            Log.w(TAG, "InterruptedException when looking for '" + mAlias + "'certificate");
86            return null;
87        }
88
89        if (key == null || chain == null || chain.length == 0) {
90            Log.w(TAG, "Empty client certificate chain?");
91            return null;
92        }
93
94        // Get the encoded certificate chain.
95        byte[][] encoded_chain = new byte[chain.length][];
96        try {
97            for (int i = 0; i < chain.length; ++i) {
98                encoded_chain[i] = chain[i].getEncoded();
99            }
100        } catch (CertificateEncodingException e) {
101            Log.w(TAG, "Could not retrieve encoded certificate chain: " + e);
102            return null;
103        }
104
105        mEncodedChain = encoded_chain;
106        mPrivateKey = key;
107        return null;
108    }
109
110    @Override
111    protected void onPostExecute(Void result) {
112        // Back to the UI thread.
113        nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mPrivateKey);
114    }
115
116
117    /**
118     * Create a new asynchronous request to select a client certificate.
119     *
120     * @param nativePtr The native object responsible for this request.
121     * @param keyTypes The list of supported key exchange types.
122     * @param encodedPrincipals The list of CA DistinguishedNames.
123     * @param host_name The server host name is available (empty otherwise).
124     * @param port The server port if available (0 otherwise).
125     * @return true on success.
126     * Note that nativeOnSystemRequestComplete will be called iff this
127     * method returns true.
128     */
129    @CalledByNative
130    static private boolean selectClientCertificate(
131            int nativePtr, String[] keyTypes, byte[][] encodedPrincipals,
132            String hostName, int port) {
133        ThreadUtils.assertOnUiThread();
134
135        Activity activity = ActivityStatus.getActivity();
136        if (activity == null) {
137            Log.w(TAG, "No active Chromium main activity!?");
138            return false;
139        }
140
141        // Build the list of principals from encoded versions.
142        Principal[] principals = null;
143        if (encodedPrincipals.length > 0) {
144            principals = new X500Principal[encodedPrincipals.length];
145            try {
146                for (int n = 0; n < encodedPrincipals.length; n++) {
147                    principals[n] = new X500Principal(encodedPrincipals[n]);
148                }
149            } catch (Exception e) {
150                // Bail on error.
151                Log.w(TAG, "Exception while decoding issuers list: " + e);
152                return false;
153            }
154        }
155
156        // All good, create new request, add it to our list and launch
157        // the certificate selection activity.
158        SSLClientCertificateRequest request = new SSLClientCertificateRequest(nativePtr);
159
160        KeyChain.choosePrivateKeyAlias(
161                activity, request, keyTypes, principals, hostName, port, null);
162        return true;
163    }
164
165    // Called to pass request results to native side.
166    private static native void nativeOnSystemRequestCompletion(
167            int requestPtr, byte[][] certChain, PrivateKey privateKey);
168}