blob: 35928ff87daacd4b90ae169b582548e8f42237b4 [file] [log] [blame]
Christopher Wiley5a3f23a2013-02-20 17:29:57 -08001// 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 <algorithm>
6#include <string>
7#include <vector>
8
9#include <base/callback.h>
Utkarsh Sanghi83bd64b2014-07-29 16:01:43 -070010#include <chromeos/minijail/minijail.h>
11#include <chromeos/minijail/mock_minijail.h>
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080012#include <gtest/gtest.h>
13
14#include "shill/callbacks.h"
15#include "shill/crypto_util_proxy.h"
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080016#include "shill/mock_crypto_util_proxy.h"
17#include "shill/mock_event_dispatcher.h"
18#include "shill/mock_file_io.h"
Alex Vakulenkoa41ab512014-07-23 14:24:23 -070019#include "shill/mock_glib.h"
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080020#include "shill/mock_process_killer.h"
21
22using base::Bind;
Utkarsh Sanghi83bd64b2014-07-29 16:01:43 -070023using chromeos::MockMinijail;
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080024using 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;
Christopher Wileyb3e70d22013-04-26 17:28:37 -070034using testing::WithoutArgs;
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080035using testing::_;
36
37namespace shill {
38
39namespace {
Ben Chanbe0849f2014-08-15 23:02:32 -070040
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[] = "bytes bytes bytes";
53const int kTestStdinFd = 9111;
54const int kTestStdoutFd = 9119;
55const pid_t kTestShimPid = 989898;
56
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080057} // namespace
58
59MATCHER_P(IsCryptoUtilCommandLine, command, "") {
Christopher Wiley956400a2013-04-05 10:47:25 -070060 if (arg.size() != 3) {
61 LOG(ERROR) << "Expected 3 command line arguments, but got "
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080062 << 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
Christopher Wiley956400a2013-04-05 10:47:25 -070075 if (arg[2] != NULL) {
76 return false;
77 }
78
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080079 return true;
Alex Vakulenko8a532292014-06-16 17:18:44 -070080}
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080081
82MATCHER_P(ErrorIsOfType, error_type, "") {
83 if (error_type != arg.type()) {
84 return false;
85 }
86
87 return true;
Alex Vakulenko8a532292014-06-16 17:18:44 -070088}
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080089
90class CryptoUtilProxyTest : public testing::Test {
91 public:
92 CryptoUtilProxyTest()
Christopher Wiley5447d2e2013-03-19 17:46:03 -070093 : crypto_util_proxy_(&dispatcher_, &glib_) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080094 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) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800127 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());
Utkarsh Sanghie4c6aff2014-07-30 14:49:03 -0700134 EXPECT_CALL(minijail_, DropRoot(_, StrEq("shill-crypto"),
135 StrEq("shill-crypto")))
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800136 .WillOnce(Return(true));
137 EXPECT_CALL(minijail_, RunPipesAndDestroy(_,
138 IsCryptoUtilCommandLine(command),
Alex Vakulenko8a532292014-06-16 17:18:44 -0700139 NotNull(), // pid
140 NotNull(), // stdin
141 NotNull(), // stdout
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800142 NULL)) // 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
Christopher Wiley67e425e2013-05-02 15:54:51 -0700173 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 }
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800182 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
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700190 void AssertShimDead() {
191 EXPECT_FALSE(crypto_util_proxy_.shim_pid_);
192 }
193
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800194 void HandleShimKill(int /*pid*/, const base::Closure &callback) {
195 callback.Run();
196 }
197
Christopher Wiley67e425e2013-05-02 15:54:51 -0700198 void StopAndCheckShim(const Error &error) {
199 ExpectCleanup(error);
200 crypto_util_proxy_.CleanupShim(error);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800201 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_;
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700210 MockGLib glib_;
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800211 MockFileIO file_io_;
212 MockCryptoUtilProxy crypto_util_proxy_;
213 std::vector<uint8_t> test_ssid_;
214};
215
216TEST_F(CryptoUtilProxyTest, BasicAPIUsage) {
217 {
218 InSequence seq;
219 // Delegate the API call to the real implementation for this test.
220 EXPECT_CALL(crypto_util_proxy_,
221 VerifyDestination(_, _, _, _, _, _, _, _, _))
222 .WillOnce(Invoke(&crypto_util_proxy_,
223 &MockCryptoUtilProxy::RealVerifyDestination));
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700224 EXPECT_CALL(glib_, B64Decode(StrEq(kTestSignedData), _))
225 .WillOnce(Return(true));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800226 // API calls are just thin wrappers that write up a message to a shim, then
227 // send it via StartShimForCommand. Expect that a shim will be started in
228 // response to the API being called.
229 EXPECT_CALL(crypto_util_proxy_,
230 StartShimForCommand(CryptoUtilProxy::kCommandVerify, _, _))
231 .WillOnce(Return(true));
232 ResultBoolCallback result_callback =
233 Bind(&MockCryptoUtilProxy::TestResultBoolCallback,
234 crypto_util_proxy_.
235 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr());
236 Error error;
237 EXPECT_TRUE(crypto_util_proxy_.VerifyDestination(kTestCertificate,
238 kTestPublicKey,
239 kTestNonce,
240 kTestSignedData,
241 kTestDestinationUDN,
242 test_ssid_,
243 kTestBSSID,
244 result_callback,
245 &error));
246 EXPECT_TRUE(error.IsSuccess());
247 }
248 {
249 // And very similarly...
250 InSequence seq;
251 EXPECT_CALL(crypto_util_proxy_, EncryptData(_, _, _, _))
252 .WillOnce(Invoke(&crypto_util_proxy_,
253 &MockCryptoUtilProxy::RealEncryptData));
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700254 EXPECT_CALL(glib_, B64Decode(StrEq(kTestPublicKey), _))
255 .WillOnce(Return(true));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800256 EXPECT_CALL(crypto_util_proxy_,
257 StartShimForCommand(CryptoUtilProxy::kCommandEncrypt, _, _))
258 .WillOnce(Return(true));
259 ResultStringCallback result_callback =
260 Bind(&MockCryptoUtilProxy::TestResultStringCallback,
261 crypto_util_proxy_.
262 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr());
263 Error error;
264 // Normally, we couldn't have these two operations run successfully without
265 // finishing the first one, since only one shim can be in flight at a time.
266 // However, this works because we didn't actually start a shim, we just
267 // trapped the call in our mock.
268 EXPECT_TRUE(crypto_util_proxy_.EncryptData(kTestPublicKey, kTestData,
269 result_callback, &error));
270 EXPECT_TRUE(error.IsSuccess());
271 }
272}
273
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700274TEST_F(CryptoUtilProxyTest, ShimCleanedBeforeCallback) {
275 // Some operations, like VerifyAndEncryptData in the manager, chain two
276 // shim operations together. Make sure that we don't call back with results
277 // before the shim state is clean.
278 {
279 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
280 kTestSerializedCommandMessage);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700281 Error e(Error::kOperationFailed);
282 ExpectCleanup(e);
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700283 EXPECT_CALL(crypto_util_proxy_,
284 TestResultHandlerCallback(
285 StrEq(""), ErrorIsOfType(Error::kOperationFailed)))
286 .Times(1)
287 .WillOnce(WithoutArgs(Invoke(this,
288 &CryptoUtilProxyTest::AssertShimDead)));
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700289 crypto_util_proxy_.HandleShimError(e);
290 }
291 {
292 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
293 kTestSerializedCommandMessage);
294 EXPECT_CALL(crypto_util_proxy_,
295 TestResultHandlerCallback(
296 StrEq(""), ErrorIsOfType(Error::kSuccess)))
297 .Times(1)
298 .WillOnce(WithoutArgs(Invoke(this,
299 &CryptoUtilProxyTest::AssertShimDead)));
Christopher Wiley67e425e2013-05-02 15:54:51 -0700300 ExpectCleanup(Error(Error::kSuccess));
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700301 InputData data;
302 data.buf = NULL;
303 data.len = 0;
304 crypto_util_proxy_.HandleShimOutput(&data);
305 }
306}
307
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800308// Verify that even when we have errors, we'll call the result handler.
309// Ultimately, this is supposed to make sure that we always return something to
310// our callers over DBus.
311TEST_F(CryptoUtilProxyTest, FailuresReturnValues) {
312 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
313 kTestSerializedCommandMessage);
314 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback(
315 StrEq(""), ErrorIsOfType(Error::kOperationFailed))).Times(1);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800316 Error e(Error::kOperationFailed);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700317 ExpectCleanup(e);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800318 crypto_util_proxy_.HandleShimError(e);
319}
320
321TEST_F(CryptoUtilProxyTest, TimeoutsTriggerFailure) {
322 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
323 kTestSerializedCommandMessage);
324 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback(
325 StrEq(""), ErrorIsOfType(Error::kOperationTimeout))).Times(1);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700326 ExpectCleanup(Error(Error::kOperationTimeout));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800327 // This timeout is scheduled by StartShimForCommand.
328 crypto_util_proxy_.HandleShimTimeout();
329}
330
331TEST_F(CryptoUtilProxyTest, OnlyOneInstanceInFlightAtATime) {
332 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
333 kTestSerializedCommandMessage);
334 // Can't start things twice.
335 EXPECT_FALSE(crypto_util_proxy_.RealStartShimForCommand(
336 CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage,
337 Bind(&MockCryptoUtilProxy::TestResultHandlerCallback,
338 crypto_util_proxy_.
339 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr())));
340 // But if some error (or completion) caused us to clean up the shim...
Christopher Wiley67e425e2013-05-02 15:54:51 -0700341 StopAndCheckShim(Error(Error::kSuccess));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800342 // Then we could start the shim again.
343 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
344 kTestSerializedCommandMessage);
345 // Clean up after ourselves.
Christopher Wiley67e425e2013-05-02 15:54:51 -0700346 StopAndCheckShim(Error(Error::kOperationFailed));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800347}
348
349// This test walks the CryptoUtilProxy through the life time of a shim by
350// simulating the API call, file I/O operations, and the final handler on shim
351// completion.
352TEST_F(CryptoUtilProxyTest, ShimLifeTime) {
353 const int kBytesAtATime = 10;
354 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
355 kTestSerializedCommandMessage);
356 // Emulate the operating system pulling bytes through the pipe, and the event
357 // loop notifying us that the file descriptor is ready.
358 int bytes_left = strlen(kTestSerializedCommandMessage);
359 while (bytes_left > 0) {
360 int bytes_written = min(kBytesAtATime, bytes_left);
361 EXPECT_CALL(file_io_, Write(kTestStdinFd, _, bytes_left))
362 .Times(1).WillOnce(Return(bytes_written));
363 bytes_left -= bytes_written;
364 if (bytes_left < 1) {
365 EXPECT_CALL(file_io_, Close(kTestStdinFd));
366 }
367 crypto_util_proxy_.HandleShimStdinReady(crypto_util_proxy_.shim_stdin_);
368 Mock::VerifyAndClearExpectations(&crypto_util_proxy_);
369 }
370
371 // At this point, the shim goes off and does terribly complex crypto stuff,
372 // before responding with a string of bytes over stdout. Emulate the shim
373 // and the event loop to push those bytes back.
374 const int response_length = bytes_left =
375 strlen(kTestSerializedCommandResponse);
376 InputData data;
377 while (bytes_left > 0) {
378 int bytes_written = min(kBytesAtATime, bytes_left);
379 data.len = bytes_written;
380 data.buf = reinterpret_cast<unsigned char *>(const_cast<char *>(
381 kTestSerializedCommandResponse + response_length - bytes_left));
382 bytes_left -= bytes_written;
383 crypto_util_proxy_.HandleShimOutput(&data);
384 }
385 // Write 0 bytes in to signify the end of the stream. This should in turn
386 // cause our callback to be called.
387 data.len = 0;
388 data.buf = NULL;
389 EXPECT_CALL(
390 crypto_util_proxy_,
391 TestResultHandlerCallback(string(kTestSerializedCommandResponse),
392 ErrorIsOfType(Error::kSuccess))).Times(1);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700393 ExpectCleanup(Error(Error::kSuccess));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800394 crypto_util_proxy_.HandleShimOutput(&data);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800395}
396
397} // namespace shill