// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CRYPTO_UNEXPORTABLE_KEY_H_
#define CRYPTO_UNEXPORTABLE_KEY_H_

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/compiler_specific.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "crypto/crypto_export.h"
#include "crypto/signature_verifier.h"

#if BUILDFLAG(IS_APPLE)
#import <Security/Security.h>
#endif  // BUILDFLAG(IS_APPLE)

#if BUILDFLAG(IS_WIN)
#include "base/win/windows_types.h"

// NCRYPT_KEY_HANDLE is defined in <ncrypt.h>, but including it here would pull
// in Windows headers. We use an alias instead.
using NCRYPT_KEY_HANDLE = ULONG_PTR;
#endif  // BUILDFLAG(IS_WIN)

namespace crypto {

class StatefulKey;
class StatefulUnexportableKeyProvider;

// UnexportableSigningKey provides a hardware-backed signing oracle on platforms
// that support it. Current support is:
//   Windows: RSA_PKCS1_SHA256 via TPM 1.2+ and ECDSA_SHA256 via TPM 2.0.
//   macOS and iOS: ECDSA_SHA256 via the Secure Enclave.
//   Tests: ECDSA_SHA256 via ScopedMockUnexportableSigningKeyForTesting.
//
// See also //components/unexportable_keys for a higher-level key management
// API.
class CRYPTO_EXPORT UnexportableSigningKey {
 public:
  virtual ~UnexportableSigningKey() = default;

  // Algorithm returns the algorithm of the key in this object.
  virtual SignatureVerifier::SignatureAlgorithm Algorithm() const = 0;

  // GetSubjectPublicKeyInfo returns an SPKI that contains the public key of
  // this object.
  virtual std::vector<uint8_t> GetSubjectPublicKeyInfo() const = 0;

  // GetWrappedKey returns a handle to the private key of this object. Usually,
  // it is the private key encrypted to a key that is kept in hardware and the
  // unencrypted private key never exists in the CPU's memory, hence the name.
  // On Mac, this is instead a hash of the public key and the wrapped key
  // material is stored in the Keychain.
  //
  // A key handle may be used with a future instance of this code to recreate
  // the key so long as it's running on the same computer.
  //
  // Note: on Windows it is possible to export this wrapped key off machine, but
  // it must be sealed with an AEAD first. The wrapped key may contain machine
  // identifiers and other values that you wouldn't want to export. Additionally
  // |UnexportableKeyProvider::FromWrappedSigningKey| should not be presented
  // attacked-controlled input and the AEAD would serve to authenticate the
  // wrapped key.
  virtual std::vector<uint8_t> GetWrappedKey() const = 0;

  // Returns true if the underlying key is stored in "hardware". Something like
  // ARM TrustZone would count as hardware for these purposes. Ideally all
  // implementations of this class would return true here, because software
  // implementations aren't really "unexportable", but a software implementation
  // does exist.
  virtual bool IsHardwareBacked() const;

#if BUILDFLAG(IS_APPLE)
  // Returns the underlying reference to a Keychain key owned by the current
  // instance.
  virtual SecKeyRef GetSecKeyRef() const = 0;
#endif  // BUILDFLAG(IS_APPLE)

#if BUILDFLAG(IS_WIN)
  // Returns the underlying NCrypt key handle owned by the current instance.
  virtual NCRYPT_KEY_HANDLE GetNCryptKeyHandle() const = 0;
#endif  // BUILDFLAG(IS_WIN)

  // Typesafe downcast to `StatefulKey`. Returns nullptr if the key is not
  // stateful.
  virtual const StatefulKey* AsStatefulKey() const LIFETIME_BOUND;

  // SignSlowly returns a signature of |data|, or |nullopt| if an error occurs
  // during signing.
  //
  // Note: this may take a second or more to run.
  virtual std::optional<std::vector<uint8_t>> SignSlowly(
      base::span<const uint8_t> data) = 0;

#if BUILDFLAG(IS_WIN)
  // Will verify whether the key can be used to sign TLS 1.3 payloads as
  // required by the spec. Specifically, it will verify whether RSA keys
  // support the RSA-PSS algorithm with the expected salt lengths.
  virtual bool SupportsTls13() = 0;
#endif  // BUILDFLAG(IS_WIN)
};

// An attestation/certification statement proving the binding of an
// unexportable signing key to the hardware-backed attestation key of the
// device.
//
// Because Apple's Secure Enclave does not provide a platform API for hardware
// key attestation, a custom, software-based format is used on macOS/iOS
// (Format::kSecureEnclave) where the browser acts as a proxy to attest the key.
// This provides future compatibility when a native attestation API becomes
// available.
struct CRYPTO_EXPORT AttestationStatement {
  enum Format {
    // TPM 2.0 platform attestation format.
    // `statement` is a binary TPMS_ATTEST structure.
    // `signature` is a binary TPMT_SIGNATURE structure.
    kTpm,
    // Custom Secure Enclave format used on macOS/iOS.
    // `statement` is the concatenation of the server's challenge and the
    // SHA-256 hash of the signing key's Subject PublicKey Info (SPKI).
    // TODO(crbug.com/406190025): Make this generic once we use the
    // crypto::sign algorithms.
    // `signature` is the signature over `statement` signed using the Secure
    // Enclave attestation key in raw IEEE P1363 format (concatenation of
    // big-endian `r` and `s`, 64 bytes for P-256).
    kSecureEnclave,
  };
  Format format = kTpm;
  std::vector<uint8_t> statement;
  std::vector<uint8_t> signature;
};

class CRYPTO_EXPORT UnexportableAttestationKey : public UnexportableSigningKey {
 public:
  // Performs an attestation/certification over the given signing key using
  // the attestation key (e.g., an AIK certifying a generated RSA binding key).
  virtual std::optional<AttestationStatement> CertifySlowly(
      const UnexportableSigningKey& signing_key,
      base::span<const uint8_t> challenge) = 0;
};

// StatefulKey is an interface for keys that are backed by some permanent state,
// such as the keychain on macOS.
class CRYPTO_EXPORT StatefulKey {
 public:
  virtual ~StatefulKey() = default;

  // Returns the tag of the stateful key stored by the platform. For example,
  // on macOS, this is the application tag set when creating the key.
  virtual std::string GetKeyTag() const = 0;

  // Returns the creation time of the key.
  virtual base::Time GetCreationTime() const = 0;
};

// UnexportableKeyProvider creates |UnexportableSigningKey|s.
class CRYPTO_EXPORT UnexportableKeyProvider {
 public:
  virtual ~UnexportableKeyProvider();

  // Platform-specific configuration parameters for the provider.
  struct Config {
#if BUILDFLAG(IS_APPLE)
    // Determines the level of user verification needed to sign with the key.
    // https://developer.apple.com/documentation/security/secaccesscontrolcreateflags?language=objc
    enum class AccessControl {
      // No access control. User presence is not required to access this secret.
      kNone,

      // Either biometry or the local account password are required to access
      // this secret. This is equivalent to kSecAccessControlUserPresence.
      // Note that if you set this and choose not to pass an authenticated
      // LAContext when signing, macOS will prompt the user for biometrics and
      // the thread will block until that resolves.
      kUserPresence,
    };

    // The keychain access group the key is shared with. The binary must be
    // codesigned with the corresponding entitlement.
    // https://developer.apple.com/documentation/bundleresources/entitlements/keychain-access-groups?language=objc
    // This must be set to a non empty value when using unexportable keys on
    // macOS.
    std::string keychain_access_group;

    // An optional application tag that will be set for all keys created by this
    // provider. If non empty, this should uniquely identify a group of related
    // keys, and can be used to query or delete all credentials with the same
    // tag.
    // https://developer.apple.com/documentation/security/ksecattrapplicationtag?language=objc
    std::string application_tag;

    // The access control set for keys created by the provider.
    AccessControl access_control = AccessControl::kNone;
#endif  // BUILDFLAG(IS_APPLE)
  };

  // SelectAlgorithm returns which signature algorithm from
  // |acceptable_algorithms| would be used if |acceptable_algorithms| was passed
  // to |GenerateSigningKeySlowly|.
  //
  // Note: on Windows, calling this function may trigger a synchronous load of
  // `ncrypt.dll`. This loading happens only once per process lifetime.
  // Therefore, it is acceptable to call this function on the UI thread after it
  // has been invoked at least once (e.g., during initialization on a background
  // thread) to avoid blocking the UI thread and causing potential hangs.
  virtual std::optional<SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
      base::span<const SignatureVerifier::SignatureAlgorithm>
          acceptable_algorithms) = 0;

  // GenerateSigningKeySlowly creates a new opaque signing key in hardware. The
  // first supported value of |acceptable_algorithms| determines the type of the
  // key. Returns nullptr if no supported hardware exists, if no value in
  // |acceptable_algorithms| is supported, or if there was an error creating the
  // key.
  //
  // Note: this may take one or two seconds to run.
  virtual std::unique_ptr<UnexportableSigningKey> GenerateSigningKeySlowly(
      base::span<const SignatureVerifier::SignatureAlgorithm>
          acceptable_algorithms) = 0;

  // FromWrappedSigningKey creates an |UnexportableSigningKey| from
  // |wrapped_key|, which must have resulted from calling |GetWrappedKey| on a
  // previous instance of |UnexportableSigningKey|. Returns nullptr if
  // |wrapped_key| cannot be imported.
  //
  // Note: this may take up to a second.
  //
  // Note: do not call this with attacker-controlled data. The underlying
  // interfaces to the secure hardware may not be robust. See |GetWrappedKey|.
  virtual std::unique_ptr<UnexportableSigningKey> FromWrappedSigningKeySlowly(
      base::span<const uint8_t> wrapped_key) = 0;

  // Generates a new hardware-backed attestation key (e.g., an AIK).
  virtual std::unique_ptr<UnexportableAttestationKey>
  GenerateAttestationKeySlowly(
      base::span<const SignatureVerifier::SignatureAlgorithm>
          acceptable_algorithms);

  // Reconstructs an attestation key from a previously wrapped key.
  virtual std::unique_ptr<UnexportableAttestationKey>
  FromWrappedAttestationKeySlowly(base::span<const uint8_t> wrapped_key);

  // Typesafe downcast to `StatefulUnexportableKeyProvider`. Returns nullptr if
  // the provider is not stateful.
  virtual StatefulUnexportableKeyProvider* AsStatefulUnexportableKeyProvider()
      LIFETIME_BOUND = 0;
};

// StatefulUnexportableKeyProvider provides an interface for managing keys that
// are backed by some permanent state, such as the keychain on macOS.
class CRYPTO_EXPORT StatefulUnexportableKeyProvider
    : public UnexportableKeyProvider {
 public:
  // `GetAllKeysSlowly()` returns all previously stored keys matching
  // `Config` or nullopt in case of failures.
  //
  // NOTE: For macOS this will perform prefix matching on
  // `Config::application_tag`. That is, if `Config::application_tag` is
  // "com.example.foo", this will return keys with application tags like
  // "com.example.foo.1", "com.example.foo.1234", etc.
  //
  // This can sometimes block, and therefore must not be called from the UI
  // thread.
  virtual std::optional<std::vector<std::unique_ptr<UnexportableSigningKey>>>
  GetAllKeysSlowly() = 0;

  // Deletes all state associated with all signing keys matching `Config` that
  // match one of the provided wrapped keys. Returns the number of keys deleted,
  // or nullopt if unsuccessful. This can sometimes block, and therefore must
  // not be called from the UI thread.
  //
  // NOTE: For macOS this will perform prefix matching on
  // `Config::application_tag`. That is, if `Config::application_tag` is
  // "com.example.foo", this will delete keys with application tags like
  // "com.example.foo.1", "com.example.foo.1234", etc, assuming the wrapped key
  // matches exactly.
  virtual std::optional<size_t> DeleteWrappedKeysSlowly(
      base::span<const base::span<const uint8_t>> wrapped_keys) = 0;

  // Deletes all state associated with the provided keys. Returns the number of
  // keys deleted, or nullopt if unsuccessful. This can sometimes block, and
  // therefore must not be called from the UI thread.
  //
  // NOTE: For macOS this will perform prefix matching on
  // `Config::application_tag`. That is, only matching keys where the
  // application tag starts with the `Config::application_tag` will be deleted.
  virtual std::optional<size_t> DeleteKeysSlowly(
      base::span<const UnexportableSigningKey* const> keys) = 0;

  // `DeleteAllKeysSlowly()` deletes all state associated with all keys matching
  // `UnexportableKeyProvider::Config`.
  //
  // NOTE: For macOS, this will perform prefix matching iff
  // `Config::application_tag` is set. That is, if `Config::application_tag` is
  // "com.example.foo", this will delete keys with application tags like
  // "com.example.foo.1", "com.example.foo.1234", etc.
  //
  // Returns the number of keys deleted, or nullopt if unsuccessful. This can
  // sometimes block, and therefore must not be called from the UI thread.
  virtual std::optional<size_t> DeleteAllKeysSlowly() = 0;
};

// This is an experimental API as it uses an unofficial Windows API.
// The current implementation is here to gather metrics only. It should not be
// used outside of metrics gathering without knowledge of crypto OWNERS.
//
// UnexportableSigningKey provides a software-backed signing oracle based in a
// specialized virtual machine on platforms that support it. Current support is:
//   Windows: RSA_PKCS1_SHA256 and ECDSA_SHA256.
//
// These keys differs from UnexportableSigningKey in several ways:
// - They are backed not by hardware, but by a specialized limited virtual
// machine resistant to attacks.
// - The latency of operations are expected to be about 100 times less, making
// them much more practical in cases that would otherwise disrupt the user
// experience.
// - The keys are stored in the virtual machine by name, this namespace is
// shared by all applications and there is a limited number of available keys
// (~65k from testing).
//
// For more info see:
// https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/credential-guard
class CRYPTO_EXPORT VirtualUnexportableSigningKey {
 public:
  virtual ~VirtualUnexportableSigningKey();

  // Algorithm returns the algorithm of the key in this object.
  virtual SignatureVerifier::SignatureAlgorithm Algorithm() const = 0;

  // GetSubjectPublicKeyInfo returns an SPKI that contains the public key of
  // this object.
  virtual std::vector<uint8_t> GetSubjectPublicKeyInfo() const = 0;

  // GetKeyName may be used with a future instance of this code to recreate
  // the key so long as it's running on the same computer.
  //
  // Note: All local applications can enumerate all keys on device and
  // recreate them. Private keys can also be exported with the first HANDLE
  // after creation.
  virtual std::string GetKeyName() const = 0;

  // Sign returns a signature of |data|, or |nullopt| if an error occurs
  // during signing.
  //
  // Note: this is expected to be under 10ms.
  virtual std::optional<std::vector<uint8_t>> Sign(
      base::span<const uint8_t> data) = 0;

  // Deletes the key from storage in the virtual machine. As the virtual machine
  // has limited storage shared by all applications it is important to delete
  // keys no longer in use.
  virtual void DeleteKey() = 0;
};

// VirtualUnexportableKeyProvider creates |VirtualUnexportableSigningKey|s.
class CRYPTO_EXPORT VirtualUnexportableKeyProvider {
 public:
  virtual ~VirtualUnexportableKeyProvider();

  // SelectAlgorithm returns which signature algorithm from
  // |acceptable_algorithms| would be used if |acceptable_algorithms| was passed
  // to |GenerateSigningKeySlowly|.
  virtual std::optional<SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
      base::span<const SignatureVerifier::SignatureAlgorithm>
          acceptable_algorithms) = 0;

  // GenerateSigningKey creates a new opaque signing key in a virtual machine.
  // The first supported value of |acceptable_algorithms| determines the type of
  // the key. Returns nullptr if it is not supported in the operating system,
  // if no value in |acceptable_algorithms| is supported, or if there was an
  // error creating the key.
  // As the namespace is shared between all applications care should be taken to
  // use a name that will not already be used by other applications. If a new
  // key is created with the same name as a current key the creation will fail.
  // Do not create a key with NULL or empty string as the name.
  //
  // Note: This may take milliseconds to run.
  virtual std::unique_ptr<VirtualUnexportableSigningKey> GenerateSigningKey(
      base::span<const SignatureVerifier::SignatureAlgorithm>
          acceptable_algorithms,
      std::string name) = 0;

  // FromKeyName creates an |UnexportableSigningKey| from |name|, which is the
  // name used to create the key. Returns nullptr if |name| cannot be imported.
  //
  // Note: This may take milliseconds to run.
  virtual std::unique_ptr<VirtualUnexportableSigningKey> FromKeyName(
      std::string name) = 0;
};

// GetUnexportableKeyProvider returns an |UnexportableKeyProvider|
// for the current platform, or nullptr if there isn't one. This can be called
// from any thread but, in tests, but be sequenced with
// |SetUnexportableSigningKeyProvider|.
CRYPTO_EXPORT std::unique_ptr<UnexportableKeyProvider>
GetUnexportableKeyProvider(UnexportableKeyProvider::Config config);

// GetMicrosoftSoftwareUnexportableKeyProvider returns an
// |UnexportableKeyProvider| that is backed by the Microsoft Software Key
// Storage Provider. Keys stored in this fashion are available to both the
// software that created them, and any software running locally with
// administrative privileges.
// Microsoft Software keys are less secure than TPM backed keys, so
// |GetUnexportableKeyProvider| should be preferred, but they are more widely
// available.
CRYPTO_EXPORT std::unique_ptr<UnexportableKeyProvider>
GetMicrosoftSoftwareUnexportableKeyProvider();

// GetVirtualUnexportableKeyProvider_DO_NOT_USE_METRICS_ONLY returns a
// |VirtualUnexportableKeyProvider| for the current platform, or nullptr if
// there isn't one. This should currently only be used for metrics gathering.
CRYPTO_EXPORT std::unique_ptr<VirtualUnexportableKeyProvider>
GetVirtualUnexportableKeyProvider_DO_NOT_USE_METRICS_ONLY();

// `GetSoftwareUnsecureUnexportableKeyProvider()` returns a fake software
// implementation of `UnexportableKeyProvider` that can be used on platforms
// that do not have a native secure implementation.
// This should be used for development purposes only since these keys are not
// backed by hardware and are not stored securely.
CRYPTO_EXPORT std::unique_ptr<UnexportableKeyProvider>
GetSoftwareUnsecureUnexportableKeyProvider();

namespace internal {

CRYPTO_EXPORT bool HasScopedUnexportableKeyProvider();

CRYPTO_EXPORT void SetUnexportableKeyProviderForTesting(
    std::unique_ptr<UnexportableKeyProvider> (*func)());

}  // namespace internal

}  // namespace crypto

#endif  // CRYPTO_UNEXPORTABLE_KEY_H_
