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_DSS_SIGN
:
152 key_types
.push_back("DSA");
154 case net::CLIENT_CERT_ECDSA_SIGN
:
155 key_types
.push_back("ECDSA");
158 // Ignore unknown types.
163 ScopedJavaLocalRef
<jobjectArray
> key_types_ref
=
164 base::android::ToJavaArrayOfStrings(env
, key_types
);
165 if (key_types_ref
.is_null()) {
166 LOG(ERROR
) << "Could not create key types array (String[])";
170 // Build the |encoded_principals| JNI parameter, as a byte[][]
171 ScopedJavaLocalRef
<jobjectArray
> principals_ref
=
172 base::android::ToJavaArrayOfByteArray(
173 env
, cert_request_info
->cert_authorities
);
174 if (principals_ref
.is_null()) {
175 LOG(ERROR
) << "Could not create principals array (byte[][])";
179 // Build the |host_name| and |port| JNI parameters, as a String and
181 ScopedJavaLocalRef
<jstring
> host_name_ref
=
182 base::android::ConvertUTF8ToJavaString(
183 env
, cert_request_info
->host_and_port
.host());
185 Java_AwContentsClientBridge_selectClientCertificate(
190 principals_ref
.obj(),
192 cert_request_info
->host_and_port
.port());
194 // Release the guard.
195 ignore_result(guard
.Release());
198 // This method is inspired by OnSystemRequestCompletion() in
199 // chrome/browser/ui/android/ssl_client_certificate_request.cc
200 void AwContentsClientBridge::ProvideClientCertificateResponse(
204 jobjectArray encoded_chain_ref
,
205 jobject private_key_ref
) {
206 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
208 content::ClientCertificateDelegate
* delegate
=
209 pending_client_cert_request_delegates_
.Lookup(request_id
);
212 if (encoded_chain_ref
== NULL
|| private_key_ref
== NULL
) {
213 LOG(ERROR
) << "No client certificate selected";
214 pending_client_cert_request_delegates_
.Remove(request_id
);
215 delegate
->ContinueWithCertificate(nullptr);
220 // Make sure callback is run on error.
221 base::ScopedClosureRunner
guard(base::Bind(
222 &AwContentsClientBridge::HandleErrorInClientCertificateResponse
,
223 base::Unretained(this),
226 // Convert the encoded chain to a vector of strings.
227 std::vector
<std::string
> encoded_chain_strings
;
228 if (encoded_chain_ref
) {
229 base::android::JavaArrayOfByteArrayToStringVector(
230 env
, encoded_chain_ref
, &encoded_chain_strings
);
233 std::vector
<base::StringPiece
> encoded_chain
;
234 for (size_t i
= 0; i
< encoded_chain_strings
.size(); ++i
)
235 encoded_chain
.push_back(encoded_chain_strings
[i
]);
237 // Create the X509Certificate object from the encoded chain.
238 scoped_refptr
<net::X509Certificate
> client_cert(
239 net::X509Certificate::CreateFromDERCertChain(encoded_chain
));
240 if (!client_cert
.get()) {
241 LOG(ERROR
) << "Could not decode client certificate chain";
245 // Create an EVP_PKEY wrapper for the private key JNI reference.
246 crypto::ScopedEVP_PKEY
private_key(
247 net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref
));
248 if (!private_key
.get()) {
249 LOG(ERROR
) << "Could not create OpenSSL wrapper for private key";
253 // Release the guard and |pending_client_cert_request_delegates_| references
255 pending_client_cert_request_delegates_
.Remove(request_id
);
256 ignore_result(guard
.Release());
258 // RecordClientCertificateKey() must be called on the I/O thread,
259 // before the delegate is called with the selected certificate on
261 content::BrowserThread::PostTaskAndReply(
262 content::BrowserThread::IO
, FROM_HERE
,
263 base::Bind(&RecordClientCertificateKey
, client_cert
,
264 base::Passed(&private_key
)),
265 base::Bind(&content::ClientCertificateDelegate::ContinueWithCertificate
,
266 base::Owned(delegate
), client_cert
));
269 void AwContentsClientBridge::RunJavaScriptDialog(
270 content::JavaScriptMessageType message_type
,
271 const GURL
& origin_url
,
272 const base::string16
& message_text
,
273 const base::string16
& default_prompt_text
,
274 const content::JavaScriptDialogManager::DialogClosedCallback
& callback
) {
275 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
276 JNIEnv
* env
= AttachCurrentThread();
278 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
280 callback
.Run(false, base::string16());
284 int callback_id
= pending_js_dialog_callbacks_
.Add(
285 new content::JavaScriptDialogManager::DialogClosedCallback(callback
));
286 ScopedJavaLocalRef
<jstring
> jurl(
287 ConvertUTF8ToJavaString(env
, origin_url
.spec()));
288 ScopedJavaLocalRef
<jstring
> jmessage(
289 ConvertUTF16ToJavaString(env
, message_text
));
291 switch (message_type
) {
292 case content::JAVASCRIPT_MESSAGE_TYPE_ALERT
: {
293 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsAlert");
294 Java_AwContentsClientBridge_handleJsAlert(
295 env
, obj
.obj(), jurl
.obj(), jmessage
.obj(), callback_id
);
298 case content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM
: {
299 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsConfirm");
300 Java_AwContentsClientBridge_handleJsConfirm(
301 env
, obj
.obj(), jurl
.obj(), jmessage
.obj(), callback_id
);
304 case content::JAVASCRIPT_MESSAGE_TYPE_PROMPT
: {
305 ScopedJavaLocalRef
<jstring
> jdefault_value(
306 ConvertUTF16ToJavaString(env
, default_prompt_text
));
307 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsPrompt");
308 Java_AwContentsClientBridge_handleJsPrompt(env
,
312 jdefault_value
.obj(),
321 void AwContentsClientBridge::RunBeforeUnloadDialog(
322 const GURL
& origin_url
,
323 const base::string16
& message_text
,
324 const content::JavaScriptDialogManager::DialogClosedCallback
& callback
) {
325 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
326 JNIEnv
* env
= AttachCurrentThread();
328 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
330 callback
.Run(false, base::string16());
334 int callback_id
= pending_js_dialog_callbacks_
.Add(
335 new content::JavaScriptDialogManager::DialogClosedCallback(callback
));
336 ScopedJavaLocalRef
<jstring
> jurl(
337 ConvertUTF8ToJavaString(env
, origin_url
.spec()));
338 ScopedJavaLocalRef
<jstring
> jmessage(
339 ConvertUTF16ToJavaString(env
, message_text
));
341 devtools_instrumentation::ScopedEmbedderCallbackTask("onJsBeforeUnload");
342 Java_AwContentsClientBridge_handleJsBeforeUnload(
343 env
, obj
.obj(), jurl
.obj(), jmessage
.obj(), callback_id
);
346 bool AwContentsClientBridge::ShouldOverrideUrlLoading(
347 const base::string16
& url
) {
348 JNIEnv
* env
= AttachCurrentThread();
349 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
352 ScopedJavaLocalRef
<jstring
> jurl
= ConvertUTF16ToJavaString(env
, url
);
353 devtools_instrumentation::ScopedEmbedderCallbackTask(
354 "shouldOverrideUrlLoading");
355 return Java_AwContentsClientBridge_shouldOverrideUrlLoading(
360 void AwContentsClientBridge::ConfirmJsResult(JNIEnv
* env
,
364 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
365 content::JavaScriptDialogManager::DialogClosedCallback
* callback
=
366 pending_js_dialog_callbacks_
.Lookup(id
);
368 LOG(WARNING
) << "Unexpected JS dialog confirm. " << id
;
371 base::string16 prompt_text
;
373 prompt_text
= ConvertJavaStringToUTF16(env
, prompt
);
375 callback
->Run(true, prompt_text
);
376 pending_js_dialog_callbacks_
.Remove(id
);
379 void AwContentsClientBridge::CancelJsResult(JNIEnv
*, jobject
, int id
) {
380 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
381 content::JavaScriptDialogManager::DialogClosedCallback
* callback
=
382 pending_js_dialog_callbacks_
.Lookup(id
);
384 LOG(WARNING
) << "Unexpected JS dialog cancel. " << id
;
387 callback
->Run(false, base::string16());
388 pending_js_dialog_callbacks_
.Remove(id
);
391 // Use to cleanup if there is an error in client certificate response.
392 void AwContentsClientBridge::HandleErrorInClientCertificateResponse(
394 content::ClientCertificateDelegate
* delegate
=
395 pending_client_cert_request_delegates_
.Lookup(request_id
);
396 pending_client_cert_request_delegates_
.Remove(request_id
);
401 bool RegisterAwContentsClientBridge(JNIEnv
* env
) {
402 return RegisterNativesImpl(env
);
405 } // namespace android_webview