blob: 060a4bbaf038e9ce477182f8d0aa824799916708 [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/crypto_util_proxy.h"
#include <iterator>
#include <string>
#include <vector>
#include <base/posix/eintr_wrapper.h>
#include <base/stringprintf.h>
#include "shill/event_dispatcher.h"
#include "shill/process_killer.h"
#include "shill/file_io.h"
using base::Bind;
using base::Callback;
using base::StringPrintf;
using shill_protos::EncryptDataMessage;
using shill_protos::EncryptDataResponse;
using shill_protos::VerifyCredentialsMessage;
using shill_protos::VerifyCredentialsResponse;
using std::distance;
using std::string;
using std::vector;
namespace shill {
// statics
const char CryptoUtilProxy::kCommandEncrypt[] = "encrypt";
const char CryptoUtilProxy::kCommandVerify[] = "verify";
const char CryptoUtilProxy::kCryptoUtilShimPath[] = SHIMDIR "/crypto-util";
const char CryptoUtilProxy::kDestinationVerificationUser[] = "chronos";
const int CryptoUtilProxy::kShimJobTimeoutMilliseconds = 30 * 1000;
CryptoUtilProxy::CryptoUtilProxy(EventDispatcher *dispatcher)
: dispatcher_(dispatcher),
minijail_(Minijail::GetInstance()),
process_killer_(ProcessKiller::GetInstance()),
file_io_(FileIO::GetInstance()),
input_buffer_(),
next_input_byte_(),
output_buffer_(),
shim_stdin_(-1),
shim_stdout_(-1),
shim_pid_(0) {
}
CryptoUtilProxy::~CryptoUtilProxy() {
// Just in case we had a pending operation.
CleanupShim();
}
bool CryptoUtilProxy::VerifyDestination(
const string &certificate,
const string &public_key,
const string &nonce,
const string &signed_data,
const string &destination_udn,
const vector<uint8_t> &ssid,
const string &bssid,
const ResultBoolCallback &result_callback,
Error *error) {
// TODO(wiley) Check that the MAC address looks right.
string unsigned_data(reinterpret_cast<const char *>(&ssid[0]),
ssid.size());
unsigned_data.append(StringPrintf(",%s,%s,%s,%s",
destination_udn.c_str(),
bssid.c_str(),
public_key.c_str(),
nonce.c_str()));
VerifyCredentialsMessage message;
message.set_certificate(certificate);
message.set_signed_data(signed_data);
message.set_unsigned_data(unsigned_data);
message.set_mac_address(bssid);
string raw_bytes;
if (!message.SerializeToString(&raw_bytes)) {
Error::PopulateAndLog(error, Error::kOperationFailed,
"Failed to send arguments to shim.");
return false;
}
StringCallback wrapped_result_handler = Bind(
&CryptoUtilProxy::HandleVerifyResult,
AsWeakPtr(), result_callback);
if (!StartShimForCommand(kCommandVerify, raw_bytes,
wrapped_result_handler)) {
Error::PopulateAndLog(error, Error::kOperationFailed,
"Failed to start shim to verify credentials.");
return false;
}
LOG(INFO) << "Started credential verification";
return true;
}
bool CryptoUtilProxy::EncryptData(
const string &public_key,
const string &data,
const ResultStringCallback &result_callback,
Error *error) {
EncryptDataMessage message;
message.set_public_key(public_key);
message.set_data(data);
string raw_bytes;
if (!message.SerializeToString(&raw_bytes)) {
Error::PopulateAndLog(error, Error::kOperationFailed,
"Failed to send arguments to shim.");
return false;
}
StringCallback wrapped_result_handler = Bind(
&CryptoUtilProxy::HandleEncryptResult,
AsWeakPtr(), result_callback);
if (!StartShimForCommand(kCommandEncrypt, raw_bytes,
wrapped_result_handler)) {
Error::PopulateAndLog(error, Error::kOperationFailed,
"Failed to start shim to verify credentials.");
return false;
}
LOG(INFO) << "Started data signing";
return true;
}
bool CryptoUtilProxy::StartShimForCommand(
const string &command,
const string &input,
const StringCallback &result_handler) {
if (shim_pid_) {
LOG(ERROR) << "Can't run concurrent shim operations.";
return false;
}
if (input.length() < 1) {
LOG(ERROR) << "Refusing to start a shim with no input data.";
return false;
}
struct minijail * jail = minijail_->New();
if (!minijail_->DropRoot(jail, kDestinationVerificationUser)) {
LOG(ERROR) << "Minijail failed to drop root priviledges?";
return false;
}
vector<char *> args;
args.push_back(const_cast<char *>(kCryptoUtilShimPath));
args.push_back(const_cast<char *>(command.c_str()));
if (!minijail_->RunPipesAndDestroy(jail, args, &shim_pid_,
&shim_stdin_, &shim_stdout_, NULL)) {
LOG(ERROR) << "Minijail couldn't run our child process";
return false;
}
// Invariant: if the shim process could be in flight, shim_pid_ != 0 and we
// have a callback scheduled to kill the shim process.
input_buffer_ = input;
next_input_byte_ = input_buffer_.begin();
output_buffer_.clear();
result_handler_ = result_handler;
shim_job_timeout_callback_.Reset(Bind(&CryptoUtilProxy::HandleShimTimeout,
AsWeakPtr()));
dispatcher_->PostDelayedTask(shim_job_timeout_callback_.callback(),
kShimJobTimeoutMilliseconds);
do {
if (file_io_->SetFdNonBlocking(shim_stdin_) ||
file_io_->SetFdNonBlocking(shim_stdout_)) {
LOG(ERROR) << "Unable to set shim pipes to be non blocking.";
break;
}
shim_stdout_handler_.reset(dispatcher_->CreateInputHandler(
shim_stdout_,
Bind(&CryptoUtilProxy::HandleShimOutput, AsWeakPtr()),
Bind(&CryptoUtilProxy::HandleShimError, AsWeakPtr())));
shim_stdin_handler_.reset(dispatcher_->CreateReadyHandler(
shim_stdin_,
IOHandler::kModeOutput,
Bind(&CryptoUtilProxy::HandleShimStdinReady, AsWeakPtr())));
LOG(INFO) << "Started crypto-util shim at " << shim_pid_;
return true;
} while (false);
// We've started a shim, but failed to set up the plumbing to communicate
// with it. Since we can't go forward, go backward and clean it up.
CleanupShim();
return false;
}
void CryptoUtilProxy::CleanupShim() {
LOG(INFO) << __func__;
if (shim_stdin_ > -1) {
file_io_->Close(shim_stdin_);
shim_stdin_ = -1;
}
if (shim_stdout_ > -1) {
file_io_->Close(shim_stdout_);
shim_stdout_ = -1;
}
input_buffer_.clear();
output_buffer_.clear();
shim_stdout_handler_.reset();
shim_stdin_handler_.reset();
result_handler_ = StringCallback();
// TODO(wiley) Change dhcp_config.cc to use the process killer. Change the
// process killer to send TERM before KILL a la dhcp_config.cc.
if (shim_pid_) {
process_killer_->Kill(shim_pid_, Bind(&CryptoUtilProxy::OnShimDeath,
AsWeakPtr()));
} else {
OnShimDeath();
}
}
void CryptoUtilProxy::OnShimDeath() {
shim_pid_ = 0;
shim_job_timeout_callback_.Cancel();
}
void CryptoUtilProxy::HandleShimStdinReady(int fd) {
CHECK(fd == shim_stdin_);
CHECK(shim_pid_);
size_t bytes_to_write = distance<string::const_iterator>(next_input_byte_,
input_buffer_.end());
ssize_t bytes_written = file_io_->Write(shim_stdin_,
&(*next_input_byte_),
bytes_to_write);
if (bytes_written < 0) {
HandleShimError(Error(Error::kOperationFailed,
"Failed to write any bytes to output buffer"));
return;
}
next_input_byte_ += bytes_written;
if (next_input_byte_ == input_buffer_.end()) {
// Done writing out the proto buffer, close the pipe so that the shim
// knows that's all there is. Close our handler first.
shim_stdin_handler_.reset();
file_io_->Close(shim_stdin_);
shim_stdin_ = -1;
input_buffer_.clear();
next_input_byte_ = input_buffer_.begin();
}
}
void CryptoUtilProxy::HandleShimOutput(InputData *data) {
CHECK(shim_pid_);
CHECK(!result_handler_.is_null());
if (data->len > 0) {
// Everyone is shipping features and I'm just here copying bytes from one
// buffer to another.
output_buffer_.append(reinterpret_cast<char *>(data->buf), data->len);
return;
}
// EOF -> we're done!
shim_stdout_handler_.reset();
file_io_->Close(shim_stdout_);
shim_stdout_ = -1;
Error no_error;
result_handler_.Run(output_buffer_, no_error);
output_buffer_.clear();
CleanupShim();
}
void CryptoUtilProxy::HandleShimError(const Error &error) {
CHECK(shim_pid_);
CHECK(!result_handler_.is_null());
// Abort abort abort. There is very little we can do here.
result_handler_.Run("", error);
CleanupShim();
}
void CryptoUtilProxy::HandleShimTimeout() {
Error e(Error::kOperationTimeout);
HandleShimError(e);
}
void CryptoUtilProxy::HandleVerifyResult(
const ResultBoolCallback &result_handler,
const std::string &result,
const Error &error) {
if (!error.IsSuccess()) {
result_handler.Run(error, false);
return;
}
VerifyCredentialsResponse response;
Error e;
if (!response.ParseFromString(result) || !response.has_ret()) {
e.Populate(Error::kInternalError, "Failed parsing shim result.");
result_handler.Run(e, false);
return;
}
result_handler.Run(e, ParseResponseReturnCode(response.ret(), &e));
}
// static
bool CryptoUtilProxy::ParseResponseReturnCode(int proto_return_code,
Error *e) {
bool success = false;
switch (proto_return_code) {
case shill_protos::OK:
success = true;
break;
case shill_protos::ERROR_UNKNOWN:
e->Populate(Error::kInternalError, "Internal shim error.");
break;
case shill_protos::ERROR_OUT_OF_MEMORY:
e->Populate(Error::kInternalError, "Shim is out of memory.");
break;
case shill_protos::ERROR_CRYPTO_OPERATION_FAILED:
e->Populate(Error::kOperationFailed, "Invalid credentials.");
break;
case shill_protos::ERROR_INVALID_ARGUMENTS:
e->Populate(Error::kInvalidArguments, "Invalid arguments.");
break;
default:
e->Populate(Error::kInternalError, "Unknown error.");
break;
}
return success;
}
void CryptoUtilProxy::HandleEncryptResult(
const ResultStringCallback &result_handler,
const std::string &result,
const Error &error) {
if (!error.IsSuccess()) {
result_handler.Run(error, "");
return;
}
EncryptDataResponse response;
Error e;
if (!response.ParseFromString(result) || !response.has_ret()) {
e.Populate(Error::kInternalError, "Failed parsing shim result.");
result_handler.Run(e, "");
return;
}
if (!ParseResponseReturnCode(response.ret(), &e)) {
result_handler.Run(e, "");
return;
}
if (!response.has_encrypted_data() ||
response.encrypted_data().empty()) {
e.Populate(Error::kInternalError,
"Shim returned successfully, but included no encrypted data.");
result_handler.Run(e, "");
return;
}
result_handler.Run(e, response.encrypted_data());
}
} // namespace shill