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 <algorithm> 20#include <string> 21#include <vector> 22 23#include <base/callback.h> 24#include <gtest/gtest.h> 25 26#include "shill/callbacks.h" 27#include "shill/mock_crypto_util_proxy.h" 28#include "shill/mock_event_dispatcher.h" 29#include "shill/mock_file_io.h" 30#include "shill/mock_process_manager.h" 31 32using base::Bind; 33using std::min; 34using std::string; 35using std::vector; 36using testing::AnyOf; 37using testing::DoAll; 38using testing::InSequence; 39using testing::Invoke; 40using testing::Mock; 41using testing::NotNull; 42using testing::Return; 43using testing::StrEq; 44using testing::WithoutArgs; 45using testing::_; 46 47namespace shill { 48 49namespace { 50 51const char kTestBSSID[] = "00:11:22:33:44:55"; 52const char kTestCertificate[] = "testcertgoeshere"; 53const char kTestData[] = "thisisthetestdata"; 54const char kTestDestinationUDN[] = "TEST1234-5678-ABCD"; 55const char kTestNonce[] = "abort abort abort"; 56const char kTestPublicKey[] = "YWJvcnQgYWJvcnQgYWJvcnQK"; 57const char kTestSerializedCommandMessage[] = 58 "Since we're not testing protocol buffer seriallization, and no data " 59 "actually makes it to a shim, we're safe to write whatever we want here."; 60const char kTestSerializedCommandResponse[] = 61 "Similarly, we never ask a protocol buffer to deserialize this string."; 62const char kTestSignedData[] = "Ynl0ZXMgYnl0ZXMgYnl0ZXMK"; 63const int kTestStdinFd = 9111; 64const int kTestStdoutFd = 9119; 65const pid_t kTestShimPid = 989898; 66 67} // namespace 68 69MATCHER_P(ErrorIsOfType, error_type, "") { 70 if (error_type != arg.type()) { 71 return false; 72 } 73 74 return true; 75} 76 77class CryptoUtilProxyTest : public testing::Test { 78 public: 79 CryptoUtilProxyTest() 80 : crypto_util_proxy_(&dispatcher_) { 81 test_ssid_.push_back(78); 82 test_ssid_.push_back(69); 83 test_ssid_.push_back(80); 84 test_ssid_.push_back(84); 85 test_ssid_.push_back(85); 86 test_ssid_.push_back(78); 87 test_ssid_.push_back(69); 88 } 89 90 virtual void SetUp() { 91 crypto_util_proxy_.process_manager_ = &process_manager_; 92 crypto_util_proxy_.file_io_ = &file_io_; 93 } 94 95 virtual void TearDown() { 96 // Note that |crypto_util_proxy_| needs its process manager reference in 97 // order not to segfault when it tries to kill any outstanding shims on 98 // shutdown. Thus we don't clear out those fields here, and we make sure 99 // to declare the proxy after mocks it consumes. 100 } 101 102 // TODO(quiche): Consider refactoring 103 // HandleStartInMinijailWithPipes, HandleStopProcess, and 104 // HandleUpdateExitCallback into a FakeProcessManager. b/24210150 105 pid_t HandleStartInMinijailWithPipes( 106 const tracked_objects::Location& /* spawn_source */, 107 const base::FilePath& /* program */, 108 vector<string> /* program_args */, 109 const std::string& /* run_as_user */, 110 const std::string& /* run_as_group */, 111 uint64_t /* capabilities_mask */, 112 const base::Callback<void(int)>& exit_callback, 113 int* stdin, 114 int* stdout, 115 int* /* stderr */) { 116 exit_callback_ = exit_callback; 117 *stdin = kTestStdinFd; 118 *stdout = kTestStdoutFd; 119 return kTestShimPid; 120 } 121 122 void StartAndCheckShim(const std::string& command, 123 const std::string& shim_stdin) { 124 InSequence seq; 125 // Delegate the start call to the real implementation just for this test. 126 EXPECT_CALL(crypto_util_proxy_, StartShimForCommand(_, _, _)) 127 .WillOnce(Invoke(&crypto_util_proxy_, 128 &MockCryptoUtilProxy::RealStartShimForCommand)); 129 // All shims should be spawned in a Minijail. 130 EXPECT_CALL( 131 process_manager_, 132 StartProcessInMinijailWithPipes( 133 _, // caller location 134 base::FilePath(CryptoUtilProxy::kCryptoUtilShimPath), 135 AnyOf( 136 vector<string>{CryptoUtilProxy::kCommandVerify}, 137 vector<string>{CryptoUtilProxy::kCommandEncrypt}), 138 "shill-crypto", 139 "shill-crypto", 140 0, // no capabilities required 141 _, // exit_callback 142 NotNull(), // stdin 143 NotNull(), // stdout 144 nullptr)) // stderr 145 .WillOnce(Invoke(this, 146 &CryptoUtilProxyTest::HandleStartInMinijailWithPipes)); 147 // We should always schedule a shim timeout callback. 148 EXPECT_CALL(dispatcher_, PostDelayedTask(_, _)); 149 // We don't allow file I/O to block. 150 EXPECT_CALL(file_io_, 151 SetFdNonBlocking(kTestStdinFd)) 152 .WillOnce(Return(0)); 153 EXPECT_CALL(file_io_, 154 SetFdNonBlocking(kTestStdoutFd)) 155 .WillOnce(Return(0)); 156 // We instead do file I/O through async callbacks registered with the event 157 // dispatcher. 158 EXPECT_CALL(dispatcher_, CreateInputHandler(_, _, _)).Times(1); 159 EXPECT_CALL(dispatcher_, CreateReadyHandler(_, _, _)).Times(1); 160 // The shim is left in flight, not killed. 161 EXPECT_CALL(process_manager_, StopProcess(_)).Times(0); 162 crypto_util_proxy_.StartShimForCommand( 163 command, shim_stdin, 164 Bind(&MockCryptoUtilProxy::TestResultHandlerCallback, 165 crypto_util_proxy_.base::SupportsWeakPtr<MockCryptoUtilProxy>:: 166 AsWeakPtr())); 167 EXPECT_EQ(shim_stdin, crypto_util_proxy_.input_buffer_); 168 EXPECT_TRUE(crypto_util_proxy_.output_buffer_.empty()); 169 EXPECT_EQ(crypto_util_proxy_.shim_pid_, kTestShimPid); 170 Mock::VerifyAndClearExpectations(&crypto_util_proxy_); 171 Mock::VerifyAndClearExpectations(&dispatcher_); 172 Mock::VerifyAndClearExpectations(&process_manager_); 173 } 174 175 void ExpectCleanup(const Error& expected_result) { 176 if (crypto_util_proxy_.shim_stdin_ > -1) { 177 EXPECT_CALL(file_io_, 178 Close(crypto_util_proxy_.shim_stdin_)).Times(1); 179 } 180 if (crypto_util_proxy_.shim_stdout_ > -1) { 181 EXPECT_CALL(file_io_, 182 Close(crypto_util_proxy_.shim_stdout_)).Times(1); 183 } 184 if (crypto_util_proxy_.shim_pid_) { 185 EXPECT_CALL(process_manager_, UpdateExitCallback(_, _)) 186 .Times(1) 187 .WillOnce(Invoke(this, 188 &CryptoUtilProxyTest::HandleUpdateExitCallback)); 189 EXPECT_CALL(process_manager_, StopProcess(crypto_util_proxy_.shim_pid_)) 190 .Times(1) 191 .WillOnce(Invoke(this, 192 &CryptoUtilProxyTest::HandleStopProcess)); 193 } 194 } 195 196 void AssertShimDead() { 197 EXPECT_FALSE(crypto_util_proxy_.shim_pid_); 198 } 199 200 bool HandleUpdateExitCallback(pid_t /*pid*/, 201 const base::Callback<void(int)>& new_callback) { 202 exit_callback_ = new_callback; 203 return true; 204 } 205 206 bool HandleStopProcess(pid_t /*pid*/) { 207 const int kExitStatus = -1; 208 // NB: in the real world, this ordering is not guaranteed. That 209 // is, StopProcess() might return before executing the callback. 210 exit_callback_.Run(kExitStatus); 211 return true; 212 } 213 214 void StopAndCheckShim(const Error& error) { 215 ExpectCleanup(error); 216 crypto_util_proxy_.CleanupShim(error); 217 crypto_util_proxy_.OnShimDeath(-1); 218 EXPECT_EQ(crypto_util_proxy_.shim_pid_, 0); 219 Mock::VerifyAndClearExpectations(&process_manager_); 220 } 221 222 protected: 223 MockProcessManager process_manager_; 224 MockEventDispatcher dispatcher_; 225 MockFileIO file_io_; 226 MockCryptoUtilProxy crypto_util_proxy_; 227 std::vector<uint8_t> test_ssid_; 228 base::Callback<void(int)> exit_callback_; 229}; 230 231TEST_F(CryptoUtilProxyTest, BasicAPIUsage) { 232 { 233 InSequence seq; 234 // Delegate the API call to the real implementation for this test. 235 EXPECT_CALL(crypto_util_proxy_, 236 VerifyDestination(_, _, _, _, _, _, _, _, _)) 237 .WillOnce(Invoke(&crypto_util_proxy_, 238 &MockCryptoUtilProxy::RealVerifyDestination)); 239 // API calls are just thin wrappers that write up a message to a shim, then 240 // send it via StartShimForCommand. Expect that a shim will be started in 241 // response to the API being called. 242 EXPECT_CALL(crypto_util_proxy_, 243 StartShimForCommand(CryptoUtilProxy::kCommandVerify, _, _)) 244 .WillOnce(Return(true)); 245 ResultBoolCallback result_callback = 246 Bind(&MockCryptoUtilProxy::TestResultBoolCallback, 247 crypto_util_proxy_. 248 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr()); 249 Error error; 250 EXPECT_TRUE(crypto_util_proxy_.VerifyDestination(kTestCertificate, 251 kTestPublicKey, 252 kTestNonce, 253 kTestSignedData, 254 kTestDestinationUDN, 255 test_ssid_, 256 kTestBSSID, 257 result_callback, 258 &error)); 259 EXPECT_TRUE(error.IsSuccess()); 260 } 261 { 262 // And very similarly... 263 InSequence seq; 264 EXPECT_CALL(crypto_util_proxy_, EncryptData(_, _, _, _)) 265 .WillOnce(Invoke(&crypto_util_proxy_, 266 &MockCryptoUtilProxy::RealEncryptData)); 267 EXPECT_CALL(crypto_util_proxy_, 268 StartShimForCommand(CryptoUtilProxy::kCommandEncrypt, _, _)) 269 .WillOnce(Return(true)); 270 ResultStringCallback result_callback = 271 Bind(&MockCryptoUtilProxy::TestResultStringCallback, 272 crypto_util_proxy_. 273 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr()); 274 Error error; 275 // Normally, we couldn't have these two operations run successfully without 276 // finishing the first one, since only one shim can be in flight at a time. 277 // However, this works because we didn't actually start a shim, we just 278 // trapped the call in our mock. 279 EXPECT_TRUE(crypto_util_proxy_.EncryptData(kTestPublicKey, kTestData, 280 result_callback, &error)); 281 EXPECT_TRUE(error.IsSuccess()); 282 } 283} 284 285TEST_F(CryptoUtilProxyTest, ShimCleanedBeforeCallback) { 286 // Some operations, like VerifyAndEncryptData in the manager, chain two 287 // shim operations together. Make sure that we don't call back with results 288 // before the shim state is clean. 289 { 290 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, 291 kTestSerializedCommandMessage); 292 Error e(Error::kOperationFailed); 293 ExpectCleanup(e); 294 EXPECT_CALL(crypto_util_proxy_, 295 TestResultHandlerCallback( 296 StrEq(""), ErrorIsOfType(Error::kOperationFailed))) 297 .Times(1) 298 .WillOnce(WithoutArgs(Invoke(this, 299 &CryptoUtilProxyTest::AssertShimDead))); 300 crypto_util_proxy_.HandleShimError(e); 301 } 302 { 303 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, 304 kTestSerializedCommandMessage); 305 EXPECT_CALL(crypto_util_proxy_, 306 TestResultHandlerCallback( 307 StrEq(""), ErrorIsOfType(Error::kSuccess))) 308 .Times(1) 309 .WillOnce(WithoutArgs(Invoke(this, 310 &CryptoUtilProxyTest::AssertShimDead))); 311 ExpectCleanup(Error(Error::kSuccess)); 312 InputData data; 313 data.buf = nullptr; 314 data.len = 0; 315 crypto_util_proxy_.HandleShimOutput(&data); 316 } 317} 318 319// Verify that even when we have errors, we'll call the result handler. 320// Ultimately, this is supposed to make sure that we always return something to 321// our callers over DBus. 322TEST_F(CryptoUtilProxyTest, FailuresReturnValues) { 323 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, 324 kTestSerializedCommandMessage); 325 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback( 326 StrEq(""), ErrorIsOfType(Error::kOperationFailed))).Times(1); 327 Error e(Error::kOperationFailed); 328 ExpectCleanup(e); 329 crypto_util_proxy_.HandleShimError(e); 330} 331 332TEST_F(CryptoUtilProxyTest, TimeoutsTriggerFailure) { 333 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, 334 kTestSerializedCommandMessage); 335 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback( 336 StrEq(""), ErrorIsOfType(Error::kOperationTimeout))).Times(1); 337 ExpectCleanup(Error(Error::kOperationTimeout)); 338 // This timeout is scheduled by StartShimForCommand. 339 crypto_util_proxy_.HandleShimTimeout(); 340} 341 342TEST_F(CryptoUtilProxyTest, OnlyOneInstanceInFlightAtATime) { 343 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, 344 kTestSerializedCommandMessage); 345 // Can't start things twice. 346 EXPECT_FALSE(crypto_util_proxy_.RealStartShimForCommand( 347 CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage, 348 Bind(&MockCryptoUtilProxy::TestResultHandlerCallback, 349 crypto_util_proxy_. 350 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr()))); 351 // But if some error (or completion) caused us to clean up the shim... 352 StopAndCheckShim(Error(Error::kSuccess)); 353 // Then we could start the shim again. 354 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, 355 kTestSerializedCommandMessage); 356 // Clean up after ourselves. 357 StopAndCheckShim(Error(Error::kOperationFailed)); 358} 359 360// This test walks the CryptoUtilProxy through the life time of a shim by 361// simulating the API call, file I/O operations, and the final handler on shim 362// completion. 363TEST_F(CryptoUtilProxyTest, ShimLifeTime) { 364 const int kBytesAtATime = 10; 365 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, 366 kTestSerializedCommandMessage); 367 // Emulate the operating system pulling bytes through the pipe, and the event 368 // loop notifying us that the file descriptor is ready. 369 int bytes_left = strlen(kTestSerializedCommandMessage); 370 while (bytes_left > 0) { 371 int bytes_written = min(kBytesAtATime, bytes_left); 372 EXPECT_CALL(file_io_, Write(kTestStdinFd, _, bytes_left)) 373 .Times(1).WillOnce(Return(bytes_written)); 374 bytes_left -= bytes_written; 375 if (bytes_left < 1) { 376 EXPECT_CALL(file_io_, Close(kTestStdinFd)); 377 } 378 crypto_util_proxy_.HandleShimStdinReady(crypto_util_proxy_.shim_stdin_); 379 Mock::VerifyAndClearExpectations(&crypto_util_proxy_); 380 } 381 382 // At this point, the shim goes off and does terribly complex crypto stuff, 383 // before responding with a string of bytes over stdout. Emulate the shim 384 // and the event loop to push those bytes back. 385 const int response_length = bytes_left = 386 strlen(kTestSerializedCommandResponse); 387 InputData data; 388 while (bytes_left > 0) { 389 int bytes_written = min(kBytesAtATime, bytes_left); 390 data.len = bytes_written; 391 data.buf = reinterpret_cast<unsigned char*>(const_cast<char*>( 392 kTestSerializedCommandResponse + response_length - bytes_left)); 393 bytes_left -= bytes_written; 394 crypto_util_proxy_.HandleShimOutput(&data); 395 } 396 // Write 0 bytes in to signify the end of the stream. This should in turn 397 // cause our callback to be called. 398 data.len = 0; 399 data.buf = nullptr; 400 EXPECT_CALL( 401 crypto_util_proxy_, 402 TestResultHandlerCallback(string(kTestSerializedCommandResponse), 403 ErrorIsOfType(Error::kSuccess))).Times(1); 404 ExpectCleanup(Error(Error::kSuccess)); 405 crypto_util_proxy_.HandleShimOutput(&data); 406} 407 408} // namespace shill 409