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/bind_helpers.h" | |
10 #include "base/callback_helpers.h" | |
7 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "base/memory/ref_counted.h" | |
13 #include "chrome/browser/ui/android/ssl_client_certificate_request.h" | |
14 #include "content/public/browser/browser_thread.h" | |
15 #include "net/android/keystore_openssl.h" | |
16 #include "net/base/openssl_client_key_store.h" | |
17 #include "net/base/ssl_cert_request_info.h" | |
18 #include "net/base/x509_certificate.h" | |
19 | |
20 // On other platforms, the list of client certificates compatible with | |
21 // the SSLCertRequestInfo is built using system APIs that do not require | |
22 // user interaction. After this, ShowSSLClientCertificateSelector() is | |
23 // merely used to display a tab sub-window asking the user to select | |
24 // one of these certificates. | |
25 | |
26 // On Android, things are a bit different, because getting the list of | |
27 // compatible client certificates is only possible using an API that shows | |
28 // a system UI dialog. More precisely: | |
29 // | |
30 // - The application must call KeyChain.choosePrivateKeyAlias() and | |
31 // pass it the request parameters directly. | |
32 // | |
33 // - This API always launches a system activity (CertInstaller), that | |
34 // will display a list of compatible installed client certificates, | |
35 // if any, or prompt the user to install one manually otherwise. | |
36 // | |
37 // - Also, the first time this API is called, the CertInstaller will | |
38 // first prompt the user to enter the secure storage's password | |
39 // (which is the user's PIN code / password by default). This establishes | |
40 // a trust relationship between the KeyChain system application, and | |
41 // the application calling the API. It persists until the application | |
42 // is killed. | |
43 // | |
44 // - The client certificate selection result is sent back to the | |
45 // application through a UI thread callback. It only contains a | |
46 // string alias for the selected certificate, or 'null' to indicate | |
47 // that the user has canceled the selection, or couldn't unlock | |
48 // access to the secure storage. | |
49 // | |
8 | 50 |
9 namespace chrome { | 51 namespace chrome { |
10 | 52 |
11 // Client Auth is not implemented on Android yet. | 53 using chrome::android::SSLClientCertificateRequest; |
54 | |
55 namespace { | |
56 | |
57 typedef net::OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY; | |
58 | |
59 // Helper class used to guarantee that a callback.Run(NULL) is called on | |
60 // scope exit, unless Release() was used before. | |
61 class CallbackGuard { | |
62 public: | |
63 explicit CallbackGuard(chrome::SelectCertificateCallback* callback) | |
64 : callback_(callback) { | |
65 } | |
66 | |
67 ~CallbackGuard() { | |
68 if (callback_) | |
69 base::ResetAndReturn(callback_).Run(NULL); | |
70 } | |
71 | |
72 void Release() { | |
73 callback_ = NULL; | |
74 } | |
75 | |
76 private: | |
77 chrome::SelectCertificateCallback* callback_; | |
78 | |
79 DISALLOW_COPY_AND_ASSIGN(CallbackGuard); | |
80 }; | |
81 | |
82 // Helper class used to pass a (certificate,private key) pair between | |
83 // threads. | |
84 class CertificateAndKey { | |
Ryan Sleevi
2013/03/05 18:02:41
Why is this a class and not a struct
digit1
2013/03/06 01:48:33
It used to require to be a class when if was refco
| |
85 public: | |
86 CertificateAndKey() { } | |
87 ~CertificateAndKey() { } | |
Ryan Sleevi
2013/03/05 18:02:41
no spaces in braces
| |
88 scoped_refptr<net::X509Certificate> client_cert; | |
89 ScopedEVP_PKEY private_key; | |
Ryan Sleevi
2013/03/05 18:02:41
It's still not clear to me why you need this class
digit1
2013/03/06 01:48:33
I've originally tried passing a ScopedEVP_PKEY dir
Ryan Sleevi
2013/03/06 01:55:29
You have to use base::Passed() here, which will cr
digit1
2013/03/06 21:34:11
thanks, this works and has been implemented in the
| |
90 private: | |
91 DISALLOW_COPY_AND_ASSIGN(CertificateAndKey); | |
92 }; | |
93 | |
94 // A SSLClientCertificateRequest used to pass the client | |
95 // certificate to the proper callback, after recording its private | |
96 // key into the net::OpenSSLClientKeyStore. | |
97 class Request : public SSLClientCertificateRequest { | |
98 public: | |
99 explicit Request(const chrome::SelectCertificateCallback& callback) | |
100 : callback_(callback) { | |
101 } | |
102 | |
103 virtual void OnCertificateSelected( | |
104 std::vector<base::StringPiece>* encoded_chain, | |
105 jobject private_key) OVERRIDE; | |
106 | |
107 protected: | |
108 ~Request() { } | |
109 | |
110 private: | |
111 Request(); | |
112 | |
113 // Called on the I/O thread to record a (certificate,private_key) pair | |
114 // into the net::OpenSSLClientKeyStore. | |
115 static void DoRecordClientCertificateKey( | |
116 scoped_ptr<CertificateAndKey> pair); | |
117 | |
118 chrome::SelectCertificateCallback callback_; | |
119 }; | |
120 | |
121 // Called on the UI thread once the user has selected a client certificate | |
122 // using the system-provided dialog, or failed to select one. | |
123 // |encoded_chain| is a pointer to the encoded client certificate chain. | |
124 // Each item in the list is a DER-encoded X.509 certificate. | |
125 // |private_key| is a pointer to a JNI local reference to the platform | |
126 // PrivateKey object for this client certificate chain. | |
127 void Request::OnCertificateSelected( | |
128 std::vector<base::StringPiece>* encoded_chain, | |
129 jobject private_key_ref) { | |
130 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
131 | |
132 // Ensure callback_.Run(NULL) is called on error exit. | |
133 CallbackGuard guard(&callback_); | |
134 | |
135 if (encoded_chain == NULL || private_key_ref == NULL) { | |
136 LOG(ERROR) << "Client certificate selection cancelled by the user"; | |
137 return; | |
138 } | |
139 | |
140 scoped_ptr<CertificateAndKey> pair(new CertificateAndKey); | |
141 | |
142 // Create the X509Certificate object from the encoded chain. | |
143 pair->client_cert = | |
144 net::X509Certificate::CreateFromDERCertChain(*encoded_chain); | |
145 if (!pair->client_cert.get()) { | |
146 LOG(ERROR) << "Could not decode client certificate chain"; | |
147 return; | |
148 } | |
149 | |
150 // Create an EVP_PKEY wrapper for the private key JNI reference. | |
151 pair->private_key.reset( | |
152 net::android::GetOpenSSLPrivateKeyWrapper(private_key_ref)); | |
153 if (!pair->private_key.get()) { | |
154 LOG(ERROR) << "Could not create OpenSSL wrapper for private key"; | |
155 return; | |
156 } | |
157 | |
158 guard.Release(); | |
159 | |
160 // DoRecordClientCertificateKey() must be called on the I/O thread, | |
161 // before the callback is called with the selected certificate on | |
162 // the UI thread. | |
163 // | |
164 // Note that the UI thread closure is computed first, because |pair| | |
165 // becomes NULL after the base::Passed() call, and the order of | |
166 // parameter evaluation is unspecified in C++. | |
167 base::Closure on_ui_thread = | |
168 base::Bind(callback_, pair->client_cert); | |
169 | |
170 content::BrowserThread::PostTaskAndReply( | |
171 content::BrowserThread::IO, | |
172 FROM_HERE, | |
173 base::Bind(&Request::DoRecordClientCertificateKey, | |
174 base::Passed(&pair)), | |
175 on_ui_thread); | |
176 } | |
177 | |
178 void Request::DoRecordClientCertificateKey( | |
179 scoped_ptr<CertificateAndKey> pair) { | |
180 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
181 net::OpenSSLClientKeyStore::GetInstance()->RecordClientCertPrivateKey( | |
182 pair->client_cert.get(), pair->private_key.get()); | |
183 } | |
184 | |
185 } // namespace | |
186 | |
12 void ShowSSLClientCertificateSelector( | 187 void ShowSSLClientCertificateSelector( |
13 content::WebContents* contents, | 188 content::WebContents* contents, |
14 const net::HttpNetworkSession* network_session, | 189 const net::HttpNetworkSession* network_session, |
15 net::SSLCertRequestInfo* cert_request_info, | 190 net::SSLCertRequestInfo* cert_request_info, |
16 const base::Callback<void(net::X509Certificate*)>& callback) { | 191 const chrome::SelectCertificateCallback& callback) { |
17 NOTIMPLEMENTED(); | 192 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
193 scoped_refptr<Request> request(new Request(callback)); | |
194 if (!request->Start(cert_request_info)) { | |
195 LOG(ERROR) << "Could not start client certificate request!"; | |
196 callback.Run(NULL); | |
Ryan Sleevi
2013/03/05 18:02:41
DESIGN: In general, we avoid recursing directly in
digit1
2013/03/06 01:48:33
This has been addressed in the last patch, thanks.
| |
197 } | |
18 } | 198 } |
19 | 199 |
20 } // namespace chrome | 200 } // namespace chrome |
OLD | NEW |