// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/ash/crosapi/keystore_service_ash.h"
#include <initializer_list>
#include <optional>
#include <string_view>
#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gmock_move_support.h"
#include "base/test/gtest_util.h"
#include "base/test/mock_log.h"
#include "chrome/browser/ash/attestation/mock_tpm_challenge_key.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key_result.h"
#include "chrome/browser/ash/platform_keys/key_permissions/mock_key_permissions_service.h"
#include "chrome/browser/ash/platform_keys/mock_platform_keys_service.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
#include "chromeos/crosapi/cpp/keystore_service_util.h"
#include "chromeos/crosapi/mojom/keystore_error.mojom.h"
#include "chromeos/crosapi/mojom/keystore_service.mojom.h"
#include "content/public/test/browser_task_environment.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/ssl/ssl_info.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock-actions.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
// The tests in this file mostly focus on verifying that KeystoreService can
// forward messages to and from PlatformKeysService, KeyPermissionsService,
// TpmChallengeKey and correctly re-encode arguments in both directions.
namespace crosapi {
namespace {
using ::ash::platform_keys::MockKeyPermissionsService;
using ::ash::platform_keys::MockPlatformKeysService;
using ::attestation::KEY_TYPE_ECC;
using ::attestation::KEY_TYPE_RSA;
using ::base::test::RunOnceCallback;
using ::chromeos::platform_keys::HashAlgorithm;
using ::chromeos::platform_keys::Status;
using ::chromeos::platform_keys::TokenId;
using ::crosapi::keystore_service_util::MakeEcKeystoreSigningAlgorithm;
using ::crosapi::keystore_service_util::MakeRsaKeystoreSigningAlgorithm;
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::StrictMock;
using ::testing::UnorderedElementsAre;
using ::testing::WithArg;
constexpr char kData[] = "\1\2\3\4\5\6\7";
const char kDeprecatedMethodErr[] = "Deprecated method was called.";
#define EXPECT_ERROR_LOG(matcher) \
if (DLOG_IS_ON(ERROR)) { \
EXPECT_CALL(log_, Log(logging::LOGGING_ERROR, _, _, _, matcher)) \
.WillOnce(testing::Return(true)); /* suppress logging */ \
}
std::string GetSubjectPublicKeyInfo(
const scoped_refptr<net::X509Certificate>& certificate) {
std::string_view spki_der_piece;
bool ok = net::asn1::ExtractSPKIFromDERCert(
net::x509_util::CryptoBufferAsStringPiece(certificate->cert_buffer()),
&spki_der_piece);
CHECK(ok && !spki_der_piece.empty());
return std::string(spki_der_piece);
}
// Returns a list with one certificate.
std::unique_ptr<net::CertificateList> GetCertificateList() {
static base::NoDestructor<net::CertificateList> cert_list;
if (cert_list->empty()) {
net::SSLInfo ssl_info = net::SSLInfo();
ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
CHECK(ssl_info.is_valid());
cert_list->push_back(ssl_info.cert);
}
return std::make_unique<net::CertificateList>(*cert_list);
}
const std::string& GetPublicKeyStr() {
static base::NoDestructor<const std::string> result(
GetSubjectPublicKeyInfo(*GetCertificateList()->begin()));
return *result;
}
const std::vector<uint8_t>& GetPublicKeyBin() {
static base::NoDestructor<const std::vector<uint8_t>> result(
GetPublicKeyStr().begin(), GetPublicKeyStr().end());
return *result;
}
const std::string& GetDataStr() {
static base::NoDestructor<const std::string> result(kData);
return *result;
}
const std::vector<uint8_t>& GetDataBin() {
static base::NoDestructor<const std::vector<uint8_t>> result(
GetDataStr().begin(), GetDataStr().end());
return *result;
}
std::vector<uint8_t> CertToBlob(
const scoped_refptr<net::X509Certificate>& cert) {
const uint8_t* cert_buffer =
reinterpret_cast<const uint8_t*>(CRYPTO_BUFFER_data(cert->cert_buffer()));
return std::vector<uint8_t>(
cert_buffer, cert_buffer + CRYPTO_BUFFER_len(cert->cert_buffer()));
}
void AssertBlobEq(const mojom::KeystoreBinaryResultPtr& result,
const std::vector<uint8_t>& expected_blob) {
ASSERT_TRUE(result);
ASSERT_TRUE(result->is_blob());
EXPECT_EQ(result->get_blob(), expected_blob);
}
void AssertCertListEq(
const std::vector<std::vector<uint8_t>>& received_cert_list,
std::unique_ptr<net::CertificateList> expected_cert_list) {
ASSERT_EQ(received_cert_list.size(), expected_cert_list->size());
for (size_t i = 0; i < received_cert_list.size(); ++i) {
const scoped_refptr<net::X509Certificate>& expected_cert =
(*expected_cert_list)[i];
const std::vector<uint8_t>& received_binary_cert = received_cert_list[i];
scoped_refptr<net::X509Certificate> received_cert =
net::X509Certificate::CreateFromBytes(received_binary_cert);
ASSERT_TRUE(received_cert);
EXPECT_TRUE(expected_cert->EqualsIncludingChain(received_cert.get()));
}
}
template <typename T>
void AssertErrorEq(const T& result, mojom::KeystoreError expected_error) {
ASSERT_TRUE(result);
ASSERT_TRUE(result->is_error());
EXPECT_EQ(result->get_error(), expected_error);
}
// Matches a certificate of the type `scoped_refptr<net::X509Certificate>`.
MATCHER_P(CertEq, expected_cert, "Certificates don't match.") {
return expected_cert && arg && expected_cert->EqualsIncludingChain(arg.get());
}
// Matches strings that start with `expected_prefix`.
MATCHER_P(StrStartsWith, expected_prefix, "Unexpected string.") {
return base::StartsWith(arg, expected_prefix);
}
class KeystoreServiceAshTest : public testing::Test {
public:
KeystoreServiceAshTest()
: keystore_service_(&platform_keys_service_, &key_permissions_service_) {}
KeystoreServiceAshTest(const KeystoreServiceAshTest&) = delete;
auto operator=(const KeystoreServiceAshTest&) = delete;
~KeystoreServiceAshTest() override = default;
protected:
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
StrictMock<MockPlatformKeysService> platform_keys_service_;
StrictMock<MockKeyPermissionsService> key_permissions_service_;
KeystoreServiceAsh keystore_service_;
base::test::MockLog log_;
};
// A mock for observing callbacks that return a single result of the type |T|
// and saving it.
template <typename T>
struct CallbackObserver {
MOCK_METHOD(void, Callback, (T result));
auto GetCallback() {
EXPECT_CALL(*this, Callback).WillOnce(MoveArg<0>(&result));
return base::BindOnce(&CallbackObserver<T>::Callback,
base::Unretained(this));
}
std::optional<T> result;
};
// A mock for observing callbacks that return a single result of the type |T| by
// const reference and saving it.
template <typename T>
struct CallbackObserverRef {
MOCK_METHOD(void, Callback, (const T& result));
auto GetCallback() {
EXPECT_CALL(*this, Callback).WillOnce(testing::SaveArg<0>(&result));
return base::BindOnce(&CallbackObserverRef<T>::Callback,
base::Unretained(this));
}
std::optional<T> result;
};
// A mock for observing status results returned via a callback.
struct StatusCallbackObserver {
MOCK_METHOD(void, Callback, (bool is_error, mojom::KeystoreError error));
auto GetCallback() {
EXPECT_CALL(*this, Callback)
.WillOnce(
DoAll(MoveArg<0>(&result_is_error), MoveArg<1>(&result_error)));
return base::BindOnce(&StatusCallbackObserver::Callback,
base::Unretained(this));
}
bool has_value() const { return result_is_error.has_value(); }
std::optional<bool> result_is_error;
mojom::KeystoreError result_error = mojom::KeystoreError::kUnknown;
};
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, UserKeystoreRsaAlgoGenerateKeySuccess) {
const unsigned int modulus_length = 2048;
EXPECT_CALL(
platform_keys_service_,
GenerateRSAKey(TokenId::kUser, modulus_length, /*sw_backed=*/false,
/*callback=*/_))
.WillOnce(RunOnceCallback<3>(GetPublicKeyBin(), Status::kSuccess));
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
keystore_service_.GenerateKey(
mojom::KeystoreType::kUser,
MakeRsaKeystoreSigningAlgorithm(modulus_length, /*sw_backed=*/false),
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertBlobEq(observer.result.value(), GetPublicKeyBin());
}
TEST_F(KeystoreServiceAshTest, DeviceKeystoreEcAlgoGenerateKeySuccess) {
const std::string named_curve = "test_named_curve";
EXPECT_CALL(platform_keys_service_,
GenerateECKey(TokenId::kSystem, named_curve, /*callback=*/_))
.WillOnce(RunOnceCallback<2>(GetPublicKeyBin(), Status::kSuccess));
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
keystore_service_.GenerateKey(mojom::KeystoreType::kDevice,
MakeEcKeystoreSigningAlgorithm(named_curve),
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertBlobEq(observer.result.value(), GetPublicKeyBin());
}
TEST_F(KeystoreServiceAshTest, UserKeystoreUnsupportedEcCurveGenerateKeyFail) {
EXPECT_CALL(platform_keys_service_, GenerateECKey)
.WillOnce(
RunOnceCallback<2>(std::vector<uint8_t>(), Status::kErrorInternal));
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
keystore_service_.GenerateKey(mojom::KeystoreType::kUser,
MakeEcKeystoreSigningAlgorithm("named_curve_1"),
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(), mojom::KeystoreError::kInternal);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, SignRsaSuccess) {
// Accepted and returned data are the same. This is not realistic, but doesn't
// matter here.
EXPECT_CALL(
platform_keys_service_,
SignRsaPkcs1(std::optional<TokenId>(TokenId::kUser), GetDataBin(),
GetPublicKeyBin(), HashAlgorithm::HASH_ALGORITHM_SHA256,
/*callback=*/_))
.WillOnce(RunOnceCallback<4>(GetDataBin(), Status::kSuccess));
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
keystore_service_.Sign(
/*is_keystore_provided=*/true, mojom::KeystoreType::kUser,
GetPublicKeyBin(), mojom::KeystoreSigningScheme::kRsassaPkcs1V15Sha256,
GetDataBin(), observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertBlobEq(observer.result.value(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, SignEcSuccess) {
// Accepted and returned data are the same. This is not realistic, but doesn't
// matter here.
EXPECT_CALL(platform_keys_service_,
SignEcdsa(std::optional<TokenId>(TokenId::kSystem), GetDataBin(),
GetPublicKeyBin(), HashAlgorithm::HASH_ALGORITHM_SHA512,
/*callback=*/_))
.WillOnce(RunOnceCallback<4>(GetDataBin(), Status::kSuccess));
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
keystore_service_.Sign(
/*is_keystore_provided=*/true, mojom::KeystoreType::kDevice,
GetPublicKeyBin(), mojom::KeystoreSigningScheme::kEcdsaSha512,
GetDataBin(), observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertBlobEq(observer.result.value(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, UsingkRsassaPkcs1V15NoneSignSuccess) {
EXPECT_CALL(platform_keys_service_,
SignRSAPKCS1Raw(std::optional<TokenId>(TokenId::kSystem),
GetDataBin(), GetPublicKeyBin(),
/*callback=*/_))
.WillOnce(RunOnceCallback<3>(GetDataBin(), Status::kSuccess));
mojom::KeystoreSigningScheme sign_scheme =
mojom::KeystoreSigningScheme::kRsassaPkcs1V15None;
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
keystore_service_.Sign(
/*is_keystore_provided=*/true, mojom::KeystoreType::kDevice,
GetPublicKeyBin(), sign_scheme, GetDataBin(), observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
AssertBlobEq(observer.result.value(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, KeyNotAllowedSignFail) {
EXPECT_CALL(platform_keys_service_, SignEcdsa)
.WillOnce(RunOnceCallback<4>(std::vector<uint8_t>(),
Status::kErrorKeyNotAllowedForSigning));
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
keystore_service_.Sign(
/*is_keystore_provided=*/true, mojom::KeystoreType::kDevice,
GetPublicKeyBin(), mojom::KeystoreSigningScheme::kEcdsaSha512,
GetDataBin(), observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(),
mojom::KeystoreError::kKeyNotAllowedForSigning);
}
TEST_F(KeystoreServiceAshTest, UnknownSignSchemeSignFail) {
CallbackObserver<mojom::KeystoreBinaryResultPtr> observer;
mojom::KeystoreSigningScheme unknown_sign_scheme =
mojom::KeystoreSigningScheme::kUnknown;
keystore_service_.Sign(
/*is_keystore_provided=*/true, mojom::KeystoreType::kDevice,
GetPublicKeyBin(), unknown_sign_scheme, GetDataBin(),
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
AssertErrorEq(observer.result.value(),
mojom::KeystoreError::kUnsupportedAlgorithmType);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, RemoveKeySuccess) {
EXPECT_CALL(platform_keys_service_,
RemoveKey(TokenId::kSystem, GetPublicKeyBin(), /*callback=*/_))
.WillOnce(RunOnceCallback<2>(Status::kSuccess));
StatusCallbackObserver observer;
keystore_service_.RemoveKey(mojom::KeystoreType::kDevice, GetPublicKeyBin(),
observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, false);
}
TEST_F(KeystoreServiceAshTest, RemoveKeyFail) {
EXPECT_CALL(platform_keys_service_,
RemoveKey(TokenId::kSystem, GetPublicKeyBin(), /*callback=*/_))
.WillOnce(RunOnceCallback<2>(Status::kErrorKeyNotFound));
StatusCallbackObserver observer;
keystore_service_.RemoveKey(mojom::KeystoreType::kDevice, GetPublicKeyBin(),
observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, true);
EXPECT_EQ(observer.result_error, mojom::KeystoreError::kKeyNotFound);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, SelectClientCertificatesSuccess) {
std::vector<std::vector<uint8_t>> cert_authorities_bin = {
{1, 2, 3}, {2, 3, 4}, {3, 4, 5}};
std::vector<std::string> cert_authorities_str = {"\1\2\3", "\2\3\4",
"\3\4\5"};
EXPECT_CALL(platform_keys_service_,
SelectClientCertificates(cert_authorities_str,
/*callback=*/_))
.WillOnce(WithArg<1>([](auto callback) {
std::move(callback).Run(GetCertificateList(), Status::kSuccess);
}));
CallbackObserver<mojom::KeystoreSelectClientCertificatesResultPtr> observer;
keystore_service_.SelectClientCertificates(cert_authorities_bin,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_certificates());
AssertCertListEq(observer.result.value()->get_certificates(),
GetCertificateList());
}
TEST_F(KeystoreServiceAshTest, SelectClientCertificatesFail) {
EXPECT_CALL(platform_keys_service_, SelectClientCertificates)
.WillOnce(WithArg<1>([](auto callback) {
std::move(callback).Run({}, Status::kErrorInternal);
}));
CallbackObserver<mojom::KeystoreSelectClientCertificatesResultPtr> observer;
keystore_service_.SelectClientCertificates({}, observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(), mojom::KeystoreError::kInternal);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, GetKeyTagsSuccess) {
EXPECT_CALL(key_permissions_service_,
IsCorporateKey(GetPublicKeyBin(), /*callback=*/_))
.WillOnce(
RunOnceCallback<1>(std::optional<bool>(true), Status::kSuccess));
CallbackObserver<mojom::GetKeyTagsResultPtr> observer;
keystore_service_.GetKeyTags(GetPublicKeyBin(), observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_tags());
EXPECT_EQ(observer.result.value()->get_tags(),
static_cast<uint64_t>(mojom::KeyTag::kCorporate));
}
TEST_F(KeystoreServiceAshTest, GetKeyTagsFail) {
EXPECT_CALL(key_permissions_service_, IsCorporateKey)
.WillOnce(RunOnceCallback<1>(std::nullopt, Status::kErrorInternal));
CallbackObserver<mojom::GetKeyTagsResultPtr> observer;
keystore_service_.GetKeyTags(GetPublicKeyBin(), observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(), mojom::KeystoreError::kInternal);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, AddKeyTagsSuccess) {
const uint64_t tags = static_cast<uint64_t>(mojom::KeyTag::kCorporate);
EXPECT_CALL(key_permissions_service_,
SetCorporateKey(GetPublicKeyBin(), /*callback=*/_))
.WillOnce(RunOnceCallback<1>(Status::kSuccess));
StatusCallbackObserver observer;
keystore_service_.AddKeyTags(GetPublicKeyBin(), tags, observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, false);
}
TEST_F(KeystoreServiceAshTest, AddKeyTagsFail) {
const uint64_t tags = static_cast<uint64_t>(mojom::KeyTag::kCorporate);
EXPECT_CALL(key_permissions_service_,
SetCorporateKey(GetPublicKeyBin(), /*callback=*/_))
.WillOnce(RunOnceCallback<1>(Status::kErrorInternal));
StatusCallbackObserver observer;
keystore_service_.AddKeyTags(GetPublicKeyBin(), tags, observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, true);
EXPECT_EQ(observer.result_error, mojom::KeystoreError::kInternal);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, CanUserGrantPermissionForKey) {
EXPECT_CALL(key_permissions_service_,
CanUserGrantPermissionForKey(GetPublicKeyBin(), /*callback=*/_))
.WillOnce(RunOnceCallback<1>(false));
CallbackObserver<bool> observer;
keystore_service_.CanUserGrantPermissionForKey(GetPublicKeyBin(),
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
EXPECT_EQ(observer.result, false);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, GetPublicKeySuccess) {
const std::vector<uint8_t> cert_bin =
CertToBlob(GetCertificateList()->front());
CallbackObserver<mojom::GetPublicKeyResultPtr> observer;
keystore_service_.GetPublicKey(
cert_bin, mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_success_result());
const mojom::GetPublicKeySuccessResultPtr& success_result =
observer.result.value()->get_success_result();
ASSERT_EQ(success_result->public_key, GetPublicKeyBin());
ASSERT_TRUE(success_result->algorithm_properties->is_pkcs115());
const mojom::KeystorePKCS115ParamsPtr& params =
success_result->algorithm_properties->get_pkcs115();
EXPECT_EQ(params->modulus_length, 2048u);
EXPECT_EQ(params->public_exponent, (std::vector<uint8_t>{1, 0, 1}));
}
TEST_F(KeystoreServiceAshTest, WrongAlgoGetPublicKeyFail) {
const std::vector<uint8_t> cert_bin =
CertToBlob(GetCertificateList()->front());
CallbackObserver<mojom::GetPublicKeyResultPtr> observer;
keystore_service_.GetPublicKey(cert_bin,
mojom::KeystoreSigningAlgorithmName::kUnknown,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(),
mojom::KeystoreError::kAlgorithmNotPermittedByCertificate);
}
TEST_F(KeystoreServiceAshTest, BadCertificateGetPublicKeyFail) {
// Using some random sequence as certificate
const std::vector<uint8_t> bad_cert_bin = {10, 11, 12, 13, 14, 15};
CallbackObserver<mojom::GetPublicKeyResultPtr> observer;
keystore_service_.GetPublicKey(
bad_cert_bin, mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
AssertErrorEq(observer.result.value(),
mojom::KeystoreError::kCertificateInvalid);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, GetKeyStoresEmptySuccess) {
EXPECT_CALL(platform_keys_service_, GetTokens)
.WillOnce(RunOnceCallback<0>(std::vector<TokenId>({}), Status::kSuccess));
CallbackObserver<mojom::GetKeyStoresResultPtr> observer;
keystore_service_.GetKeyStores(observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_key_stores());
EXPECT_TRUE(observer.result.value()->get_key_stores().empty());
}
TEST_F(KeystoreServiceAshTest, GetKeyStoresUserSuccess) {
EXPECT_CALL(platform_keys_service_, GetTokens)
.WillOnce(RunOnceCallback<0>(std::vector<TokenId>({TokenId::kUser}),
Status::kSuccess));
CallbackObserver<mojom::GetKeyStoresResultPtr> observer;
keystore_service_.GetKeyStores(observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_key_stores());
EXPECT_THAT(observer.result.value()->get_key_stores(),
ElementsAre(crosapi::mojom::KeystoreType::kUser));
}
TEST_F(KeystoreServiceAshTest, GetKeyStoresDeviceSuccess) {
EXPECT_CALL(platform_keys_service_, GetTokens)
.WillOnce(RunOnceCallback<0>(std::vector<TokenId>({TokenId::kSystem}),
Status::kSuccess));
CallbackObserver<mojom::GetKeyStoresResultPtr> observer;
keystore_service_.GetKeyStores(observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_key_stores());
EXPECT_THAT(observer.result.value()->get_key_stores(),
ElementsAre(crosapi::mojom::KeystoreType::kDevice));
}
TEST_F(KeystoreServiceAshTest, GetKeyStoresDeviceUserSuccess) {
EXPECT_CALL(platform_keys_service_, GetTokens)
.WillOnce(RunOnceCallback<0>(
std::vector<TokenId>({TokenId::kUser, TokenId::kSystem}),
Status::kSuccess));
CallbackObserver<mojom::GetKeyStoresResultPtr> observer;
keystore_service_.GetKeyStores(observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_key_stores());
EXPECT_THAT(observer.result.value()->get_key_stores(),
UnorderedElementsAre(crosapi::mojom::KeystoreType::kUser,
crosapi::mojom::KeystoreType::kDevice));
}
TEST_F(KeystoreServiceAshTest, GetKeyStoresFail) {
EXPECT_CALL(platform_keys_service_, GetTokens)
.WillOnce(
RunOnceCallback<0>(std::vector<TokenId>({}), Status::kErrorInternal));
CallbackObserver<mojom::GetKeyStoresResultPtr> observer;
keystore_service_.GetKeyStores(observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(), mojom::KeystoreError::kInternal);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, GetCertificatesSuccess) {
EXPECT_CALL(platform_keys_service_,
GetCertificates(TokenId::kUser, /*callback=*/_))
.WillOnce(RunOnceCallback<1>(GetCertificateList(), Status::kSuccess));
CallbackObserver<mojom::GetCertificatesResultPtr> observer;
keystore_service_.GetCertificates(mojom::KeystoreType::kUser,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_certificates());
AssertCertListEq(observer.result.value()->get_certificates(),
GetCertificateList());
}
TEST_F(KeystoreServiceAshTest, InternalErrorThenGetCertificatesFail) {
EXPECT_CALL(platform_keys_service_,
GetCertificates(TokenId::kUser, /*callback=*/_))
.WillOnce(RunOnceCallback<1>(std::make_unique<net::CertificateList>(),
Status::kErrorInternal));
CallbackObserver<mojom::GetCertificatesResultPtr> observer;
keystore_service_.GetCertificates(mojom::KeystoreType::kUser,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(), mojom::KeystoreError::kInternal);
}
TEST_F(KeystoreServiceAshTest, UnsupportedKeystoreTypeThenGetCertificatesFail) {
CallbackObserver<mojom::GetCertificatesResultPtr> observer;
mojom::KeystoreType wrong_keystore_type = static_cast<mojom::KeystoreType>(2);
keystore_service_.GetCertificates(wrong_keystore_type,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
AssertErrorEq(observer.result.value(),
mojom::KeystoreError::kUnsupportedKeystoreType);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, AddCertificateSuccess) {
auto cert_list = GetCertificateList();
EXPECT_CALL(platform_keys_service_,
ImportCertificate(TokenId::kSystem, CertEq(cert_list->front()),
/*callback=*/_))
.WillOnce(RunOnceCallback<2>(Status::kSuccess));
StatusCallbackObserver observer;
keystore_service_.AddCertificate(mojom::KeystoreType::kDevice,
CertToBlob(cert_list->front()),
observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, false);
}
TEST_F(KeystoreServiceAshTest, WrongKeystoreTypeThenAddCertificateFail) {
auto valid_cert_blob = CertToBlob(GetCertificateList()->front());
StatusCallbackObserver observer;
mojom::KeystoreType wrong_keystore_type = static_cast<mojom::KeystoreType>(2);
keystore_service_.AddCertificate(wrong_keystore_type, valid_cert_blob,
observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, true);
EXPECT_EQ(observer.result_error,
mojom::KeystoreError::kUnsupportedKeystoreType);
}
TEST_F(KeystoreServiceAshTest, InvalidCertificateThenAddCertificateFail) {
auto valid_cert = GetCertificateList()->front();
StatusCallbackObserver observer;
// Mocking very long input as a reason for invalid certificate.
EXPECT_CALL(platform_keys_service_,
ImportCertificate(TokenId::kSystem, CertEq(valid_cert),
/*callback=*/_))
.WillOnce(RunOnceCallback<2>(Status::kErrorInputTooLong));
keystore_service_.AddCertificate(mojom::KeystoreType::kDevice,
CertToBlob(valid_cert),
observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, true);
EXPECT_EQ(observer.result_error, mojom::KeystoreError::kInputTooLong);
}
TEST_F(KeystoreServiceAshTest, NotParsebleCertThenAddCertificateFail) {
std::vector<uint8_t> empty_cert_blob;
StatusCallbackObserver observer;
keystore_service_.AddCertificate(mojom::KeystoreType::kDevice,
empty_cert_blob, observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, true);
EXPECT_EQ(observer.result_error, mojom::KeystoreError::kCertificateInvalid);
}
//------------------------------------------------------------------------------
TEST_F(KeystoreServiceAshTest, RemoveCertificateSuccess) {
auto cert_list = GetCertificateList();
EXPECT_CALL(platform_keys_service_,
RemoveCertificate(TokenId::kSystem, CertEq(cert_list->front()),
/*callback=*/_))
.WillOnce(RunOnceCallback<2>(Status::kSuccess));
StatusCallbackObserver observer;
keystore_service_.RemoveCertificate(mojom::KeystoreType::kDevice,
CertToBlob(cert_list->front()),
observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, false);
}
TEST_F(KeystoreServiceAshTest, RemoveCertificateFail) {
auto cert_list = GetCertificateList();
EXPECT_CALL(platform_keys_service_,
RemoveCertificate(TokenId::kSystem, CertEq(cert_list->front()),
/*callback=*/_))
.WillOnce(RunOnceCallback<2>(Status::kErrorCertificateInvalid));
StatusCallbackObserver observer;
keystore_service_.RemoveCertificate(mojom::KeystoreType::kDevice,
CertToBlob(cert_list->front()),
observer.GetCallback());
ASSERT_TRUE(observer.has_value());
EXPECT_EQ(observer.result_is_error, true);
EXPECT_EQ(observer.result_error, mojom::KeystoreError::kCertificateInvalid);
}
//------------------------------------------------------------------------------
ash::attestation::MockTpmChallengeKey* InjectMockChallengeKey() {
auto mock_challenge_key =
std::make_unique<ash::attestation::MockTpmChallengeKey>();
ash::attestation::MockTpmChallengeKey* challenge_key_ptr =
mock_challenge_key.get();
ash::attestation::TpmChallengeKeyFactory::SetForTesting(
std::move(mock_challenge_key));
return challenge_key_ptr;
}
TEST_F(KeystoreServiceAshTest, ChallengeUserKeyNoMigrateSuccess) {
// Incoming challenge and outgoing challenge response are imitated with the
// same data blob. It is not realistic, but good enough for this test.
ash::attestation::MockTpmChallengeKey* challenge_key_ptr =
InjectMockChallengeKey();
EXPECT_CALL(
*challenge_key_ptr,
BuildResponse(::attestation::ENTERPRISE_USER,
/*profile=*/_, /*callback=*/_, /*challenge=*/GetDataStr(),
/*register_key=*/false,
/*key_crypto_type=*/KEY_TYPE_RSA,
/*key_name=*/std::string(),
/*signals=*/_))
.WillOnce(RunOnceCallback<2>(
ash::attestation::TpmChallengeKeyResult::MakeChallengeResponse(
GetDataStr())));
CallbackObserver<mojom::ChallengeAttestationOnlyKeystoreResultPtr> observer;
keystore_service_.ChallengeAttestationOnlyKeystore(
mojom::KeystoreType::kUser, /*challenge=*/GetDataBin(), /*migrate=*/false,
mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_challenge_response());
EXPECT_EQ(observer.result.value()->get_challenge_response(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, ChallengeUserKeyMigrateSuccess) {
// Incoming challenge and outgoing challenge response are imitated with the
// same data blob. It is not realistic, but good enough for this test.
ash::attestation::MockTpmChallengeKey* challenge_key_ptr =
InjectMockChallengeKey();
EXPECT_CALL(
*challenge_key_ptr,
BuildResponse(::attestation::ENTERPRISE_USER,
/*profile=*/_, /*callback=*/_, /*challenge=*/GetDataStr(),
/*register_key=*/true,
/*key_crypto_type=*/KEY_TYPE_RSA,
/*key_name=*/std::string(),
/*signals=*/_))
.WillOnce(RunOnceCallback<2>(
ash::attestation::TpmChallengeKeyResult::MakeChallengeResponse(
GetDataStr())));
CallbackObserver<mojom::ChallengeAttestationOnlyKeystoreResultPtr> observer;
keystore_service_.ChallengeAttestationOnlyKeystore(
mojom::KeystoreType::kUser, /*challenge=*/GetDataBin(), /*migrate=*/true,
mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_challenge_response());
EXPECT_EQ(observer.result.value()->get_challenge_response(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, ChallengeDeviceKeyNoMigrateSuccess) {
// Incoming challenge and outgoing challenge response are imitated with the
// same data blob. It is not realistic, but good enough for this test.
ash::attestation::MockTpmChallengeKey* challenge_key_ptr =
InjectMockChallengeKey();
EXPECT_CALL(
*challenge_key_ptr,
BuildResponse(::attestation::ENTERPRISE_MACHINE,
/*profile=*/_, /*callback=*/_, /*challenge=*/GetDataStr(),
/*register_key=*/false,
/*key_crypto_type=*/KEY_TYPE_RSA,
/*key_name=*/std::string(),
/*signals=*/_))
.WillOnce(RunOnceCallback<2>(
ash::attestation::TpmChallengeKeyResult::MakeChallengeResponse(
GetDataStr())));
CallbackObserver<mojom::ChallengeAttestationOnlyKeystoreResultPtr> observer;
keystore_service_.ChallengeAttestationOnlyKeystore(
mojom::KeystoreType::kDevice, /*challenge=*/GetDataBin(),
/*migrate=*/false, mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_challenge_response());
EXPECT_EQ(observer.result.value()->get_challenge_response(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, ChallengeDeviceKeyMigrateSuccess) {
// Incoming challenge and outgoing challenge response are imitated with the
// same data blob. It is not realistic, but good enough for this test.
ash::attestation::MockTpmChallengeKey* challenge_key_ptr =
InjectMockChallengeKey();
EXPECT_CALL(
*challenge_key_ptr,
BuildResponse(::attestation::ENTERPRISE_MACHINE,
/*profile=*/_, /*callback=*/_, /*challenge=*/GetDataStr(),
/*register_key=*/true,
/*key_crypto_type=*/KEY_TYPE_RSA,
/*key_name=*/StrStartsWith("attest-ent-machine-keystore-"),
/*signals=*/_))
.WillOnce(RunOnceCallback<2>(
ash::attestation::TpmChallengeKeyResult::MakeChallengeResponse(
GetDataStr())));
CallbackObserver<mojom::ChallengeAttestationOnlyKeystoreResultPtr> observer;
keystore_service_.ChallengeAttestationOnlyKeystore(
mojom::KeystoreType::kDevice, /*challenge=*/GetDataBin(),
/*migrate=*/true, mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_challenge_response());
EXPECT_EQ(observer.result.value()->get_challenge_response(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, ChallengeUserEcdsaKeyMigrateSuccess) {
// Incoming challenge and outgoing challenge response are imitated with the
// same data blob. It is not realistic, but good enough for this test.
ash::attestation::MockTpmChallengeKey* challenge_key_ptr =
InjectMockChallengeKey();
EXPECT_CALL(
*challenge_key_ptr,
BuildResponse(::attestation::ENTERPRISE_USER,
/*profile=*/_, /*callback=*/_, /*challenge=*/GetDataStr(),
/*register_key=*/true,
/*key_crypto_type=*/KEY_TYPE_ECC,
/*key_name=*/std::string(),
/*signals=*/_))
.WillOnce(RunOnceCallback<2>(
ash::attestation::TpmChallengeKeyResult::MakeChallengeResponse(
GetDataStr())));
CallbackObserver<mojom::ChallengeAttestationOnlyKeystoreResultPtr> observer;
keystore_service_.ChallengeAttestationOnlyKeystore(
mojom::KeystoreType::kUser, /*challenge=*/GetDataBin(), /*migrate=*/true,
mojom::KeystoreSigningAlgorithmName::kEcdsa, observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_challenge_response());
EXPECT_EQ(observer.result.value()->get_challenge_response(), GetDataBin());
}
TEST_F(KeystoreServiceAshTest, ChallengeKeyFail) {
ash::attestation::MockTpmChallengeKey* challenge_key_ptr =
InjectMockChallengeKey();
auto challenge_result = ash::attestation::TpmChallengeKeyResult::MakeError(
ash::attestation::TpmChallengeKeyResultCode::kDbusError);
EXPECT_CALL(
*challenge_key_ptr,
BuildResponse(::attestation::ENTERPRISE_USER,
/*profile=*/_, /*callback=*/_, /*challenge=*/GetDataStr(),
/*register_key=*/false,
/*key_crypto_type=*/KEY_TYPE_RSA,
/*key_name=*/std::string(),
/*signals=*/_))
.WillOnce(RunOnceCallback<2>(challenge_result));
CallbackObserver<mojom::ChallengeAttestationOnlyKeystoreResultPtr> observer;
keystore_service_.ChallengeAttestationOnlyKeystore(
mojom::KeystoreType::kUser, /*challenge=*/GetDataBin(),
/*migrate=*/false, mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value() && observer.result.value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(),
challenge_result.GetErrorMessage());
}
TEST_F(KeystoreServiceAshTest, WrongKeystoreTypeChallengeFail) {
CallbackObserver<mojom::ChallengeAttestationOnlyKeystoreResultPtr> observer;
auto wrong_keystore_type = static_cast<mojom::KeystoreType>(3);
keystore_service_.ChallengeAttestationOnlyKeystore(
wrong_keystore_type, /*challenge=*/GetDataBin(),
/*migrate=*/false, mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(),
chromeos::platform_keys::KeystoreErrorToString(
mojom::KeystoreError::kUnsupportedKeystoreType));
}
// ---------------- Deprecated methods which should fail when they are called
TEST_F(KeystoreServiceAshTest, DeprecatedGetPublicKeyShouldFail) {
CallbackObserver<mojom::DEPRECATED_GetPublicKeyResultPtr> observer;
const std::vector<uint8_t> cert_bin =
CertToBlob(GetCertificateList()->front());
EXPECT_ERROR_LOG(
testing::HasSubstr("DEPRECATED_GetPublicKey method was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_GetPublicKey(
cert_bin, mojom::KeystoreSigningAlgorithmName::kRsassaPkcs115,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(), kDeprecatedMethodErr);
}
TEST_F(KeystoreServiceAshTest, DeprecatedExtensionSignCallShouldFail) {
CallbackObserver<mojom::DEPRECATED_ExtensionKeystoreBinaryResultPtr> observer;
EXPECT_ERROR_LOG(
testing::HasSubstr("DEPRECATED_ExtensionSign method was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_ExtensionSign(
mojom::KeystoreType::kDevice,
/*public_key=*/{1, 2, 3, 4, 5},
mojom::KeystoreSigningScheme::kRsassaPkcs1V15Sha256,
/*data=*/{10, 11, 12, 13, 14, 15}, /*extension_id*/ "123",
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(), kDeprecatedMethodErr);
}
TEST_F(KeystoreServiceAshTest,
DeprecatedChallengeAttestationOnlyKeystoreCallShouldFail) {
CallbackObserver<mojom::DEPRECATED_KeystoreStringResultPtr> observer;
const std::string challenge = "123";
EXPECT_ERROR_LOG(testing::HasSubstr(
"DEPRECATED_ChallengeAttestationOnlyKeystore was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_ChallengeAttestationOnlyKeystore(
challenge, mojom::KeystoreType::kDevice,
/*migrate=*/false, observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(), kDeprecatedMethodErr);
}
TEST_F(KeystoreServiceAshTest, DeprecatedGetKeyStoresCallShouldFail) {
CallbackObserver<mojom::DEPRECATED_GetKeyStoresResultPtr> observer;
EXPECT_ERROR_LOG(
testing::HasSubstr("DEPRECATED_GetKeyStores method was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_GetKeyStores(observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(), kDeprecatedMethodErr);
}
TEST_F(KeystoreServiceAshTest, DeprecatedAddCertificateCallShouldFail) {
auto cert_list = GetCertificateList();
CallbackObserverRef<std::string> observer;
EXPECT_ERROR_LOG(
testing::HasSubstr("DEPRECATED_AddCertificate method was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_AddCertificate(mojom::KeystoreType::kDevice,
CertToBlob(cert_list->front()),
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
EXPECT_EQ(observer.result.value(), kDeprecatedMethodErr);
}
TEST_F(KeystoreServiceAshTest, DeprecatedGetCertificatesCallShouldFail) {
CallbackObserver<mojom::DEPRECATED_GetCertificatesResultPtr> observer;
EXPECT_ERROR_LOG(
testing::HasSubstr("DEPRECATED_GetCertificates method was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_GetCertificates(mojom::KeystoreType::kUser,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(), kDeprecatedMethodErr);
}
TEST_F(KeystoreServiceAshTest, DeprecatedRemoveCertificateShouldFail) {
auto cert_list = GetCertificateList();
CallbackObserverRef<std::string> observer;
EXPECT_ERROR_LOG(
testing::HasSubstr("DEPRECATED_RemoveCertificate method was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_RemoveCertificate(mojom::KeystoreType::kDevice,
CertToBlob(cert_list->front()),
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
EXPECT_EQ(observer.result, kDeprecatedMethodErr);
}
TEST_F(KeystoreServiceAshTest, DeprecatedExtensionGenerateKeyCallShouldFail) {
auto cert_list = GetCertificateList();
CallbackObserver<mojom::DEPRECATED_ExtensionKeystoreBinaryResultPtr> observer;
const std::optional<std::string>& extension_id = "123";
crosapi::mojom::KeystorePKCS115ParamsPtr params =
crosapi::mojom::KeystorePKCS115Params::New();
params->modulus_length = 1024;
crosapi::mojom::KeystoreSigningAlgorithmPtr algo =
crosapi::mojom::KeystoreSigningAlgorithm::NewPkcs115(std::move(params));
EXPECT_ERROR_LOG(
testing::HasSubstr("DEPRECATED_ExtensionGenerateKey method was called."));
log_.StartCapturingLogs();
keystore_service_.DEPRECATED_ExtensionGenerateKey(
mojom::KeystoreType::kDevice, std::move(algo), extension_id,
observer.GetCallback());
ASSERT_TRUE(observer.result.has_value());
ASSERT_TRUE(observer.result.value()->is_error_message());
EXPECT_EQ(observer.result.value()->get_error_message(), kDeprecatedMethodErr);
}
//------------------------------------------------------------------------------
} // namespace
} // namespace crosapi