blob: b145f15118cc2ff0c8f31f098050360c337e4c9d [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/arc/enterprise/cert_store/arc_cert_installer.h"
#include <string>
#include <vector>
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/ash/arc/policy/arc_policy_bridge.h"
#include "chrome/test/base/testing_profile.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/mojom/policy.mojom.h"
#include "components/arc/session/arc_bridge_service.h"
#include "components/arc/test/fake_policy_instance.h"
#include "components/policy/core/common/remote_commands/remote_commands_queue.h"
#include "content/public/test/browser_task_environment.h"
#include "crypto/rsa_private_key.h"
#include "net/cert/scoped_nss_types.h"
#include "net/cert/x509_util.h"
#include "net/cert/x509_util_nss.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace arc {
using testing::_;
using testing::Invoke;
using testing::StrictMock;
using testing::WithArg;
namespace {
MATCHER_P(IsCommandPayloadForName, name, "") {
constexpr char kAliasFormat[] = "\\\"alias\\\":\\\"%s\\\"";
return arg.find(base::StringPrintf(kAliasFormat, name.c_str())) !=
std::string::npos;
}
MATCHER_P(IsCommandWithStatus, status, "") {
return arg->status() == status;
}
constexpr char kCNFormat[] = "CN=%s";
constexpr char kFakeName1[] = "fake1";
constexpr char kFakeName2[] = "fake2";
constexpr char kFakeName3[] = "fake3";
class MockRemoteCommandsQueueObserver
: public StrictMock<policy::RemoteCommandsQueue::Observer> {
public:
MOCK_METHOD1(OnJobStarted, void(policy::RemoteCommandJob* command));
MOCK_METHOD1(OnJobFinished, void(policy::RemoteCommandJob* command));
};
class MockPolicyInstance : public FakePolicyInstance {
public:
MOCK_METHOD2(OnCommandReceived,
void(const std::string& command,
OnCommandReceivedCallback callback));
};
void AddCert(const std::string& cn, std::vector<CertDescription>* certs) {
std::string der_cert;
net::ScopedCERTCertificate cert;
std::unique_ptr<crypto::RSAPrivateKey> key(
crypto::RSAPrivateKey::Create(1024));
ASSERT_TRUE(net::x509_util::CreateSelfSignedCert(
key->key(), net::x509_util::DIGEST_SHA256, cn, 1, base::Time::UnixEpoch(),
base::Time::UnixEpoch(), {}, &der_cert));
cert = net::x509_util::CreateCERTCertificateFromBytes(
reinterpret_cast<const uint8_t*>(der_cert.data()), der_cert.size());
ASSERT_TRUE(cert);
certs->push_back(CertDescription(key.release(), cert.release()));
}
} // namespace
class ArcCertInstallerTest : public testing::Test {
public:
ArcCertInstallerTest()
: arc_service_manager_(std::make_unique<arc::ArcServiceManager>()),
profile_(std::make_unique<TestingProfile>()),
arc_policy_bridge_(arc::ArcPolicyBridge::GetForBrowserContextForTesting(
profile_.get())),
policy_instance_(std::make_unique<arc::MockPolicyInstance>()) {
arc_service_manager_->arc_bridge_service()->policy()->SetInstance(
policy_instance_.get());
}
~ArcCertInstallerTest() override {
arc_service_manager_->arc_bridge_service()->policy()->CloseInstance(
policy_instance_.get());
}
void SetUp() override {
auto mock_queue = std::make_unique<policy::RemoteCommandsQueue>();
queue_ = mock_queue.get();
installer_ =
std::make_unique<ArcCertInstaller>(profile(), std::move(mock_queue));
queue_->AddObserver(&observer_);
}
void TearDown() override {
queue_->RemoveObserver(&observer_);
installer_.reset();
queue_ = nullptr;
}
void ExpectArcCommandForName(const std::string& name,
mojom::CommandResultType status) {
EXPECT_CALL(*policy_instance_.get(),
OnCommandReceived(IsCommandPayloadForName(name), _))
.WillOnce(WithArg<1>(Invoke(
[status](FakePolicyInstance::OnCommandReceivedCallback callback) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), status));
})));
}
Profile* profile() { return profile_.get(); }
ArcCertInstaller* installer() { return installer_.get(); }
MockRemoteCommandsQueueObserver* observer() { return &observer_; }
private:
content::BrowserTaskEnvironment task_environment_;
// ArcServiceManager needs to be created before ArcPolicyBridge (since the
// Bridge depends on the Manager), and it needs to be destroyed after Profile
// (because BrowserContextKeyedServices are destroyed together with Profile,
// and ArcPolicyBridge is such a service).
const std::unique_ptr<arc::ArcServiceManager> arc_service_manager_;
const std::unique_ptr<TestingProfile> profile_;
arc::ArcPolicyBridge* const arc_policy_bridge_;
const std::unique_ptr<arc::MockPolicyInstance> policy_instance_;
policy::RemoteCommandsQueue* queue_;
std::unique_ptr<ArcCertInstaller> installer_;
MockRemoteCommandsQueueObserver observer_;
DISALLOW_COPY_AND_ASSIGN(ArcCertInstallerTest);
};
// Tests that installation of an empty cert list completes successfully.
TEST_F(ArcCertInstallerTest, NoCertsTest) {
installer()->InstallArcCerts(
std::vector<CertDescription>(),
base::BindOnce([](bool result) { EXPECT_TRUE(result); }));
}
// Tests that installing certs completes successfully if there are two certs
// available.
TEST_F(ArcCertInstallerTest, BasicCertTest) {
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName1), &certs);
AddCert(base::StringPrintf(kCNFormat, kFakeName2), &certs);
ExpectArcCommandForName(kFakeName1, mojom::CommandResultType::SUCCESS);
ExpectArcCommandForName(kFakeName2, mojom::CommandResultType::SUCCESS);
EXPECT_CALL(*observer(), OnJobStarted(IsCommandWithStatus(
policy::RemoteCommandJob::Status::RUNNING)))
.Times(2);
EXPECT_CALL(*observer(), OnJobFinished(IsCommandWithStatus(
policy::RemoteCommandJob::Status::SUCCEEDED)))
.Times(2);
base::RunLoop run_loop;
installer()->InstallArcCerts(std::move(certs),
base::BindOnce(
[](base::RunLoop* run_loop, bool result) {
EXPECT_TRUE(result);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
// Tests that consequent calls complete successfully and install each cert once
// (3 times in total for 3 distinct certs).
TEST_F(ArcCertInstallerTest, ConsequentInstallTest) {
ExpectArcCommandForName(kFakeName1, mojom::CommandResultType::SUCCESS);
ExpectArcCommandForName(kFakeName2, mojom::CommandResultType::SUCCESS);
EXPECT_CALL(*observer(), OnJobStarted(IsCommandWithStatus(
policy::RemoteCommandJob::Status::RUNNING)))
.Times(3);
EXPECT_CALL(*observer(), OnJobFinished(IsCommandWithStatus(
policy::RemoteCommandJob::Status::SUCCEEDED)))
.Times(3);
{
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName1), &certs);
AddCert(base::StringPrintf(kCNFormat, kFakeName2), &certs);
base::RunLoop run_loop;
installer()->InstallArcCerts(std::move(certs),
base::BindOnce(
[](base::RunLoop* run_loop, bool result) {
EXPECT_TRUE(result);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
ExpectArcCommandForName(kFakeName3, mojom::CommandResultType::SUCCESS);
{
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName1), &certs);
AddCert(base::StringPrintf(kCNFormat, kFakeName3), &certs);
base::RunLoop run_loop;
installer()->InstallArcCerts(std::move(certs),
base::BindOnce(
[](base::RunLoop* run_loop, bool result) {
EXPECT_TRUE(result);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
}
// Tests that starting the second cert installation before finishing the
// first one fails.
TEST_F(ArcCertInstallerTest, FailureIncompleteInstallationTest) {
ExpectArcCommandForName(kFakeName1, mojom::CommandResultType::SUCCESS);
EXPECT_CALL(*observer(), OnJobStarted(IsCommandWithStatus(
policy::RemoteCommandJob::Status::RUNNING)));
EXPECT_CALL(*observer(), OnJobFinished(IsCommandWithStatus(
policy::RemoteCommandJob::Status::SUCCEEDED)));
{
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName1), &certs);
installer()->InstallArcCerts(std::move(certs),
base::BindOnce([](bool result) {
// The first installation has not finished
// before the second started.
EXPECT_FALSE(result);
}));
}
{
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName1), &certs);
base::RunLoop run_loop;
installer()->InstallArcCerts(std::move(certs),
base::BindOnce(
[](base::RunLoop* run_loop, bool result) {
EXPECT_TRUE(result);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
}
// Tests the failed certificate installation.
TEST_F(ArcCertInstallerTest, FailedRequiredSmartCardTest) {
ExpectArcCommandForName(kFakeName1, mojom::CommandResultType::FAILURE);
EXPECT_CALL(*observer(), OnJobStarted(IsCommandWithStatus(
policy::RemoteCommandJob::Status::RUNNING)));
EXPECT_CALL(*observer(), OnJobFinished(IsCommandWithStatus(
policy::RemoteCommandJob::Status::FAILED)));
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName1), &certs);
base::RunLoop run_loop;
installer()->InstallArcCerts(std::move(certs),
base::BindOnce(
[](base::RunLoop* run_loop, bool result) {
EXPECT_FALSE(result);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
// Tests that the failed installation does not fail the consequent operation
// if the cert is no longer required.
TEST_F(ArcCertInstallerTest, FailiedNotRequiredSmartCardTest) {
EXPECT_CALL(*observer(), OnJobStarted(IsCommandWithStatus(
policy::RemoteCommandJob::Status::RUNNING)))
.Times(2);
{
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName1), &certs);
installer()->InstallArcCerts(std::move(certs),
base::BindOnce([](bool result) {
// The first installation has not finished
// before the second started.
EXPECT_FALSE(result);
}));
}
ExpectArcCommandForName(kFakeName1, mojom::CommandResultType::FAILURE);
ExpectArcCommandForName(kFakeName2, mojom::CommandResultType::SUCCESS);
EXPECT_CALL(*observer(), OnJobFinished(IsCommandWithStatus(
policy::RemoteCommandJob::Status::SUCCEEDED)));
EXPECT_CALL(*observer(), OnJobFinished(IsCommandWithStatus(
policy::RemoteCommandJob::Status::FAILED)));
{
std::vector<CertDescription> certs;
AddCert(base::StringPrintf(kCNFormat, kFakeName2), &certs);
base::RunLoop run_loop;
installer()->InstallArcCerts(std::move(certs),
base::BindOnce(
[](base::RunLoop* run_loop, bool result) {
EXPECT_TRUE(result);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
}
} // namespace arc