// // Copyright (C) 2013 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/crypto_util_proxy.h" #include #include #include #include #include #include "shill/callbacks.h" #include "shill/mock_crypto_util_proxy.h" #include "shill/mock_event_dispatcher.h" #include "shill/mock_file_io.h" #include "shill/mock_process_manager.h" using base::Bind; using std::min; using std::string; using std::vector; using testing::AnyOf; using testing::DoAll; using testing::InSequence; using testing::Invoke; using testing::Mock; using testing::NotNull; using testing::Return; using testing::StrEq; using testing::WithoutArgs; using testing::_; namespace shill { namespace { const char kTestBSSID[] = "00:11:22:33:44:55"; const char kTestCertificate[] = "testcertgoeshere"; const char kTestData[] = "thisisthetestdata"; const char kTestDestinationUDN[] = "TEST1234-5678-ABCD"; const char kTestNonce[] = "abort abort abort"; const char kTestPublicKey[] = "YWJvcnQgYWJvcnQgYWJvcnQK"; const char kTestSerializedCommandMessage[] = "Since we're not testing protocol buffer seriallization, and no data " "actually makes it to a shim, we're safe to write whatever we want here."; const char kTestSerializedCommandResponse[] = "Similarly, we never ask a protocol buffer to deserialize this string."; const char kTestSignedData[] = "Ynl0ZXMgYnl0ZXMgYnl0ZXMK"; const int kTestStdinFd = 9111; const int kTestStdoutFd = 9119; const pid_t kTestShimPid = 989898; } // namespace MATCHER_P(ErrorIsOfType, error_type, "") { if (error_type != arg.type()) { return false; } return true; } class CryptoUtilProxyTest : public testing::Test { public: CryptoUtilProxyTest() : crypto_util_proxy_(&dispatcher_) { test_ssid_.push_back(78); test_ssid_.push_back(69); test_ssid_.push_back(80); test_ssid_.push_back(84); test_ssid_.push_back(85); test_ssid_.push_back(78); test_ssid_.push_back(69); } virtual void SetUp() { crypto_util_proxy_.process_manager_ = &process_manager_; crypto_util_proxy_.file_io_ = &file_io_; } virtual void TearDown() { // Note that |crypto_util_proxy_| needs its process manager reference in // order not to segfault when it tries to kill any outstanding shims on // shutdown. Thus we don't clear out those fields here, and we make sure // to declare the proxy after mocks it consumes. } // TODO(quiche): Consider refactoring // HandleStartInMinijailWithPipes, HandleStopProcess, and // HandleUpdateExitCallback into a FakeProcessManager. b/24210150 pid_t HandleStartInMinijailWithPipes( const tracked_objects::Location& /* spawn_source */, const base::FilePath& /* program */, vector /* program_args */, const std::string& /* run_as_user */, const std::string& /* run_as_group */, uint64_t /* capabilities_mask */, const base::Callback& exit_callback, int* stdin, int* stdout, int* /* stderr */) { exit_callback_ = exit_callback; *stdin = kTestStdinFd; *stdout = kTestStdoutFd; return kTestShimPid; } void StartAndCheckShim(const std::string& command, const std::string& shim_stdin) { InSequence seq; // Delegate the start call to the real implementation just for this test. EXPECT_CALL(crypto_util_proxy_, StartShimForCommand(_, _, _)) .WillOnce(Invoke(&crypto_util_proxy_, &MockCryptoUtilProxy::RealStartShimForCommand)); // All shims should be spawned in a Minijail. EXPECT_CALL( process_manager_, StartProcessInMinijailWithPipes( _, // caller location base::FilePath(CryptoUtilProxy::kCryptoUtilShimPath), AnyOf( vector{CryptoUtilProxy::kCommandVerify}, vector{CryptoUtilProxy::kCommandEncrypt}), "shill-crypto", "shill-crypto", 0, // no capabilities required _, // exit_callback NotNull(), // stdin NotNull(), // stdout nullptr)) // stderr .WillOnce(Invoke(this, &CryptoUtilProxyTest::HandleStartInMinijailWithPipes)); // We should always schedule a shim timeout callback. EXPECT_CALL(dispatcher_, PostDelayedTask(_, _)); // We don't allow file I/O to block. EXPECT_CALL(file_io_, SetFdNonBlocking(kTestStdinFd)) .WillOnce(Return(0)); EXPECT_CALL(file_io_, SetFdNonBlocking(kTestStdoutFd)) .WillOnce(Return(0)); // We instead do file I/O through async callbacks registered with the event // dispatcher. EXPECT_CALL(dispatcher_, CreateInputHandler(_, _, _)).Times(1); EXPECT_CALL(dispatcher_, CreateReadyHandler(_, _, _)).Times(1); // The shim is left in flight, not killed. EXPECT_CALL(process_manager_, StopProcess(_)).Times(0); crypto_util_proxy_.StartShimForCommand( command, shim_stdin, Bind(&MockCryptoUtilProxy::TestResultHandlerCallback, crypto_util_proxy_.base::SupportsWeakPtr:: AsWeakPtr())); EXPECT_EQ(shim_stdin, crypto_util_proxy_.input_buffer_); EXPECT_TRUE(crypto_util_proxy_.output_buffer_.empty()); EXPECT_EQ(crypto_util_proxy_.shim_pid_, kTestShimPid); Mock::VerifyAndClearExpectations(&crypto_util_proxy_); Mock::VerifyAndClearExpectations(&dispatcher_); Mock::VerifyAndClearExpectations(&process_manager_); } void ExpectCleanup(const Error& expected_result) { if (crypto_util_proxy_.shim_stdin_ > -1) { EXPECT_CALL(file_io_, Close(crypto_util_proxy_.shim_stdin_)).Times(1); } if (crypto_util_proxy_.shim_stdout_ > -1) { EXPECT_CALL(file_io_, Close(crypto_util_proxy_.shim_stdout_)).Times(1); } if (crypto_util_proxy_.shim_pid_) { EXPECT_CALL(process_manager_, UpdateExitCallback(_, _)) .Times(1) .WillOnce(Invoke(this, &CryptoUtilProxyTest::HandleUpdateExitCallback)); EXPECT_CALL(process_manager_, StopProcess(crypto_util_proxy_.shim_pid_)) .Times(1) .WillOnce(Invoke(this, &CryptoUtilProxyTest::HandleStopProcess)); } } void AssertShimDead() { EXPECT_FALSE(crypto_util_proxy_.shim_pid_); } bool HandleUpdateExitCallback(pid_t /*pid*/, const base::Callback& new_callback) { exit_callback_ = new_callback; return true; } bool HandleStopProcess(pid_t /*pid*/) { const int kExitStatus = -1; // NB: in the real world, this ordering is not guaranteed. That // is, StopProcess() might return before executing the callback. exit_callback_.Run(kExitStatus); return true; } void StopAndCheckShim(const Error& error) { ExpectCleanup(error); crypto_util_proxy_.CleanupShim(error); crypto_util_proxy_.OnShimDeath(-1); EXPECT_EQ(crypto_util_proxy_.shim_pid_, 0); Mock::VerifyAndClearExpectations(&process_manager_); } protected: MockProcessManager process_manager_; MockEventDispatcher dispatcher_; MockFileIO file_io_; MockCryptoUtilProxy crypto_util_proxy_; std::vector test_ssid_; base::Callback exit_callback_; }; TEST_F(CryptoUtilProxyTest, BasicAPIUsage) { { InSequence seq; // Delegate the API call to the real implementation for this test. EXPECT_CALL(crypto_util_proxy_, VerifyDestination(_, _, _, _, _, _, _, _, _)) .WillOnce(Invoke(&crypto_util_proxy_, &MockCryptoUtilProxy::RealVerifyDestination)); // API calls are just thin wrappers that write up a message to a shim, then // send it via StartShimForCommand. Expect that a shim will be started in // response to the API being called. EXPECT_CALL(crypto_util_proxy_, StartShimForCommand(CryptoUtilProxy::kCommandVerify, _, _)) .WillOnce(Return(true)); ResultBoolCallback result_callback = Bind(&MockCryptoUtilProxy::TestResultBoolCallback, crypto_util_proxy_. base::SupportsWeakPtr::AsWeakPtr()); Error error; EXPECT_TRUE(crypto_util_proxy_.VerifyDestination(kTestCertificate, kTestPublicKey, kTestNonce, kTestSignedData, kTestDestinationUDN, test_ssid_, kTestBSSID, result_callback, &error)); EXPECT_TRUE(error.IsSuccess()); } { // And very similarly... InSequence seq; EXPECT_CALL(crypto_util_proxy_, EncryptData(_, _, _, _)) .WillOnce(Invoke(&crypto_util_proxy_, &MockCryptoUtilProxy::RealEncryptData)); EXPECT_CALL(crypto_util_proxy_, StartShimForCommand(CryptoUtilProxy::kCommandEncrypt, _, _)) .WillOnce(Return(true)); ResultStringCallback result_callback = Bind(&MockCryptoUtilProxy::TestResultStringCallback, crypto_util_proxy_. base::SupportsWeakPtr::AsWeakPtr()); Error error; // Normally, we couldn't have these two operations run successfully without // finishing the first one, since only one shim can be in flight at a time. // However, this works because we didn't actually start a shim, we just // trapped the call in our mock. EXPECT_TRUE(crypto_util_proxy_.EncryptData(kTestPublicKey, kTestData, result_callback, &error)); EXPECT_TRUE(error.IsSuccess()); } } TEST_F(CryptoUtilProxyTest, ShimCleanedBeforeCallback) { // Some operations, like VerifyAndEncryptData in the manager, chain two // shim operations together. Make sure that we don't call back with results // before the shim state is clean. { StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage); Error e(Error::kOperationFailed); ExpectCleanup(e); EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback( StrEq(""), ErrorIsOfType(Error::kOperationFailed))) .Times(1) .WillOnce(WithoutArgs(Invoke(this, &CryptoUtilProxyTest::AssertShimDead))); crypto_util_proxy_.HandleShimError(e); } { StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage); EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback( StrEq(""), ErrorIsOfType(Error::kSuccess))) .Times(1) .WillOnce(WithoutArgs(Invoke(this, &CryptoUtilProxyTest::AssertShimDead))); ExpectCleanup(Error(Error::kSuccess)); InputData data; data.buf = nullptr; data.len = 0; crypto_util_proxy_.HandleShimOutput(&data); } } // Verify that even when we have errors, we'll call the result handler. // Ultimately, this is supposed to make sure that we always return something to // our callers over DBus. TEST_F(CryptoUtilProxyTest, FailuresReturnValues) { StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage); EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback( StrEq(""), ErrorIsOfType(Error::kOperationFailed))).Times(1); Error e(Error::kOperationFailed); ExpectCleanup(e); crypto_util_proxy_.HandleShimError(e); } TEST_F(CryptoUtilProxyTest, TimeoutsTriggerFailure) { StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage); EXPECT_CALL(crypto_util_proxy_, TestResultHandlerCallback( StrEq(""), ErrorIsOfType(Error::kOperationTimeout))).Times(1); ExpectCleanup(Error(Error::kOperationTimeout)); // This timeout is scheduled by StartShimForCommand. crypto_util_proxy_.HandleShimTimeout(); } TEST_F(CryptoUtilProxyTest, OnlyOneInstanceInFlightAtATime) { StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage); // Can't start things twice. EXPECT_FALSE(crypto_util_proxy_.RealStartShimForCommand( CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage, Bind(&MockCryptoUtilProxy::TestResultHandlerCallback, crypto_util_proxy_. base::SupportsWeakPtr::AsWeakPtr()))); // But if some error (or completion) caused us to clean up the shim... StopAndCheckShim(Error(Error::kSuccess)); // Then we could start the shim again. StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage); // Clean up after ourselves. StopAndCheckShim(Error(Error::kOperationFailed)); } // This test walks the CryptoUtilProxy through the life time of a shim by // simulating the API call, file I/O operations, and the final handler on shim // completion. TEST_F(CryptoUtilProxyTest, ShimLifeTime) { const int kBytesAtATime = 10; StartAndCheckShim(CryptoUtilProxy::kCommandEncrypt, kTestSerializedCommandMessage); // Emulate the operating system pulling bytes through the pipe, and the event // loop notifying us that the file descriptor is ready. int bytes_left = strlen(kTestSerializedCommandMessage); while (bytes_left > 0) { int bytes_written = min(kBytesAtATime, bytes_left); EXPECT_CALL(file_io_, Write(kTestStdinFd, _, bytes_left)) .Times(1).WillOnce(Return(bytes_written)); bytes_left -= bytes_written; if (bytes_left < 1) { EXPECT_CALL(file_io_, Close(kTestStdinFd)); } crypto_util_proxy_.HandleShimStdinReady(crypto_util_proxy_.shim_stdin_); Mock::VerifyAndClearExpectations(&crypto_util_proxy_); } // At this point, the shim goes off and does terribly complex crypto stuff, // before responding with a string of bytes over stdout. Emulate the shim // and the event loop to push those bytes back. const int response_length = bytes_left = strlen(kTestSerializedCommandResponse); InputData data; while (bytes_left > 0) { int bytes_written = min(kBytesAtATime, bytes_left); data.len = bytes_written; data.buf = reinterpret_cast(const_cast( kTestSerializedCommandResponse + response_length - bytes_left)); bytes_left -= bytes_written; crypto_util_proxy_.HandleShimOutput(&data); } // Write 0 bytes in to signify the end of the stream. This should in turn // cause our callback to be called. data.len = 0; data.buf = nullptr; EXPECT_CALL( crypto_util_proxy_, TestResultHandlerCallback(string(kTestSerializedCommandResponse), ErrorIsOfType(Error::kSuccess))).Times(1); ExpectCleanup(Error(Error::kSuccess)); crypto_util_proxy_.HandleShimOutput(&data); } } // namespace shill