// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "crypto/user_verifying_key.h" #include #include #include #include "base/functional/bind.h" #include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" #include "base/test/task_environment.h" #include "crypto/fake_apple_keychain_v2.h" #include "crypto/features.h" #include "crypto/scoped_fake_apple_keychain_v2.h" #include "crypto/scoped_lacontext.h" #include "testing/gtest/include/gtest/gtest.h" namespace crypto { namespace { constexpr char kTestKeychainAccessGroup[] = "test-keychain-access-group"; constexpr SignatureVerifier::SignatureAlgorithm kAcceptableAlgos[] = { SignatureVerifier::ECDSA_SHA256}; UserVerifyingKeyProvider::Config MakeConfig() { UserVerifyingKeyProvider::Config config; config.keychain_access_group = kTestKeychainAccessGroup; return config; } class UserVerifyingKeyMacTest : public testing::Test { public: std::unique_ptr GenerateUserVerifyingSigningKey() { std::unique_ptr key; base::RunLoop run_loop; provider_->GenerateUserVerifyingSigningKey( kAcceptableAlgos, base::BindLambdaForTesting( [&](std::unique_ptr result) { key = std::move(result); run_loop.Quit(); })); run_loop.Run(); return key; } std::unique_ptr GetUserVerifyingSigningKey( std::string key_label) { std::unique_ptr key; base::RunLoop run_loop; provider_->GetUserVerifyingSigningKey( key_label, base::BindLambdaForTesting( [&](std::unique_ptr result) { key = std::move(result); run_loop.Quit(); })); run_loop.Run(); return key; } bool DeleteUserVerifyingKey(std::string key_label) { std::optional deleted; base::RunLoop run_loop; provider_->DeleteUserVerifyingKey( key_label, base::BindLambdaForTesting([&](bool result) { deleted = result; run_loop.Quit(); })); run_loop.Run(); return *deleted; } std::optional> Sign(UserVerifyingSigningKey* key, base::span message) { std::optional> signature; base::RunLoop run_loop; key->Sign(message, base::BindLambdaForTesting( [&](std::optional> result) { signature = std::move(result); run_loop.Quit(); })); run_loop.Run(); return signature; } protected: ScopedFakeAppleKeychainV2 scoped_fake_apple_keychain_{ kTestKeychainAccessGroup}; base::test::TaskEnvironment task_environment_; base::test::ScopedFeatureList scoped_feature_list_{ kEnableMacUnexportableKeys}; std::unique_ptr provider_ = crypto::GetUserVerifyingKeyProvider(MakeConfig()); }; TEST_F(UserVerifyingKeyMacTest, RoundTrip) { for (bool use_lacontext : {false, true}) { SCOPED_TRACE(use_lacontext); UserVerifyingKeyProvider::Config config = MakeConfig(); if (use_lacontext) { config.lacontext = ScopedLAContext([[LAContext alloc] init]); } provider_ = crypto::GetUserVerifyingKeyProvider(std::move(config)); std::unique_ptr key = GenerateUserVerifyingSigningKey(); ASSERT_TRUE(key); ASSERT_TRUE(!key->GetKeyLabel().empty()); const std::vector spki = key->GetPublicKey(); const uint8_t message[] = {1, 2, 3, 4}; std::optional> signature = Sign(key.get(), message); ASSERT_TRUE(signature); crypto::SignatureVerifier verifier; ASSERT_TRUE(verifier.VerifyInit(kAcceptableAlgos[0], *signature, spki)); verifier.VerifyUpdate(message); ASSERT_TRUE(verifier.VerifyFinal()); std::unique_ptr key2 = GetUserVerifyingSigningKey(key->GetKeyLabel()); ASSERT_TRUE(key2); std::optional> signature2 = Sign(key.get(), message); ASSERT_TRUE(signature2); crypto::SignatureVerifier verifier2; ASSERT_TRUE(verifier2.VerifyInit(kAcceptableAlgos[0], *signature2, spki)); verifier2.VerifyUpdate(message); ASSERT_TRUE(verifier2.VerifyFinal()); } } TEST_F(UserVerifyingKeyMacTest, SecureEnclaveAvailability) { using UVMethod = FakeAppleKeychainV2::UVMethod; struct { bool enclave_available; UVMethod uv_method; bool expected_uvk_available; } kTests[] = { {false, UVMethod::kNone, false}, {false, UVMethod::kPasswordOnly, false}, {false, UVMethod::kBiometrics, false}, {true, UVMethod::kNone, false}, {true, UVMethod::kPasswordOnly, true}, {true, UVMethod::kBiometrics, true}, }; for (auto test : kTests) { SCOPED_TRACE(test.enclave_available); SCOPED_TRACE(static_cast(test.uv_method)); scoped_fake_apple_keychain_.keychain()->set_secure_enclave_available( test.enclave_available); scoped_fake_apple_keychain_.keychain()->set_uv_method(test.uv_method); std::optional result; base::RunLoop run_loop; AreUserVerifyingKeysSupported(MakeConfig(), base::BindLambdaForTesting([&](bool ret) { result = ret; run_loop.Quit(); })); run_loop.Run(); EXPECT_EQ(result.value(), test.expected_uvk_available); } } TEST_F(UserVerifyingKeyMacTest, DeleteSigningKey) { std::unique_ptr key = GenerateUserVerifyingSigningKey(); ASSERT_TRUE(key); EXPECT_TRUE(DeleteUserVerifyingKey(key->GetKeyLabel())); EXPECT_FALSE(GetUserVerifyingSigningKey(key->GetKeyLabel())); EXPECT_FALSE(DeleteUserVerifyingKey(key->GetKeyLabel())); } } // namespace } // namespace crypto