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