blob: c14d384ab5b9e2e01a838f058cbadcae74bf320b [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>
Ben Chana0ddf462014-02-06 11:32:42 -080012#include <base/strings/string_util.h>
13#include <base/strings/stringprintf.h>
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080014
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
Paul Stewarta794cd62015-06-16 13:13:10 -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),
Utkarsh Sanghi83bd64b2014-07-29 16:01:43 -070043 minijail_(chromeos::Minijail::GetInstance()),
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080044 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(
Paul Stewarta794cd62015-06-16 13:13:10 -070060 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) {
69 string unsigned_data(reinterpret_cast<const char*>(&ssid[0]),
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080070 ssid.size());
Alex Vakulenkoccab3f92015-06-15 12:53:22 -070071 string upper_case_bssid(base::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)) {
Paul Stewart34f424e2015-01-16 15:30:20 -080079 Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
Christopher Wiley5447d2e2013-03-19 17:46:03 -070080 "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)) {
Paul Stewart34f424e2015-01-16 15:30:20 -080092 Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080093 "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)) {
Paul Stewart34f424e2015-01-16 15:30:20 -0800101 Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800102 "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(
Paul Stewarta794cd62015-06-16 13:13:10 -0700110 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)) {
Paul Stewart34f424e2015-01-16 15:30:20 -0800116 Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700117 "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)) {
Paul Stewart34f424e2015-01-16 15:30:20 -0800126 Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800127 "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)) {
Paul Stewart34f424e2015-01-16 15:30:20 -0800135 Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800136 "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(
Paul Stewarta794cd62015-06-16 13:13:10 -0700144 const string& command,
145 const string& input,
146 const StringCallback& result_handler) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800147 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 }
Paul Stewarta794cd62015-06-16 13:13:10 -0700155 struct minijail* jail = minijail_->New();
Utkarsh Sanghie4c6aff2014-07-30 14:49:03 -0700156 if (!minijail_->DropRoot(jail, kDestinationVerificationUser,
157 kDestinationVerificationUser)) {
158 LOG(ERROR) << "Minijail failed to drop root privileges?";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800159 return false;
160 }
Paul Stewarta794cd62015-06-16 13:13:10 -0700161 vector<char*> args;
162 args.push_back(const_cast<char*>(kCryptoUtilShimPath));
163 args.push_back(const_cast<char*>(command.c_str()));
Ben Chancc225ef2014-09-30 13:26:51 -0700164 args.push_back(nullptr);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800165 if (!minijail_->RunPipesAndDestroy(jail, args, &shim_pid_,
Ben Chancc225ef2014-09-30 13:26:51 -0700166 &shim_stdin_, &shim_stdout_, nullptr)) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800167 LOG(ERROR) << "Minijail couldn't run our child process";
168 return false;
169 }
170 // Invariant: if the shim process could be in flight, shim_pid_ != 0 and we
171 // have a callback scheduled to kill the shim process.
172 input_buffer_ = input;
173 next_input_byte_ = input_buffer_.begin();
174 output_buffer_.clear();
175 result_handler_ = result_handler;
176 shim_job_timeout_callback_.Reset(Bind(&CryptoUtilProxy::HandleShimTimeout,
177 AsWeakPtr()));
178 dispatcher_->PostDelayedTask(shim_job_timeout_callback_.callback(),
179 kShimJobTimeoutMilliseconds);
180 do {
181 if (file_io_->SetFdNonBlocking(shim_stdin_) ||
182 file_io_->SetFdNonBlocking(shim_stdout_)) {
183 LOG(ERROR) << "Unable to set shim pipes to be non blocking.";
184 break;
185 }
186 shim_stdout_handler_.reset(dispatcher_->CreateInputHandler(
187 shim_stdout_,
188 Bind(&CryptoUtilProxy::HandleShimOutput, AsWeakPtr()),
Peter Qiu3161caa2014-10-29 09:47:22 -0700189 Bind(&CryptoUtilProxy::HandleShimReadError, AsWeakPtr())));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800190 shim_stdin_handler_.reset(dispatcher_->CreateReadyHandler(
191 shim_stdin_,
192 IOHandler::kModeOutput,
193 Bind(&CryptoUtilProxy::HandleShimStdinReady, AsWeakPtr())));
194 LOG(INFO) << "Started crypto-util shim at " << shim_pid_;
195 return true;
196 } while (false);
197 // We've started a shim, but failed to set up the plumbing to communicate
198 // with it. Since we can't go forward, go backward and clean it up.
Christopher Wiley67e425e2013-05-02 15:54:51 -0700199 // Kill the callback, since we're signalling failure by returning false.
200 result_handler_.Reset();
201 HandleShimError(Error(Error::kOperationAborted));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800202 return false;
203}
204
Paul Stewarta794cd62015-06-16 13:13:10 -0700205void CryptoUtilProxy::CleanupShim(const Error& shim_result) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800206 LOG(INFO) << __func__;
Christopher Wiley67e425e2013-05-02 15:54:51 -0700207 shim_result_.CopyFrom(shim_result);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800208 if (shim_stdin_ > -1) {
209 file_io_->Close(shim_stdin_);
210 shim_stdin_ = -1;
211 }
212 if (shim_stdout_ > -1) {
213 file_io_->Close(shim_stdout_);
214 shim_stdout_ = -1;
215 }
Christopher Wiley67e425e2013-05-02 15:54:51 -0700216 // Leave the output buffer so that we use it with the result handler.
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800217 input_buffer_.clear();
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800218
219 shim_stdout_handler_.reset();
220 shim_stdin_handler_.reset();
221
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800222 // TODO(wiley) Change dhcp_config.cc to use the process killer. Change the
223 // process killer to send TERM before KILL a la dhcp_config.cc.
224 if (shim_pid_) {
225 process_killer_->Kill(shim_pid_, Bind(&CryptoUtilProxy::OnShimDeath,
226 AsWeakPtr()));
227 } else {
228 OnShimDeath();
229 }
230}
231
232void CryptoUtilProxy::OnShimDeath() {
Christopher Wiley67e425e2013-05-02 15:54:51 -0700233 // Make sure the proxy is completely clean before calling back out. This
234 // requires we copy some state locally.
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800235 shim_pid_ = 0;
236 shim_job_timeout_callback_.Cancel();
Christopher Wiley67e425e2013-05-02 15:54:51 -0700237 StringCallback handler(result_handler_);
238 result_handler_.Reset();
239 string output(output_buffer_);
240 output_buffer_.clear();
241 Error result;
242 result.CopyFrom(shim_result_);
243 shim_result_.Reset();
244 if (!handler.is_null()) {
245 handler.Run(output, result);
246 }
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800247}
248
249void CryptoUtilProxy::HandleShimStdinReady(int fd) {
250 CHECK(fd == shim_stdin_);
251 CHECK(shim_pid_);
252 size_t bytes_to_write = distance<string::const_iterator>(next_input_byte_,
253 input_buffer_.end());
254 ssize_t bytes_written = file_io_->Write(shim_stdin_,
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700255 &(*next_input_byte_),
256 bytes_to_write);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800257 if (bytes_written < 0) {
258 HandleShimError(Error(Error::kOperationFailed,
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700259 "Failed to write any bytes to output buffer"));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800260 return;
261 }
262 next_input_byte_ += bytes_written;
263 if (next_input_byte_ == input_buffer_.end()) {
Christopher Wiley956400a2013-04-05 10:47:25 -0700264 LOG(INFO) << "Finished writing output buffer to shim.";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800265 // Done writing out the proto buffer, close the pipe so that the shim
266 // knows that's all there is. Close our handler first.
267 shim_stdin_handler_.reset();
268 file_io_->Close(shim_stdin_);
269 shim_stdin_ = -1;
270 input_buffer_.clear();
271 next_input_byte_ = input_buffer_.begin();
272 }
273}
274
Paul Stewarta794cd62015-06-16 13:13:10 -0700275void CryptoUtilProxy::HandleShimOutput(InputData* data) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800276 CHECK(shim_pid_);
277 CHECK(!result_handler_.is_null());
278 if (data->len > 0) {
279 // Everyone is shipping features and I'm just here copying bytes from one
280 // buffer to another.
Paul Stewarta794cd62015-06-16 13:13:10 -0700281 output_buffer_.append(reinterpret_cast<char*>(data->buf), data->len);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800282 return;
283 }
284 // EOF -> we're done!
Christopher Wiley956400a2013-04-05 10:47:25 -0700285 LOG(INFO) << "Finished reading " << output_buffer_.length()
286 << " bytes from shim.";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800287 shim_stdout_handler_.reset();
288 file_io_->Close(shim_stdout_);
289 shim_stdout_ = -1;
290 Error no_error;
Christopher Wiley67e425e2013-05-02 15:54:51 -0700291 CleanupShim(no_error);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800292}
293
Paul Stewarta794cd62015-06-16 13:13:10 -0700294void CryptoUtilProxy::HandleShimError(const Error& error) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800295 // Abort abort abort. There is very little we can do here.
Christopher Wiley67e425e2013-05-02 15:54:51 -0700296 output_buffer_.clear();
297 CleanupShim(error);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800298}
299
Paul Stewarta794cd62015-06-16 13:13:10 -0700300void CryptoUtilProxy::HandleShimReadError(const string& error_msg) {
Peter Qiu3161caa2014-10-29 09:47:22 -0700301 Error e(Error::kOperationFailed, error_msg);
302 HandleShimError(e);
303}
304
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800305void CryptoUtilProxy::HandleShimTimeout() {
306 Error e(Error::kOperationTimeout);
307 HandleShimError(e);
308}
309
310void CryptoUtilProxy::HandleVerifyResult(
Paul Stewarta794cd62015-06-16 13:13:10 -0700311 const ResultBoolCallback& result_handler,
312 const std::string& result,
313 const Error& error) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800314 if (!error.IsSuccess()) {
315 result_handler.Run(error, false);
316 return;
317 }
318 VerifyCredentialsResponse response;
319 Error e;
320
321 if (!response.ParseFromString(result) || !response.has_ret()) {
322 e.Populate(Error::kInternalError, "Failed parsing shim result.");
323 result_handler.Run(e, false);
324 return;
325 }
326
327 result_handler.Run(e, ParseResponseReturnCode(response.ret(), &e));
328}
329
330// static
331bool CryptoUtilProxy::ParseResponseReturnCode(int proto_return_code,
Paul Stewarta794cd62015-06-16 13:13:10 -0700332 Error* e) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800333 bool success = false;
334 switch (proto_return_code) {
335 case shill_protos::OK:
336 success = true;
337 break;
338 case shill_protos::ERROR_UNKNOWN:
339 e->Populate(Error::kInternalError, "Internal shim error.");
340 break;
341 case shill_protos::ERROR_OUT_OF_MEMORY:
342 e->Populate(Error::kInternalError, "Shim is out of memory.");
343 break;
344 case shill_protos::ERROR_CRYPTO_OPERATION_FAILED:
345 e->Populate(Error::kOperationFailed, "Invalid credentials.");
346 break;
347 case shill_protos::ERROR_INVALID_ARGUMENTS:
348 e->Populate(Error::kInvalidArguments, "Invalid arguments.");
349 break;
350 default:
351 e->Populate(Error::kInternalError, "Unknown error.");
352 break;
353 }
354 return success;
355}
356
357void CryptoUtilProxy::HandleEncryptResult(
Paul Stewarta794cd62015-06-16 13:13:10 -0700358 const ResultStringCallback& result_handler,
359 const std::string& result,
360 const Error& error) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800361 if (!error.IsSuccess()) {
362 result_handler.Run(error, "");
363 return;
364 }
365 EncryptDataResponse response;
366 Error e;
367
368 if (!response.ParseFromString(result) || !response.has_ret()) {
369 e.Populate(Error::kInternalError, "Failed parsing shim result.");
370 result_handler.Run(e, "");
371 return;
372 }
373
374 if (!ParseResponseReturnCode(response.ret(), &e)) {
375 result_handler.Run(e, "");
376 return;
377 }
378
379 if (!response.has_encrypted_data() ||
380 response.encrypted_data().empty()) {
381 e.Populate(Error::kInternalError,
382 "Shim returned successfully, but included no encrypted data.");
383 result_handler.Run(e, "");
384 return;
385 }
386
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700387 string encoded_data;
388 if (!glib_->B64Encode(response.encrypted_data(), &encoded_data)) {
389 e.Populate(Error::kInternalError, "Failed to encode result.");
390 result_handler.Run(e, "");
391 return;
392 }
393
394 result_handler.Run(e, encoded_data);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800395}
396
397} // namespace shill