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