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.
5 #include "android_webview/native/aw_contents_client_bridge.h"
7 #include "android_webview/common/devtools_instrumentation.h"
8 #include "base/android/jni_android.h"
9 #include "base/android/jni_array.h"
10 #include "base/android/jni_string.h"
11 #include "base/callback_helpers.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "jni/AwContentsClientBridge_jni.h"
14 #include "net/android/keystore_openssl.h"
15 #include "net/cert/x509_certificate.h"
16 #include "net/ssl/openssl_client_key_store.h"
17 #include "net/ssl/ssl_cert_request_info.h"
18 #include "net/ssl/ssl_client_cert_type.h"
21 using base::android::AttachCurrentThread;
22 using base::android::ConvertJavaStringToUTF16;
23 using base::android::ConvertUTF8ToJavaString;
24 using base::android::ConvertUTF16ToJavaString;
25 using base::android::JavaRef;
26 using base::android::ScopedJavaLocalRef;
27 using content::BrowserThread;
29 namespace android_webview {
31 typedef net::OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY;
35 // Must be called on the I/O thread to record a client certificate
36 // and its private key in the OpenSSLClientKeyStore.
37 void RecordClientCertificateKey(
38 const scoped_refptr<net::X509Certificate>& client_cert,
39 ScopedEVP_PKEY private_key) {
40 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
41 net::OpenSSLClientKeyStore::GetInstance()->RecordClientCertPrivateKey(
42 client_cert.get(), private_key.get());
47 AwContentsClientBridge::AwContentsClientBridge(JNIEnv* env, jobject obj)
48 : java_ref_(env, obj) {
50 Java_AwContentsClientBridge_setNativeContentsClientBridge(
51 env, obj, reinterpret_cast<intptr_t>(this));
54 AwContentsClientBridge::~AwContentsClientBridge() {
55 JNIEnv* env = AttachCurrentThread();
57 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
60 // Clear the weak reference from the java peer to the native object since
61 // it is possible that java object lifetime can exceed the AwContens.
62 Java_AwContentsClientBridge_setNativeContentsClientBridge(env, obj.obj(), 0);
65 void AwContentsClientBridge::AllowCertificateError(
67 net::X509Certificate* cert,
68 const GURL& request_url,
69 const base::Callback<void(bool)>& callback,
70 bool* cancel_request) {
72 DCHECK_CURRENTLY_ON(BrowserThread::UI);
73 JNIEnv* env = AttachCurrentThread();
75 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
79 std::string der_string;
80 net::X509Certificate::GetDEREncoded(cert->os_cert_handle(), &der_string);
81 ScopedJavaLocalRef<jbyteArray> jcert = base::android::ToJavaByteArray(
83 reinterpret_cast<const uint8*>(der_string.data()),
85 ScopedJavaLocalRef<jstring> jurl(ConvertUTF8ToJavaString(
86 env, request_url.spec()));
87 // We need to add the callback before making the call to java side,
88 // as it may do a synchronous callback prior to returning.
89 int request_id = pending_cert_error_callbacks_.Add(
90 new CertErrorCallback(callback));
91 *cancel_request = !Java_AwContentsClientBridge_allowCertificateError(
92 env, obj.obj(), cert_error, jcert.obj(), jurl.obj(), request_id);
93 // if the request is cancelled, then cancel the stored callback
94 if (*cancel_request) {
95 pending_cert_error_callbacks_.Remove(request_id);
99 void AwContentsClientBridge::ProceedSslError(JNIEnv* env, jobject obj,
100 jboolean proceed, jint id) {
101 DCHECK_CURRENTLY_ON(BrowserThread::UI);
102 CertErrorCallback* callback = pending_cert_error_callbacks_.Lookup(id);
103 if (!callback || callback->is_null()) {
104 LOG(WARNING) << "Ignoring unexpected ssl error proceed callback";
107 callback->Run(proceed);
108 pending_cert_error_callbacks_.Remove(id);
111 // This method is inspired by SelectClientCertificate() in
112 // chrome/browser/ui/android/ssl_client_certificate_request.cc
113 void AwContentsClientBridge::SelectClientCertificate(
114 net::SSLCertRequestInfo* cert_request_info,
115 const SelectCertificateCallback& callback) {
116 DCHECK_CURRENTLY_ON(BrowserThread::UI);
118 // Add the callback to id map.
119 int request_id = pending_client_cert_request_callbacks_.Add(
120 new SelectCertificateCallback(callback));
121 // Make sure callback is run on error.
122 base::ScopedClosureRunner guard(base::Bind(
123 &AwContentsClientBridge::HandleErrorInClientCertificateResponse,
124 base::Unretained(this),
127 JNIEnv* env = base::android::AttachCurrentThread();
128 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
132 // Build the |key_types| JNI parameter, as a String[]
133 std::vector<std::string> key_types;
134 for (size_t i = 0; i < cert_request_info->cert_key_types.size(); ++i) {
135 switch (cert_request_info->cert_key_types[i]) {
136 case net::CLIENT_CERT_RSA_SIGN:
137 key_types.push_back("RSA");
139 case net::CLIENT_CERT_DSS_SIGN:
140 key_types.push_back("DSA");
142 case net::CLIENT_CERT_ECDSA_SIGN:
143 key_types.push_back("ECDSA");
146 // Ignore unknown types.
151 ScopedJavaLocalRef<jobjectArray> key_types_ref =
152 base::android::ToJavaArrayOfStrings(env, key_types);
153 if (key_types_ref.is_null()) {
154 LOG(ERROR) << "Could not create key types array (String[])";
158 // Build the |encoded_principals| JNI parameter, as a byte[][]
159 ScopedJavaLocalRef<jobjectArray> principals_ref =
160 base::android::ToJavaArrayOfByteArray(
161 env, cert_request_info->cert_authorities);
162 if (principals_ref.is_null()) {
163 LOG(ERROR) << "Could not create principals array (byte[][])";
167 // Build the |host_name| and |port| JNI parameters, as a String and
169 ScopedJavaLocalRef<jstring> host_name_ref =
170 base::android::ConvertUTF8ToJavaString(
171 env, cert_request_info->host_and_port.host());
173 Java_AwContentsClientBridge_selectClientCertificate(
178 principals_ref.obj(),
180 cert_request_info->host_and_port.port());
182 // Release the guard.
183 ignore_result(guard.Release());
186 // This method is inspired by OnSystemRequestCompletion() in
187 // chrome/browser/ui/android/ssl_client_certificate_request.cc
188 void AwContentsClientBridge::ProvideClientCertificateResponse(
192 jobjectArray encoded_chain_ref,
193 jobject private_key_ref) {
194 DCHECK_CURRENTLY_ON(BrowserThread::UI);
196 SelectCertificateCallback* callback =
197 pending_client_cert_request_callbacks_.Lookup(request_id);
200 // Make sure callback is run on error.
201 base::ScopedClosureRunner guard(base::Bind(
202 &AwContentsClientBridge::HandleErrorInClientCertificateResponse,
203 base::Unretained(this),
205 if (encoded_chain_ref == NULL || private_key_ref == NULL) {
206 LOG(ERROR) << "Client certificate request cancelled";
209 // Convert the encoded chain to a vector of strings.
210 std::vector<std::string> encoded_chain_strings;
211 if (encoded_chain_ref) {
212 base::android::JavaArrayOfByteArrayToStringVector(
213 env, encoded_chain_ref, &encoded_chain_strings);
216 std::vector<base::StringPiece> encoded_chain;
217 for (size_t i = 0; i < encoded_chain_strings.size(); ++i)
218 encoded_chain.push_back(encoded_chain_strings[i]);
220 // Create the X509Certificate object from the encoded chain.
221 scoped_refptr<net::X509Certificate> client_cert(
222 net::X509Certificate::CreateFromDERCertChain(encoded_chain));
223 if (!client_cert.get()) {
224 LOG(ERROR) << "Could not decode client certificate chain";
228 // Create an EVP_PKEY wrapper for the private key JNI reference.
229 ScopedEVP_PKEY private_key(
230 net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref));
231 if (!private_key.get()) {
232 LOG(ERROR) << "Could not create OpenSSL wrapper for private key";
236 // RecordClientCertificateKey() must be called on the I/O thread,
237 // before the callback is called with the selected certificate on
239 content::BrowserThread::PostTaskAndReply(
240 content::BrowserThread::IO,
242 base::Bind(&RecordClientCertificateKey,
244 base::Passed(&private_key)),
245 base::Bind(*callback, client_cert));
246 pending_client_cert_request_callbacks_.Remove(request_id);
248 // Release the guard.
249 ignore_result(guard.Release());
252 void AwContentsClientBridge::RunJavaScriptDialog(
253 content::JavaScriptMessageType message_type,
254 const GURL& origin_url,
255 const base::string16& message_text,
256 const base::string16& default_prompt_text,
257 const content::JavaScriptDialogManager::DialogClosedCallback& callback) {
258 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
259 JNIEnv* env = AttachCurrentThread();
261 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
265 int callback_id = pending_js_dialog_callbacks_.Add(
266 new content::JavaScriptDialogManager::DialogClosedCallback(callback));
267 ScopedJavaLocalRef<jstring> jurl(
268 ConvertUTF8ToJavaString(env, origin_url.spec()));
269 ScopedJavaLocalRef<jstring> jmessage(
270 ConvertUTF16ToJavaString(env, message_text));
272 switch (message_type) {
273 case content::JAVASCRIPT_MESSAGE_TYPE_ALERT: {
274 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsAlert");
275 Java_AwContentsClientBridge_handleJsAlert(
276 env, obj.obj(), jurl.obj(), jmessage.obj(), callback_id);
279 case content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM: {
280 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsConfirm");
281 Java_AwContentsClientBridge_handleJsConfirm(
282 env, obj.obj(), jurl.obj(), jmessage.obj(), callback_id);
285 case content::JAVASCRIPT_MESSAGE_TYPE_PROMPT: {
286 ScopedJavaLocalRef<jstring> jdefault_value(
287 ConvertUTF16ToJavaString(env, default_prompt_text));
288 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsPrompt");
289 Java_AwContentsClientBridge_handleJsPrompt(env,
293 jdefault_value.obj(),
302 void AwContentsClientBridge::RunBeforeUnloadDialog(
303 const GURL& origin_url,
304 const base::string16& message_text,
305 const content::JavaScriptDialogManager::DialogClosedCallback& callback) {
306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
307 JNIEnv* env = AttachCurrentThread();
309 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
313 int callback_id = pending_js_dialog_callbacks_.Add(
314 new content::JavaScriptDialogManager::DialogClosedCallback(callback));
315 ScopedJavaLocalRef<jstring> jurl(
316 ConvertUTF8ToJavaString(env, origin_url.spec()));
317 ScopedJavaLocalRef<jstring> jmessage(
318 ConvertUTF16ToJavaString(env, message_text));
320 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsBeforeUnload");
321 Java_AwContentsClientBridge_handleJsBeforeUnload(
322 env, obj.obj(), jurl.obj(), jmessage.obj(), callback_id);
325 bool AwContentsClientBridge::ShouldOverrideUrlLoading(
326 const base::string16& url) {
327 JNIEnv* env = AttachCurrentThread();
328 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
331 ScopedJavaLocalRef<jstring> jurl = ConvertUTF16ToJavaString(env, url);
332 devtools_instrumentation::ScopedEmbedderCallbackTask(
333 "shouldOverrideUrlLoading");
334 return Java_AwContentsClientBridge_shouldOverrideUrlLoading(
339 void AwContentsClientBridge::ConfirmJsResult(JNIEnv* env,
343 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
344 content::JavaScriptDialogManager::DialogClosedCallback* callback =
345 pending_js_dialog_callbacks_.Lookup(id);
347 LOG(WARNING) << "Unexpected JS dialog confirm. " << id;
350 base::string16 prompt_text;
352 prompt_text = ConvertJavaStringToUTF16(env, prompt);
354 callback->Run(true, prompt_text);
355 pending_js_dialog_callbacks_.Remove(id);
358 void AwContentsClientBridge::CancelJsResult(JNIEnv*, jobject, int id) {
359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
360 content::JavaScriptDialogManager::DialogClosedCallback* callback =
361 pending_js_dialog_callbacks_.Lookup(id);
363 LOG(WARNING) << "Unexpected JS dialog cancel. " << id;
366 callback->Run(false, base::string16());
367 pending_js_dialog_callbacks_.Remove(id);
370 // Use to cleanup if there is an error in client certificate response.
371 void AwContentsClientBridge::HandleErrorInClientCertificateResponse(
373 SelectCertificateCallback* callback =
374 pending_client_cert_request_callbacks_.Lookup(request_id);
375 callback->Run(scoped_refptr<net::X509Certificate>());
376 pending_client_cert_request_callbacks_.Remove(request_id);
379 bool RegisterAwContentsClientBridge(JNIEnv* env) {
380 return RegisterNativesImpl(env);
383 } // namespace android_webview