blob: 40a35b1760a838499264052eedd0e75af1e075c5 [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>
10#include <gtest/gtest.h>
11
12#include "shill/callbacks.h"
13#include "shill/crypto_util_proxy.h"
14#include "shill/minijail.h"
15#include "shill/mock_crypto_util_proxy.h"
16#include "shill/mock_event_dispatcher.h"
Christopher Wiley5447d2e2013-03-19 17:46:03 -070017#include "shill/mock_glib.h"
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080018#include "shill/mock_file_io.h"
19#include "shill/mock_minijail.h"
20#include "shill/mock_process_killer.h"
21
22using base::Bind;
23using std::min;
24using std::string;
25using std::vector;
26using testing::DoAll;
27using testing::InSequence;
28using testing::Invoke;
29using testing::Mock;
30using testing::NotNull;
31using testing::Return;
32using testing::StrEq;
Christopher Wileyb3e70d22013-04-26 17:28:37 -070033using testing::WithoutArgs;
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080034using testing::_;
35
36namespace shill {
37
38namespace {
39 static const char kTestBSSID[] = "00:11:22:33:44:55";
40 static const char kTestCertificate[] = "testcertgoeshere";
41 static const char kTestData[] = "thisisthetestdata";
42 static const char kTestDestinationUDN[] = "TEST1234-5678-ABCD";
43 static const char kTestNonce[] = "abort abort abort";
Christopher Wiley5447d2e2013-03-19 17:46:03 -070044 static const char kTestPublicKey[] = "YWJvcnQgYWJvcnQgYWJvcnQK";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080045 static const char kTestSSID[] = "SomeDestinationSSID";
46 static const char kTestSerializedCommandMessage[] =
47 "Since we're not testing protocol buffer seriallization, and no data "
48 "actually makes it to a shim, we're safe to write whatever we want here.";
49 static const char kTestSerializedCommandResponse[] =
50 "Similarly, we never ask a protocol buffer to deserialize this string.";
51 static const char kTestSignedData[] = "bytes bytes bytes";
52 static const int kTestStdinFd = 9111;
53 static const int kTestStdoutFd = 9119;
54 static const pid_t kTestShimPid = 989898;
55} // namespace
56
57MATCHER_P(IsCryptoUtilCommandLine, command, "") {
Christopher Wiley956400a2013-04-05 10:47:25 -070058 if (arg.size() != 3) {
59 LOG(ERROR) << "Expected 3 command line arguments, but got "
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080060 << arg.size() << ".";
61 return false;
62 }
63
64 if (strcmp(arg[0], CryptoUtilProxy::kCryptoUtilShimPath)) {
65 return false;
66 }
67
68 if (strcmp(arg[1], CryptoUtilProxy::kCommandVerify) &&
69 strcmp(arg[1], CryptoUtilProxy::kCommandEncrypt)) {
70 return false;
71 }
72
Christopher Wiley956400a2013-04-05 10:47:25 -070073 if (arg[2] != NULL) {
74 return false;
75 }
76
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080077 return true;
78};
79
80MATCHER_P(ErrorIsOfType, error_type, "") {
81 if (error_type != arg.type()) {
82 return false;
83 }
84
85 return true;
86};
87
88class CryptoUtilProxyTest : public testing::Test {
89 public:
90 CryptoUtilProxyTest()
Christopher Wiley5447d2e2013-03-19 17:46:03 -070091 : crypto_util_proxy_(&dispatcher_, &glib_) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080092 test_ssid_.push_back(78);
93 test_ssid_.push_back(69);
94 test_ssid_.push_back(80);
95 test_ssid_.push_back(84);
96 test_ssid_.push_back(85);
97 test_ssid_.push_back(78);
98 test_ssid_.push_back(69);
99 }
100
101 virtual void SetUp() {
102 crypto_util_proxy_.minijail_ = &minijail_;
103 crypto_util_proxy_.process_killer_ = &process_killer_;
104 crypto_util_proxy_.file_io_ = &file_io_;
105 }
106
107 virtual void TearDown() {
108 // Note that |crypto_util_proxy_| needs its process killer reference in
109 // order not to segfault when it tries to kill any outstanding shims on
110 // shutdown. Thus we don't clear out those fields here, and we make sure
111 // to declare the proxy after mocks it consumes.
112 }
113
114 bool HandleRunPipesAndDestroy(struct minijail *jail, vector<char *> args,
115 int *shim_pid, int *stdin, int *stdout,
116 int *stderr) {
117 *shim_pid = kTestShimPid;
118 *stdin = kTestStdinFd;
119 *stdout = kTestStdoutFd;
120 return true;
121 }
122
123 void StartAndCheckShim(const std::string &command,
124 const std::string &shim_stdin) {
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800125 InSequence seq;
126 // Delegate the start call to the real implementation just for this test.
127 EXPECT_CALL(crypto_util_proxy_, StartShimForCommand(_, _, _))
128 .WillOnce(Invoke(&crypto_util_proxy_,
129 &MockCryptoUtilProxy::RealStartShimForCommand));
130 // All shims should be spawned in a Minijail.
131 EXPECT_CALL(minijail_, New());
Christopher Wiley0d05c112013-03-19 17:49:38 -0700132 EXPECT_CALL(minijail_, DropRoot(_, StrEq("shill-crypto")))
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800133 .WillOnce(Return(true));
134 EXPECT_CALL(minijail_, RunPipesAndDestroy(_,
135 IsCryptoUtilCommandLine(command),
136 NotNull(), // pid
137 NotNull(), // stdin
138 NotNull(), // stdout
139 NULL)) // stderr
140 .WillOnce(Invoke(this, &CryptoUtilProxyTest::HandleRunPipesAndDestroy));
141 // We should always schedule a shim timeout callback.
142 EXPECT_CALL(dispatcher_, PostDelayedTask(_, _));
143 // We don't allow file I/O to block.
144 EXPECT_CALL(file_io_,
145 SetFdNonBlocking(kTestStdinFd))
146 .WillOnce(Return(0));
147 EXPECT_CALL(file_io_,
148 SetFdNonBlocking(kTestStdoutFd))
149 .WillOnce(Return(0));
150 // We instead do file I/O through async callbacks registered with the event
151 // dispatcher.
152 EXPECT_CALL(dispatcher_, CreateInputHandler(_, _, _)).Times(1);
153 EXPECT_CALL(dispatcher_, CreateReadyHandler(_, _, _)).Times(1);
154 // The shim is left in flight, not killed.
155 EXPECT_CALL(process_killer_, Kill(_, _)).Times(0);
156 crypto_util_proxy_.StartShimForCommand(
157 command, shim_stdin,
158 Bind(&MockCryptoUtilProxy::TestResultHandlerCallback,
159 crypto_util_proxy_.base::SupportsWeakPtr<MockCryptoUtilProxy>::
160 AsWeakPtr()));
161 EXPECT_EQ(shim_stdin, crypto_util_proxy_.input_buffer_);
162 EXPECT_TRUE(crypto_util_proxy_.output_buffer_.empty());
163 EXPECT_EQ(crypto_util_proxy_.shim_pid_, kTestShimPid);
164 Mock::VerifyAndClearExpectations(&crypto_util_proxy_);
165 Mock::VerifyAndClearExpectations(&minijail_);
166 Mock::VerifyAndClearExpectations(&dispatcher_);
167 Mock::VerifyAndClearExpectations(&process_killer_);
168 }
169
Christopher Wiley67e425e2013-05-02 15:54:51 -0700170 void ExpectCleanup(const Error &expected_result) {
171 if (crypto_util_proxy_.shim_stdin_ > -1) {
172 EXPECT_CALL(file_io_,
173 Close(crypto_util_proxy_.shim_stdin_)).Times(1);
174 }
175 if (crypto_util_proxy_.shim_stdout_ > -1) {
176 EXPECT_CALL(file_io_,
177 Close(crypto_util_proxy_.shim_stdout_)).Times(1);
178 }
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800179 if (crypto_util_proxy_.shim_pid_) {
180 EXPECT_CALL(process_killer_, Kill(crypto_util_proxy_.shim_pid_, _))
181 .Times(1)
182 .WillOnce(Invoke(this,
183 &CryptoUtilProxyTest::HandleShimKill));
184 }
185 }
186
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700187 void AssertShimDead() {
188 EXPECT_FALSE(crypto_util_proxy_.shim_pid_);
189 }
190
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800191 void HandleShimKill(int /*pid*/, const base::Closure &callback) {
192 callback.Run();
193 }
194
Christopher Wiley67e425e2013-05-02 15:54:51 -0700195 void StopAndCheckShim(const Error &error) {
196 ExpectCleanup(error);
197 crypto_util_proxy_.CleanupShim(error);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800198 crypto_util_proxy_.OnShimDeath();
199 EXPECT_EQ(crypto_util_proxy_.shim_pid_, 0);
200 Mock::VerifyAndClearExpectations(&process_killer_);
201 }
202
203 protected:
204 MockMinijail minijail_;
205 MockProcessKiller process_killer_;
206 MockEventDispatcher dispatcher_;
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700207 MockGLib glib_;
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800208 MockFileIO file_io_;
209 MockCryptoUtilProxy crypto_util_proxy_;
210 std::vector<uint8_t> test_ssid_;
211};
212
213TEST_F(CryptoUtilProxyTest, BasicAPIUsage) {
214 {
215 InSequence seq;
216 // Delegate the API call to the real implementation for this test.
217 EXPECT_CALL(crypto_util_proxy_,
218 VerifyDestination(_, _, _, _, _, _, _, _, _))
219 .WillOnce(Invoke(&crypto_util_proxy_,
220 &MockCryptoUtilProxy::RealVerifyDestination));
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700221 EXPECT_CALL(glib_, B64Decode(StrEq(kTestSignedData), _))
222 .WillOnce(Return(true));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800223 // 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));
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700251 EXPECT_CALL(glib_, B64Decode(StrEq(kTestPublicKey), _))
252 .WillOnce(Return(true));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800253 EXPECT_CALL(crypto_util_proxy_,
254 StartShimForCommand(CryptoUtilProxy::kCommandEncrypt, _, _))
255 .WillOnce(Return(true));
256 ResultStringCallback result_callback =
257 Bind(&MockCryptoUtilProxy::TestResultStringCallback,
258 crypto_util_proxy_.
259 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr());
260 Error error;
261 // Normally, we couldn't have these two operations run successfully without
262 // finishing the first one, since only one shim can be in flight at a time.
263 // However, this works because we didn't actually start a shim, we just
264 // trapped the call in our mock.
265 EXPECT_TRUE(crypto_util_proxy_.EncryptData(kTestPublicKey, kTestData,
266 result_callback, &error));
267 EXPECT_TRUE(error.IsSuccess());
268 }
269}
270
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700271TEST_F(CryptoUtilProxyTest, ShimCleanedBeforeCallback) {
272 // Some operations, like VerifyAndEncryptData in the manager, chain two
273 // shim operations together. Make sure that we don't call back with results
274 // before the shim state is clean.
275 {
276 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
277 kTestSerializedCommandMessage);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700278 Error e(Error::kOperationFailed);
279 ExpectCleanup(e);
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700280 EXPECT_CALL(crypto_util_proxy_,
281 TestResultHandlerCallback(
282 StrEq(""), ErrorIsOfType(Error::kOperationFailed)))
283 .Times(1)
284 .WillOnce(WithoutArgs(Invoke(this,
285 &CryptoUtilProxyTest::AssertShimDead)));
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700286 crypto_util_proxy_.HandleShimError(e);
287 }
288 {
289 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
290 kTestSerializedCommandMessage);
291 EXPECT_CALL(crypto_util_proxy_,
292 TestResultHandlerCallback(
293 StrEq(""), ErrorIsOfType(Error::kSuccess)))
294 .Times(1)
295 .WillOnce(WithoutArgs(Invoke(this,
296 &CryptoUtilProxyTest::AssertShimDead)));
Christopher Wiley67e425e2013-05-02 15:54:51 -0700297 ExpectCleanup(Error(Error::kSuccess));
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700298 InputData data;
299 data.buf = NULL;
300 data.len = 0;
301 crypto_util_proxy_.HandleShimOutput(&data);
302 }
303}
304
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800305// Verify that even when we have errors, we'll call the result handler.
306// Ultimately, this is supposed to make sure that we always return something to
307// our callers over DBus.
308TEST_F(CryptoUtilProxyTest, FailuresReturnValues) {
309 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
310 kTestSerializedCommandMessage);
311 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback(
312 StrEq(""), ErrorIsOfType(Error::kOperationFailed))).Times(1);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800313 Error e(Error::kOperationFailed);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700314 ExpectCleanup(e);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800315 crypto_util_proxy_.HandleShimError(e);
316}
317
318TEST_F(CryptoUtilProxyTest, TimeoutsTriggerFailure) {
319 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
320 kTestSerializedCommandMessage);
321 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback(
322 StrEq(""), ErrorIsOfType(Error::kOperationTimeout))).Times(1);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700323 ExpectCleanup(Error(Error::kOperationTimeout));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800324 // This timeout is scheduled by StartShimForCommand.
325 crypto_util_proxy_.HandleShimTimeout();
326}
327
328TEST_F(CryptoUtilProxyTest, OnlyOneInstanceInFlightAtATime) {
329 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
330 kTestSerializedCommandMessage);
331 // Can't start things twice.
332 EXPECT_FALSE(crypto_util_proxy_.RealStartShimForCommand(
333 CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage,
334 Bind(&MockCryptoUtilProxy::TestResultHandlerCallback,
335 crypto_util_proxy_.
336 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr())));
337 // But if some error (or completion) caused us to clean up the shim...
Christopher Wiley67e425e2013-05-02 15:54:51 -0700338 StopAndCheckShim(Error(Error::kSuccess));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800339 // Then we could start the shim again.
340 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
341 kTestSerializedCommandMessage);
342 // Clean up after ourselves.
Christopher Wiley67e425e2013-05-02 15:54:51 -0700343 StopAndCheckShim(Error(Error::kOperationFailed));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800344}
345
346// This test walks the CryptoUtilProxy through the life time of a shim by
347// simulating the API call, file I/O operations, and the final handler on shim
348// completion.
349TEST_F(CryptoUtilProxyTest, ShimLifeTime) {
350 const int kBytesAtATime = 10;
351 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
352 kTestSerializedCommandMessage);
353 // Emulate the operating system pulling bytes through the pipe, and the event
354 // loop notifying us that the file descriptor is ready.
355 int bytes_left = strlen(kTestSerializedCommandMessage);
356 while (bytes_left > 0) {
357 int bytes_written = min(kBytesAtATime, bytes_left);
358 EXPECT_CALL(file_io_, Write(kTestStdinFd, _, bytes_left))
359 .Times(1).WillOnce(Return(bytes_written));
360 bytes_left -= bytes_written;
361 if (bytes_left < 1) {
362 EXPECT_CALL(file_io_, Close(kTestStdinFd));
363 }
364 crypto_util_proxy_.HandleShimStdinReady(crypto_util_proxy_.shim_stdin_);
365 Mock::VerifyAndClearExpectations(&crypto_util_proxy_);
366 }
367
368 // At this point, the shim goes off and does terribly complex crypto stuff,
369 // before responding with a string of bytes over stdout. Emulate the shim
370 // and the event loop to push those bytes back.
371 const int response_length = bytes_left =
372 strlen(kTestSerializedCommandResponse);
373 InputData data;
374 while (bytes_left > 0) {
375 int bytes_written = min(kBytesAtATime, bytes_left);
376 data.len = bytes_written;
377 data.buf = reinterpret_cast<unsigned char *>(const_cast<char *>(
378 kTestSerializedCommandResponse + response_length - bytes_left));
379 bytes_left -= bytes_written;
380 crypto_util_proxy_.HandleShimOutput(&data);
381 }
382 // Write 0 bytes in to signify the end of the stream. This should in turn
383 // cause our callback to be called.
384 data.len = 0;
385 data.buf = NULL;
386 EXPECT_CALL(
387 crypto_util_proxy_,
388 TestResultHandlerCallback(string(kTestSerializedCommandResponse),
389 ErrorIsOfType(Error::kSuccess))).Times(1);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700390 ExpectCleanup(Error(Error::kSuccess));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800391 crypto_util_proxy_.HandleShimOutput(&data);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700392
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800393}
394
395} // namespace shill