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