blob: a3e4eb6b9c5cc99b65870f4b9a121bb600fe54ca [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "biod/cros_fp_biometrics_manager.h"
#include <algorithm>
#include <utility>
#include <base/bind.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_object_proxy.h>
#include <gtest/gtest.h>
#include "biod/cros_fp_device_interface.h"
namespace biod {
namespace {
constexpr int kMaxTemplateCount = 5;
constexpr char kRecordID[] = "record0";
constexpr char kUserID[] = "0123456789";
constexpr char kLabel[] = "label0";
const std::vector<uint8_t> kFakePositiveMatchSecret1 = {0x00, 0x01, 0x02};
const std::vector<uint8_t> kFakePositiveMatchSecret2 = {0xcc, 0xdd, 0xee, 0xff};
// Validation value corresponding to kFakePositiveMatchSecret1 and kUserID.
const std::vector<uint8_t> kFakeValidationValue1 = {
0x90, 0xea, 0xfb, 0x75, 0xee, 0x37, 0xeb, 0xb1, 0xb5, 0xe7, 0x81,
0x47, 0xac, 0xdd, 0xff, 0xbe, 0x20, 0x59, 0x25, 0x24, 0x82, 0xe0,
0x05, 0xdd, 0x95, 0x09, 0x8e, 0x5a, 0xdc, 0xcc, 0x12, 0x9f,
};
// Validation value corresponding to kFakePositiveMatchSecret2 and kUserID.
const std::vector<uint8_t> kFakeValidationValue2 = {
0xde, 0xe9, 0x4d, 0xbd, 0xbe, 0x63, 0x8b, 0x9e, 0xc9, 0x25, 0x27,
0xf1, 0xf6, 0x86, 0x6f, 0xb3, 0x31, 0xf6, 0xb6, 0x52, 0x99, 0x66,
0x89, 0x88, 0x73, 0x0a, 0xd4, 0x0b, 0xd2, 0x34, 0x7b, 0x71,
};
} // namespace
class FakeCrosFpDevice : public CrosFpDeviceInterface {
public:
FakeCrosFpDevice() { positive_match_secret_ = kFakePositiveMatchSecret1; }
// CrosFpDeviceInterface overrides:
~FakeCrosFpDevice() override = default;
bool SetFpMode(const FpMode& mode) override { return false; }
bool GetFpMode(FpMode* mode) override { return false; }
bool GetFpStats(int* capture_ms, int* matcher_ms, int* overall_ms) override {
return false;
}
bool GetDirtyMap(std::bitset<32>* bitmap) override { return false; }
bool SupportsPositiveMatchSecret() override { return true; }
bool GetPositiveMatchSecret(int index, brillo::SecureBlob* secret) override {
if (positive_match_secret_.empty())
return false;
secret->resize(FP_POSITIVE_MATCH_SECRET_BYTES);
// Zero-pad the secret if it's too short.
std::fill(secret->begin(), secret->end(), 0);
std::copy(positive_match_secret_.begin(), positive_match_secret_.end(),
secret->begin());
return true;
}
bool GetTemplate(int index, VendorTemplate* out) override { return true; }
bool UploadTemplate(const VendorTemplate& tmpl) override { return false; }
bool SetContext(std::string user_id) override { return false; }
bool ResetContext() override { return false; }
bool InitEntropy(bool reset) override { return false; }
int MaxTemplateCount() override { return kMaxTemplateCount; }
int TemplateVersion() override { return FP_TEMPLATE_FORMAT_VERSION; }
EcCmdVersionSupportStatus EcCmdVersionSupported(uint16_t cmd,
uint32_t ver) override {
return EcCmdVersionSupportStatus::UNSUPPORTED;
}
private:
friend class CrosFpBiometricsManagerPeer;
std::vector<uint8_t> positive_match_secret_;
};
class FakeCrosFpDeviceFactoryImpl : public CrosFpDeviceFactory {
std::unique_ptr<CrosFpDeviceInterface> Create(
const MkbpCallback& callback, BiodMetrics* biod_metrics) override {
return std::make_unique<FakeCrosFpDevice>();
}
};
// Using a peer class to control access to the class under test is better than
// making the text fixture a friend class.
class CrosFpBiometricsManagerPeer {
public:
CrosFpBiometricsManagerPeer() {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
const scoped_refptr<dbus::MockBus> mock_bus = new dbus::MockBus(options);
// Set EXPECT_CALL, otherwise gmock forces an failure due to "uninteresting
// call" because we use StrictMock.
// https://github.com/google/googletest/blob/fb49e6c164490a227bbb7cf5223b846c836a0305/googlemock/docs/cook_book.md#the-nice-the-strict-and-the-naggy-nicestrictnaggy
const scoped_refptr<dbus::MockObjectProxy> power_manager_proxy =
new dbus::MockObjectProxy(
mock_bus.get(), power_manager::kPowerManagerServiceName,
dbus::ObjectPath(power_manager::kPowerManagerServicePath));
EXPECT_CALL(*mock_bus,
GetObjectProxy(
power_manager::kPowerManagerServiceName,
dbus::ObjectPath(power_manager::kPowerManagerServicePath)))
.WillOnce(testing::Return(power_manager_proxy.get()));
cros_fp_biometrics_manager_ = CrosFpBiometricsManager::Create(
mock_bus, std::make_unique<FakeCrosFpDeviceFactoryImpl>());
// Keep a pointer to the fake device to manipulate it later.
fake_cros_dev_ = static_cast<FakeCrosFpDevice*>(
cros_fp_biometrics_manager_->cros_dev_.get());
}
// Methods to access or modify the fake device.
void SetDevicePositiveMatchSecret(const std::vector<uint8_t>& new_secret) {
fake_cros_dev_->positive_match_secret_ = new_secret;
}
// Methods to access or modify CrosFpBiometricsManager private fields.
bool SupportsPositiveMatchSecret() {
return cros_fp_biometrics_manager_->use_positive_match_secret_;
}
void SetUsePositiveMatchSecret(bool use) {
cros_fp_biometrics_manager_->use_positive_match_secret_ = use;
}
// Add a record to cros_fp_biometrics_manager_, return the index.
int AddRecord(int record_format_version,
const std::string& record_id,
const std::string& user_id,
const std::string& label,
const std::vector<uint8_t>& validation_value) {
CrosFpBiometricsManager::InternalRecord internal_record = {
record_format_version, record_id, user_id, label, validation_value};
cros_fp_biometrics_manager_->records_.emplace_back(
std::move(internal_record));
return cros_fp_biometrics_manager_->records_.size() - 1;
}
bool ValidationValueEquals(int index,
const std::vector<uint8_t>& reference_value) {
return cros_fp_biometrics_manager_->records_[index].validation_val ==
reference_value;
}
// Methods to execute CrosFpBiometricsManager private methods.
bool ComputeValidationValue(const std::vector<uint8_t>& secret,
const std::string& user_id,
std::vector<uint8_t>* out) {
const brillo::SecureBlob secret_blob(secret);
return cros_fp_biometrics_manager_->ComputeValidationValue(secret_blob,
user_id, out);
}
bool ValidationValueIsCorrect(uint32_t match_idx) {
return cros_fp_biometrics_manager_->ValidationValueIsCorrect(match_idx);
}
BiometricsManager::AttemptMatches CalculateMatches(int match_idx,
bool matched) {
return cros_fp_biometrics_manager_->CalculateMatches(match_idx, matched);
}
bool MigrateToValidationValue(int match_idx) {
CrosFpBiometricsManager::MigrationStatus status =
cros_fp_biometrics_manager_->MigrateToValidationValue(match_idx);
return status == CrosFpBiometricsManager::MigrationStatus::kSuccess;
}
static void InsertEmptyPositiveMatchSalt(VendorTemplate* tmpl) {
CrosFpBiometricsManager::InsertEmptyPositiveMatchSalt(tmpl);
}
bool RecordNeedsValidationValue(int index) {
CrosFpBiometricsManager::Record current_record(
cros_fp_biometrics_manager_->weak_factory_.GetWeakPtr(), index);
return current_record.NeedsNewValidationValue();
}
private:
std::unique_ptr<CrosFpBiometricsManager> cros_fp_biometrics_manager_;
FakeCrosFpDevice* fake_cros_dev_;
};
class CrosFpBiometricsManagerTest : public ::testing::Test {
public:
bool BytesAreZeros(const uint8_t* start, size_t size) {
const std::vector<uint8_t> zeros(size);
return std::memcmp(start, zeros.data(), size) == 0;
}
protected:
CrosFpBiometricsManagerPeer cros_fp_biometrics_manager_peer_;
};
TEST_F(CrosFpBiometricsManagerTest, TestComputeValidationValue) {
EXPECT_TRUE(cros_fp_biometrics_manager_peer_.SupportsPositiveMatchSecret());
const std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>
kSecretValidationValuePairs = {
std::make_pair(kFakePositiveMatchSecret1, kFakeValidationValue1),
std::make_pair(kFakePositiveMatchSecret2, kFakeValidationValue2),
};
for (const auto& pair : kSecretValidationValuePairs) {
std::vector<uint8_t> validation_value;
EXPECT_TRUE(cros_fp_biometrics_manager_peer_.ComputeValidationValue(
pair.first, kUserID, &validation_value));
EXPECT_EQ(validation_value, pair.second);
}
}
TEST_F(CrosFpBiometricsManagerTest, TestValidationValueIsCorrect) {
ASSERT_TRUE(cros_fp_biometrics_manager_peer_.SupportsPositiveMatchSecret());
cros_fp_biometrics_manager_peer_.SetDevicePositiveMatchSecret(
kFakePositiveMatchSecret1);
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersion, kRecordID, kUserID, kLabel, kFakeValidationValue1);
bool ret = cros_fp_biometrics_manager_peer_.ValidationValueIsCorrect(index);
EXPECT_TRUE(ret);
// Make the device return a wrong positive_match_secret.
cros_fp_biometrics_manager_peer_.SetDevicePositiveMatchSecret(
kFakePositiveMatchSecret2);
ret = cros_fp_biometrics_manager_peer_.ValidationValueIsCorrect(index);
EXPECT_FALSE(ret);
}
TEST_F(CrosFpBiometricsManagerTest, TestCalculateMatchesNotMatched) {
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersion, kRecordID, kUserID, kLabel, kFakeValidationValue1);
BiometricsManager::AttemptMatches matches =
cros_fp_biometrics_manager_peer_.CalculateMatches(index, false);
// If matched is false then we should report no matches.
EXPECT_TRUE(matches.empty());
}
TEST_F(CrosFpBiometricsManagerTest, TestCalculateMatchesInvalidIndex) {
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersion, kRecordID, kUserID, kLabel, kFakeValidationValue1);
BiometricsManager::AttemptMatches matches =
cros_fp_biometrics_manager_peer_.CalculateMatches(index + 1, true);
// If index is invalid then we should report no matches.
EXPECT_TRUE(matches.empty());
}
TEST_F(CrosFpBiometricsManagerTest,
TestCalculateMatchesWithPositiveMatchSecret) {
ASSERT_TRUE(cros_fp_biometrics_manager_peer_.SupportsPositiveMatchSecret());
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersion, kRecordID, kUserID, kLabel, kFakeValidationValue1);
BiometricsManager::AttemptMatches matches =
cros_fp_biometrics_manager_peer_.CalculateMatches(index, true);
EXPECT_EQ(matches,
BiometricsManager::AttemptMatches({{kUserID, {kRecordID}}}));
}
TEST_F(CrosFpBiometricsManagerTest,
TestCalculateMatchesWithoutPositiveMatchSecret) {
// If not supporting positive match secret, we should just report matches.
cros_fp_biometrics_manager_peer_.SetUsePositiveMatchSecret(false);
ASSERT_FALSE(cros_fp_biometrics_manager_peer_.SupportsPositiveMatchSecret());
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersion, kRecordID, kUserID, kLabel, kFakeValidationValue1);
BiometricsManager::AttemptMatches matches =
cros_fp_biometrics_manager_peer_.CalculateMatches(index, true);
EXPECT_EQ(matches,
BiometricsManager::AttemptMatches({{kUserID, {kRecordID}}}));
}
TEST_F(CrosFpBiometricsManagerTest, TestCalculateMatchesOnMigration) {
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersionNoValidationValue, kRecordID, kUserID, kLabel,
std::vector<uint8_t>());
// IF firmware supports positive match secret but the record does not have
// validation value:
EXPECT_TRUE(cros_fp_biometrics_manager_peer_.SupportsPositiveMatchSecret());
EXPECT_TRUE(
cros_fp_biometrics_manager_peer_.RecordNeedsValidationValue(index));
// THEN we accept the match since we are going to do migration.
auto matches = cros_fp_biometrics_manager_peer_.CalculateMatches(index, true);
EXPECT_EQ(matches,
BiometricsManager::AttemptMatches({{kUserID, {kRecordID}}}));
}
TEST_F(CrosFpBiometricsManagerTest, TestMigrateToValidationValue) {
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersion, kRecordID, kUserID, kLabel, std::vector<uint8_t>());
bool ret = cros_fp_biometrics_manager_peer_.MigrateToValidationValue(index);
EXPECT_TRUE(ret);
// After migration, the record at |index| should have the validation value
// corresponding to the device's positive match secret.
EXPECT_TRUE(cros_fp_biometrics_manager_peer_.ValidationValueEquals(
index, kFakeValidationValue1));
}
TEST_F(CrosFpBiometricsManagerTest, TestMigrateToValidationValueFailures) {
int index = cros_fp_biometrics_manager_peer_.AddRecord(
kRecordFormatVersion, kRecordID, kUserID, kLabel, std::vector<uint8_t>());
// Setting the devices positive match secret to empty will make the fake
// device return false when asked for positive match secret.
cros_fp_biometrics_manager_peer_.SetDevicePositiveMatchSecret(
std::vector<uint8_t>());
EXPECT_FALSE(
cros_fp_biometrics_manager_peer_.MigrateToValidationValue(index));
}
TEST_F(CrosFpBiometricsManagerTest, TestInsertEmptyPositiveMatchSalt) {
// Prepare a template of old format, with zero-length template field.
size_t metadata_size = sizeof(struct ec_fp_template_encryption_metadata);
std::vector<uint8_t> tmpl(metadata_size, 0xff);
CrosFpBiometricsManagerPeer::InsertEmptyPositiveMatchSalt(&tmpl);
EXPECT_EQ(tmpl.size(), metadata_size + FP_POSITIVE_MATCH_SALT_BYTES);
EXPECT_TRUE(
BytesAreZeros(tmpl.data() + metadata_size, FP_POSITIVE_MATCH_SALT_BYTES));
}
} // namespace biod