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; |
} |