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