Home | History | Annotate | Download | only in native
      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.
      4 
      5 #include "android_webview/native/aw_contents_client_bridge.h"
      6 
      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 "content/public/browser/browser_thread.h"
     14 #include "content/public/browser/render_process_host.h"
     15 #include "content/public/browser/render_view_host.h"
     16 #include "content/public/browser/web_contents.h"
     17 #include "jni/AwContentsClientBridge_jni.h"
     18 #include "net/android/keystore_openssl.h"
     19 #include "net/cert/x509_certificate.h"
     20 #include "net/ssl/openssl_client_key_store.h"
     21 #include "net/ssl/ssl_cert_request_info.h"
     22 #include "net/ssl/ssl_client_cert_type.h"
     23 #include "url/gurl.h"
     24 
     25 using base::android::AttachCurrentThread;
     26 using base::android::ConvertJavaStringToUTF16;
     27 using base::android::ConvertUTF8ToJavaString;
     28 using base::android::ConvertUTF16ToJavaString;
     29 using base::android::JavaRef;
     30 using base::android::ScopedJavaLocalRef;
     31 using content::BrowserThread;
     32 
     33 namespace android_webview {
     34 
     35 typedef net::OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY;
     36 
     37 namespace {
     38 
     39 // Must be called on the I/O thread to record a client certificate
     40 // and its private key in the OpenSSLClientKeyStore.
     41 void RecordClientCertificateKey(
     42     const scoped_refptr<net::X509Certificate>& client_cert,
     43     ScopedEVP_PKEY private_key) {
     44   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     45   net::OpenSSLClientKeyStore::GetInstance()->RecordClientCertPrivateKey(
     46       client_cert.get(), private_key.get());
     47 }
     48 
     49 }  // namespace
     50 
     51 AwContentsClientBridge::AwContentsClientBridge(JNIEnv* env, jobject obj)
     52     : java_ref_(env, obj) {
     53   DCHECK(obj);
     54   Java_AwContentsClientBridge_setNativeContentsClientBridge(
     55       env, obj, reinterpret_cast<intptr_t>(this));
     56 }
     57 
     58 AwContentsClientBridge::~AwContentsClientBridge() {
     59   JNIEnv* env = AttachCurrentThread();
     60 
     61   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
     62   if (obj.is_null())
     63     return;
     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(), 0);
     67 }
     68 
     69 void AwContentsClientBridge::AllowCertificateError(
     70     int cert_error,
     71     net::X509Certificate* cert,
     72     const GURL& request_url,
     73     const base::Callback<void(bool)>& callback,
     74     bool* cancel_request) {
     75 
     76   DCHECK_CURRENTLY_ON(BrowserThread::UI);
     77   JNIEnv* env = AttachCurrentThread();
     78 
     79   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
     80   if (obj.is_null())
     81     return;
     82 
     83   std::string der_string;
     84   net::X509Certificate::GetDEREncoded(cert->os_cert_handle(), &der_string);
     85   ScopedJavaLocalRef<jbyteArray> jcert = base::android::ToJavaByteArray(
     86       env,
     87       reinterpret_cast<const uint8*>(der_string.data()),
     88       der_string.length());
     89   ScopedJavaLocalRef<jstring> jurl(ConvertUTF8ToJavaString(
     90       env, request_url.spec()));
     91   // We need to add the callback before making the call to java side,
     92   // as it may do a synchronous callback prior to returning.
     93   int request_id = pending_cert_error_callbacks_.Add(
     94       new CertErrorCallback(callback));
     95   *cancel_request = !Java_AwContentsClientBridge_allowCertificateError(
     96       env, obj.obj(), cert_error, jcert.obj(), jurl.obj(), request_id);
     97   // if the request is cancelled, then cancel the stored callback
     98   if (*cancel_request) {
     99     pending_cert_error_callbacks_.Remove(request_id);
    100   }
    101 }
    102 
    103 void AwContentsClientBridge::ProceedSslError(JNIEnv* env, jobject obj,
    104                                              jboolean proceed, jint id) {
    105   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    106   CertErrorCallback* callback = pending_cert_error_callbacks_.Lookup(id);
    107   if (!callback || callback->is_null()) {
    108     LOG(WARNING) << "Ignoring unexpected ssl error proceed callback";
    109     return;
    110   }
    111   callback->Run(proceed);
    112   pending_cert_error_callbacks_.Remove(id);
    113 }
    114 
    115 // This method is inspired by SelectClientCertificate() in
    116 // chrome/browser/ui/android/ssl_client_certificate_request.cc
    117 void AwContentsClientBridge::SelectClientCertificate(
    118       net::SSLCertRequestInfo* cert_request_info,
    119       const SelectCertificateCallback& callback) {
    120   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    121 
    122   // Add the callback to id map.
    123   int request_id = pending_client_cert_request_callbacks_.Add(
    124       new SelectCertificateCallback(callback));
    125   // Make sure callback is run on error.
    126   base::ScopedClosureRunner guard(base::Bind(
    127       &AwContentsClientBridge::HandleErrorInClientCertificateResponse,
    128       base::Unretained(this),
    129       request_id));
    130 
    131   JNIEnv* env = base::android::AttachCurrentThread();
    132   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
    133   if (obj.is_null())
    134     return;
    135 
    136   // Build the |key_types| JNI parameter, as a String[]
    137   std::vector<std::string> key_types;
    138   for (size_t i = 0; i < cert_request_info->cert_key_types.size(); ++i) {
    139     switch (cert_request_info->cert_key_types[i]) {
    140       case net::CLIENT_CERT_RSA_SIGN:
    141         key_types.push_back("RSA");
    142         break;
    143       case net::CLIENT_CERT_DSS_SIGN:
    144         key_types.push_back("DSA");
    145         break;
    146       case net::CLIENT_CERT_ECDSA_SIGN:
    147         key_types.push_back("ECDSA");
    148         break;
    149       default:
    150         // Ignore unknown types.
    151         break;
    152     }
    153   }
    154 
    155   ScopedJavaLocalRef<jobjectArray> key_types_ref =
    156       base::android::ToJavaArrayOfStrings(env, key_types);
    157   if (key_types_ref.is_null()) {
    158     LOG(ERROR) << "Could not create key types array (String[])";
    159     return;
    160   }
    161 
    162   // Build the |encoded_principals| JNI parameter, as a byte[][]
    163   ScopedJavaLocalRef<jobjectArray> principals_ref =
    164       base::android::ToJavaArrayOfByteArray(
    165           env, cert_request_info->cert_authorities);
    166   if (principals_ref.is_null()) {
    167     LOG(ERROR) << "Could not create principals array (byte[][])";
    168     return;
    169   }
    170 
    171   // Build the |host_name| and |port| JNI parameters, as a String and
    172   // a jint.
    173   ScopedJavaLocalRef<jstring> host_name_ref =
    174       base::android::ConvertUTF8ToJavaString(
    175           env, cert_request_info->host_and_port.host());
    176 
    177   Java_AwContentsClientBridge_selectClientCertificate(
    178       env,
    179       obj.obj(),
    180       request_id,
    181       key_types_ref.obj(),
    182       principals_ref.obj(),
    183       host_name_ref.obj(),
    184       cert_request_info->host_and_port.port());
    185 
    186   // Release the guard.
    187   ignore_result(guard.Release());
    188 }
    189 
    190 // This method is inspired by OnSystemRequestCompletion() in
    191 // chrome/browser/ui/android/ssl_client_certificate_request.cc
    192 void AwContentsClientBridge::ProvideClientCertificateResponse(
    193     JNIEnv* env,
    194     jobject obj,
    195     int request_id,
    196     jobjectArray encoded_chain_ref,
    197     jobject private_key_ref) {
    198   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    199 
    200   SelectCertificateCallback* callback =
    201       pending_client_cert_request_callbacks_.Lookup(request_id);
    202   DCHECK(callback);
    203 
    204   // Make sure callback is run on error.
    205   base::ScopedClosureRunner guard(base::Bind(
    206       &AwContentsClientBridge::HandleErrorInClientCertificateResponse,
    207       base::Unretained(this),
    208       request_id));
    209   if (encoded_chain_ref == NULL || private_key_ref == NULL) {
    210     LOG(ERROR) << "Client certificate request cancelled";
    211     return;
    212   }
    213   // Convert the encoded chain to a vector of strings.
    214   std::vector<std::string> encoded_chain_strings;
    215   if (encoded_chain_ref) {
    216     base::android::JavaArrayOfByteArrayToStringVector(
    217         env, encoded_chain_ref, &encoded_chain_strings);
    218   }
    219 
    220   std::vector<base::StringPiece> encoded_chain;
    221   for (size_t i = 0; i < encoded_chain_strings.size(); ++i)
    222     encoded_chain.push_back(encoded_chain_strings[i]);
    223 
    224   // Create the X509Certificate object from the encoded chain.
    225   scoped_refptr<net::X509Certificate> client_cert(
    226       net::X509Certificate::CreateFromDERCertChain(encoded_chain));
    227   if (!client_cert.get()) {
    228     LOG(ERROR) << "Could not decode client certificate chain";
    229     return;
    230   }
    231 
    232   // Create an EVP_PKEY wrapper for the private key JNI reference.
    233   ScopedEVP_PKEY private_key(
    234       net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref));
    235   if (!private_key.get()) {
    236     LOG(ERROR) << "Could not create OpenSSL wrapper for private key";
    237     return;
    238   }
    239 
    240   // RecordClientCertificateKey() must be called on the I/O thread,
    241   // before the callback is called with the selected certificate on
    242   // the UI thread.
    243   content::BrowserThread::PostTaskAndReply(
    244       content::BrowserThread::IO,
    245       FROM_HERE,
    246       base::Bind(&RecordClientCertificateKey,
    247                  client_cert,
    248                  base::Passed(&private_key)),
    249       base::Bind(*callback, client_cert));
    250   pending_client_cert_request_callbacks_.Remove(request_id);
    251 
    252   // Release the guard.
    253   ignore_result(guard.Release());
    254 }
    255 
    256 void AwContentsClientBridge::RunJavaScriptDialog(
    257     content::JavaScriptMessageType message_type,
    258     const GURL& origin_url,
    259     const base::string16& message_text,
    260     const base::string16& default_prompt_text,
    261     const content::JavaScriptDialogManager::DialogClosedCallback& callback) {
    262   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    263   JNIEnv* env = AttachCurrentThread();
    264 
    265   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
    266   if (obj.is_null()) {
    267     callback.Run(false, base::string16());
    268     return;
    269   }
    270 
    271   int callback_id = pending_js_dialog_callbacks_.Add(
    272       new content::JavaScriptDialogManager::DialogClosedCallback(callback));
    273   ScopedJavaLocalRef<jstring> jurl(
    274       ConvertUTF8ToJavaString(env, origin_url.spec()));
    275   ScopedJavaLocalRef<jstring> jmessage(
    276       ConvertUTF16ToJavaString(env, message_text));
    277 
    278   switch (message_type) {
    279     case content::JAVASCRIPT_MESSAGE_TYPE_ALERT: {
    280       devtools_instrumentation::ScopedEmbedderCallbackTask("onJsAlert");
    281       Java_AwContentsClientBridge_handleJsAlert(
    282           env, obj.obj(), jurl.obj(), jmessage.obj(), callback_id);
    283       break;
    284     }
    285     case content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM: {
    286       devtools_instrumentation::ScopedEmbedderCallbackTask("onJsConfirm");
    287       Java_AwContentsClientBridge_handleJsConfirm(
    288           env, obj.obj(), jurl.obj(), jmessage.obj(), callback_id);
    289       break;
    290     }
    291     case content::JAVASCRIPT_MESSAGE_TYPE_PROMPT: {
    292       ScopedJavaLocalRef<jstring> jdefault_value(
    293           ConvertUTF16ToJavaString(env, default_prompt_text));
    294       devtools_instrumentation::ScopedEmbedderCallbackTask("onJsPrompt");
    295       Java_AwContentsClientBridge_handleJsPrompt(env,
    296                                                  obj.obj(),
    297                                                  jurl.obj(),
    298                                                  jmessage.obj(),
    299                                                  jdefault_value.obj(),
    300                                                  callback_id);
    301       break;
    302     }
    303     default:
    304        NOTREACHED();
    305   }
    306 }
    307 
    308 void AwContentsClientBridge::RunBeforeUnloadDialog(
    309     const GURL& origin_url,
    310     const base::string16& message_text,
    311     const content::JavaScriptDialogManager::DialogClosedCallback& callback) {
    312   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    313   JNIEnv* env = AttachCurrentThread();
    314 
    315   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
    316   if (obj.is_null()) {
    317     callback.Run(false, base::string16());
    318     return;
    319   }
    320 
    321   int callback_id = pending_js_dialog_callbacks_.Add(
    322       new content::JavaScriptDialogManager::DialogClosedCallback(callback));
    323   ScopedJavaLocalRef<jstring> jurl(
    324       ConvertUTF8ToJavaString(env, origin_url.spec()));
    325   ScopedJavaLocalRef<jstring> jmessage(
    326       ConvertUTF16ToJavaString(env, message_text));
    327 
    328   devtools_instrumentation::ScopedEmbedderCallbackTask("onJsBeforeUnload");
    329   Java_AwContentsClientBridge_handleJsBeforeUnload(
    330       env, obj.obj(), jurl.obj(), jmessage.obj(), callback_id);
    331 }
    332 
    333 bool AwContentsClientBridge::ShouldOverrideUrlLoading(
    334     const base::string16& url) {
    335   JNIEnv* env = AttachCurrentThread();
    336   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
    337   if (obj.is_null())
    338     return false;
    339   ScopedJavaLocalRef<jstring> jurl = ConvertUTF16ToJavaString(env, url);
    340   devtools_instrumentation::ScopedEmbedderCallbackTask(
    341       "shouldOverrideUrlLoading");
    342   return Java_AwContentsClientBridge_shouldOverrideUrlLoading(
    343       env, obj.obj(),
    344       jurl.obj());
    345 }
    346 
    347 void AwContentsClientBridge::ConfirmJsResult(JNIEnv* env,
    348                                              jobject,
    349                                              int id,
    350                                              jstring prompt) {
    351   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    352   content::JavaScriptDialogManager::DialogClosedCallback* callback =
    353       pending_js_dialog_callbacks_.Lookup(id);
    354   if (!callback) {
    355     LOG(WARNING) << "Unexpected JS dialog confirm. " << id;
    356     return;
    357   }
    358   base::string16 prompt_text;
    359   if (prompt) {
    360     prompt_text = ConvertJavaStringToUTF16(env, prompt);
    361   }
    362   callback->Run(true, prompt_text);
    363   pending_js_dialog_callbacks_.Remove(id);
    364 }
    365 
    366 void AwContentsClientBridge::CancelJsResult(JNIEnv*, jobject, int id) {
    367   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    368   content::JavaScriptDialogManager::DialogClosedCallback* callback =
    369       pending_js_dialog_callbacks_.Lookup(id);
    370   if (!callback) {
    371     LOG(WARNING) << "Unexpected JS dialog cancel. " << id;
    372     return;
    373   }
    374   callback->Run(false, base::string16());
    375   pending_js_dialog_callbacks_.Remove(id);
    376 }
    377 
    378 // Use to cleanup if there is an error in client certificate response.
    379 void AwContentsClientBridge::HandleErrorInClientCertificateResponse(
    380     int request_id) {
    381   SelectCertificateCallback* callback =
    382       pending_client_cert_request_callbacks_.Lookup(request_id);
    383   callback->Run(scoped_refptr<net::X509Certificate>());
    384   pending_client_cert_request_callbacks_.Remove(request_id);
    385 }
    386 
    387 bool RegisterAwContentsClientBridge(JNIEnv* env) {
    388   return RegisterNativesImpl(env);
    389 }
    390 
    391 }  // namespace android_webview
    392