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