• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
32 using base::Bind;
33 using std::min;
34 using std::string;
35 using std::vector;
36 using testing::AnyOf;
37 using testing::DoAll;
38 using testing::InSequence;
39 using testing::Invoke;
40 using testing::Mock;
41 using testing::NotNull;
42 using testing::Return;
43 using testing::StrEq;
44 using testing::WithoutArgs;
45 using testing::_;
46 
47 namespace shill {
48 
49 namespace {
50 
51 const char kTestBSSID[] = "00:11:22:33:44:55";
52 const char kTestCertificate[] = "testcertgoeshere";
53 const char kTestData[] = "thisisthetestdata";
54 const char kTestDestinationUDN[] = "TEST1234-5678-ABCD";
55 const char kTestNonce[] = "abort abort abort";
56 const char kTestPublicKey[] = "YWJvcnQgYWJvcnQgYWJvcnQK";
57 const 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.";
60 const char kTestSerializedCommandResponse[] =
61     "Similarly, we never ask a protocol buffer to deserialize this string.";
62 const char kTestSignedData[] = "Ynl0ZXMgYnl0ZXMgYnl0ZXMK";
63 const int kTestStdinFd = 9111;
64 const int kTestStdoutFd = 9119;
65 const pid_t kTestShimPid = 989898;
66 
67 }  // namespace
68 
69 MATCHER_P(ErrorIsOfType, error_type, "") {
70   if (error_type != arg.type()) {
71     return false;
72   }
73 
74   return true;
75 }
76 
77 class CryptoUtilProxyTest : public testing::Test {
78  public:
CryptoUtilProxyTest()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 
SetUp()90   virtual void SetUp() {
91     crypto_util_proxy_.process_manager_ = &process_manager_;
92     crypto_util_proxy_.file_io_ = &file_io_;
93   }
94 
TearDown()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
HandleStartInMinijailWithPipes(const tracked_objects::Location &,const base::FilePath &,vector<string>,const std::string &,const std::string &,uint64_t,const base::Callback<void (int)> & exit_callback,int * stdin,int * stdout,int *)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 
StartAndCheckShim(const std::string & command,const std::string & shim_stdin)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 
ExpectCleanup(const Error & expected_result)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 
AssertShimDead()196   void AssertShimDead() {
197     EXPECT_FALSE(crypto_util_proxy_.shim_pid_);
198   }
199 
HandleUpdateExitCallback(pid_t,const base::Callback<void (int)> & new_callback)200   bool HandleUpdateExitCallback(pid_t /*pid*/,
201                                 const base::Callback<void(int)>& new_callback) {
202     exit_callback_ = new_callback;
203     return true;
204   }
205 
HandleStopProcess(pid_t)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 
StopAndCheckShim(const Error & error)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 
TEST_F(CryptoUtilProxyTest,BasicAPIUsage)231 TEST_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 
TEST_F(CryptoUtilProxyTest,ShimCleanedBeforeCallback)285 TEST_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.
TEST_F(CryptoUtilProxyTest,FailuresReturnValues)322 TEST_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 
TEST_F(CryptoUtilProxyTest,TimeoutsTriggerFailure)332 TEST_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 
TEST_F(CryptoUtilProxyTest,OnlyOneInstanceInFlightAtATime)342 TEST_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.
TEST_F(CryptoUtilProxyTest,ShimLifeTime)363 TEST_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