chromium/crypto/user_verifying_key_mac_unittest.mm

// 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 <iterator>
#include <memory>

#include <LocalAuthentication/LocalAuthentication.h>

#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<UserVerifyingSigningKey> GenerateUserVerifyingSigningKey() {
    std::unique_ptr<UserVerifyingSigningKey> key;
    base::RunLoop run_loop;
    provider_->GenerateUserVerifyingSigningKey(
        kAcceptableAlgos,
        base::BindLambdaForTesting(
            [&](base::expected<std::unique_ptr<UserVerifyingSigningKey>,
                               UserVerifyingKeyCreationError> result) {
              if (result.has_value()) {
                key = std::move(result.value());
              }
              run_loop.Quit();
            }));
    run_loop.Run();
    return key;
  }

  std::unique_ptr<UserVerifyingSigningKey> GetUserVerifyingSigningKey(
      std::string key_label) {
    std::unique_ptr<UserVerifyingSigningKey> key;
    base::RunLoop run_loop;
    provider_->GetUserVerifyingSigningKey(
        key_label,
        base::BindLambdaForTesting(
            [&](base::expected<std::unique_ptr<UserVerifyingSigningKey>,
                               UserVerifyingKeyCreationError> result) {
              if (result.has_value()) {
                key = std::move(result.value());
              }
              run_loop.Quit();
            }));
    run_loop.Run();
    return key;
  }

  bool DeleteUserVerifyingKey(std::string key_label) {
    std::optional<bool> 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<std::vector<uint8_t>> Sign(UserVerifyingSigningKey* key,
                                           base::span<const uint8_t> message) {
    std::optional<std::vector<uint8_t>> signature;
    base::RunLoop run_loop;
    key->Sign(message,
              base::BindLambdaForTesting(
                  [&](base::expected<std::vector<uint8_t>,
                                     UserVerifyingKeySigningError> result) {
                    if (result.has_value()) {
                      signature = std::move(result.value());
                    }
                    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<UserVerifyingKeyProvider> 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<UserVerifyingSigningKey> key =
        GenerateUserVerifyingSigningKey();
    ASSERT_TRUE(key);
    ASSERT_TRUE(!key->GetKeyLabel().empty());

    const std::vector<uint8_t> spki = key->GetPublicKey();
    const uint8_t message[] = {1, 2, 3, 4};
    std::optional<std::vector<uint8_t>> 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<UserVerifyingSigningKey> key2 =
        GetUserVerifyingSigningKey(key->GetKeyLabel());
    ASSERT_TRUE(key2);

    std::optional<std::vector<uint8_t>> 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<int>(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<bool> 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<UserVerifyingSigningKey> key =
      GenerateUserVerifyingSigningKey();
  ASSERT_TRUE(key);

  EXPECT_TRUE(DeleteUserVerifyingKey(key->GetKeyLabel()));
  EXPECT_FALSE(GetUserVerifyingSigningKey(key->GetKeyLabel()));
  EXPECT_FALSE(DeleteUserVerifyingKey(key->GetKeyLabel()));
}

}  // namespace

}  // namespace crypto