Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Side by Side Diff: chrome/browser/ui/android/ssl_client_certificate_selector_android.cc

Issue 12220104: Wire up SSL client authentication for OpenSSL/Android through the net/ stack (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 #include "chrome/browser/ui/android/ssl_client_certificate_selector_android.h"
6
7 #include <string.h>
8
9 #include <openssl/evp.h>
10 #include <openssl/x509.h>
11
12 #include "base/android/jni_array.h"
13 #include "base/android/jni_string.h"
14 #include "base/android/scoped_java_ref.h"
15 #include "base/callback.h"
16 #include "base/logging.h"
17 #include "chrome/browser/ssl/ssl_client_auth_observer.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "crypto/openssl_util.h"
20 #include "jni/SSLClientCertificateRequest_jni.h"
21 #include "net/android/keystore_openssl.h"
22 #include "net/base/host_port_pair.h"
23 #include "net/base/openssl_key_store.h"
24 #include "net/base/ssl_cert_request_info.h"
25 #include "net/base/ssl_client_cert_type.h"
26 #include "net/base/x509_certificate.h"
27
28 // On other platforms, the list of client certificates compatible with
29 // the SSLCertRequestInfo is built using system APIs that do not require
30 // user interaction. After this, ShowSSLClientCertificateSelector() is
31 // merely used to display a tab sub-window asking the user to select
32 // one of these certificates.
33
34 // On Android, things are a bit different, because getting the list of
35 // compatible client certificates is only possible using an API that shows
36 // a system UI dialog. More precisely:
37 //
38 // - The application must call KeyChain.choosePrivateKeyAlias() and
39 // pass it the request parameters directly.
40 //
41 // - This API always launches a system activity (CertInstaller), that
42 // will display a list of compatible installed client certificates,
43 // if any, or prompt the user to install one manually otherwise.
44 //
45 // - Also, the first time this API is called, the CertInstaller will
46 // first prompt the user to enter the secure storage's password
47 // (which is the user's PIN code / password by default). This establishes
48 // a trust relationship between the KeyChain system application, and
49 // the application calling the API. It persists until the application
50 // is killed.
51 //
52 // - The client certificate selection result is sent back to the
53 // application through a UI thread callback. It only contains a
54 // string alias for the selected certificate, or 'null' to indicate
55 // that the user has canceled the selection, or couldn't unlock
56 // access to the secure storage.
57 //
58 // Note that:
59 //
60 // - There is no way, when the result if 'null', to know from the
61 // application if the user cancelled the request, or couldn't access
62 // the secure storage.
63 //
64 // - There is no way to cancel a request once it has started. Each call
65 // to KeyChain.choosePrivateKeyAlias() launches a new activity, which
66 // runs in a completely different process, and steals the focus from
67 // the browser.
68
69 namespace {
70
71 typedef crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ScopedEVP_PKEY;
72
73 } // namespace
74
75 namespace browser {
Ryan Sleevi 2013/02/12 00:25:17 This should be in "namespace chrome {" The unname
digit1 2013/02/12 15:05:25 Done.
76
77 // Start a new client certificate request. This launches a system
78 // UI dialog to let the user choose a certificate matching the
79 // SSLCertRequestInfo.
80 // |request_info| is the SSL client certificate request info.
81 // Returns true on success, or false otherwise.
82 // Note that success simply means that the system activity that
83 // implements client certificate selection has been launched. The only
84 // way to know if the user has properly selected a certificate (versus
85 // no certificate being available, or the user cancelling the operation)
86 // is to look at the value sent back in nativeOnRequestCompletion()
87 // below.
88 bool SSLClientCertRequest::Start() {
89 JNIEnv* env = base::android::AttachCurrentThread();
90 net::SSLCertRequestInfo* request_info = cert_request_info_;
91
92 // Convert the object's address into a pointer that will be passed
93 // to the Java method through JNI.
94 jint this_java = reinterpret_cast<jint>(this);
95
96 // Build the |key_types| JNI parameter, as a String[]
97 std::vector<std::string> key_types;
98 for (size_t n = 0; n < request_info->cert_key_types.size(); ++n) {
99 switch (request_info->cert_key_types[n]) {
100 case net::CLIENT_CERT_RSA_SIGN:
101 key_types.push_back("RSA");
102 break;
103 case net::CLIENT_CERT_DSS_SIGN:
104 key_types.push_back("DSA");
105 break;
106 case net::CLIENT_CERT_ECDSA_SIGN:
107 key_types.push_back("ECDSA");
108 break;
109 default:
110 // Ignore unknown types.
111 break;
112 }
113 }
114 ScopedJavaLocalRef<jobjectArray> key_types_ref =
115 base::android::ToJavaArrayOfStrings(env, key_types);
116 if (key_types_ref.is_null()) {
117 LOG(ERROR) << "Could not create key types array (String[])";
118 return false;
119 }
120
121 // Build the |encoded_principals| JNI parameter, as a byte[][]
122 ScopedJavaLocalRef<jobjectArray> principals_ref =
123 base::android::ToJavaArrayOfByteArray(
124 env, request_info->cert_authorities);
125 if (principals_ref.is_null()) {
126 LOG(ERROR) << "Could not create principals array (byte[][])";
127 return false;
128 }
129
130 // Build the |host_name| and |port| JNI parameters, as a String and
131 // a jint.
132 net::HostPortPair host_and_port =
133 net::HostPortPair::FromString(request_info->host_and_port);
134
135 ScopedJavaLocalRef<jstring> host_name_ref =
136 base::android::ConvertUTF8ToJavaString(env, host_and_port.host());
137 if (host_name_ref.is_null()) {
138 LOG(ERROR) << "Could not extract host name from: '"
139 << request_info->host_and_port << "'";
140 return false;
141 }
142
143 jint port = host_and_port.port();
144 if (port <= 0 || port >= 65535) {
Ryan Sleevi 2013/02/12 00:25:17 BUG: > rather than >= ?
digit1 2013/02/12 15:05:25 That's embarrassing :) For some reason I've always
145 LOG(ERROR) << "Invalid port number in: "
146 << request_info->host_and_port << "'";
147 return false;
148 }
149
150 return Java_SSLClientCertificateRequest_selectClientCertificate(
151 env, this_java, key_types_ref.obj(), principals_ref.obj(),
152 host_name_ref.obj(), port, NULL);
153 }
154
155 void SSLClientCertRequest::OnRequestCompletion(
156 JNIEnv* env,
157 jobject obj,
158 jstring private_key_alias_ref,
159 jobjectArray encoded_chain_ref,
160 jobject private_key_ref) {
161
162 net::X509Certificate* result = NULL;
163
164 do {
Ryan Sleevi 2013/02/12 00:25:17 Chromium code specifically tries to avoid patterns
digit1 2013/02/12 15:05:25 Thanks for the suggestion, I've applied your rewri
165 // When the request is cancelled by the user.
166 if (private_key_alias_ref == NULL || private_key_ref == NULL) {
167 LOG(INFO) << "Client certificate request cancelled";
Ryan Sleevi 2013/02/12 20:12:58 seems unnecessarily log spammy (and throughout)
digit1 2013/02/13 18:24:34 Actually, given the level of platform bugs we've e
Ryan Sleevi 2013/02/13 23:32:26 We specifically discourage logging for the "nice t
digit1 2013/02/14 06:23:50 Point taken then, I'll remove the line.
168 break;
169 }
170
171 // Convert private key alias JNI reference to string.
172 std::string private_key_alias;
173 if (private_key_alias_ref) {
174 private_key_alias = base::android::ConvertJavaStringToUTF8(
175 env, private_key_alias_ref);
176 }
177
178 // Convert the encoded chain to a vector of strings.
179 std::vector<std::string> encoded_chain_strings;
180 if (encoded_chain_ref) {
181 base::android::JavaArrayOfByteArrayToStringVector(
182 env, encoded_chain_ref, &encoded_chain_strings);
183 }
184
185 std::vector<base::StringPiece> encoded_chain;
186 for (size_t n = 0; n < encoded_chain_strings.size(); ++n)
187 encoded_chain.push_back(encoded_chain_strings[n]);
188
189 // Create the X509Certificate object from the encoded chain.
190 scoped_refptr<net::X509Certificate> cert(
191 net::X509Certificate::CreateFromDERCertChain(encoded_chain));
192 if (!cert.get()) {
193 LOG(ERROR) << "Could not decode client certificate chain";
194 break;
195 }
196
197 // Create an EVP_PKEY wrapper for the private key JNI reference, then
198 // ensure that it will be used for the rest of the SSL handshake that
199 // needs it. If this fails, log an error, but don't exit prematurely.
200 ScopedEVP_PKEY private_key(
201 net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref));
202
203 if (!private_key.get()) {
204 LOG(ERROR) << "Could not create OpenSSL wrapper for private key";
205 break;
206 }
207
208 net::UseOpenSSLClientCertSigningPrivateKey(*cert.get(), private_key.get());
209
210 cert.swap(&result);
211
212 } while (0);
213
214 // Done, pass the certificate to the callback now.
215 // Note: the callback takes ownership of the certificate.
216 callback_.Run(result);
217 callback_.Reset();
218
219 // Delete the request now.
220 delete this;
221 }
222
223 bool RegisterSSLClientCertificateSelectorAndroid(JNIEnv* env) {
224 return RegisterNativesImpl(env);
225 }
226
227 } // namespace browser
228
229 namespace chrome {
230
231 // Client Auth is not implemented on Android yet.
Ryan Sleevi 2013/02/12 20:12:58 This function isn't correct, is it?
232 void ShowSSLClientCertificateSelector(
233 content::WebContents* contents,
234 const net::HttpNetworkSession* network_session,
235 net::SSLCertRequestInfo* cert_request_info,
236 const chrome::SelectCertificateCallback& callback) {
237 DVLOG(1) << __FUNCTION__ << " " << contents;
Ryan Sleevi 2013/02/12 20:12:58 This seems extra extra spammy. Minimally, why log
digit1 2013/02/13 18:24:34 This came directly from the version in chrome/brow
238 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
239
240 // Create a new request, then try to start it.
241 browser::SSLClientCertRequest* request =
242 new browser::SSLClientCertRequest(cert_request_info, callback);
243 if (!request->Start()) {
244 // If the request could not start for some reason, consider
245 // that no client certificate was selected.
246 callback.Run(NULL);
247 delete request;
248 }
249 // Note: A started request will delete itself when it receives
250 // an answer from the system.
251 }
252
253 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698