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
26using base::android::AttachCurrentThread;
27using base::android::ConvertJavaStringToUTF16;
28using base::android::ConvertUTF8ToJavaString;
29using base::android::ConvertUTF16ToJavaString;
30using base::android::JavaRef;
31using base::android::ScopedJavaLocalRef;
32using content::BrowserThread;
33
34namespace android_webview {
35
36namespace {
37
38// Must be called on the I/O thread to record a client certificate
39// and its private key in the OpenSSLClientKeyStore.
40void 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
50AwContentsClientBridge::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
57AwContentsClientBridge::~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
68void 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
102void 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
116void 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
191void 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
255void 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
307void 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
332bool 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
346void 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
365void 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.
378void 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
386bool RegisterAwContentsClientBridge(JNIEnv* env) {
387  return RegisterNativesImpl(env);
388}
389
390}  // namespace android_webview
391