blob: a1992802b44801a04ec905572f2ad3d7529b19b3 [file] [log] [blame]
Christopher Wiley5a3f23a2013-02-20 17:29:57 -08001// Copyright (c) 2013 The Chromium OS 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 "shill/crypto_util_proxy.h"
6
7#include <iterator>
8#include <string>
9#include <vector>
10
11#include <base/posix/eintr_wrapper.h>
Christopher Wileyc2c22ca2013-04-19 11:56:05 -070012#include <base/string_util.h>
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080013#include <base/stringprintf.h>
14
15#include "shill/event_dispatcher.h"
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080016#include "shill/file_io.h"
Christopher Wiley5447d2e2013-03-19 17:46:03 -070017#include "shill/glib.h"
18#include "shill/process_killer.h"
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080019
20using base::Bind;
21using base::Callback;
22using base::StringPrintf;
23using shill_protos::EncryptDataMessage;
24using shill_protos::EncryptDataResponse;
25using shill_protos::VerifyCredentialsMessage;
26using shill_protos::VerifyCredentialsResponse;
27using std::distance;
28using std::string;
29using std::vector;
30
31namespace shill {
32
33// statics
34const char CryptoUtilProxy::kCommandEncrypt[] = "encrypt";
35const char CryptoUtilProxy::kCommandVerify[] = "verify";
36const char CryptoUtilProxy::kCryptoUtilShimPath[] = SHIMDIR "/crypto-util";
Christopher Wiley0d05c112013-03-19 17:49:38 -070037const char CryptoUtilProxy::kDestinationVerificationUser[] = "shill-crypto";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080038const int CryptoUtilProxy::kShimJobTimeoutMilliseconds = 30 * 1000;
39
Christopher Wiley5447d2e2013-03-19 17:46:03 -070040CryptoUtilProxy::CryptoUtilProxy(EventDispatcher *dispatcher, GLib *glib)
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080041 : dispatcher_(dispatcher),
Christopher Wiley5447d2e2013-03-19 17:46:03 -070042 glib_(glib),
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080043 minijail_(Minijail::GetInstance()),
44 process_killer_(ProcessKiller::GetInstance()),
45 file_io_(FileIO::GetInstance()),
46 input_buffer_(),
47 next_input_byte_(),
48 output_buffer_(),
49 shim_stdin_(-1),
50 shim_stdout_(-1),
51 shim_pid_(0) {
52}
53
54CryptoUtilProxy::~CryptoUtilProxy() {
55 // Just in case we had a pending operation.
Christopher Wiley67e425e2013-05-02 15:54:51 -070056 HandleShimError(Error(Error::kOperationAborted));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080057}
58
59bool CryptoUtilProxy::VerifyDestination(
60 const string &certificate,
61 const string &public_key,
62 const string &nonce,
63 const string &signed_data,
64 const string &destination_udn,
65 const vector<uint8_t> &ssid,
66 const string &bssid,
67 const ResultBoolCallback &result_callback,
68 Error *error) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080069 string unsigned_data(reinterpret_cast<const char *>(&ssid[0]),
70 ssid.size());
Christopher Wileyc2c22ca2013-04-19 11:56:05 -070071 string upper_case_bssid(StringToUpperASCII(bssid));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080072 unsigned_data.append(StringPrintf(",%s,%s,%s,%s",
73 destination_udn.c_str(),
Christopher Wileyc2c22ca2013-04-19 11:56:05 -070074 upper_case_bssid.c_str(),
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080075 public_key.c_str(),
76 nonce.c_str()));
Christopher Wiley5447d2e2013-03-19 17:46:03 -070077 string decoded_signed_data;
78 if (!glib_->B64Decode(signed_data, &decoded_signed_data)) {
79 Error::PopulateAndLog(error, Error::kOperationFailed,
80 "Failed to decode signed data.");
81 return false;
82 }
83
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080084 VerifyCredentialsMessage message;
85 message.set_certificate(certificate);
Christopher Wiley5447d2e2013-03-19 17:46:03 -070086 message.set_signed_data(decoded_signed_data);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080087 message.set_unsigned_data(unsigned_data);
Christopher Wileyeb7d7362013-03-12 12:00:43 -070088 message.set_mac_address(bssid);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080089
90 string raw_bytes;
91 if (!message.SerializeToString(&raw_bytes)) {
92 Error::PopulateAndLog(error, Error::kOperationFailed,
93 "Failed to send arguments to shim.");
94 return false;
95 }
96 StringCallback wrapped_result_handler = Bind(
97 &CryptoUtilProxy::HandleVerifyResult,
98 AsWeakPtr(), result_callback);
99 if (!StartShimForCommand(kCommandVerify, raw_bytes,
100 wrapped_result_handler)) {
101 Error::PopulateAndLog(error, Error::kOperationFailed,
102 "Failed to start shim to verify credentials.");
103 return false;
104 }
105 LOG(INFO) << "Started credential verification";
106 return true;
107}
108
109bool CryptoUtilProxy::EncryptData(
110 const string &public_key,
111 const string &data,
112 const ResultStringCallback &result_callback,
113 Error *error) {
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700114 string decoded_public_key;
115 if (!glib_->B64Decode(public_key, &decoded_public_key)) {
116 Error::PopulateAndLog(error, Error::kOperationFailed,
117 "Unable to decode public key.");
118 return false;
119 }
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800120
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700121 EncryptDataMessage message;
122 message.set_public_key(decoded_public_key);
123 message.set_data(data);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800124 string raw_bytes;
125 if (!message.SerializeToString(&raw_bytes)) {
126 Error::PopulateAndLog(error, Error::kOperationFailed,
127 "Failed to send arguments to shim.");
128 return false;
129 }
130 StringCallback wrapped_result_handler = Bind(
131 &CryptoUtilProxy::HandleEncryptResult,
132 AsWeakPtr(), result_callback);
133 if (!StartShimForCommand(kCommandEncrypt, raw_bytes,
134 wrapped_result_handler)) {
135 Error::PopulateAndLog(error, Error::kOperationFailed,
136 "Failed to start shim to verify credentials.");
137 return false;
138 }
139 LOG(INFO) << "Started data signing";
140 return true;
141}
142
143bool CryptoUtilProxy::StartShimForCommand(
144 const string &command,
145 const string &input,
146 const StringCallback &result_handler) {
147 if (shim_pid_) {
148 LOG(ERROR) << "Can't run concurrent shim operations.";
149 return false;
150 }
151 if (input.length() < 1) {
152 LOG(ERROR) << "Refusing to start a shim with no input data.";
153 return false;
154 }
155 struct minijail * jail = minijail_->New();
156 if (!minijail_->DropRoot(jail, kDestinationVerificationUser)) {
157 LOG(ERROR) << "Minijail failed to drop root priviledges?";
158 return false;
159 }
160 vector<char *> args;
161 args.push_back(const_cast<char *>(kCryptoUtilShimPath));
162 args.push_back(const_cast<char *>(command.c_str()));
Christopher Wiley956400a2013-04-05 10:47:25 -0700163 args.push_back(NULL);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800164 if (!minijail_->RunPipesAndDestroy(jail, args, &shim_pid_,
165 &shim_stdin_, &shim_stdout_, NULL)) {
166 LOG(ERROR) << "Minijail couldn't run our child process";
167 return false;
168 }
169 // Invariant: if the shim process could be in flight, shim_pid_ != 0 and we
170 // have a callback scheduled to kill the shim process.
171 input_buffer_ = input;
172 next_input_byte_ = input_buffer_.begin();
173 output_buffer_.clear();
174 result_handler_ = result_handler;
175 shim_job_timeout_callback_.Reset(Bind(&CryptoUtilProxy::HandleShimTimeout,
176 AsWeakPtr()));
177 dispatcher_->PostDelayedTask(shim_job_timeout_callback_.callback(),
178 kShimJobTimeoutMilliseconds);
179 do {
180 if (file_io_->SetFdNonBlocking(shim_stdin_) ||
181 file_io_->SetFdNonBlocking(shim_stdout_)) {
182 LOG(ERROR) << "Unable to set shim pipes to be non blocking.";
183 break;
184 }
185 shim_stdout_handler_.reset(dispatcher_->CreateInputHandler(
186 shim_stdout_,
187 Bind(&CryptoUtilProxy::HandleShimOutput, AsWeakPtr()),
188 Bind(&CryptoUtilProxy::HandleShimError, AsWeakPtr())));
189 shim_stdin_handler_.reset(dispatcher_->CreateReadyHandler(
190 shim_stdin_,
191 IOHandler::kModeOutput,
192 Bind(&CryptoUtilProxy::HandleShimStdinReady, AsWeakPtr())));
193 LOG(INFO) << "Started crypto-util shim at " << shim_pid_;
194 return true;
195 } while (false);
196 // We've started a shim, but failed to set up the plumbing to communicate
197 // with it. Since we can't go forward, go backward and clean it up.
Christopher Wiley67e425e2013-05-02 15:54:51 -0700198 // Kill the callback, since we're signalling failure by returning false.
199 result_handler_.Reset();
200 HandleShimError(Error(Error::kOperationAborted));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800201 return false;
202}
203
Christopher Wiley67e425e2013-05-02 15:54:51 -0700204void CryptoUtilProxy::CleanupShim(const Error &shim_result) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800205 LOG(INFO) << __func__;
Christopher Wiley67e425e2013-05-02 15:54:51 -0700206 shim_result_.CopyFrom(shim_result);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800207 if (shim_stdin_ > -1) {
208 file_io_->Close(shim_stdin_);
209 shim_stdin_ = -1;
210 }
211 if (shim_stdout_ > -1) {
212 file_io_->Close(shim_stdout_);
213 shim_stdout_ = -1;
214 }
Christopher Wiley67e425e2013-05-02 15:54:51 -0700215 // Leave the output buffer so that we use it with the result handler.
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800216 input_buffer_.clear();
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800217
218 shim_stdout_handler_.reset();
219 shim_stdin_handler_.reset();
220
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800221 // TODO(wiley) Change dhcp_config.cc to use the process killer. Change the
222 // process killer to send TERM before KILL a la dhcp_config.cc.
223 if (shim_pid_) {
224 process_killer_->Kill(shim_pid_, Bind(&CryptoUtilProxy::OnShimDeath,
225 AsWeakPtr()));
226 } else {
227 OnShimDeath();
228 }
229}
230
231void CryptoUtilProxy::OnShimDeath() {
Christopher Wiley67e425e2013-05-02 15:54:51 -0700232 // Make sure the proxy is completely clean before calling back out. This
233 // requires we copy some state locally.
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800234 shim_pid_ = 0;
235 shim_job_timeout_callback_.Cancel();
Christopher Wiley67e425e2013-05-02 15:54:51 -0700236 StringCallback handler(result_handler_);
237 result_handler_.Reset();
238 string output(output_buffer_);
239 output_buffer_.clear();
240 Error result;
241 result.CopyFrom(shim_result_);
242 shim_result_.Reset();
243 if (!handler.is_null()) {
244 handler.Run(output, result);
245 }
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800246}
247
248void CryptoUtilProxy::HandleShimStdinReady(int fd) {
249 CHECK(fd == shim_stdin_);
250 CHECK(shim_pid_);
251 size_t bytes_to_write = distance<string::const_iterator>(next_input_byte_,
252 input_buffer_.end());
253 ssize_t bytes_written = file_io_->Write(shim_stdin_,
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700254 &(*next_input_byte_),
255 bytes_to_write);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800256 if (bytes_written < 0) {
257 HandleShimError(Error(Error::kOperationFailed,
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700258 "Failed to write any bytes to output buffer"));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800259 return;
260 }
261 next_input_byte_ += bytes_written;
262 if (next_input_byte_ == input_buffer_.end()) {
Christopher Wiley956400a2013-04-05 10:47:25 -0700263 LOG(INFO) << "Finished writing output buffer to shim.";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800264 // Done writing out the proto buffer, close the pipe so that the shim
265 // knows that's all there is. Close our handler first.
266 shim_stdin_handler_.reset();
267 file_io_->Close(shim_stdin_);
268 shim_stdin_ = -1;
269 input_buffer_.clear();
270 next_input_byte_ = input_buffer_.begin();
271 }
272}
273
274void CryptoUtilProxy::HandleShimOutput(InputData *data) {
275 CHECK(shim_pid_);
276 CHECK(!result_handler_.is_null());
277 if (data->len > 0) {
278 // Everyone is shipping features and I'm just here copying bytes from one
279 // buffer to another.
280 output_buffer_.append(reinterpret_cast<char *>(data->buf), data->len);
281 return;
282 }
283 // EOF -> we're done!
Christopher Wiley956400a2013-04-05 10:47:25 -0700284 LOG(INFO) << "Finished reading " << output_buffer_.length()
285 << " bytes from shim.";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800286 shim_stdout_handler_.reset();
287 file_io_->Close(shim_stdout_);
288 shim_stdout_ = -1;
289 Error no_error;
Christopher Wiley67e425e2013-05-02 15:54:51 -0700290 CleanupShim(no_error);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800291}
292
293void CryptoUtilProxy::HandleShimError(const Error &error) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800294 // Abort abort abort. There is very little we can do here.
Christopher Wiley67e425e2013-05-02 15:54:51 -0700295 output_buffer_.clear();
296 CleanupShim(error);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800297}
298
299void CryptoUtilProxy::HandleShimTimeout() {
300 Error e(Error::kOperationTimeout);
301 HandleShimError(e);
302}
303
304void CryptoUtilProxy::HandleVerifyResult(
305 const ResultBoolCallback &result_handler,
306 const std::string &result,
307 const Error &error) {
308 if (!error.IsSuccess()) {
309 result_handler.Run(error, false);
310 return;
311 }
312 VerifyCredentialsResponse response;
313 Error e;
314
315 if (!response.ParseFromString(result) || !response.has_ret()) {
316 e.Populate(Error::kInternalError, "Failed parsing shim result.");
317 result_handler.Run(e, false);
318 return;
319 }
320
321 result_handler.Run(e, ParseResponseReturnCode(response.ret(), &e));
322}
323
324// static
325bool CryptoUtilProxy::ParseResponseReturnCode(int proto_return_code,
326 Error *e) {
327 bool success = false;
328 switch (proto_return_code) {
329 case shill_protos::OK:
330 success = true;
331 break;
332 case shill_protos::ERROR_UNKNOWN:
333 e->Populate(Error::kInternalError, "Internal shim error.");
334 break;
335 case shill_protos::ERROR_OUT_OF_MEMORY:
336 e->Populate(Error::kInternalError, "Shim is out of memory.");
337 break;
338 case shill_protos::ERROR_CRYPTO_OPERATION_FAILED:
339 e->Populate(Error::kOperationFailed, "Invalid credentials.");
340 break;
341 case shill_protos::ERROR_INVALID_ARGUMENTS:
342 e->Populate(Error::kInvalidArguments, "Invalid arguments.");
343 break;
344 default:
345 e->Populate(Error::kInternalError, "Unknown error.");
346 break;
347 }
348 return success;
349}
350
351void CryptoUtilProxy::HandleEncryptResult(
352 const ResultStringCallback &result_handler,
353 const std::string &result,
354 const Error &error) {
355 if (!error.IsSuccess()) {
356 result_handler.Run(error, "");
357 return;
358 }
359 EncryptDataResponse response;
360 Error e;
361
362 if (!response.ParseFromString(result) || !response.has_ret()) {
363 e.Populate(Error::kInternalError, "Failed parsing shim result.");
364 result_handler.Run(e, "");
365 return;
366 }
367
368 if (!ParseResponseReturnCode(response.ret(), &e)) {
369 result_handler.Run(e, "");
370 return;
371 }
372
373 if (!response.has_encrypted_data() ||
374 response.encrypted_data().empty()) {
375 e.Populate(Error::kInternalError,
376 "Shim returned successfully, but included no encrypted data.");
377 result_handler.Run(e, "");
378 return;
379 }
380
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700381 string encoded_data;
382 if (!glib_->B64Encode(response.encrypted_data(), &encoded_data)) {
383 e.Populate(Error::kInternalError, "Failed to encode result.");
384 result_handler.Run(e, "");
385 return;
386 }
387
388 result_handler.Run(e, encoded_data);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800389}
390
391} // namespace shill