blob: da01ddb15ac14f2672f8c775188346cfdf4bbb91 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <absl/types/variant.h>
#include <cryptohome/proto_bindings/auth_factor.pb.h>
#include <libhwsec-foundation/error/testing_helper.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cryptohome/auth_factor/auth_factor_metadata.h"
#include "cryptohome/auth_factor/auth_factor_type.h"
#include "cryptohome/auth_factor/auth_factor_utils.h"
#include "cryptohome/mock_platform.h"
namespace cryptohome {
namespace {
using ::brillo::SecureBlob;
using ::hwsec_foundation::error::testing::IsOk;
using ::testing::_;
using ::testing::IsEmpty;
using ::testing::NiceMock;
using ::testing::Return;
constexpr char kLabel[] = "some-label";
constexpr char kPinLabel[] = "some-pin-label";
constexpr char kObfuscatedUsername[] = "obfuscated";
std::unique_ptr<AuthFactor> CreatePasswordAuthFactor() {
AuthFactorMetadata metadata = {.metadata = PasswordAuthFactorMetadata()};
return std::make_unique<AuthFactor>(
AuthFactorType::kPassword, kLabel, metadata,
AuthBlockState{
.state = TpmBoundToPcrAuthBlockState{
.scrypt_derived = false,
.salt = SecureBlob("fake salt"),
.tpm_key = SecureBlob("fake tpm key"),
.extended_tpm_key = SecureBlob("fake extended tpm key"),
.tpm_public_key_hash = SecureBlob("fake tpm public key hash"),
}});
}
std::unique_ptr<AuthFactor> CreatePinAuthFactor() {
AuthFactorMetadata metadata = {.metadata = PinAuthFactorMetadata()};
return std::make_unique<AuthFactor>(
AuthFactorType::kPin, kPinLabel, metadata,
AuthBlockState{.state = PinWeaverAuthBlockState{
.le_label = 0xbaadf00d,
.salt = SecureBlob("fake salt"),
.chaps_iv = SecureBlob("fake chaps IV"),
.fek_iv = SecureBlob("fake file encryption IV"),
.reset_salt = SecureBlob("more fake salt"),
}});
}
} // namespace
TEST(AuthFactorUtilsTest, AuthFactorTypeConversionIsInvertable) {
// Test a round trip of conversion gets back the original types.
EXPECT_EQ(
AuthFactorTypeFromProto(AuthFactorTypeToProto(AuthFactorType::kPassword)),
AuthFactorType::kPassword);
EXPECT_EQ(
AuthFactorTypeFromProto(AuthFactorTypeToProto(AuthFactorType::kPin)),
AuthFactorType::kPin);
EXPECT_EQ(AuthFactorTypeFromProto(
AuthFactorTypeToProto(AuthFactorType::kCryptohomeRecovery)),
AuthFactorType::kCryptohomeRecovery);
EXPECT_EQ(AuthFactorTypeToProto(*AuthFactorTypeFromProto(
user_data_auth::AUTH_FACTOR_TYPE_PASSWORD)),
user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
EXPECT_EQ(AuthFactorTypeToProto(
*AuthFactorTypeFromProto(user_data_auth::AUTH_FACTOR_TYPE_PIN)),
user_data_auth::AUTH_FACTOR_TYPE_PIN);
EXPECT_EQ(AuthFactorTypeToProto(*AuthFactorTypeFromProto(
user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY)),
user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY);
EXPECT_EQ(AuthFactorTypeToProto(*AuthFactorTypeFromProto(
user_data_auth::AUTH_FACTOR_TYPE_KIOSK)),
user_data_auth::AUTH_FACTOR_TYPE_KIOSK);
EXPECT_EQ(AuthFactorTypeToProto(*AuthFactorTypeFromProto(
user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD)),
user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD);
// These proto types are known to not be supported
EXPECT_EQ(
AuthFactorTypeFromProto(user_data_auth::AUTH_FACTOR_TYPE_UNSPECIFIED),
AuthFactorType::kUnspecified);
}
TEST(AuthFactorUtilsTest, AuthFactorTypeConversionFromProtoCoversAllValues) {
// With proto enums we can't use a "complete" switch to cover every value so
// we enfore that every value is given an explicit mapping (even if just to
// Unspecified) via this test.
for (int raw_type = user_data_auth::AuthFactorType_MIN;
raw_type <= user_data_auth::AuthFactorType_MAX; ++raw_type) {
if (!user_data_auth::AuthFactorType_IsValid(raw_type)) {
continue;
}
auto type = static_cast<user_data_auth::AuthFactorType>(raw_type);
EXPECT_NE(AuthFactorTypeFromProto(type), std::nullopt)
<< "user_data_auth::AuthFactorType has no mapping for "
<< user_data_auth::AuthFactorType_Name(type);
}
}
TEST(AuthFactorUtilsTest, AuthFactorMetaDataCheck) {
// Setup
user_data_auth::AuthFactor auth_factor_proto;
auth_factor_proto.mutable_password_metadata();
auth_factor_proto.set_type(user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
auth_factor_proto.set_label(kLabel);
// Test
AuthFactorMetadata auth_factor_metadata;
AuthFactorType auth_factor_type;
std::string auth_factor_label;
EXPECT_TRUE(GetAuthFactorMetadata(auth_factor_proto, auth_factor_metadata,
auth_factor_type, auth_factor_label));
// Verify
EXPECT_TRUE(absl::holds_alternative<PasswordAuthFactorMetadata>(
auth_factor_metadata.metadata));
EXPECT_EQ(auth_factor_type, AuthFactorType::kPassword);
EXPECT_EQ(auth_factor_label, kLabel);
}
// Test `GetAuthFactorProto()` for a password auth factor.
TEST(AuthFactorUtilsTest, GetProtoPassword) {
// Setup
AuthFactorMetadata metadata = {.metadata = PasswordAuthFactorMetadata()};
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kPassword, kLabel);
// Verify
ASSERT_TRUE(proto.has_value());
EXPECT_EQ(proto.value().type(), user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
EXPECT_EQ(proto.value().label(), kLabel);
ASSERT_TRUE(proto.value().has_password_metadata());
}
// Test `GetAuthFactorProto()` fails when the password metadata is missing.
TEST(AuthFactorUtilsTest, GetProtoPasswordErrorNoMetadata) {
// Setup
AuthFactorMetadata metadata;
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kPassword, kLabel);
// Verify
EXPECT_FALSE(proto.has_value());
}
// Test `LoadUserAuthFactorProtos()` with no auth factors available.
TEST(AuthFactorUtilsTest, LoadUserAuthFactorProtosNoFactors) {
// Setup
NiceMock<MockPlatform> platform;
AuthFactorManager manager(&platform);
google::protobuf::RepeatedPtrField<user_data_auth::AuthFactor> protos;
// Test
LoadUserAuthFactorProtos(&manager, kObfuscatedUsername, &protos);
// Verify
EXPECT_THAT(protos, IsEmpty());
}
// Test `LoadUserAuthFactorProtos()` with an some auth factors available.
TEST(AuthFactorUtilsTest, LoadUserAuthFactorProtosWithFactors) {
// Setup
NiceMock<MockPlatform> platform;
AuthFactorManager manager(&platform);
auto factor1 = CreatePasswordAuthFactor();
ASSERT_THAT(manager.SaveAuthFactor(kObfuscatedUsername, *factor1), IsOk());
auto factor2 = CreatePinAuthFactor();
ASSERT_THAT(manager.SaveAuthFactor(kObfuscatedUsername, *factor2), IsOk());
google::protobuf::RepeatedPtrField<user_data_auth::AuthFactor> protos;
// Test
LoadUserAuthFactorProtos(&manager, kObfuscatedUsername, &protos);
// Sort the protos by label. This is done to produce a consistent ordering
// which makes it easier to verify the results.
std::sort(protos.pointer_begin(), protos.pointer_end(),
[](const user_data_auth::AuthFactor* lhs,
const user_data_auth::AuthFactor* rhs) {
return lhs->label() < rhs->label();
});
// Verify
ASSERT_EQ(protos.size(), 2);
EXPECT_EQ(protos[0].label(), kLabel);
EXPECT_TRUE(protos[0].has_password_metadata());
EXPECT_EQ(protos[1].label(), kPinLabel);
EXPECT_TRUE(protos[1].has_pin_metadata());
}
// Test `LoadUserAuthFactorProtos()` with some auth factors that we can't read.
TEST(AuthFactorUtilsTest, LoadUserAuthFactorProtosWithUnreadableFactors) {
// Setup
NiceMock<MockPlatform> platform;
AuthFactorManager manager(&platform);
auto factor1 = CreatePasswordAuthFactor();
ASSERT_THAT(manager.SaveAuthFactor(kObfuscatedUsername, *factor1), IsOk());
auto factor2 = CreatePinAuthFactor();
ASSERT_THAT(manager.SaveAuthFactor(kObfuscatedUsername, *factor2), IsOk());
google::protobuf::RepeatedPtrField<user_data_auth::AuthFactor> protos;
// Make all file reads fail now, so that we can't read the auth factors.
EXPECT_CALL(platform, ReadFile(_, _)).WillRepeatedly(Return(false));
// Test
LoadUserAuthFactorProtos(&manager, kObfuscatedUsername, &protos);
// Verify
EXPECT_THAT(protos, IsEmpty());
}
// Test `GetAuthFactorProto()` for a pin auth factor.
TEST(AuthFactorUtilsTest, GetProtoPin) {
// Setup
AuthFactorMetadata metadata = {.metadata = PinAuthFactorMetadata()};
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kPin, kLabel);
// Verify
ASSERT_TRUE(proto.has_value());
EXPECT_EQ(proto.value().type(), user_data_auth::AUTH_FACTOR_TYPE_PIN);
EXPECT_EQ(proto.value().label(), kLabel);
ASSERT_TRUE(proto.value().has_pin_metadata());
}
// Test `GetAuthFactorProto()` for a kiosk auth factor.
TEST(AuthFactorUtilsTest, GetProtoKiosk) {
// Setup
AuthFactorMetadata metadata = {.metadata = KioskAuthFactorMetadata()};
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kKiosk, kLabel);
// Verify
ASSERT_TRUE(proto.has_value());
EXPECT_EQ(proto.value().type(), user_data_auth::AUTH_FACTOR_TYPE_KIOSK);
EXPECT_EQ(proto.value().label(), kLabel);
ASSERT_TRUE(proto.value().has_kiosk_metadata());
}
// Test `GetAuthFactorProto()` for a recovery auth factor.
TEST(AuthFactorUtilsTest, GetProtoRecovery) {
// Setup
AuthFactorMetadata metadata = {.metadata =
CryptohomeRecoveryAuthFactorMetadata()};
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kCryptohomeRecovery, kLabel);
// Verify
ASSERT_TRUE(proto.has_value());
EXPECT_EQ(proto.value().type(),
user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY);
EXPECT_EQ(proto.value().label(), kLabel);
ASSERT_TRUE(proto.value().has_cryptohome_recovery_metadata());
}
// Test `GetAuthFactorProto()` for when pin auth factor does not have metadata.
TEST(AuthFactorUtilsTest, GetProtoPinNullOpt) {
// Setup
AuthFactorMetadata metadata;
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kPin, kLabel);
// Verify
ASSERT_FALSE(proto.has_value());
}
// Test `GetAuthFactorProto()` for when kiosk auth factor does not have
// metadata.
TEST(AuthFactorUtilsTest, GetProtoKioskNullOpt) {
// Setup
AuthFactorMetadata metadata;
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kKiosk, kLabel);
// Verify
ASSERT_FALSE(proto.has_value());
}
// Test `GetAuthFactorProto()` for when recovery auth factor does not have
// metadata.
TEST(AuthFactorUtilsTest, GetProtoRecoveryNullOpt) {
// Setup
AuthFactorMetadata metadata;
// Test
std::optional<user_data_auth::AuthFactor> proto =
GetAuthFactorProto(metadata, AuthFactorType::kCryptohomeRecovery, kLabel);
// Verify
ASSERT_FALSE(proto.has_value());
}
// Test `NeedsResetSecret()` to return correct value.
TEST(AuthFactorUtilsTest, NeedsResetSecret) {
EXPECT_TRUE(NeedsResetSecret(AuthFactorType::kPin));
EXPECT_FALSE(NeedsResetSecret(AuthFactorType::kPassword));
EXPECT_FALSE(NeedsResetSecret(AuthFactorType::kKiosk));
EXPECT_FALSE(NeedsResetSecret(AuthFactorType::kCryptohomeRecovery));
EXPECT_FALSE(NeedsResetSecret(AuthFactorType::kSmartCard));
EXPECT_FALSE(NeedsResetSecret(AuthFactorType::kUnspecified));
static_assert(static_cast<int>(AuthFactorType::kUnspecified) == 5,
"All types of AuthFactorType are not all included here");
}
} // namespace cryptohome