Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ssl/ssl_client_certificate_selector.h" | 5 #include "chrome/browser/ssl/ssl_client_certificate_selector.h" |
| 6 | 6 |
| 7 #include <openssl/evp.h> | |
| 8 | |
| 9 #include "base/callback_helpers.h" | |
| 7 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/memory/ref_counted.h" | |
| 12 #include "chrome/browser/ui/android/ssl_client_certificate_request.h" | |
| 13 #include "content/public/browser/browser_thread.h" | |
| 14 #include "net/android/keystore_openssl.h" | |
| 15 #include "net/base/openssl_client_key_store.h" | |
| 16 #include "net/base/ssl_cert_request_info.h" | |
| 17 #include "net/base/x509_certificate.h" | |
| 18 | |
| 19 // On other platforms, the list of client certificates compatible with | |
| 20 // the SSLCertRequestInfo is built using system APIs that do not require | |
| 21 // user interaction. After this, ShowSSLClientCertificateSelector() is | |
| 22 // merely used to display a tab sub-window asking the user to select | |
| 23 // one of these certificates. | |
| 24 | |
| 25 // On Android, things are a bit different, because getting the list of | |
| 26 // compatible client certificates is only possible using an API that shows | |
| 27 // a system UI dialog. More precisely: | |
| 28 // | |
| 29 // - The application must call KeyChain.choosePrivateKeyAlias() and | |
| 30 // pass it the request parameters directly. | |
| 31 // | |
| 32 // - This API always launches a system activity (CertInstaller), that | |
| 33 // will display a list of compatible installed client certificates, | |
| 34 // if any, or prompt the user to install one manually otherwise. | |
| 35 // | |
| 36 // - Also, the first time this API is called, the CertInstaller will | |
| 37 // first prompt the user to enter the secure storage's password | |
| 38 // (which is the user's PIN code / password by default). This establishes | |
| 39 // a trust relationship between the KeyChain system application, and | |
| 40 // the application calling the API. It persists until the application | |
| 41 // is killed. | |
| 42 // | |
| 43 // - The client certificate selection result is sent back to the | |
| 44 // application through a UI thread callback. It only contains a | |
| 45 // string alias for the selected certificate, or 'null' to indicate | |
| 46 // that the user has canceled the selection, or couldn't unlock | |
| 47 // access to the secure storage. | |
| 48 // | |
| 8 | 49 |
| 9 namespace chrome { | 50 namespace chrome { |
| 10 | 51 |
| 11 // Client Auth is not implemented on Android yet. | 52 using browser::android::SSLClientCertificateRequest; |
| 53 | |
| 54 namespace { | |
| 55 | |
| 56 typedef net::OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY; | |
| 57 | |
| 58 // Helper class used to guarantee that a callback.Run(NULL) is called on | |
| 59 // scope exit, unless Reset() was used before. | |
| 60 class CallbackGuard { | |
|
Ryan Sleevi
2013/03/04 23:47:21
We already have this, in src/base/bind_helpers.h
digit1
2013/03/05 16:54:00
I'm not sure I didn't already mentioned already, b
Ryan Sleevi
2013/03/05 18:02:41
Can you paste the error? I really want to avoid cr
digit1
2013/03/06 01:48:33
Ok, the error was due to the NULL inside the base:
| |
| 61 public: | |
| 62 explicit CallbackGuard(chrome::SelectCertificateCallback& callback) | |
|
Ryan Sleevi
2013/03/04 23:47:21
I've mentioned it in past reviews, but in Google C
digit1
2013/03/05 16:54:00
Sorry about that, I've fixed it.
| |
| 63 : callback_(&callback) { | |
| 64 } | |
| 65 | |
| 66 ~CallbackGuard() { | |
| 67 if (callback_) | |
| 68 base::ResetAndReturn(callback_).Run(NULL); | |
| 69 } | |
| 70 | |
| 71 void Reset() { | |
| 72 callback_ = NULL; | |
| 73 } | |
| 74 | |
| 75 private: | |
| 76 chrome::SelectCertificateCallback* callback_; | |
| 77 | |
| 78 DISALLOW_COPY_AND_ASSIGN(CallbackGuard); | |
| 79 }; | |
| 80 | |
| 81 // Helper class used to pass a (certificate,private key) pair between | |
| 82 // threads. | |
| 83 class CertificateAndKey | |
| 84 : public base::RefCountedThreadSafe<CertificateAndKey> { | |
|
Ryan Sleevi
2013/03/04 23:47:21
1) We have RefCountedData<T> for this
2) You shoul
digit1
2013/03/05 16:54:00
1) It looks like RefCountedData<> would be appropr
| |
| 85 public: | |
| 86 CertificateAndKey() { } | |
| 87 | |
| 88 scoped_refptr<net::X509Certificate> client_cert; | |
| 89 ScopedEVP_PKEY private_key; | |
| 90 private: | |
| 91 friend class base::RefCountedThreadSafe<CertificateAndKey>; | |
| 92 | |
| 93 ~CertificateAndKey() { } | |
| 94 | |
| 95 DISALLOW_COPY_AND_ASSIGN(CertificateAndKey); | |
| 96 }; | |
| 97 | |
| 98 // A SSLClientCertificateRequest used to pass the client | |
| 99 // certificate to the proper callback, after recording its private | |
| 100 // key into the net::OpenSSLClientKeyStore. | |
| 101 class Request : public SSLClientCertificateRequest { | |
|
Ryan Sleevi
2013/03/04 23:47:21
I'm not sure why you've got the inheritance route.
digit1
2013/03/05 16:54:00
This change reflect's Marcus' recommendation. He m
Ryan Sleevi
2013/03/05 18:02:41
Can you not just pass a base::Closure/Callback in
| |
| 102 public: | |
| 103 explicit Request(const chrome::SelectCertificateCallback& callback) | |
| 104 : callback_(callback) { | |
| 105 } | |
| 106 | |
| 107 virtual void OnCompletion( | |
| 108 std::vector<base::StringPiece>* encoded_chain, | |
| 109 jobject private_key) OVERRIDE; | |
| 110 | |
| 111 protected: | |
| 112 ~Request() { } | |
| 113 | |
| 114 private: | |
| 115 Request(); | |
| 116 | |
| 117 // Called on the I/O thread to record a (certificate,private_key) pair | |
| 118 // into the net::OpenSSLClientKeyStore. | |
| 119 static void DoRecordClientCertificateKey( | |
| 120 scoped_refptr<CertificateAndKey> pair); | |
|
Ryan Sleevi
2013/03/04 23:47:21
I've pointed out in past reviews, only scoped_ptr<
digit1
2013/03/05 16:54:00
The above line comes from the fact that a scoped_p
Ryan Sleevi
2013/03/05 18:02:41
I think you missed what I was saying: scoped_refpt
| |
| 121 | |
| 122 // Called on the UI thread, as a reply to DoRecordClientCertificateKey(), | |
| 123 // to send the selected certificate to the callback. | |
| 124 void DoSendCertificate(scoped_refptr<net::X509Certificate> client_cert); | |
| 125 | |
| 126 chrome::SelectCertificateCallback callback_; | |
| 127 }; | |
| 128 | |
| 129 // Called on the UI thread once the user has selected a client certificate | |
| 130 // using the system-provided dialog, or failed to select one. | |
| 131 // |encoded_chain| is a pointer to the encoded client certificate chain. | |
| 132 // Each item in the list is a DER-encoded X.509 certificate. | |
| 133 // |private_key| is a pointer to a JNI local reference to the platform | |
| 134 // PrivateKey object for this client certificate chain. | |
| 135 void Request::OnCompletion(std::vector<base::StringPiece>* encoded_chain, | |
| 136 jobject private_key_ref) { | |
| 137 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 138 | |
| 139 // Ensure callback_.Run(NULL) is called on error exit. | |
| 140 CallbackGuard guard(callback_); | |
| 141 | |
| 142 if (encoded_chain == NULL || private_key_ref == NULL) { | |
| 143 LOG(ERROR) << "Client certificate selection cancelled by the user"; | |
| 144 return; | |
| 145 } | |
| 146 | |
| 147 scoped_refptr<CertificateAndKey> pair(new CertificateAndKey); | |
| 148 | |
| 149 // Create the X509Certificate object from the encoded chain. | |
| 150 pair->client_cert = | |
| 151 net::X509Certificate::CreateFromDERCertChain(*encoded_chain); | |
| 152 if (!pair->client_cert.get()) { | |
| 153 LOG(ERROR) << "Could not decode client certificate chain"; | |
| 154 return; | |
| 155 } | |
| 156 | |
| 157 // Create an EVP_PKEY wrapper for the private key JNI reference. | |
| 158 pair->private_key.reset( | |
| 159 net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref)); | |
| 160 if (!pair->private_key.get()) { | |
| 161 LOG(ERROR) << "Could not create OpenSSL wrapper for private key"; | |
| 162 return; | |
| 163 } | |
| 164 | |
| 165 guard.Reset(); | |
| 166 | |
| 167 // DoRecordClientCertificateKey() must be called on the I/O thread, | |
| 168 // before DoSendCertificate() can be called on the UI thread to pass | |
| 169 // the client certificate to the callback. | |
| 170 content::BrowserThread::PostTaskAndReply( | |
| 171 content::BrowserThread::IO, | |
| 172 FROM_HERE, | |
| 173 base::Bind(&Request::DoRecordClientCertificateKey, pair), | |
| 174 base::Bind(&Request::DoSendCertificate, this, pair->client_cert)); | |
|
Ryan Sleevi
2013/03/04 23:47:21
Why have a DoSendCertificate helper?
Why not simp
digit1
2013/03/05 16:54:00
That's an excellent suggestion, I didn't think abo
| |
| 175 } | |
| 176 | |
| 177 void Request::DoRecordClientCertificateKey( | |
| 178 scoped_refptr<CertificateAndKey> pair) { | |
| 179 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
| 180 net::OpenSSLClientKeyStore::GetInstance()->RecordClientCertPrivateKey( | |
| 181 pair->client_cert.get(), pair->private_key.get()); | |
| 182 } | |
| 183 | |
| 184 void Request::DoSendCertificate( | |
| 185 scoped_refptr<net::X509Certificate> client_cert) { | |
| 186 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 187 base::ResetAndReturn(&callback_).Run(client_cert); | |
| 188 } | |
| 189 | |
| 190 } // namespace | |
| 191 | |
| 12 void ShowSSLClientCertificateSelector( | 192 void ShowSSLClientCertificateSelector( |
| 13 content::WebContents* contents, | 193 content::WebContents* contents, |
| 14 const net::HttpNetworkSession* network_session, | 194 const net::HttpNetworkSession* network_session, |
| 15 net::SSLCertRequestInfo* cert_request_info, | 195 net::SSLCertRequestInfo* cert_request_info, |
| 16 const base::Callback<void(net::X509Certificate*)>& callback) { | 196 const chrome::SelectCertificateCallback& callback) { |
| 17 NOTIMPLEMENTED(); | 197 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 198 scoped_refptr<Request> request(new Request(callback)); | |
|
Ryan Sleevi
2013/03/04 23:47:21
Not sure why the RefCounting
digit1
2013/03/05 16:54:00
The refcounting is used to ensure that the request
| |
| 199 if (!request->Start(cert_request_info)) { | |
| 200 LOG(ERROR) << "Could not start client certificate request!"; | |
| 201 callback.Run(NULL); | |
| 202 } | |
| 18 } | 203 } |
| 19 | 204 |
| 20 } // namespace chrome | 205 } // namespace chrome |
| OLD | NEW |