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 "android_webview/native/aw_contents.h"
9 #include "base/android/jni_android.h"
10 #include "base/android/jni_array.h"
11 #include "base/android/jni_string.h"
12 #include "base/callback_helpers.h"
13 #include "base/macros.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/client_certificate_delegate.h"
16 #include "content/public/browser/render_process_host.h"
17 #include "content/public/browser/render_view_host.h"
18 #include "content/public/browser/web_contents.h"
19 #include "crypto/scoped_openssl_types.h"
20 #include "jni/AwContentsClientBridge_jni.h"
21 #include "net/android/keystore_openssl.h"
22 #include "net/cert/x509_certificate.h"
23 #include "net/ssl/openssl_client_key_store.h"
24 #include "net/ssl/ssl_cert_request_info.h"
25 #include "net/ssl/ssl_client_cert_type.h"
28 using base::android::AttachCurrentThread
;
29 using base::android::ConvertJavaStringToUTF16
;
30 using base::android::ConvertUTF8ToJavaString
;
31 using base::android::ConvertUTF16ToJavaString
;
32 using base::android::JavaRef
;
33 using base::android::ScopedJavaLocalRef
;
34 using content::BrowserThread
;
36 namespace android_webview
{
40 // Must be called on the I/O thread to record a client certificate
41 // and its private key in the OpenSSLClientKeyStore.
42 void RecordClientCertificateKey(
43 const scoped_refptr
<net::X509Certificate
>& client_cert
,
44 crypto::ScopedEVP_PKEY private_key
) {
45 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
46 net::OpenSSLClientKeyStore::GetInstance()->RecordClientCertPrivateKey(
47 client_cert
.get(), private_key
.get());
52 AwContentsClientBridge::AwContentsClientBridge(JNIEnv
* env
, jobject obj
)
53 : java_ref_(env
, obj
) {
55 Java_AwContentsClientBridge_setNativeContentsClientBridge(
56 env
, obj
, reinterpret_cast<intptr_t>(this));
59 AwContentsClientBridge::~AwContentsClientBridge() {
60 JNIEnv
* env
= AttachCurrentThread();
62 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
64 // Clear the weak reference from the java peer to the native object since
65 // it is possible that java object lifetime can exceed the AwContens.
66 Java_AwContentsClientBridge_setNativeContentsClientBridge(env
, obj
.obj(),
70 for (IDMap
<content::ClientCertificateDelegate
>::iterator
iter(
71 &pending_client_cert_request_delegates_
);
72 !iter
.IsAtEnd(); iter
.Advance()) {
73 delete iter
.GetCurrentValue();
77 void AwContentsClientBridge::AllowCertificateError(
79 net::X509Certificate
* cert
,
80 const GURL
& request_url
,
81 const base::Callback
<void(bool)>& callback
,
82 bool* cancel_request
) {
84 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
85 JNIEnv
* env
= AttachCurrentThread();
87 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
91 std::string der_string
;
92 net::X509Certificate::GetDEREncoded(cert
->os_cert_handle(), &der_string
);
93 ScopedJavaLocalRef
<jbyteArray
> jcert
= base::android::ToJavaByteArray(
95 reinterpret_cast<const uint8
*>(der_string
.data()),
97 ScopedJavaLocalRef
<jstring
> jurl(ConvertUTF8ToJavaString(
98 env
, request_url
.spec()));
99 // We need to add the callback before making the call to java side,
100 // as it may do a synchronous callback prior to returning.
101 int request_id
= pending_cert_error_callbacks_
.Add(
102 new CertErrorCallback(callback
));
103 *cancel_request
= !Java_AwContentsClientBridge_allowCertificateError(
104 env
, obj
.obj(), cert_error
, jcert
.obj(), jurl
.obj(), request_id
);
105 // if the request is cancelled, then cancel the stored callback
106 if (*cancel_request
) {
107 pending_cert_error_callbacks_
.Remove(request_id
);
111 void AwContentsClientBridge::ProceedSslError(JNIEnv
* env
, jobject obj
,
112 jboolean proceed
, jint id
) {
113 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
114 CertErrorCallback
* callback
= pending_cert_error_callbacks_
.Lookup(id
);
115 if (!callback
|| callback
->is_null()) {
116 LOG(WARNING
) << "Ignoring unexpected ssl error proceed callback";
119 callback
->Run(proceed
);
120 pending_cert_error_callbacks_
.Remove(id
);
123 // This method is inspired by SelectClientCertificate() in
124 // chrome/browser/ui/android/ssl_client_certificate_request.cc
125 void AwContentsClientBridge::SelectClientCertificate(
126 net::SSLCertRequestInfo
* cert_request_info
,
127 scoped_ptr
<content::ClientCertificateDelegate
> delegate
) {
128 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
130 // Add the callback to id map.
132 pending_client_cert_request_delegates_
.Add(delegate
.release());
133 // Make sure callback is run on error.
134 base::ScopedClosureRunner
guard(base::Bind(
135 &AwContentsClientBridge::HandleErrorInClientCertificateResponse
,
136 base::Unretained(this),
139 JNIEnv
* env
= base::android::AttachCurrentThread();
140 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
144 // Build the |key_types| JNI parameter, as a String[]
145 std::vector
<std::string
> key_types
;
146 for (size_t i
= 0; i
< cert_request_info
->cert_key_types
.size(); ++i
) {
147 switch (cert_request_info
->cert_key_types
[i
]) {
148 case net::CLIENT_CERT_RSA_SIGN
:
149 key_types
.push_back("RSA");
151 case net::CLIENT_CERT_ECDSA_SIGN
:
152 key_types
.push_back("ECDSA");
155 // Ignore unknown types.
160 ScopedJavaLocalRef
<jobjectArray
> key_types_ref
=
161 base::android::ToJavaArrayOfStrings(env
, key_types
);
162 if (key_types_ref
.is_null()) {
163 LOG(ERROR
) << "Could not create key types array (String[])";
167 // Build the |encoded_principals| JNI parameter, as a byte[][]
168 ScopedJavaLocalRef
<jobjectArray
> principals_ref
=
169 base::android::ToJavaArrayOfByteArray(
170 env
, cert_request_info
->cert_authorities
);
171 if (principals_ref
.is_null()) {
172 LOG(ERROR
) << "Could not create principals array (byte[][])";
176 // Build the |host_name| and |port| JNI parameters, as a String and
178 ScopedJavaLocalRef
<jstring
> host_name_ref
=
179 base::android::ConvertUTF8ToJavaString(
180 env
, cert_request_info
->host_and_port
.host());
182 Java_AwContentsClientBridge_selectClientCertificate(
187 principals_ref
.obj(),
189 cert_request_info
->host_and_port
.port());
191 // Release the guard.
192 ignore_result(guard
.Release());
195 // This method is inspired by OnSystemRequestCompletion() in
196 // chrome/browser/ui/android/ssl_client_certificate_request.cc
197 void AwContentsClientBridge::ProvideClientCertificateResponse(
201 jobjectArray encoded_chain_ref
,
202 jobject private_key_ref
) {
203 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
205 content::ClientCertificateDelegate
* delegate
=
206 pending_client_cert_request_delegates_
.Lookup(request_id
);
209 if (encoded_chain_ref
== NULL
|| private_key_ref
== NULL
) {
210 LOG(ERROR
) << "No client certificate selected";
211 pending_client_cert_request_delegates_
.Remove(request_id
);
212 delegate
->ContinueWithCertificate(nullptr);
217 // Make sure callback is run on error.
218 base::ScopedClosureRunner
guard(base::Bind(
219 &AwContentsClientBridge::HandleErrorInClientCertificateResponse
,
220 base::Unretained(this),
223 // Convert the encoded chain to a vector of strings.
224 std::vector
<std::string
> encoded_chain_strings
;
225 if (encoded_chain_ref
) {
226 base::android::JavaArrayOfByteArrayToStringVector(
227 env
, encoded_chain_ref
, &encoded_chain_strings
);
230 std::vector
<base::StringPiece
> encoded_chain
;
231 for (size_t i
= 0; i
< encoded_chain_strings
.size(); ++i
)
232 encoded_chain
.push_back(encoded_chain_strings
[i
]);
234 // Create the X509Certificate object from the encoded chain.
235 scoped_refptr
<net::X509Certificate
> client_cert(
236 net::X509Certificate::CreateFromDERCertChain(encoded_chain
));
237 if (!client_cert
.get()) {
238 LOG(ERROR
) << "Could not decode client certificate chain";
242 // Create an EVP_PKEY wrapper for the private key JNI reference.
243 crypto::ScopedEVP_PKEY
private_key(
244 net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref
));
245 if (!private_key
.get()) {
246 LOG(ERROR
) << "Could not create OpenSSL wrapper for private key";
250 // Release the guard and |pending_client_cert_request_delegates_| references
252 pending_client_cert_request_delegates_
.Remove(request_id
);
253 ignore_result(guard
.Release());
255 // RecordClientCertificateKey() must be called on the I/O thread,
256 // before the delegate is called with the selected certificate on
258 content::BrowserThread::PostTaskAndReply(
259 content::BrowserThread::IO
, FROM_HERE
,
260 base::Bind(&RecordClientCertificateKey
, client_cert
,
261 base::Passed(&private_key
)),
262 base::Bind(&content::ClientCertificateDelegate::ContinueWithCertificate
,
263 base::Owned(delegate
), client_cert
));
266 void AwContentsClientBridge::RunJavaScriptDialog(
267 content::JavaScriptMessageType message_type
,
268 const GURL
& origin_url
,
269 const base::string16
& message_text
,
270 const base::string16
& default_prompt_text
,
271 const content::JavaScriptDialogManager::DialogClosedCallback
& callback
) {
272 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
273 JNIEnv
* env
= AttachCurrentThread();
275 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
277 callback
.Run(false, base::string16());
281 int callback_id
= pending_js_dialog_callbacks_
.Add(
282 new content::JavaScriptDialogManager::DialogClosedCallback(callback
));
283 ScopedJavaLocalRef
<jstring
> jurl(
284 ConvertUTF8ToJavaString(env
, origin_url
.spec()));
285 ScopedJavaLocalRef
<jstring
> jmessage(
286 ConvertUTF16ToJavaString(env
, message_text
));
288 switch (message_type
) {
289 case content::JAVASCRIPT_MESSAGE_TYPE_ALERT
: {
290 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsAlert");
291 Java_AwContentsClientBridge_handleJsAlert(
292 env
, obj
.obj(), jurl
.obj(), jmessage
.obj(), callback_id
);
295 case content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM
: {
296 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsConfirm");
297 Java_AwContentsClientBridge_handleJsConfirm(
298 env
, obj
.obj(), jurl
.obj(), jmessage
.obj(), callback_id
);
301 case content::JAVASCRIPT_MESSAGE_TYPE_PROMPT
: {
302 ScopedJavaLocalRef
<jstring
> jdefault_value(
303 ConvertUTF16ToJavaString(env
, default_prompt_text
));
304 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsPrompt");
305 Java_AwContentsClientBridge_handleJsPrompt(env
,
309 jdefault_value
.obj(),
318 void AwContentsClientBridge::RunBeforeUnloadDialog(
319 const GURL
& origin_url
,
320 const base::string16
& message_text
,
321 const content::JavaScriptDialogManager::DialogClosedCallback
& callback
) {
322 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
323 JNIEnv
* env
= AttachCurrentThread();
325 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
327 callback
.Run(false, base::string16());
331 int callback_id
= pending_js_dialog_callbacks_
.Add(
332 new content::JavaScriptDialogManager::DialogClosedCallback(callback
));
333 ScopedJavaLocalRef
<jstring
> jurl(
334 ConvertUTF8ToJavaString(env
, origin_url
.spec()));
335 ScopedJavaLocalRef
<jstring
> jmessage(
336 ConvertUTF16ToJavaString(env
, message_text
));
338 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsBeforeUnload");
339 Java_AwContentsClientBridge_handleJsBeforeUnload(
340 env
, obj
.obj(), jurl
.obj(), jmessage
.obj(), callback_id
);
343 void AwContentsClientBridge::ConfirmJsResult(JNIEnv
* env
,
347 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
348 content::JavaScriptDialogManager::DialogClosedCallback
* callback
=
349 pending_js_dialog_callbacks_
.Lookup(id
);
351 LOG(WARNING
) << "Unexpected JS dialog confirm. " << id
;
354 base::string16 prompt_text
;
356 prompt_text
= ConvertJavaStringToUTF16(env
, prompt
);
358 callback
->Run(true, prompt_text
);
359 pending_js_dialog_callbacks_
.Remove(id
);
362 void AwContentsClientBridge::CancelJsResult(JNIEnv
*, jobject
, int id
) {
363 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
364 content::JavaScriptDialogManager::DialogClosedCallback
* callback
=
365 pending_js_dialog_callbacks_
.Lookup(id
);
367 LOG(WARNING
) << "Unexpected JS dialog cancel. " << id
;
370 callback
->Run(false, base::string16());
371 pending_js_dialog_callbacks_
.Remove(id
);
374 // Use to cleanup if there is an error in client certificate response.
375 void AwContentsClientBridge::HandleErrorInClientCertificateResponse(
377 content::ClientCertificateDelegate
* delegate
=
378 pending_client_cert_request_delegates_
.Lookup(request_id
);
379 pending_client_cert_request_delegates_
.Remove(request_id
);
384 bool RegisterAwContentsClientBridge(JNIEnv
* env
) {
385 return RegisterNativesImpl(env
);
388 } // namespace android_webview