Home | History | Annotate | Download | only in shill
      1 //
      2 // Copyright (C) 2013 The Android Open Source Project
      3 //
      4 // Licensed under the Apache License, Version 2.0 (the "License");
      5 // you may not use this file except in compliance with the License.
      6 // You may obtain a copy of the License at
      7 //
      8 //      http://www.apache.org/licenses/LICENSE-2.0
      9 //
     10 // Unless required by applicable law or agreed to in writing, software
     11 // distributed under the License is distributed on an "AS IS" BASIS,
     12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 // See the License for the specific language governing permissions and
     14 // limitations under the License.
     15 //
     16 
     17 #include "shill/crypto_util_proxy.h"
     18 
     19 #include <iterator>
     20 #include <string>
     21 #include <vector>
     22 
     23 #include <base/files/file_path.h>
     24 #include <base/posix/eintr_wrapper.h>
     25 #include <base/strings/string_util.h>
     26 #include <base/strings/stringprintf.h>
     27 #include <brillo/data_encoding.h>
     28 
     29 #include "shill/event_dispatcher.h"
     30 #include "shill/file_io.h"
     31 #include "shill/process_manager.h"
     32 
     33 using base::Bind;
     34 using base::Callback;
     35 using base::StringPrintf;
     36 using shill_protos::EncryptDataMessage;
     37 using shill_protos::EncryptDataResponse;
     38 using shill_protos::VerifyCredentialsMessage;
     39 using shill_protos::VerifyCredentialsResponse;
     40 using std::distance;
     41 using std::string;
     42 using std::vector;
     43 
     44 namespace shill {
     45 
     46 // statics
     47 const char CryptoUtilProxy::kCommandEncrypt[] = "encrypt";
     48 const char CryptoUtilProxy::kCommandVerify[] = "verify";
     49 const char CryptoUtilProxy::kCryptoUtilShimPath[] = SHIMDIR "/crypto-util";
     50 const char CryptoUtilProxy::kDestinationVerificationUser[] = "shill-crypto";
     51 const uint64_t CryptoUtilProxy::kRequiredCapabilities = 0;
     52 const int CryptoUtilProxy::kShimJobTimeoutMilliseconds = 30 * 1000;
     53 
     54 namespace {
     55 void DoNothingWithExitStatus(int /* exit_status */) {
     56 }
     57 }  // namespace
     58 
     59 CryptoUtilProxy::CryptoUtilProxy(EventDispatcher* dispatcher)
     60     : dispatcher_(dispatcher),
     61       process_manager_(ProcessManager::GetInstance()),
     62       file_io_(FileIO::GetInstance()),
     63       input_buffer_(),
     64       next_input_byte_(),
     65       output_buffer_(),
     66       shim_stdin_(-1),
     67       shim_stdout_(-1),
     68       shim_pid_(0) {
     69 }
     70 
     71 CryptoUtilProxy::~CryptoUtilProxy() {
     72   // Just in case we had a pending operation.
     73   HandleShimError(Error(Error::kOperationAborted));
     74 }
     75 
     76 bool CryptoUtilProxy::VerifyDestination(
     77     const string& certificate,
     78     const string& public_key,
     79     const string& nonce,
     80     const string& signed_data,
     81     const string& destination_udn,
     82     const vector<uint8_t>& ssid,
     83     const string& bssid,
     84     const ResultBoolCallback& result_callback,
     85     Error* error) {
     86   string unsigned_data(reinterpret_cast<const char*>(&ssid[0]),
     87                        ssid.size());
     88   string upper_case_bssid(base::ToUpperASCII(bssid));
     89   unsigned_data.append(StringPrintf(",%s,%s,%s,%s",
     90                                     destination_udn.c_str(),
     91                                     upper_case_bssid.c_str(),
     92                                     public_key.c_str(),
     93                                     nonce.c_str()));
     94   string decoded_signed_data;
     95   if (!brillo::data_encoding::Base64Decode(signed_data, &decoded_signed_data)) {
     96     Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
     97                           "Failed to decode signed data.");
     98     return false;
     99   }
    100 
    101   VerifyCredentialsMessage message;
    102   message.set_certificate(certificate);
    103   message.set_signed_data(decoded_signed_data);
    104   message.set_unsigned_data(unsigned_data);
    105   message.set_mac_address(bssid);
    106 
    107   string raw_bytes;
    108   if (!message.SerializeToString(&raw_bytes)) {
    109     Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
    110                           "Failed to send arguments to shim.");
    111     return false;
    112   }
    113   StringCallback wrapped_result_handler = Bind(
    114       &CryptoUtilProxy::HandleVerifyResult,
    115       AsWeakPtr(), result_callback);
    116   if (!StartShimForCommand(kCommandVerify, raw_bytes,
    117                            wrapped_result_handler)) {
    118     Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
    119                           "Failed to start shim to verify credentials.");
    120     return false;
    121   }
    122   LOG(INFO) << "Started credential verification";
    123   return true;
    124 }
    125 
    126 bool CryptoUtilProxy::EncryptData(
    127     const string& public_key,
    128     const string& data,
    129     const ResultStringCallback& result_callback,
    130     Error* error) {
    131   string decoded_public_key;
    132   if (!brillo::data_encoding::Base64Decode(public_key, &decoded_public_key)) {
    133     Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
    134                           "Unable to decode public key.");
    135     return false;
    136   }
    137 
    138   EncryptDataMessage message;
    139   message.set_public_key(decoded_public_key);
    140   message.set_data(data);
    141   string raw_bytes;
    142   if (!message.SerializeToString(&raw_bytes)) {
    143     Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
    144                           "Failed to send arguments to shim.");
    145     return false;
    146   }
    147   StringCallback wrapped_result_handler = Bind(
    148       &CryptoUtilProxy::HandleEncryptResult,
    149       AsWeakPtr(), result_callback);
    150   if (!StartShimForCommand(kCommandEncrypt, raw_bytes,
    151                            wrapped_result_handler)) {
    152     Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
    153                           "Failed to start shim to verify credentials.");
    154     return false;
    155   }
    156   LOG(INFO) << "Started data signing";
    157   return true;
    158 }
    159 
    160 bool CryptoUtilProxy::StartShimForCommand(
    161     const string& command,
    162     const string& input,
    163     const StringCallback& result_handler) {
    164   if (shim_pid_) {
    165     LOG(ERROR) << "Can't run concurrent shim operations.";
    166     return false;
    167   }
    168   if (input.length() < 1) {
    169     LOG(ERROR) << "Refusing to start a shim with no input data.";
    170     return false;
    171   }
    172   shim_pid_ = process_manager_->StartProcessInMinijailWithPipes(
    173       FROM_HERE,
    174       base::FilePath(kCryptoUtilShimPath),
    175       vector<string>{command},
    176       kDestinationVerificationUser,
    177       kDestinationVerificationUser,
    178       kRequiredCapabilities,
    179       base::Bind(DoNothingWithExitStatus),
    180       &shim_stdin_,
    181       &shim_stdout_,
    182       nullptr);
    183   if (shim_pid_ == -1) {
    184     LOG(ERROR) << "Minijail couldn't run our child process";
    185     return false;
    186   }
    187   // Invariant: if the shim process could be in flight, shim_pid_ != 0 and we
    188   // have a callback scheduled to kill the shim process.
    189   input_buffer_ = input;
    190   next_input_byte_ = input_buffer_.begin();
    191   output_buffer_.clear();
    192   result_handler_ = result_handler;
    193   shim_job_timeout_callback_.Reset(Bind(&CryptoUtilProxy::HandleShimTimeout,
    194                                         AsWeakPtr()));
    195   dispatcher_->PostDelayedTask(shim_job_timeout_callback_.callback(),
    196                                 kShimJobTimeoutMilliseconds);
    197   do {
    198     if (file_io_->SetFdNonBlocking(shim_stdin_) ||
    199         file_io_->SetFdNonBlocking(shim_stdout_)) {
    200       LOG(ERROR) << "Unable to set shim pipes to be non blocking.";
    201       break;
    202     }
    203     shim_stdout_handler_.reset(dispatcher_->CreateInputHandler(
    204         shim_stdout_,
    205         Bind(&CryptoUtilProxy::HandleShimOutput, AsWeakPtr()),
    206         Bind(&CryptoUtilProxy::HandleShimReadError, AsWeakPtr())));
    207     shim_stdin_handler_.reset(dispatcher_->CreateReadyHandler(
    208         shim_stdin_,
    209         IOHandler::kModeOutput,
    210         Bind(&CryptoUtilProxy::HandleShimStdinReady, AsWeakPtr())));
    211     LOG(INFO) << "Started crypto-util shim at " << shim_pid_;
    212     return true;
    213   } while (false);
    214   // We've started a shim, but failed to set up the plumbing to communicate
    215   // with it.  Since we can't go forward, go backward and clean it up.
    216   // Kill the callback, since we're signalling failure by returning false.
    217   result_handler_.Reset();
    218   HandleShimError(Error(Error::kOperationAborted));
    219   return false;
    220 }
    221 
    222 void CryptoUtilProxy::CleanupShim(const Error& shim_result) {
    223   LOG(INFO) << __func__;
    224   shim_result_.CopyFrom(shim_result);
    225   if (shim_stdin_ > -1) {
    226     file_io_->Close(shim_stdin_);
    227     shim_stdin_ = -1;
    228   }
    229   if (shim_stdout_ > -1) {
    230     file_io_->Close(shim_stdout_);
    231     shim_stdout_ = -1;
    232   }
    233   // Leave the output buffer so that we use it with the result handler.
    234   input_buffer_.clear();
    235 
    236   shim_stdout_handler_.reset();
    237   shim_stdin_handler_.reset();
    238 
    239   if (shim_pid_) {
    240     process_manager_->UpdateExitCallback(shim_pid_,
    241                                          Bind(&CryptoUtilProxy::OnShimDeath,
    242                                               AsWeakPtr()));
    243     process_manager_->StopProcess(shim_pid_);
    244   } else {
    245     const int kExitStatus = -1;
    246     OnShimDeath(kExitStatus);
    247   }
    248 }
    249 
    250 void CryptoUtilProxy::OnShimDeath(int /* exit_status */) {
    251   // Make sure the proxy is completely clean before calling back out.  This
    252   // requires we copy some state locally.
    253   shim_pid_ = 0;
    254   shim_job_timeout_callback_.Cancel();
    255   StringCallback handler(result_handler_);
    256   result_handler_.Reset();
    257   string output(output_buffer_);
    258   output_buffer_.clear();
    259   Error result;
    260   result.CopyFrom(shim_result_);
    261   shim_result_.Reset();
    262   if (!handler.is_null()) {
    263     handler.Run(output, result);
    264   }
    265 }
    266 
    267 void CryptoUtilProxy::HandleShimStdinReady(int fd) {
    268   CHECK(fd == shim_stdin_);
    269   CHECK(shim_pid_);
    270   size_t bytes_to_write = distance<string::const_iterator>(next_input_byte_,
    271                                                            input_buffer_.end());
    272   ssize_t bytes_written = file_io_->Write(shim_stdin_,
    273                                           &(*next_input_byte_),
    274                                           bytes_to_write);
    275   if (bytes_written < 0) {
    276     HandleShimError(Error(Error::kOperationFailed,
    277                           "Failed to write any bytes to output buffer"));
    278     return;
    279   }
    280   next_input_byte_ += bytes_written;
    281   if (next_input_byte_ == input_buffer_.end()) {
    282     LOG(INFO) << "Finished writing output buffer to shim.";
    283     // Done writing out the proto buffer, close the pipe so that the shim
    284     // knows that's all there is.  Close our handler first.
    285     shim_stdin_handler_.reset();
    286     file_io_->Close(shim_stdin_);
    287     shim_stdin_ = -1;
    288     input_buffer_.clear();
    289     next_input_byte_ = input_buffer_.begin();
    290   }
    291 }
    292 
    293 void CryptoUtilProxy::HandleShimOutput(InputData* data) {
    294   CHECK(shim_pid_);
    295   CHECK(!result_handler_.is_null());
    296   if (data->len > 0) {
    297     // Everyone is shipping features and I'm just here copying bytes from one
    298     // buffer to another.
    299     output_buffer_.append(reinterpret_cast<char*>(data->buf), data->len);
    300     return;
    301   }
    302   // EOF -> we're done!
    303   LOG(INFO) << "Finished reading " << output_buffer_.length()
    304             << " bytes from shim.";
    305   shim_stdout_handler_.reset();
    306   file_io_->Close(shim_stdout_);
    307   shim_stdout_ = -1;
    308   Error no_error;
    309   CleanupShim(no_error);
    310 }
    311 
    312 void CryptoUtilProxy::HandleShimError(const Error& error) {
    313   // Abort abort abort.  There is very little we can do here.
    314   output_buffer_.clear();
    315   CleanupShim(error);
    316 }
    317 
    318 void CryptoUtilProxy::HandleShimReadError(const string& error_msg) {
    319   Error e(Error::kOperationFailed, error_msg);
    320   HandleShimError(e);
    321 }
    322 
    323 void CryptoUtilProxy::HandleShimTimeout() {
    324   Error e(Error::kOperationTimeout);
    325   HandleShimError(e);
    326 }
    327 
    328 void CryptoUtilProxy::HandleVerifyResult(
    329     const ResultBoolCallback& result_handler,
    330     const std::string& result,
    331     const Error& error) {
    332   if (!error.IsSuccess()) {
    333     result_handler.Run(error, false);
    334     return;
    335   }
    336   VerifyCredentialsResponse response;
    337   Error e;
    338 
    339   if (!response.ParseFromString(result) || !response.has_ret()) {
    340     e.Populate(Error::kInternalError, "Failed parsing shim result.");
    341     result_handler.Run(e, false);
    342     return;
    343   }
    344 
    345   result_handler.Run(e, ParseResponseReturnCode(response.ret(), &e));
    346 }
    347 
    348 // static
    349 bool CryptoUtilProxy::ParseResponseReturnCode(int proto_return_code,
    350                                               Error* e) {
    351   bool success = false;
    352   switch (proto_return_code) {
    353   case shill_protos::OK:
    354     success = true;
    355     break;
    356   case shill_protos::ERROR_UNKNOWN:
    357     e->Populate(Error::kInternalError, "Internal shim error.");
    358     break;
    359   case shill_protos::ERROR_OUT_OF_MEMORY:
    360     e->Populate(Error::kInternalError, "Shim is out of memory.");
    361     break;
    362   case shill_protos::ERROR_CRYPTO_OPERATION_FAILED:
    363     e->Populate(Error::kOperationFailed, "Invalid credentials.");
    364     break;
    365   case shill_protos::ERROR_INVALID_ARGUMENTS:
    366     e->Populate(Error::kInvalidArguments, "Invalid arguments.");
    367     break;
    368   default:
    369     e->Populate(Error::kInternalError, "Unknown error.");
    370     break;
    371   }
    372   return success;
    373 }
    374 
    375 void CryptoUtilProxy::HandleEncryptResult(
    376     const ResultStringCallback& result_handler,
    377     const std::string& result,
    378     const Error& error) {
    379   if (!error.IsSuccess()) {
    380     result_handler.Run(error, "");
    381     return;
    382   }
    383   EncryptDataResponse response;
    384   Error e;
    385 
    386   if (!response.ParseFromString(result) || !response.has_ret()) {
    387     e.Populate(Error::kInternalError, "Failed parsing shim result.");
    388     result_handler.Run(e, "");
    389     return;
    390   }
    391 
    392   if (!ParseResponseReturnCode(response.ret(), &e)) {
    393     result_handler.Run(e, "");
    394     return;
    395   }
    396 
    397   if (!response.has_encrypted_data() ||
    398       response.encrypted_data().empty()) {
    399     e.Populate(Error::kInternalError,
    400                "Shim returned successfully, but included no encrypted data.");
    401     result_handler.Run(e, "");
    402     return;
    403   }
    404 
    405   string encoded_data(
    406       brillo::data_encoding::Base64Encode(response.encrypted_data()));
    407   result_handler.Run(e, encoded_data);
    408 }
    409 
    410 }  // namespace shill
    411