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 {
DoNothingWithExitStatus(int)55 void DoNothingWithExitStatus(int /* exit_status */) {
56 }
57 }  // namespace
58 
CryptoUtilProxy(EventDispatcher * dispatcher)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 
~CryptoUtilProxy()71 CryptoUtilProxy::~CryptoUtilProxy() {
72   // Just in case we had a pending operation.
73   HandleShimError(Error(Error::kOperationAborted));
74 }
75 
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)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 
EncryptData(const string & public_key,const string & data,const ResultStringCallback & result_callback,Error * error)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 
StartShimForCommand(const string & command,const string & input,const StringCallback & result_handler)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 
CleanupShim(const Error & shim_result)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 
OnShimDeath(int)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 
HandleShimStdinReady(int fd)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 
HandleShimOutput(InputData * data)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 
HandleShimError(const Error & error)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 
HandleShimReadError(const string & error_msg)318 void CryptoUtilProxy::HandleShimReadError(const string& error_msg) {
319   Error e(Error::kOperationFailed, error_msg);
320   HandleShimError(e);
321 }
322 
HandleShimTimeout()323 void CryptoUtilProxy::HandleShimTimeout() {
324   Error e(Error::kOperationTimeout);
325   HandleShimError(e);
326 }
327 
HandleVerifyResult(const ResultBoolCallback & result_handler,const std::string & result,const Error & error)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
ParseResponseReturnCode(int proto_return_code,Error * e)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 
HandleEncryptResult(const ResultStringCallback & result_handler,const std::string & result,const Error & error)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