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