Chromium Code Reviews| Index: android_webview/native/aw_contents_client_bridge.cc |
| diff --git a/android_webview/native/aw_contents_client_bridge.cc b/android_webview/native/aw_contents_client_bridge.cc |
| index 348505981eb1c9df73e8ffca162acf18c9e65beb..95c25c5c8bd35bc6bde31bbace70fdba88d6ddcb 100644 |
| --- a/android_webview/native/aw_contents_client_bridge.cc |
| +++ b/android_webview/native/aw_contents_client_bridge.cc |
| @@ -8,10 +8,14 @@ |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_array.h" |
| #include "base/android/jni_string.h" |
| -#include "base/callback.h" |
| +#include "base/callback_helpers.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "jni/AwContentsClientBridge_jni.h" |
| +#include "net/android/keystore_openssl.h" |
| #include "net/cert/x509_certificate.h" |
| +#include "net/ssl/openssl_client_key_store.h" |
| +#include "net/ssl/ssl_cert_request_info.h" |
| +#include "net/ssl/ssl_client_cert_type.h" |
| #include "url/gurl.h" |
| using base::android::AttachCurrentThread; |
| @@ -24,6 +28,21 @@ using content::BrowserThread; |
| namespace android_webview { |
| +typedef net::OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY; |
|
wtc
2014/04/22 22:47:16
Nit: we have crypto/scoped_nss_types.h for similar
|
| + |
| +namespace { |
|
wtc
2014/04/22 22:47:16
Nit: add a blank line after this line.
sgurun-gerrit only
2014/04/23 00:50:47
Done.
|
| +// Must be called on the I/O thread to record a client certificate |
| +// and its private key in the OpenSSLClientKeyStore. |
| +void RecordClientCertificateKey( |
| + const scoped_refptr<net::X509Certificate>& client_cert, |
| + ScopedEVP_PKEY private_key) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| + net::OpenSSLClientKeyStore::GetInstance()->RecordClientCertPrivateKey( |
| + client_cert.get(), private_key.get()); |
| +} |
| + |
| +} // namespace |
| + |
| AwContentsClientBridge::AwContentsClientBridge(JNIEnv* env, jobject obj) |
| : java_ref_(env, obj) { |
| DCHECK(obj); |
| @@ -49,7 +68,7 @@ void AwContentsClientBridge::AllowCertificateError( |
| const base::Callback<void(bool)>& callback, |
| bool* cancel_request) { |
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); |
| @@ -73,12 +92,12 @@ void AwContentsClientBridge::AllowCertificateError( |
| // if the request is cancelled, then cancel the stored callback |
| if (*cancel_request) { |
| pending_cert_error_callbacks_.Remove(request_id); |
| - } |
| + } |
| } |
| void AwContentsClientBridge::ProceedSslError(JNIEnv* env, jobject obj, |
| jboolean proceed, jint id) { |
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| CertErrorCallback* callback = pending_cert_error_callbacks_.Lookup(id); |
| if (!callback || callback->is_null()) { |
| LOG(WARNING) << "Ignoring unexpected ssl error proceed callback"; |
| @@ -88,6 +107,147 @@ void AwContentsClientBridge::ProceedSslError(JNIEnv* env, jobject obj, |
| pending_cert_error_callbacks_.Remove(id); |
| } |
| +// This method is inspired by SelectClientCertificate() in |
| +// chrome/browser/ui/android/ssl_client_certificate_request.cc |
| +void AwContentsClientBridge::SelectClientCertificate( |
| + net::SSLCertRequestInfo* cert_request_info, |
| + const SelectCertificateCallback& callback) { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + |
| + // Add the callback to id map. |
| + int request_id = pending_client_cert_request_callbacks_.Add( |
| + new SelectCertificateCallback(callback)); |
| + // Make sure callback is run on error. |
| + base::ScopedClosureRunner guard(base::Bind( |
| + &AwContentsClientBridge::HandleErrorInClientCertificateResponse, |
| + base::Unretained(this), |
| + request_id)); |
| + |
| + JNIEnv* env = base::android::AttachCurrentThread(); |
| + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); |
| + if (obj.is_null()) |
| + return; |
| + |
| + // Build the |key_types| JNI parameter, as a String[] |
| + std::vector<std::string> key_types; |
| + for (size_t n = 0; n < cert_request_info->cert_key_types.size(); ++n) { |
|
wtc
2014/04/22 22:47:16
Nit: the for loop index is usually named |i| by co
sgurun-gerrit only
2014/04/23 00:50:47
Done.
|
| + switch (cert_request_info->cert_key_types[n]) { |
| + case net::CLIENT_CERT_RSA_SIGN: |
| + key_types.push_back("RSA"); |
| + break; |
| + case net::CLIENT_CERT_DSS_SIGN: |
| + key_types.push_back("DSA"); |
| + break; |
| + case net::CLIENT_CERT_ECDSA_SIGN: |
| + key_types.push_back("ECDSA"); |
| + break; |
| + default: |
| + // Ignore unknown types. |
| + break; |
| + } |
| + } |
| + |
| + ScopedJavaLocalRef<jobjectArray> key_types_ref = |
| + base::android::ToJavaArrayOfStrings(env, key_types); |
| + if (key_types_ref.is_null()) { |
| + LOG(ERROR) << "Could not create key types array (String[])"; |
| + return; |
| + } |
| + |
| + // Build the |encoded_principals| JNI parameter, as a byte[][] |
| + ScopedJavaLocalRef<jobjectArray> principals_ref = |
| + base::android::ToJavaArrayOfByteArray( |
| + env, cert_request_info->cert_authorities); |
| + if (principals_ref.is_null()) { |
| + LOG(ERROR) << "Could not create principals array (byte[][])"; |
| + return; |
| + } |
| + |
| + // Build the |host_name| and |port| JNI parameters, as a String and |
| + // a jint. |
| + ScopedJavaLocalRef<jstring> host_name_ref = |
| + base::android::ConvertUTF8ToJavaString( |
| + env, cert_request_info->host_and_port.host()); |
| + |
| + Java_AwContentsClientBridge_selectClientCertificate( |
| + env, |
| + obj.obj(), |
| + request_id, |
| + key_types_ref.obj(), |
| + principals_ref.obj(), |
| + host_name_ref.obj(), |
| + cert_request_info->host_and_port.port()); |
| + |
| + // Release the guard. |
| + ignore_result(guard.Release()); |
| +} |
| + |
| +// This method is inspired by OnSystemRequestCompletion() in |
| +// chrome/browser/ui/android/ssl_client_certificate_request.cc |
| +void AwContentsClientBridge::ProvideClientCertificateResponse( |
| + JNIEnv* env, |
| + jobject obj, |
| + int request_id, |
| + jobjectArray encoded_chain_ref, |
| + jobject private_key_ref) { |
| + DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| + |
| + SelectCertificateCallback* callback = |
| + pending_client_cert_request_callbacks_.Lookup(request_id); |
| + DCHECK(callback); |
| + |
| + // Make sure callback is run on error. |
| + base::ScopedClosureRunner guard(base::Bind( |
| + &AwContentsClientBridge::HandleErrorInClientCertificateResponse, |
| + base::Unretained(this), |
| + request_id)); |
| + if (encoded_chain_ref == NULL || private_key_ref == NULL) { |
| + LOG(ERROR) << "Client certificate request cancelled"; |
| + return; |
| + } |
| + // Convert the encoded chain to a vector of strings. |
| + std::vector<std::string> encoded_chain_strings; |
| + if (encoded_chain_ref) { |
| + base::android::JavaArrayOfByteArrayToStringVector( |
| + env, encoded_chain_ref, &encoded_chain_strings); |
| + } |
| + |
| + std::vector<base::StringPiece> encoded_chain; |
| + for (size_t n = 0; n < encoded_chain_strings.size(); ++n) |
| + encoded_chain.push_back(encoded_chain_strings[n]); |
| + |
| + // Create the X509Certificate object from the encoded chain. |
| + scoped_refptr<net::X509Certificate> client_cert( |
| + net::X509Certificate::CreateFromDERCertChain(encoded_chain)); |
| + if (!client_cert.get()) { |
| + LOG(ERROR) << "Could not decode client certificate chain"; |
| + return; |
| + } |
| + |
| + // Create an EVP_PKEY wrapper for the private key JNI reference. |
| + ScopedEVP_PKEY private_key( |
| + net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref)); |
| + if (!private_key.get()) { |
| + LOG(ERROR) << "Could not create OpenSSL wrapper for private key"; |
| + return; |
| + } |
| + |
| + // RecordClientCertificateKey() must be called on the I/O thread, |
| + // before the callback is called with the selected certificate on |
| + // the UI thread. |
| + content::BrowserThread::PostTaskAndReply( |
| + content::BrowserThread::IO, |
| + FROM_HERE, |
| + base::Bind(&RecordClientCertificateKey, |
| + client_cert, |
| + base::Passed(&private_key)), |
| + base::Bind(*callback, client_cert)); |
| + pending_client_cert_request_callbacks_.Remove(request_id); |
| + |
| + // Release the guard. |
| + ignore_result(guard.Release()); |
| +} |
| + |
| void AwContentsClientBridge::RunJavaScriptDialog( |
| content::JavaScriptMessageType message_type, |
| const GURL& origin_url, |
| @@ -206,6 +366,15 @@ void AwContentsClientBridge::CancelJsResult(JNIEnv*, jobject, int id) { |
| pending_js_dialog_callbacks_.Remove(id); |
| } |
| +// Use to cleanup if there is an error in client certificate response. |
| +void AwContentsClientBridge::HandleErrorInClientCertificateResponse( |
| + int request_id) { |
| + SelectCertificateCallback* callback = |
| + pending_client_cert_request_callbacks_.Lookup(request_id); |
| + callback->Run(scoped_refptr<net::X509Certificate>()); |
| + pending_client_cert_request_callbacks_.Remove(request_id); |
| +} |
| + |
| bool RegisterAwContentsClientBridge(JNIEnv* env) { |
| return RegisterNativesImpl(env) >= 0; |
| } |