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