普通文本  |  409行  |  15.5 KB

//
// 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 <algorithm>
#include <string>
#include <vector>

#include <base/callback.h>
#include <gtest/gtest.h>

#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<string> /* program_args */,
      const std::string& /* run_as_user */,
      const std::string& /* run_as_group */,
      uint64_t /* capabilities_mask */,
      const base::Callback<void(int)>& 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<string>{CryptoUtilProxy::kCommandVerify},
                vector<string>{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<MockCryptoUtilProxy>::
                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<void(int)>& 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<uint8_t> test_ssid_;
  base::Callback<void(int)> 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<MockCryptoUtilProxy>::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<MockCryptoUtilProxy>::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<MockCryptoUtilProxy>::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<unsigned char*>(const_cast<char*>(
          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