blob: d635f4ce6eea6452767a7cd8a79eb99088c7ee5d [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 {
40 static const char kTestBSSID[] = "00:11:22:33:44:55";
41 static const char kTestCertificate[] = "testcertgoeshere";
42 static const char kTestData[] = "thisisthetestdata";
43 static const char kTestDestinationUDN[] = "TEST1234-5678-ABCD";
44 static const char kTestNonce[] = "abort abort abort";
Christopher Wiley5447d2e2013-03-19 17:46:03 -070045 static const char kTestPublicKey[] = "YWJvcnQgYWJvcnQgYWJvcnQK";
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080046 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;
Alex Vakulenko8a532292014-06-16 17:18:44 -070078}
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080079
80MATCHER_P(ErrorIsOfType, error_type, "") {
81 if (error_type != arg.type()) {
82 return false;
83 }
84
85 return true;
Alex Vakulenko8a532292014-06-16 17:18:44 -070086}
Christopher Wiley5a3f23a2013-02-20 17:29:57 -080087
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());
Utkarsh Sanghie4c6aff2014-07-30 14:49:03 -0700132 EXPECT_CALL(minijail_, DropRoot(_, StrEq("shill-crypto"),
133 StrEq("shill-crypto")))
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800134 .WillOnce(Return(true));
135 EXPECT_CALL(minijail_, RunPipesAndDestroy(_,
136 IsCryptoUtilCommandLine(command),
Alex Vakulenko8a532292014-06-16 17:18:44 -0700137 NotNull(), // pid
138 NotNull(), // stdin
139 NotNull(), // stdout
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800140 NULL)) // stderr
141 .WillOnce(Invoke(this, &CryptoUtilProxyTest::HandleRunPipesAndDestroy));
142 // We should always schedule a shim timeout callback.
143 EXPECT_CALL(dispatcher_, PostDelayedTask(_, _));
144 // We don't allow file I/O to block.
145 EXPECT_CALL(file_io_,
146 SetFdNonBlocking(kTestStdinFd))
147 .WillOnce(Return(0));
148 EXPECT_CALL(file_io_,
149 SetFdNonBlocking(kTestStdoutFd))
150 .WillOnce(Return(0));
151 // We instead do file I/O through async callbacks registered with the event
152 // dispatcher.
153 EXPECT_CALL(dispatcher_, CreateInputHandler(_, _, _)).Times(1);
154 EXPECT_CALL(dispatcher_, CreateReadyHandler(_, _, _)).Times(1);
155 // The shim is left in flight, not killed.
156 EXPECT_CALL(process_killer_, Kill(_, _)).Times(0);
157 crypto_util_proxy_.StartShimForCommand(
158 command, shim_stdin,
159 Bind(&MockCryptoUtilProxy::TestResultHandlerCallback,
160 crypto_util_proxy_.base::SupportsWeakPtr<MockCryptoUtilProxy>::
161 AsWeakPtr()));
162 EXPECT_EQ(shim_stdin, crypto_util_proxy_.input_buffer_);
163 EXPECT_TRUE(crypto_util_proxy_.output_buffer_.empty());
164 EXPECT_EQ(crypto_util_proxy_.shim_pid_, kTestShimPid);
165 Mock::VerifyAndClearExpectations(&crypto_util_proxy_);
166 Mock::VerifyAndClearExpectations(&minijail_);
167 Mock::VerifyAndClearExpectations(&dispatcher_);
168 Mock::VerifyAndClearExpectations(&process_killer_);
169 }
170
Christopher Wiley67e425e2013-05-02 15:54:51 -0700171 void ExpectCleanup(const Error &expected_result) {
172 if (crypto_util_proxy_.shim_stdin_ > -1) {
173 EXPECT_CALL(file_io_,
174 Close(crypto_util_proxy_.shim_stdin_)).Times(1);
175 }
176 if (crypto_util_proxy_.shim_stdout_ > -1) {
177 EXPECT_CALL(file_io_,
178 Close(crypto_util_proxy_.shim_stdout_)).Times(1);
179 }
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800180 if (crypto_util_proxy_.shim_pid_) {
181 EXPECT_CALL(process_killer_, Kill(crypto_util_proxy_.shim_pid_, _))
182 .Times(1)
183 .WillOnce(Invoke(this,
184 &CryptoUtilProxyTest::HandleShimKill));
185 }
186 }
187
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700188 void AssertShimDead() {
189 EXPECT_FALSE(crypto_util_proxy_.shim_pid_);
190 }
191
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800192 void HandleShimKill(int /*pid*/, const base::Closure &callback) {
193 callback.Run();
194 }
195
Christopher Wiley67e425e2013-05-02 15:54:51 -0700196 void StopAndCheckShim(const Error &error) {
197 ExpectCleanup(error);
198 crypto_util_proxy_.CleanupShim(error);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800199 crypto_util_proxy_.OnShimDeath();
200 EXPECT_EQ(crypto_util_proxy_.shim_pid_, 0);
201 Mock::VerifyAndClearExpectations(&process_killer_);
202 }
203
204 protected:
205 MockMinijail minijail_;
206 MockProcessKiller process_killer_;
207 MockEventDispatcher dispatcher_;
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700208 MockGLib glib_;
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800209 MockFileIO file_io_;
210 MockCryptoUtilProxy crypto_util_proxy_;
211 std::vector<uint8_t> test_ssid_;
212};
213
214TEST_F(CryptoUtilProxyTest, BasicAPIUsage) {
215 {
216 InSequence seq;
217 // Delegate the API call to the real implementation for this test.
218 EXPECT_CALL(crypto_util_proxy_,
219 VerifyDestination(_, _, _, _, _, _, _, _, _))
220 .WillOnce(Invoke(&crypto_util_proxy_,
221 &MockCryptoUtilProxy::RealVerifyDestination));
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700222 EXPECT_CALL(glib_, B64Decode(StrEq(kTestSignedData), _))
223 .WillOnce(Return(true));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800224 // API calls are just thin wrappers that write up a message to a shim, then
225 // send it via StartShimForCommand. Expect that a shim will be started in
226 // response to the API being called.
227 EXPECT_CALL(crypto_util_proxy_,
228 StartShimForCommand(CryptoUtilProxy::kCommandVerify, _, _))
229 .WillOnce(Return(true));
230 ResultBoolCallback result_callback =
231 Bind(&MockCryptoUtilProxy::TestResultBoolCallback,
232 crypto_util_proxy_.
233 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr());
234 Error error;
235 EXPECT_TRUE(crypto_util_proxy_.VerifyDestination(kTestCertificate,
236 kTestPublicKey,
237 kTestNonce,
238 kTestSignedData,
239 kTestDestinationUDN,
240 test_ssid_,
241 kTestBSSID,
242 result_callback,
243 &error));
244 EXPECT_TRUE(error.IsSuccess());
245 }
246 {
247 // And very similarly...
248 InSequence seq;
249 EXPECT_CALL(crypto_util_proxy_, EncryptData(_, _, _, _))
250 .WillOnce(Invoke(&crypto_util_proxy_,
251 &MockCryptoUtilProxy::RealEncryptData));
Christopher Wiley5447d2e2013-03-19 17:46:03 -0700252 EXPECT_CALL(glib_, B64Decode(StrEq(kTestPublicKey), _))
253 .WillOnce(Return(true));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800254 EXPECT_CALL(crypto_util_proxy_,
255 StartShimForCommand(CryptoUtilProxy::kCommandEncrypt, _, _))
256 .WillOnce(Return(true));
257 ResultStringCallback result_callback =
258 Bind(&MockCryptoUtilProxy::TestResultStringCallback,
259 crypto_util_proxy_.
260 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr());
261 Error error;
262 // Normally, we couldn't have these two operations run successfully without
263 // finishing the first one, since only one shim can be in flight at a time.
264 // However, this works because we didn't actually start a shim, we just
265 // trapped the call in our mock.
266 EXPECT_TRUE(crypto_util_proxy_.EncryptData(kTestPublicKey, kTestData,
267 result_callback, &error));
268 EXPECT_TRUE(error.IsSuccess());
269 }
270}
271
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700272TEST_F(CryptoUtilProxyTest, ShimCleanedBeforeCallback) {
273 // Some operations, like VerifyAndEncryptData in the manager, chain two
274 // shim operations together. Make sure that we don't call back with results
275 // before the shim state is clean.
276 {
277 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
278 kTestSerializedCommandMessage);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700279 Error e(Error::kOperationFailed);
280 ExpectCleanup(e);
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700281 EXPECT_CALL(crypto_util_proxy_,
282 TestResultHandlerCallback(
283 StrEq(""), ErrorIsOfType(Error::kOperationFailed)))
284 .Times(1)
285 .WillOnce(WithoutArgs(Invoke(this,
286 &CryptoUtilProxyTest::AssertShimDead)));
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700287 crypto_util_proxy_.HandleShimError(e);
288 }
289 {
290 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
291 kTestSerializedCommandMessage);
292 EXPECT_CALL(crypto_util_proxy_,
293 TestResultHandlerCallback(
294 StrEq(""), ErrorIsOfType(Error::kSuccess)))
295 .Times(1)
296 .WillOnce(WithoutArgs(Invoke(this,
297 &CryptoUtilProxyTest::AssertShimDead)));
Christopher Wiley67e425e2013-05-02 15:54:51 -0700298 ExpectCleanup(Error(Error::kSuccess));
Christopher Wileyb3e70d22013-04-26 17:28:37 -0700299 InputData data;
300 data.buf = NULL;
301 data.len = 0;
302 crypto_util_proxy_.HandleShimOutput(&data);
303 }
304}
305
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800306// Verify that even when we have errors, we'll call the result handler.
307// Ultimately, this is supposed to make sure that we always return something to
308// our callers over DBus.
309TEST_F(CryptoUtilProxyTest, FailuresReturnValues) {
310 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
311 kTestSerializedCommandMessage);
312 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback(
313 StrEq(""), ErrorIsOfType(Error::kOperationFailed))).Times(1);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800314 Error e(Error::kOperationFailed);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700315 ExpectCleanup(e);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800316 crypto_util_proxy_.HandleShimError(e);
317}
318
319TEST_F(CryptoUtilProxyTest, TimeoutsTriggerFailure) {
320 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
321 kTestSerializedCommandMessage);
322 EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback(
323 StrEq(""), ErrorIsOfType(Error::kOperationTimeout))).Times(1);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700324 ExpectCleanup(Error(Error::kOperationTimeout));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800325 // This timeout is scheduled by StartShimForCommand.
326 crypto_util_proxy_.HandleShimTimeout();
327}
328
329TEST_F(CryptoUtilProxyTest, OnlyOneInstanceInFlightAtATime) {
330 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
331 kTestSerializedCommandMessage);
332 // Can't start things twice.
333 EXPECT_FALSE(crypto_util_proxy_.RealStartShimForCommand(
334 CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage,
335 Bind(&MockCryptoUtilProxy::TestResultHandlerCallback,
336 crypto_util_proxy_.
337 base::SupportsWeakPtr<MockCryptoUtilProxy>::AsWeakPtr())));
338 // But if some error (or completion) caused us to clean up the shim...
Christopher Wiley67e425e2013-05-02 15:54:51 -0700339 StopAndCheckShim(Error(Error::kSuccess));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800340 // Then we could start the shim again.
341 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
342 kTestSerializedCommandMessage);
343 // Clean up after ourselves.
Christopher Wiley67e425e2013-05-02 15:54:51 -0700344 StopAndCheckShim(Error(Error::kOperationFailed));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800345}
346
347// This test walks the CryptoUtilProxy through the life time of a shim by
348// simulating the API call, file I/O operations, and the final handler on shim
349// completion.
350TEST_F(CryptoUtilProxyTest, ShimLifeTime) {
351 const int kBytesAtATime = 10;
352 StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt,
353 kTestSerializedCommandMessage);
354 // Emulate the operating system pulling bytes through the pipe, and the event
355 // loop notifying us that the file descriptor is ready.
356 int bytes_left = strlen(kTestSerializedCommandMessage);
357 while (bytes_left > 0) {
358 int bytes_written = min(kBytesAtATime, bytes_left);
359 EXPECT_CALL(file_io_, Write(kTestStdinFd, _, bytes_left))
360 .Times(1).WillOnce(Return(bytes_written));
361 bytes_left -= bytes_written;
362 if (bytes_left < 1) {
363 EXPECT_CALL(file_io_, Close(kTestStdinFd));
364 }
365 crypto_util_proxy_.HandleShimStdinReady(crypto_util_proxy_.shim_stdin_);
366 Mock::VerifyAndClearExpectations(&crypto_util_proxy_);
367 }
368
369 // At this point, the shim goes off and does terribly complex crypto stuff,
370 // before responding with a string of bytes over stdout. Emulate the shim
371 // and the event loop to push those bytes back.
372 const int response_length = bytes_left =
373 strlen(kTestSerializedCommandResponse);
374 InputData data;
375 while (bytes_left > 0) {
376 int bytes_written = min(kBytesAtATime, bytes_left);
377 data.len = bytes_written;
378 data.buf = reinterpret_cast<unsigned char *>(const_cast<char *>(
379 kTestSerializedCommandResponse + response_length - bytes_left));
380 bytes_left -= bytes_written;
381 crypto_util_proxy_.HandleShimOutput(&data);
382 }
383 // Write 0 bytes in to signify the end of the stream. This should in turn
384 // cause our callback to be called.
385 data.len = 0;
386 data.buf = NULL;
387 EXPECT_CALL(
388 crypto_util_proxy_,
389 TestResultHandlerCallback(string(kTestSerializedCommandResponse),
390 ErrorIsOfType(Error::kSuccess))).Times(1);
Christopher Wiley67e425e2013-05-02 15:54:51 -0700391 ExpectCleanup(Error(Error::kSuccess));
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800392 crypto_util_proxy_.HandleShimOutput(&data);
Christopher Wiley5a3f23a2013-02-20 17:29:57 -0800393}
394
395} // namespace shill