blob: b17ccd2e1c5d3bb7f79b532661202054b721d9db [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <tuple>
#include "chrome/browser/webauthn/local_credential_management_mac.h"
#include "build/build_config.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "device/fido/mac/authenticator_config.h"
#include "device/fido/mac/credential_store.h"
#include "device/fido/mac/fake_keychain.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace {
static const device::PublicKeyCredentialUserEntity kUser({1, 2, 3},
"doe@example.com",
"John Doe");
constexpr char kRpId[] = "example.com";
class LocalCredentialManagementTest : public testing::Test {
protected:
LocalCredentialManagementTest() = default;
content::BrowserTaskEnvironment task_environment_;
TestingProfile profile_;
device::fido::mac::AuthenticatorConfig config_{
.keychain_access_group = "test-keychain-access-group",
.metadata_secret = "TestMetadataSecret"};
LocalCredentialManagementMac local_cred_man_{config_};
device::fido::mac::ScopedFakeKeychain keychain_{
config_.keychain_access_group};
device::fido::mac::TouchIdCredentialStore store_{config_};
};
TEST_F(LocalCredentialManagementTest, NoCredentials) {
device::test::TestCallbackReceiver<bool> callback;
local_cred_man_.HasCredentials(callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_FALSE(std::get<0>(callback.TakeResult()));
device::test::TestCallbackReceiver<
absl::optional<std::vector<device::DiscoverableCredentialMetadata>>>
enumerate_callback;
local_cred_man_.Enumerate(enumerate_callback.callback());
EXPECT_FALSE(enumerate_callback.was_called());
enumerate_callback.WaitForCallback();
auto result = std::get<0>(enumerate_callback.TakeResult());
ASSERT_TRUE(result.has_value());
EXPECT_TRUE(result->empty());
}
TEST_F(LocalCredentialManagementTest, OneCredential) {
device::test::TestCallbackReceiver<bool> callback;
auto credential = store_.CreateCredential(
kRpId, kUser, device::fido::mac::TouchIdCredentialStore::kDiscoverable);
EXPECT_TRUE(credential);
local_cred_man_.HasCredentials(callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_TRUE(std::get<0>(callback.TakeResult()));
device::test::TestCallbackReceiver<
absl::optional<std::vector<device::DiscoverableCredentialMetadata>>>
enumerate_callback;
local_cred_man_.Enumerate(enumerate_callback.callback());
EXPECT_FALSE(enumerate_callback.was_called());
enumerate_callback.WaitForCallback();
const absl::optional<std::vector<device::DiscoverableCredentialMetadata>>
result = std::get<0>(enumerate_callback.TakeResult());
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result->size(), 1u);
EXPECT_EQ(result->at(0).rp_id, kRpId);
}
TEST_F(LocalCredentialManagementTest, DeleteCredential) {
auto credential = store_.CreateCredential(
kRpId, kUser, device::fido::mac::TouchIdCredentialStore::kDiscoverable);
ASSERT_TRUE(credential);
auto credentials = store_.FindResidentCredentials(kRpId);
ASSERT_TRUE(credentials);
EXPECT_EQ(credentials->size(), 1u);
{
device::test::TestCallbackReceiver<bool> callback;
local_cred_man_.Delete(credential->first.credential_id,
callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_TRUE(std::get<0>(callback.TakeResult()));
}
credentials = store_.FindResidentCredentials(kRpId);
ASSERT_TRUE(credentials);
EXPECT_EQ(store_.FindResidentCredentials(kRpId)->size(), 0u);
{
device::test::TestCallbackReceiver<bool> callback;
local_cred_man_.Delete(std::vector<uint8_t>{8}, callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_FALSE(std::get<0>(callback.TakeResult()));
}
}
TEST_F(LocalCredentialManagementTest, EditCredential) {
device::test::TestCallbackReceiver<bool> callback;
auto credential = store_.CreateCredential(
kRpId, kUser, device::fido::mac::TouchIdCredentialStore::kDiscoverable);
ASSERT_TRUE(credential);
auto credentials =
device::fido::mac::TouchIdCredentialStore::FindCredentialsForTesting(
config_, kRpId);
EXPECT_EQ(credentials.size(), 1u);
local_cred_man_.Edit(credential->first.credential_id, "new-username",
callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_TRUE(std::get<0>(callback.TakeResult()));
credentials =
device::fido::mac::TouchIdCredentialStore::FindCredentialsForTesting(
config_, kRpId);
EXPECT_EQ(credentials.size(), 1u);
EXPECT_EQ(credentials.front().metadata.user_name, "new-username");
}
TEST_F(LocalCredentialManagementTest, EditLongCredential) {
device::test::TestCallbackReceiver<bool> callback;
auto credential = store_.CreateCredential(
kRpId, kUser, device::fido::mac::TouchIdCredentialStore::kDiscoverable);
ASSERT_TRUE(credential);
auto credentials =
device::fido::mac::TouchIdCredentialStore::FindCredentialsForTesting(
config_, kRpId);
EXPECT_EQ(credentials.size(), 1u);
local_cred_man_.Edit(
credential->first.credential_id,
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_TRUE(std::get<0>(callback.TakeResult()));
credentials =
device::fido::mac::TouchIdCredentialStore::FindCredentialsForTesting(
config_, kRpId);
EXPECT_EQ(credentials.size(), 1u);
EXPECT_EQ(
credentials.front().metadata.user_name,
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…");
}
TEST_F(LocalCredentialManagementTest, EditUnknownCredential) {
device::test::TestCallbackReceiver<bool> callback;
auto credential = store_.CreateCredential(
kRpId, kUser, device::fido::mac::TouchIdCredentialStore::kDiscoverable);
ASSERT_TRUE(credential);
auto credentials =
device::fido::mac::TouchIdCredentialStore::FindCredentialsForTesting(
config_, kRpId);
EXPECT_EQ(credentials.size(), 1u);
uint8_t credential_id[] = {0xa};
local_cred_man_.Edit(credential_id, "new-username", callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_FALSE(std::get<0>(callback.TakeResult()));
}
class ScopedMockKeychain : device::fido::mac::Keychain {
public:
ScopedMockKeychain() { SetInstanceOverride(this); }
~ScopedMockKeychain() override { ClearInstanceOverride(); }
MOCK_METHOD(OSStatus,
ItemCopyMatching,
(CFDictionaryRef query, CFTypeRef* result),
(override));
};
class MockKeychainLocalCredentialManagementTest : public testing::Test {
protected:
content::BrowserTaskEnvironment task_environment_;
ScopedMockKeychain mock_keychain_;
LocalCredentialManagementMac local_cred_man_{
{.keychain_access_group = "test-keychain-access-group",
.metadata_secret = "TestMetadataSecret"}};
};
// Regression test for crbug.com/1401342.
TEST_F(MockKeychainLocalCredentialManagementTest, KeychainError) {
EXPECT_CALL(mock_keychain_, ItemCopyMatching)
.WillOnce(testing::Return(errSecInternalComponent));
device::test::TestCallbackReceiver<bool> callback;
local_cred_man_.HasCredentials(callback.callback());
EXPECT_FALSE(callback.was_called());
callback.WaitForCallback();
EXPECT_FALSE(std::get<0>(callback.TakeResult()));
testing::Mock::VerifyAndClearExpectations(&mock_keychain_);
EXPECT_CALL(mock_keychain_, ItemCopyMatching)
.WillOnce(testing::Return(errSecInternalComponent));
device::test::TestCallbackReceiver<
absl::optional<std::vector<device::DiscoverableCredentialMetadata>>>
enumerate_callback;
local_cred_man_.Enumerate(enumerate_callback.callback());
EXPECT_FALSE(enumerate_callback.was_called());
enumerate_callback.WaitForCallback();
auto result = std::get<0>(enumerate_callback.TakeResult());
EXPECT_FALSE(result.has_value());
}
} // namespace