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