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