current commit
261105.7 · crypto: reuse HMAC contexts in createHmac()
#pragma once

#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/dsa.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/kdf.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <list>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#ifndef OPENSSL_VERSION_PREREQ
#define OPENSSL_VERSION_PREREQ(maj, min)                                       \
  (OPENSSL_VERSION_NUMBER >= (((maj) << 28) | ((min) << 20)))
#endif

// BoringSSL reports itself as OpenSSL 1.1.1, so it has to be excluded here.
#if !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_VERSION_PREREQ(3, 0)
#error "OpenSSL 1.x is no longer supported, v3.0.0 or later is required."
#endif

// BoringSSL declares the EVP_*_do_all* APIs, but their implementation may
// live in libdecrepit. This matches standalone ncrypto's build flag.
#ifndef NCRYPTO_BSSL_LIBDECREPIT_MISSING
#define NCRYPTO_BSSL_LIBDECREPIT_MISSING 0
#endif

#if defined(OPENSSL_IS_BORINGSSL) && NCRYPTO_BSSL_LIBDECREPIT_MISSING
#define NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK 1
#else
#define NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK 0
#endif

// Backend split:
// - OpenSSL uses provider APIs and hides deprecated low-level objects.
// - BoringSSL has its own API-compatible branch and keeps using the legacy
//   low-level key types.
#ifdef OPENSSL_IS_BORINGSSL
#define NCRYPTO_USE_BORINGSSL 1
#define NCRYPTO_USE_OPENSSL_PROVIDER 0
#elif OPENSSL_VERSION_PREREQ(3, 0)
#define NCRYPTO_USE_BORINGSSL 0
#define NCRYPTO_USE_OPENSSL_PROVIDER 1
#else
#error "Unsupported crypto backend"
#endif

#define NCRYPTO_USE_LEGACY_KEY_TYPES NCRYPTO_USE_BORINGSSL

// OpenSSL 4 makes ASN1_STRING opaque and provides a checked BIT STRING length
// accessor. Earlier OpenSSL releases and pinned BoringSSL expose the decoded
// unused-bit count through ASN1_STRING::flags.
#if !NCRYPTO_USE_BORINGSSL && OPENSSL_VERSION_PREREQ(4, 0)
#define NCRYPTO_HAS_ASN1_BIT_STRING_GET_LENGTH 1
#else
#define NCRYPTO_HAS_ASN1_BIT_STRING_GET_LENGTH 0
#endif

#if NCRYPTO_USE_OPENSSL_PROVIDER
#include <openssl/core_names.h>
#include <openssl/encoder.h>
#include <openssl/param_build.h>
#endif

#if !defined(OPENSSL_IS_BORINGSSL)
#define OPENSSL_WITH_AES_OCB 1
#elif defined(OPENSSL_IS_BORINGSSL)
#define OPENSSL_WITH_AES_OCB 0
#endif

#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_PREREQ(3, 2)
#define OPENSSL_WITH_ARGON2 1
#else
#define OPENSSL_WITH_ARGON2 0
#endif

#define OPENSSL_WITH_KEM 1

#if !defined(OPENSSL_IS_BORINGSSL)
#define OPENSSL_WITH_EVP_MAC 1
#elif defined(OPENSSL_IS_BORINGSSL)
#define OPENSSL_WITH_EVP_MAC 0
#endif

#if !defined(OPENSSL_IS_BORINGSSL)
#define OPENSSL_WITH_AES_SIV 1
#elif defined(OPENSSL_IS_BORINGSSL)
#define OPENSSL_WITH_AES_SIV 0
#endif

#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 2)
#define OPENSSL_WITH_AES_GCM_SIV 1
#else
#define OPENSSL_WITH_AES_GCM_SIV 0
#endif

#if !defined(OPENSSL_IS_BORINGSSL)
#define OSSL3_CONST const
#elif defined(OPENSSL_IS_BORINGSSL)
#define OSSL3_CONST
#endif

#ifdef __GNUC__
#define NCRYPTO_MUST_USE_RESULT __attribute__((warn_unused_result))
#else
#define NCRYPTO_MUST_USE_RESULT
#endif

#ifdef OPENSSL_IS_BORINGSSL
// Boringssl has opted to use size_t for some size related
// APIs while Openssl is still using ints
using OPENSSL_SIZE_T = size_t;
#else
using OPENSSL_SIZE_T = int;
#endif

namespace ncrypto {

// ============================================================================
// Utility macros

#if NCRYPTO_DEVELOPMENT_CHECKS
#define NCRYPTO_STR(x) #x
#define NCRYPTO_REQUIRE(EXPR)                                                  \
  {                                                                            \
    if (!(EXPR)) {                                                             \
      abort();                                                                 \
    }                                                                          \
  }

#define NCRYPTO_FAIL(MESSAGE)                                                  \
  do {                                                                         \
    std::cerr << "FAIL: " << (MESSAGE) << std::endl;                           \
    abort();                                                                   \
  } while (0);
#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE)                                \
  do {                                                                         \
    if (LHS != RHS) {                                                          \
      std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \
      NCRYPTO_FAIL(MESSAGE);                                                   \
    }                                                                          \
  } while (0);
#define NCRYPTO_ASSERT_TRUE(COND)                                              \
  do {                                                                         \
    if (!(COND)) {                                                             \
      std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__    \
                << std::endl;                                                  \
      NCRYPTO_FAIL(NCRYPTO_STR(COND));                                         \
    }                                                                          \
  } while (0);
#else
#define NCRYPTO_FAIL(MESSAGE)
#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE)
#define NCRYPTO_ASSERT_TRUE(COND)
#endif

#define NCRYPTO_DISALLOW_COPY(Name)                                            \
  Name(const Name&) = delete;                                                  \
  Name& operator=(const Name&) = delete;
#define NCRYPTO_DISALLOW_MOVE(Name)                                            \
  Name(Name&&) = delete;                                                       \
  Name& operator=(Name&&) = delete;
#define NCRYPTO_DISALLOW_COPY_AND_MOVE(Name)                                   \
  NCRYPTO_DISALLOW_COPY(Name)                                                  \
  NCRYPTO_DISALLOW_MOVE(Name)
#define NCRYPTO_DISALLOW_NEW_DELETE()                                          \
  void* operator new(size_t) = delete;                                         \
  void operator delete(void*) = delete;

[[noreturn]] inline void unreachable() {
#ifdef __GNUC__
  __builtin_unreachable();
#elif defined(_MSC_VER)
  __assume(false);
#else
#endif
}

static constexpr int kX509NameFlagsMultiline =
    ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_UTF8_CONVERT |
    XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_SN;

// ============================================================================
// Error handling utilities

// Capture the current OpenSSL Error Stack. The stack will be ordered such
// that the error currently at the top of the stack is at the end of the
// list and the error at the bottom of the stack is at the beginning.
class CryptoErrorList final {
 public:
  enum class Option { NONE, CAPTURE_ON_CONSTRUCT };
  CryptoErrorList(Option option = Option::CAPTURE_ON_CONSTRUCT);

  void capture();

  // Add an error message to the end of the stack.
  void add(std::string message);

  inline const std::string& peek_back() const { return errors_.back(); }
  inline size_t size() const { return errors_.size(); }
  inline bool empty() const { return errors_.empty(); }

  inline auto begin() const noexcept { return errors_.begin(); }
  inline auto end() const noexcept { return errors_.end(); }
  inline auto rbegin() const noexcept { return errors_.rbegin(); }
  inline auto rend() const noexcept { return errors_.rend(); }

  std::optional<std::string> pop_back();
  std::optional<std::string> pop_front();

 private:
  std::list<std::string> errors_;
};

// Forcibly clears the error stack on destruction. This stops stale errors
// from popping up later in the lifecycle of crypto operations where they
// would cause spurious failures. It is a rather blunt method, though, and
// ERR_clear_error() isn't necessarily cheap.
//
// If created with a pointer to a CryptoErrorList, the current OpenSSL error
// stack will be captured before clearing the error.
class ClearErrorOnReturn final {
 public:
  ClearErrorOnReturn(CryptoErrorList* errors = nullptr);
  ~ClearErrorOnReturn();
  NCRYPTO_DISALLOW_COPY_AND_MOVE(ClearErrorOnReturn)
  NCRYPTO_DISALLOW_NEW_DELETE()

  unsigned long peekError();  // NOLINT(runtime/int)

 private:
  CryptoErrorList* errors_;
};

// Pop errors from OpenSSL's error stack that were added between when this
// was constructed and destructed.
//
// If created with a pointer to a CryptoErrorList, the current OpenSSL error
// stack will be captured before resetting the error to the mark.
class MarkPopErrorOnReturn final {
 public:
  MarkPopErrorOnReturn(CryptoErrorList* errors = nullptr);
  ~MarkPopErrorOnReturn();
  NCRYPTO_DISALLOW_COPY_AND_MOVE(MarkPopErrorOnReturn)
  NCRYPTO_DISALLOW_NEW_DELETE()

  unsigned long peekError();  // NOLINT(runtime/int)

 private:
  CryptoErrorList* errors_;
};

// TODO(@jasnell): Eventually replace with std::expected when we are able to
// bump up to c++23.
template <typename T, typename E>
struct Result final {
  const bool has_value;
  T value;
  std::optional<E> error = std::nullopt;
  // NOLINTNEXTLINE(runtime/int) -- matches ERR_peek_error()
  std::optional<unsigned long> openssl_error = std::nullopt;
  Result(T&& value) : has_value(true), value(std::move(value)) {}
  // NOLINTNEXTLINE(runtime/int) -- matches ERR_peek_error()
  Result(E&& error, std::optional<unsigned long> openssl_error = std::nullopt)
      : has_value(false),
        error(std::move(error)),
        openssl_error(std::move(openssl_error)) {}
  inline operator bool() const { return has_value; }
};

// ============================================================================
// Various smart pointer aliases for OpenSSL types.

template <typename T, void (*function)(T*)>
struct FunctionDeleter {
  void operator()(T* pointer) const { function(pointer); }
  typedef std::unique_ptr<T, FunctionDeleter> Pointer;
};

template <typename T, void (*function)(T*)>
using DeleteFnPtr = typename FunctionDeleter<T, function>::Pointer;

using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
#if NCRYPTO_USE_LEGACY_KEY_TYPES
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
#endif
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;

class BIOPointer;
class BignumPointer;
class CipherCtxPointer;
class DataPointer;
class DHPointer;
class ECKeyPointer;
class EVPKeyPointer;
class HMACCtxPointer;
class KeyAlgorithm;
class MacCache;
class EVPMacCtxPointer;
class EVPMacPointer;
class EVPMDCtxPointer;
class SSLCtxPointer;
class SSLPointer;
class X509View;
class X509Pointer;
class ECDSASigPointer;
class ECGroupPointer;
class ECPointPointer;
class ECKeyPointer;
class Dsa;
class Rsa;
class Ec;

struct StackOfXASN1Deleter {
  void operator()(STACK_OF(ASN1_OBJECT) * p) const {
    sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free);
  }
};
using StackOfASN1 = std::unique_ptr<STACK_OF(ASN1_OBJECT), StackOfXASN1Deleter>;

// An unowned, unmanaged pointer to a buffer of data.
template <typename T>
struct Buffer {
  T* data = nullptr;
  size_t len = 0;
};

class Digest final {
 public:
  static constexpr size_t MAX_SIZE = EVP_MAX_MD_SIZE;
  Digest() = default;
  Digest(const EVP_MD* md) : md_(md) {}
  Digest(const Digest& other);
  Digest& operator=(const Digest& other);
  inline Digest& operator=(const EVP_MD* md) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
    fetched_md_.reset();
#endif
    md_ = md;
    return *this;
  }
  NCRYPTO_DISALLOW_MOVE(Digest)

  size_t size() const;

  inline const EVP_MD* get() const { return md_; }
  inline operator const EVP_MD*() const { return md_; }
  inline operator bool() const { return md_ != nullptr; }

  static const Digest MD5;
  static const Digest SHA1;
  static const Digest SHA256;
  static const Digest SHA384;
  static const Digest SHA512;

  static const Digest FromName(const char* name);
  static const Digest Fetch(const char* name);
  // Returns a Digest that holds its own reference to md if md was fetched from
  // a provider.
  static const Digest Ref(const EVP_MD* md);

 private:
  const EVP_MD* md_ = nullptr;
#if NCRYPTO_USE_OPENSSL_PROVIDER
  explicit Digest(DeleteFnPtr<EVP_MD, EVP_MD_free> md);
  DeleteFnPtr<EVP_MD, EVP_MD_free> fetched_md_;
#endif
};

struct CaseInsensitiveNameHash {
  using is_transparent = void;
  size_t operator()(std::string_view name) const noexcept;
};

struct CaseInsensitiveNameEqual {
  using is_transparent = void;
  bool operator()(std::string_view lhs, std::string_view rhs) const noexcept;
};

class DigestCache final {
 public:
  struct Result {
    const EVP_MD* digest = nullptr;
    int32_t id = -1;
  };

  using AliasMap = std::unordered_map<std::string,
                                      int32_t,
                                      CaseInsensitiveNameHash,
                                      CaseInsensitiveNameEqual>;

  DigestCache() = default;
  ~DigestCache();
  NCRYPTO_DISALLOW_COPY_AND_MOVE(DigestCache)

  Result lookup(const char* name, uint64_t generation) const;
  inline Result lookup(int32_t id, uint64_t generation) const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
    if (generation_ != generation || id == -1) return {};
    const uint32_t unsigned_id = static_cast<uint32_t>(id);
    if (unsigned_id < first_id_) return {};
    const size_t index = unsigned_id - first_id_;
    if (index >= digests_.size()) return {};
    return {digests_[index].get(), id};
#elif NCRYPTO_USE_BORINGSSL
    static_cast<void>(id);
    static_cast<void>(generation);
    return {};
#else
#error "Unsupported crypto backend"
#endif
  }
  Result insert(const char* name, const EVP_MD* digest, uint64_t generation);
  void reset(uint64_t generation);
  const AliasMap& aliases() const;
#if NCRYPTO_USE_OPENSSL_PROVIDER
  // Returns an HMAC context for the cached digest that has no key yet.
  // Duplicating it avoids fetching HMAC and the digest for every context.
  const HMACCtxPointer* getHmacTemplate(const EVP_MD* digest,
                                        uint64_t generation);
#endif

 private:
  uint64_t generation_ = 0;
#if NCRYPTO_USE_OPENSSL_PROVIDER
  using EVPMDPointer = DeleteFnPtr<EVP_MD, EVP_MD_free>;

  // IDs are not reused across generations because JavaScript caches them
  // independently in each Realm.
  uint32_t first_id_ = 0;
  uint32_t next_id_ = 0;
  std::vector<EVPMDPointer> digests_;
  std::vector<std::unique_ptr<HMACCtxPointer>> hmac_templates_;
  AliasMap aliases_;
#endif
};

// Computes a fixed-length digest.
DataPointer hashDigest(const Buffer<const unsigned char>& data,
                       const EVP_MD* md);
// Computes a variable-length digest for XOF algorithms (e.g. SHAKE128).
DataPointer xofHashDigest(const Buffer<const unsigned char>& data,
                          const EVP_MD* md,
                          size_t length);

class CipherCache final {
 public:
  CipherCache() = default;
  NCRYPTO_DISALLOW_COPY_AND_MOVE(CipherCache)

  const EVP_CIPHER* lookup(const char* name, uint64_t generation);
#if NCRYPTO_USE_OPENSSL_PROVIDER
  const EVP_CIPHER* insert(const char* name,
                           DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free>&& cipher,
                           uint64_t generation);
#endif

 private:
#if NCRYPTO_USE_OPENSSL_PROVIDER
  using EVPCipherPointer = DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free>;

  uint64_t generation_ = 0;
  std::vector<EVPCipherPointer> ciphers_;
  std::unordered_map<std::string,
                     size_t,
                     CaseInsensitiveNameHash,
                     CaseInsensitiveNameEqual>
      aliases_;
#endif
};

class Cipher final {
 public:
  static constexpr size_t MAX_KEY_LENGTH = EVP_MAX_KEY_LENGTH;
  static constexpr size_t MAX_IV_LENGTH = EVP_MAX_IV_LENGTH;
#ifdef EVP_MAX_AEAD_TAG_LENGTH
  static constexpr size_t MAX_AUTH_TAG_LENGTH = EVP_MAX_AEAD_TAG_LENGTH;
#else
  static constexpr size_t MAX_AUTH_TAG_LENGTH = 16;
#endif
  static_assert(EVP_GCM_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH
#ifndef OPENSSL_IS_BORINGSSL
                && EVP_CCM_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH &&
                EVP_CHACHAPOLY_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH
#endif
  );  // NOLINT(whitespace/parens)

  Cipher() = default;
  Cipher(const EVP_CIPHER* cipher) : cipher_(cipher) {}
  Cipher(const Cipher& other);
  Cipher& operator=(const Cipher& other);
  inline Cipher& operator=(const EVP_CIPHER* cipher) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
    fetched_cipher_.reset();
#endif
    cipher_ = cipher;
    return *this;
  }
  NCRYPTO_DISALLOW_MOVE(Cipher)

  inline const EVP_CIPHER* get() const { return cipher_; }
  inline operator const EVP_CIPHER*() const { return cipher_; }
  inline operator bool() const { return cipher_ != nullptr; }

  int getNid() const;
  int getMode() const;
  int getIvLength() const;
  int getKeyLength() const;
  int getBlockSize() const;
  std::string_view getModeLabel() const;
  const char* getName() const;

  bool isGcmMode() const;
  bool isWrapMode() const;
  bool isCtrMode() const;
  bool isCcmMode() const;
  bool isCtsMode() const;
  bool isOcbMode() const;
  bool isSivMode() const;
  bool isGcmSivMode() const;
  bool isStreamMode() const;
  bool isChaCha20Poly1305() const;

  bool isSupportedAuthenticatedMode() const;

  int bytesToKey(const Digest& digest,
                 const Buffer<const unsigned char>& input,
                 unsigned char* key,
                 unsigned char* iv) const;

  static const Cipher FromName(const char* name, CipherCache* cache = nullptr);
  static const Cipher FromNameForKeyEncoding(const char* name);
  static const Cipher FromNid(int nid, CipherCache* cache = nullptr);
  static const Cipher FromCtx(const CipherCtxPointer& ctx);

  using CipherNameCallback = std::function<void(const char* name)>;

  // Iterates the known ciphers if the underlying implementation
  // is able to do so.
  static void ForEach(CipherNameCallback callback);

  // Lazily resolves common ciphers. If the underlying implementation does not
  // support the requested cipher, the returned Cipher will be empty.
  static const Cipher& AES_128_CBC();
  static const Cipher& AES_192_CBC();
  static const Cipher& AES_256_CBC();
  static const Cipher& AES_128_CTR();
  static const Cipher& AES_192_CTR();
  static const Cipher& AES_256_CTR();
  static const Cipher& AES_128_GCM();
  static const Cipher& AES_192_GCM();
  static const Cipher& AES_256_GCM();
  static const Cipher& AES_128_KW();
  static const Cipher& AES_192_KW();
  static const Cipher& AES_256_KW();
  static const Cipher& AES_128_OCB();
  static const Cipher& AES_192_OCB();
  static const Cipher& AES_256_OCB();
  static const Cipher& CHACHA20_POLY1305();

  struct CipherParams {
    int padding;
    Digest digest;
    Digest mgf1_digest;
    const Buffer<const void> label;
  };

  static DataPointer encrypt(const EVPKeyPointer& key,
                             const CipherParams& params,
                             const Buffer<const void> in);
  static DataPointer decrypt(const EVPKeyPointer& key,
                             const CipherParams& params,
                             const Buffer<const void> in);

  static DataPointer sign(const EVPKeyPointer& key,
                          const CipherParams& params,
                          const Buffer<const void> in);

  static DataPointer recover(const EVPKeyPointer& key,
                             const CipherParams& params,
                             const Buffer<const void> in);

  static constexpr bool IsValidGCMTagLength(unsigned int tag_len) {
    return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16);
  }

 private:
  const EVP_CIPHER* cipher_ = nullptr;
#if NCRYPTO_USE_OPENSSL_PROVIDER
  explicit Cipher(DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> cipher);
  DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> fetched_cipher_;
#endif
};

// ============================================================================
// DSA

class Dsa final {
 public:
  Dsa();
#if NCRYPTO_USE_OPENSSL_PROVIDER
  explicit Dsa(const EVP_PKEY* pkey);
#elif NCRYPTO_USE_BORINGSSL
  Dsa(OSSL3_CONST DSA* dsa);
#else
#error "Unsupported crypto backend"
#endif
  NCRYPTO_DISALLOW_COPY_AND_MOVE(Dsa)

#if NCRYPTO_USE_OPENSSL_PROVIDER
  inline operator bool() const {
    return dsa_;
  }
#elif NCRYPTO_USE_BORINGSSL
  inline operator bool() const { return dsa_ != nullptr; }
#else
#error "Unsupported crypto backend"
#endif
#if NCRYPTO_USE_LEGACY_KEY_TYPES
  inline operator OSSL3_CONST DSA*() const { return dsa_; }
#endif

  const BIGNUM* getP() const;
  const BIGNUM* getQ() const;
  size_t getModulusLength() const;
  size_t getDivisorLength() const;

 private:
#if NCRYPTO_USE_OPENSSL_PROVIDER
  bool dsa_ = false;
  DeleteFnPtr<BIGNUM, BN_free> p_;
  DeleteFnPtr<BIGNUM, BN_free> q_;
#elif NCRYPTO_USE_BORINGSSL
  OSSL3_CONST DSA* dsa_;
#else
#error "Unsupported crypto backend"
#endif
};

// ============================================================================
// RSA

class Rsa final {
 public:
  Rsa();
  enum class Selection { Public, Private };
  static Rsa PublicOnly(const EVPKeyPointer& key);
#if NCRYPTO_USE_OPENSSL_PROVIDER
  explicit Rsa(const EVP_PKEY* pkey, Selection selection = Selection::Private);
#elif NCRYPTO_USE_BORINGSSL
  Rsa(OSSL3_CONST RSA* rsa);
#else
#error "Unsupported crypto backend"
#endif
  NCRYPTO_DISALLOW_COPY_AND_MOVE(Rsa)

#if NCRYPTO_USE_OPENSSL_PROVIDER
  inline operator bool() const {
    return rsa_;
  }
#elif NCRYPTO_USE_BORINGSSL
  inline operator bool() const { return rsa_ != nullptr; }
#else
#error "Unsupported crypto backend"
#endif
#if NCRYPTO_USE_LEGACY_KEY_TYPES
  inline operator OSSL3_CONST RSA*() const { return rsa_; }
#endif

  struct PublicKey {
    const BIGNUM* n;
    const BIGNUM* e;
    const BIGNUM* d;
  };
  struct PrivateKey {
    const BIGNUM* p;
    const BIGNUM* q;
    const BIGNUM* dp;
    const BIGNUM* dq;
    const BIGNUM* qi;
  };
  struct OtherPrimeInfo {
    const BIGNUM* r;
    const BIGNUM* d;
    const BIGNUM* t;
  };
  struct OtherPrimeInfoPointer {
    OtherPrimeInfoPointer() = default;
    OtherPrimeInfoPointer(BignumPointer&& r,
                          BignumPointer&& d,
                          BignumPointer&& t);

    DeleteFnPtr<BIGNUM, BN_clear_free> r;
    DeleteFnPtr<BIGNUM, BN_clear_free> d;
    DeleteFnPtr<BIGNUM, BN_clear_free> t;
  };
  using OtherPrimeInfos = std::vector<OtherPrimeInfo>;
  using OtherPrimeInfoPointers = std::vector<OtherPrimeInfoPointer>;
  struct PssParams {
    std::string_view digest = "sha1";
    std::optional<std::string_view> mgf1_digest = "sha1";
    int64_t salt_length = 20;
  };

  const PublicKey getPublicKey() const;
  const PrivateKey getPrivateKey() const;
  const OtherPrimeInfos getOtherPrimeInfos() const;
  // Check that n is the product of all private-key prime factors.
  bool checkPrimeProduct() const;
  const std::optional<PssParams> getPssParams() const;

  bool setPublicKey(BignumPointer&& n, BignumPointer&& e);
  bool setPrivateKey(BignumPointer&& d,
                     BignumPointer&& q,
                     BignumPointer&& p,
                     BignumPointer&& dp,
                     BignumPointer&& dq,
                     BignumPointer&& qi,
                     OtherPrimeInfoPointers&& other_prime_infos = {});

  using CipherParams = Cipher::CipherParams;

  BIOPointer derPublicKey() const;

  static DataPointer encrypt(const EVPKeyPointer& key,
                             const CipherParams& params,
                             const Buffer<const void> in);
  static DataPointer decrypt(const EVPKeyPointer& key,
                             const CipherParams& params,
                             const Buffer<const void> in);

 private:
#if NCRYPTO_USE_OPENSSL_PROVIDER
  bool rsa_ = false;
  bool rsa_pss_ = false;
  DeleteFnPtr<BIGNUM, BN_free> n_;
  DeleteFnPtr<BIGNUM, BN_free> e_;
  DeleteFnPtr<BIGNUM, BN_clear_free> d_;
  DeleteFnPtr<BIGNUM, BN_clear_free> p_;
  DeleteFnPtr<BIGNUM, BN_clear_free> q_;
  DeleteFnPtr<BIGNUM, BN_clear_free> dp_;
  DeleteFnPtr<BIGNUM, BN_clear_free> dq_;
  DeleteFnPtr<BIGNUM, BN_clear_free> qi_;
  OtherPrimeInfoPointers other_prime_infos_;
  std::optional<PssParams> pss_params_;
#elif NCRYPTO_USE_BORINGSSL
  OSSL3_CONST RSA* rsa_;
#else
#error "Unsupported crypto backend"
#endif
};

class Ec final {
 public:
  Ec();
#if NCRYPTO_USE_OPENSSL_PROVIDER
  explicit Ec(const EVP_PKEY* pkey);
#elif NCRYPTO_USE_BORINGSSL
  Ec(OSSL3_CONST EC_KEY* key);
#else
#error "Unsupported crypto backend"
#endif
  NCRYPTO_DISALLOW_COPY_AND_MOVE(Ec)

  const EC_GROUP* getGroup() const;
  const EC_POINT* getPublicKey() const;
  point_conversion_form_t getPointConversionForm() const;
  int getCurve() const;

  inline operator bool() const { return ec_ != nullptr; }
#if NCRYPTO_USE_LEGACY_KEY_TYPES
  inline operator OSSL3_CONST EC_KEY*() const { return ec_; }
#endif

  static int GetCurveIdFromName(const char* name);
  static int GetCurveId(const EVPKeyPointer& key);
  static std::optional<std::string> GetCurveName(const EVPKeyPointer& key);
  static bool CheckCurveName(const char* name);
  static DataPointer TryExportPublic(const EVPKeyPointer& key,
                                     point_conversion_form_t form);
  static DataPointer ExportPrivate(const EVPKeyPointer& key);
  static BIOPointer ExportPrivatePkcs8(const EVPKeyPointer& key);
  static bool GetKeyComponents(const EVPKeyPointer& key,
                               BignumPointer* x,
                               BignumPointer* y,
                               BignumPointer* priv,
                               int* degree);
  static const KeyAlgorithm* GetNamedKeyAlgorithm(const char* name);

  using GetCurveCallback = std::function<bool(const char*)>;
  static bool GetCurves(GetCurveCallback callback);

 private:
#if NCRYPTO_USE_OPENSSL_PROVIDER
  DeleteFnPtr<EC_GROUP, EC_GROUP_free> ec_;
  DeleteFnPtr<EC_POINT, EC_POINT_free> pub_;
  point_conversion_form_t form_ = POINT_CONVERSION_UNCOMPRESSED;
#elif NCRYPTO_USE_BORINGSSL
  OSSL3_CONST EC_KEY* ec_ = nullptr;
#else
#error "Unsupported crypto backend"
#endif
};

// A managed pointer to a buffer of data. When destroyed the underlying
// buffer will be freed.
class DataPointer final {
 public:
  static DataPointer Alloc(size_t len);
  static DataPointer Copy(const Buffer<const void>& buffer);

  // Attempts to allocate the buffer space using the secure heap, if
  // supported/enabled. If the secure heap is disabled, then this
  // ends up being equivalent to Alloc(len). Note that allocation
  // will fail if there is not enough free space remaining in the
  // secure heap space.
  static DataPointer SecureAlloc(size_t len);

  // If the secure heap is enabled, returns the amount of data that
  // has been allocated from the heap.
  static size_t GetSecureHeapUsed();

  enum class InitSecureHeapResult {
    FAILED,
    UNABLE_TO_MEMORY_MAP,
    OK,
  };

  // Attempt to initialize the secure heap. The secure heap is not
  // supported on all operating systems and whenever boringssl is
  // used.
  static InitSecureHeapResult TryInitSecureHeap(size_t amount, size_t min);

  DataPointer() = default;
  explicit DataPointer(void* data, size_t len, bool secure = false);
  explicit DataPointer(const Buffer<void>& buffer, bool secure = false);
  DataPointer(DataPointer&& other) noexcept;
  DataPointer& operator=(DataPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(DataPointer)
  ~DataPointer();

  inline bool operator==(std::nullptr_t) noexcept { return data_ == nullptr; }
  inline operator bool() const { return data_ != nullptr; }

  template <typename T = void>
  inline T* get() const noexcept {
    return static_cast<T*>(data_);
  }

  inline size_t size() const noexcept { return len_; }
  void reset(void* data = nullptr, size_t len = 0);
  void reset(const Buffer<void>& buffer);

  // Sets the underlying data buffer to all zeros.
  void zero();

  DataPointer resize(size_t len);

  // Releases ownership of the underlying data buffer. It is the caller's
  // responsibility to ensure the buffer is appropriately freed.
  Buffer<void> release();

  // Returns a Buffer struct that is a view of the underlying data.
  template <typename T = void>
  inline operator const Buffer<T>() const {
    return {
        .data = static_cast<T*>(data_),
        .len = len_,
    };
  }

  bool isSecure() const { return secure_; }

 private:
  void* data_ = nullptr;
  size_t len_ = 0;
  bool secure_ = false;
};

class BIOPointer final {
 public:
  static BIOPointer NewMem();
  static BIOPointer NewSecMem();
  static BIOPointer New(const BIO_METHOD* method);
  static BIOPointer New(const void* data, size_t len);
  static BIOPointer New(const BIGNUM* bn);
  static BIOPointer NewFile(const char* filename, const char* mode);
  static BIOPointer NewFp(FILE* fd, int flags);

  template <typename T>
  static BIOPointer New(const Buffer<T>& buf) {
    return New(buf.data, buf.len);
  }

  BIOPointer() = default;
  BIOPointer(std::nullptr_t) : bio_(nullptr) {}
  explicit BIOPointer(BIO* bio);
  BIOPointer(BIOPointer&& other) noexcept;
  BIOPointer& operator=(BIOPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(BIOPointer)
  ~BIOPointer();

  inline bool operator==(std::nullptr_t) noexcept { return bio_ == nullptr; }
  inline operator bool() const { return bio_ != nullptr; }
  inline BIO* get() const noexcept { return bio_.get(); }

  inline operator BUF_MEM*() const {
    BUF_MEM* mem = nullptr;
    if (!bio_) return mem;
    BIO_get_mem_ptr(bio_.get(), &mem);
    return mem;
  }

  inline operator BIO*() const { return bio_.get(); }

  void reset(BIO* bio = nullptr);
  BIO* release();

  bool resetBio() const;

  static int Write(BIOPointer* bio, std::string_view message);

  // Writes the complete message and returns false if any bytes were not
  // written.
  static bool WriteAll(const BIOPointer& bio, std::string_view message);

  // Returns the number of bytes written, or a negative value on error.
  template <typename... Args>
  static int Printf(BIOPointer* bio, const char* format, Args... args) {
    if (bio == nullptr || !*bio) return -1;
    return BIO_printf(bio->get(), format, std::forward<Args>(args)...);
  }

  // Formats and writes the complete message, returning false if formatting
  // fails or any bytes were not written.
  template <typename... Args>
  static bool PrintfAll(const BIOPointer& bio,
                        const char* format,
                        Args... args) {
    if (!bio) return false;

    BIOPointer formatted = NewMem();
    if (!formatted) return false;
    int length =
        BIO_printf(formatted.get(), format, std::forward<Args>(args)...);
    if (length < 0) return false;

    BUF_MEM* mem = formatted;
    if (mem == nullptr || mem->length != static_cast<size_t>(length)) {
      return false;
    }
    if (length == 0) return true;
    return WriteAll(bio, std::string_view(mem->data, mem->length));
  }

 private:
  mutable DeleteFnPtr<BIO, BIO_free_all> bio_;
};

class BignumPointer final {
 public:
  BignumPointer() = default;
  explicit BignumPointer(BIGNUM* bignum);
  explicit BignumPointer(const unsigned char* data, size_t len);
  BignumPointer(BignumPointer&& other) noexcept;
  BignumPointer& operator=(BignumPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(BignumPointer)
  ~BignumPointer();

  int operator<=>(const BignumPointer& other) const noexcept;
  int operator<=>(const BIGNUM* other) const noexcept;
  inline operator bool() const { return bn_ != nullptr; }
  inline BIGNUM* get() const noexcept { return bn_.get(); }
  void reset(BIGNUM* bn = nullptr);
  void reset(const unsigned char* data, size_t len);
  BIGNUM* release();

  bool isZero() const;
  bool isOne() const;

  bool setWord(unsigned long w);  // NOLINT(runtime/int)
  std::optional<unsigned long> getWord() const;  // NOLINT(runtime/int)

  size_t byteLength() const;

  DataPointer toHex() const;
  DataPointer encode() const;
  DataPointer encodePadded(size_t size) const;
  size_t encodeInto(unsigned char* out) const;
  size_t encodePaddedInto(unsigned char* out, size_t size) const;

  using PrimeCheckCallback = std::function<bool(int, int)>;
  int isPrime(int checks,
              PrimeCheckCallback cb = defaultPrimeCheckCallback) const;
  struct PrimeConfig {
    int bits;
    bool safe = false;
    const BignumPointer& add;
    const BignumPointer& rem;
  };

  static BignumPointer NewPrime(
      const PrimeConfig& params,
      PrimeCheckCallback cb = defaultPrimeCheckCallback);

  bool generate(const PrimeConfig& params,
                PrimeCheckCallback cb = defaultPrimeCheckCallback) const;

  static BignumPointer New();
  static BignumPointer NewSecure();
  static BignumPointer NewSub(const BignumPointer& a, const BignumPointer& b);
  static BignumPointer NewLShift(size_t length);

  static DataPointer Encode(const BIGNUM* bn);
  static DataPointer EncodePadded(const BIGNUM* bn, size_t size);
  static size_t EncodePaddedInto(const BIGNUM* bn,
                                 unsigned char* out,
                                 size_t size);
  static int GetBitCount(const BIGNUM* bn);
  static int GetByteCount(const BIGNUM* bn);
  static std::optional<unsigned long> GetWord(  // NOLINT(runtime/int)
      const BIGNUM* bn);
  static const BIGNUM* One();

  BignumPointer clone();

 private:
  DeleteFnPtr<BIGNUM, BN_clear_free> bn_;

  static bool defaultPrimeCheckCallback(int, int) { return 1; }
};

class CipherCtxPointer final {
 public:
  static CipherCtxPointer New();

  CipherCtxPointer() = default;
  explicit CipherCtxPointer(EVP_CIPHER_CTX* ctx);
  CipherCtxPointer(CipherCtxPointer&& other) noexcept;
  CipherCtxPointer& operator=(CipherCtxPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(CipherCtxPointer)
  ~CipherCtxPointer();

  inline bool operator==(std::nullptr_t) const noexcept {
    return ctx_ == nullptr;
  }
  inline operator bool() const { return ctx_ != nullptr; }
  inline EVP_CIPHER_CTX* get() const { return ctx_.get(); }
  inline operator EVP_CIPHER_CTX*() const { return ctx_.get(); }
  void reset(EVP_CIPHER_CTX* ctx = nullptr);
  EVP_CIPHER_CTX* release();

  void setAllowWrap();

  bool setKeyLength(size_t length);
  bool setIvLength(size_t length);
  bool setAeadTag(const Buffer<const char>& tag);
  bool setAeadTagLength(size_t length);
  bool setCtsMode(const char* mode);
  bool setPadding(bool padding);
  bool setXtsStandard(const char* standard);
  bool init(const Cipher& cipher,
            bool encrypt,
            const unsigned char* key = nullptr,
            const unsigned char* iv = nullptr);

  int getBlockSize() const;
  int getMode() const;
  int getNid() const;

  bool isGcmMode() const;
  bool isOcbMode() const;
  bool isCcmMode() const;
  bool isCtsMode() const;
  bool isXtsMode() const;
  bool isWrapMode() const;
  bool isSivMode() const;
  bool isGcmSivMode() const;
  bool isChaCha20Poly1305() const;

  bool update(const Buffer<const unsigned char>& in,
              unsigned char* out,
              int* out_len,
              bool finalize = false);
  bool getAeadTag(size_t len, unsigned char* out);

 private:
  DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free> ctx_;
};

// Known key algorithms are identified by provider names, never synthetic NIDs.
// Descriptors have static lifetime; availability is queried from the backend.
class KeyAlgorithm final {
 public:
  static const KeyAlgorithm RSA;
  static const KeyAlgorithm RSA_PSS;
  static const KeyAlgorithm DSA;
  static const KeyAlgorithm DH;
  static const KeyAlgorithm EC;
  static const KeyAlgorithm ED25519;
  static const KeyAlgorithm ED448;
  static const KeyAlgorithm X25519;
  static const KeyAlgorithm X448;
  static const KeyAlgorithm SM2;
  static const KeyAlgorithm ML_DSA_44;
  static const KeyAlgorithm ML_DSA_65;
  static const KeyAlgorithm ML_DSA_87;
  static const KeyAlgorithm ML_KEM_512;
  static const KeyAlgorithm ML_KEM_768;
  static const KeyAlgorithm ML_KEM_1024;
  static const KeyAlgorithm SLH_DSA_SHA2_128F;
  static const KeyAlgorithm SLH_DSA_SHA2_128S;
  static const KeyAlgorithm SLH_DSA_SHA2_192F;
  static const KeyAlgorithm SLH_DSA_SHA2_192S;
  static const KeyAlgorithm SLH_DSA_SHA2_256F;
  static const KeyAlgorithm SLH_DSA_SHA2_256S;
  static const KeyAlgorithm SLH_DSA_SHAKE_128F;
  static const KeyAlgorithm SLH_DSA_SHAKE_128S;
  static const KeyAlgorithm SLH_DSA_SHAKE_192F;
  static const KeyAlgorithm SLH_DSA_SHAKE_192S;
  static const KeyAlgorithm SLH_DSA_SHAKE_256F;
  static const KeyAlgorithm SLH_DSA_SHAKE_256S;

  // Look up a canonical name case-insensitively, including unavailable
  // algorithms.
  static const KeyAlgorithm* FromName(const char* name);

  const char* name() const { return name_; }
  const char* keyTypeName() const {
    return key_type_name_[0] == '\0' ? nullptr : key_type_name_.data();
  }
  bool isRsa() const;
  bool isAvailable() const;
  bool isPqc() const;
  bool isOkp() const;
  bool isOneShot() const;
  bool supportsRawPublic() const;
  bool supportsRawPrivate() const;
  size_t seedSize() const;

 private:
  enum class Family { Other, EdDSA, XDH, MLDSA, MLKEM, SLHDSA };
  static constexpr size_t kMaxKeyTypeNameLength = 32;
  template <size_t N>
  constexpr KeyAlgorithm(const char (&name)[N],
                         Family family,
                         bool has_key_type = true)
      : name_(name), family_(family) {
    static_assert(N <= kMaxKeyTypeNameLength);
    if (has_key_type) {
      for (size_t i = 0; i < N; i++) {
        key_type_name_[i] =
            name[i] >= 'A' && name[i] <= 'Z' ? name[i] + ('a' - 'A') : name[i];
      }
    }
  }
  const char* name_;
  std::array<char, kMaxKeyTypeNameLength> key_type_name_{};
  Family family_;
};

class EVPKeyCtxPointer final {
 public:
  EVPKeyCtxPointer();
  explicit EVPKeyCtxPointer(EVP_PKEY_CTX* ctx);
  EVPKeyCtxPointer(EVPKeyCtxPointer&& other) noexcept;
  EVPKeyCtxPointer& operator=(EVPKeyCtxPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(EVPKeyCtxPointer)
  ~EVPKeyCtxPointer();

  inline bool operator==(std::nullptr_t) const noexcept {
    return ctx_ == nullptr;
  }
  inline operator bool() const { return ctx_ != nullptr; }
  inline EVP_PKEY_CTX* get() const { return ctx_.get(); }
  void reset(EVP_PKEY_CTX* ctx = nullptr);
  EVP_PKEY_CTX* release();

  bool initForDerive(const EVPKeyPointer& peer);
  DataPointer derive() const;

  bool initForParamgen();
  bool setDhParameters(int prime_size, uint32_t generator);
  bool setDsaParameters(uint32_t bits, std::optional<int> q_bits);
  bool setEcParameters(int curve, int encoding);
  bool setEcParameters(const char* group_name, int encoding);

  bool setRsaOaepMd(const Digest& md);
  bool setRsaMgf1Md(const Digest& md);
  bool setRsaPadding(int padding);
  bool setRsaKeygenPubExp(BignumPointer&& e);
  bool setRsaKeygenBits(int bits);
  bool setRsaPssKeygenMd(const Digest& md);
  bool setRsaPssKeygenMgf1Md(const Digest& md);
  bool setRsaPssSaltlen(int salt_len);
  bool setRsaImplicitRejection();
  bool setRsaOaepLabel(DataPointer&& data);

  bool setSignatureMd(const EVPMDCtxPointer& md);

  bool publicCheck() const;
  bool privateCheck() const;

  bool verify(const Buffer<const unsigned char>& sig,
              const Buffer<const unsigned char>& data);
  DataPointer sign(const Buffer<const unsigned char>& data);
  bool signInto(const Buffer<const unsigned char>& data,
                Buffer<unsigned char>* sig);

  static constexpr int kDefaultRsaExponent = 0x10001;

  static bool setRsaPadding(EVP_PKEY_CTX* ctx,
                            int padding,
                            std::optional<int> salt_len = std::nullopt);

  EVPKeyPointer paramgen() const;

  bool initForEncrypt();
  bool initForDecrypt();
  bool initForKeygen();
  int initForVerify();
  int initForSign();

  static EVPKeyCtxPointer New(const EVPKeyPointer& key);
  static EVPKeyCtxPointer NewFromName(const char* name);
  static EVPKeyCtxPointer NewFromAlgorithm(const KeyAlgorithm& algorithm);

 private:
  DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free> ctx_;
};

class EVPKeyPointer final {
 public:
  static EVPKeyPointer New();
  static EVPKeyPointer NewRawPublic(const KeyAlgorithm& algorithm,
                                    const Buffer<const unsigned char>& data);
  static EVPKeyPointer NewRawPrivate(const KeyAlgorithm& algorithm,
                                     const Buffer<const unsigned char>& data);
  static EVPKeyPointer NewRawSeed(const KeyAlgorithm& algorithm,
                                  const Buffer<const unsigned char>& data);
  static EVPKeyPointer NewDH(DHPointer&& dh);
#if NCRYPTO_USE_OPENSSL_PROVIDER
  static EVPKeyPointer NewRSA(const Rsa& rsa);
#elif NCRYPTO_USE_BORINGSSL
  static EVPKeyPointer NewRSA(RSAPointer&& rsa);
#else
#error "Unsupported crypto backend"
#endif

  enum class PKEncodingType {
    // RSAPublicKey / RSAPrivateKey according to PKCS#1.
    PKCS1,
    // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8.
    PKCS8,
    // SubjectPublicKeyInfo according to X.509.
    SPKI,
    // ECPrivateKey according to SEC1.
    SEC1,
  };

  enum class PKFormatType {
    DER,
    PEM,
    JWK,
    RAW_PUBLIC,
    RAW_PRIVATE,
    RAW_SEED,
    STORE,
  };

  enum class PKParseError { NOT_RECOGNIZED, NEED_PASSPHRASE, FAILED };
  using ParseKeyResult = Result<EVPKeyPointer, PKParseError>;

  struct AsymmetricKeyEncodingConfig {
    bool output_key_object = false;
    PKFormatType format = PKFormatType::DER;
    PKEncodingType type = PKEncodingType::PKCS8;
    int ec_point_form = POINT_CONVERSION_UNCOMPRESSED;
    AsymmetricKeyEncodingConfig() = default;
    AsymmetricKeyEncodingConfig(bool output_key_object,
                                PKFormatType format,
                                PKEncodingType type);
    AsymmetricKeyEncodingConfig(const AsymmetricKeyEncodingConfig&) = default;
    AsymmetricKeyEncodingConfig& operator=(const AsymmetricKeyEncodingConfig&) =
        default;
  };
  using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig;

  struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig {
    Cipher cipher;
    std::optional<DataPointer> passphrase = std::nullopt;
    PrivateKeyEncodingConfig() = default;
    PrivateKeyEncodingConfig(bool output_key_object,
                             PKFormatType format,
                             PKEncodingType type)
        : AsymmetricKeyEncodingConfig(output_key_object, format, type) {}
    PrivateKeyEncodingConfig(const PrivateKeyEncodingConfig&);
    PrivateKeyEncodingConfig& operator=(const PrivateKeyEncodingConfig&);
  };

  struct StorePrivateKeyConfig {
    std::string_view uri;
    std::optional<std::string_view> properties = std::nullopt;
    std::optional<Buffer<const char>> passphrase = std::nullopt;
  };

  static ParseKeyResult TryParsePublicKey(
      const PublicKeyEncodingConfig& config,
      const Buffer<const unsigned char>& buffer);

  static ParseKeyResult TryParsePublicKeyPEM(
      const Buffer<const unsigned char>& buffer);

  static ParseKeyResult TryParsePrivateKey(
      const PrivateKeyEncodingConfig& config,
      const Buffer<const unsigned char>& buffer);

  // Loads a private key through an OpenSSL STORE loader using the configured
  // URI (e.g. "file:", a provider-backed scheme such as "pkcs11:"). The
  // optional passphrase is used as the PIN/passphrase for encrypted or
  // token-protected keys.
  // Returns NOT_RECOGNIZED when no private key is found at the URI.
  static ParseKeyResult TryLoadPrivateKeyFromStore(
      const StorePrivateKeyConfig& config);

  EVPKeyPointer() = default;
  explicit EVPKeyPointer(EVP_PKEY* pkey);
  EVPKeyPointer(EVPKeyPointer&& other) noexcept;
  EVPKeyPointer& operator=(EVPKeyPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(EVPKeyPointer)
  ~EVPKeyPointer();

  bool assign(const ECKeyPointer& eckey);
  bool set(const ECKeyPointer& eckey);
#if NCRYPTO_USE_LEGACY_KEY_TYPES
  operator const EC_KEY*() const;
#endif

  inline bool operator==(std::nullptr_t) const noexcept {
    return pkey_ == nullptr;
  }
  inline operator bool() const { return pkey_ != nullptr; }
  inline EVP_PKEY* get() const { return pkey_.get(); }
  void reset(EVP_PKEY* pkey = nullptr);
  EVP_PKEY* release();

  static bool isA(const EVP_PKEY* key, const char* name);
  bool isA(const char* name) const;
  static bool isA(const EVP_PKEY* key, const KeyAlgorithm& algorithm);
  bool isA(const KeyAlgorithm& algorithm) const;
  // Resolve a known algorithm without caching key or provider state.
  const KeyAlgorithm* getAlgorithm() const;
  // Stable public key-type name, or nullptr for an unsupported key type.
  const char* getKeyTypeName() const;
  bool supportsRawPublic() const;
  bool supportsRawPrivate() const;
  bool supportsContextString() const;
  bool hasSmallOrderEdDsaPoint(
      const Buffer<const unsigned char>& signature) const;
  int bits() const;
  size_t size() const;

  size_t rawPublicKeySize() const;
  size_t rawPrivateKeySize() const;
  DataPointer rawPublicKey() const;
  DataPointer rawPrivateKey() const;
  BIOPointer derPublicKey() const;

  enum class RawExportError { UNSUPPORTED_KEY_TYPE, MISSING_SEED, FAILED };
  Result<DataPointer, RawExportError> rawSeed() const;

  struct RawJwkData {
    const KeyAlgorithm* algorithm = nullptr;
    DataPointer public_key;
    DataPointer private_key;
  };
  // Raw JWK material for OKP and AKP keys. Private bytes use the JWK
  // representation (a seed for ML-DSA/ML-KEM, a raw private key otherwise).
  Result<RawJwkData, RawExportError> exportRawJwk(bool include_private) const;
  static EVPKeyPointer NewRawJwk(
      const KeyAlgorithm& algorithm,
      const Buffer<const unsigned char>& public_key,
      const std::optional<Buffer<const unsigned char>>& private_key =
          std::nullopt);

  Result<BIOPointer, bool> writePrivateKey(
      const PrivateKeyEncodingConfig& config) const;
  Result<BIOPointer, bool> writePublicKey(
      const PublicKeyEncodingConfig& config) const;

  EVPKeyCtxPointer newCtx() const;

  static bool IsRSAPrivateKey(const Buffer<const unsigned char>& buffer);

  std::optional<uint32_t> getBytesOfRS() const;
  int getDefaultSignPadding() const;
  operator Rsa() const;
  operator Dsa() const;

  static bool isRsaVariant(const EVP_PKEY* key);
  bool isRsaVariant() const;
  bool isSigVariant() const;
  bool mayBeSM2() const;
  bool validateDsaParameters() const;

 private:
  DeleteFnPtr<EVP_PKEY, EVP_PKEY_free> pkey_;
};

class DHPointer final {
 public:
  enum class FindGroupOption {
    NONE,
    // There are known and documented security issues with prime groups smaller
    // than 2048 bits. When the NO_SMALL_PRIMES option is set, these small prime
    // groups will not be supported.
    NO_SMALL_PRIMES,
  };

  static BignumPointer GetStandardGenerator();

  static BignumPointer FindGroup(
      std::string_view name, FindGroupOption option = FindGroupOption::NONE);
  static DHPointer FromGroup(std::string_view name,
                             FindGroupOption option = FindGroupOption::NONE);

  static DHPointer New(BignumPointer&& p, BignumPointer&& g);
  static DHPointer New(size_t bits, unsigned int generator);

  DHPointer() = default;
#if NCRYPTO_USE_OPENSSL_PROVIDER
  explicit DHPointer(EVPKeyPointer&& key, const char* group_name = nullptr);
  DHPointer(BignumPointer&& p, BignumPointer&& g, const char* group_name);
#elif NCRYPTO_USE_BORINGSSL
  explicit DHPointer(DH* dh);
#else
#error "Unsupported crypto backend"
#endif
  DHPointer(DHPointer&& other) noexcept;
  DHPointer& operator=(DHPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(DHPointer)
  ~DHPointer();

#if NCRYPTO_USE_OPENSSL_PROVIDER
  inline bool operator==(std::nullptr_t) noexcept {
    return !operator bool();
  }
  inline operator bool() const {
    return dh_ != nullptr || (p_ && g_);
  }
#elif NCRYPTO_USE_BORINGSSL
  inline bool operator==(std::nullptr_t) noexcept { return dh_ == nullptr; }
  inline operator bool() const { return dh_ != nullptr; }
#else
#error "Unsupported crypto backend"
#endif
#if NCRYPTO_USE_BORINGSSL
  inline DH* get() const { return dh_.get(); }
  void reset(DH* dh = nullptr);
  DH* release();
#elif NCRYPTO_USE_OPENSSL_PROVIDER
  inline EVP_PKEY* get() const {
    return dh_.get();
  }
  void reset(EVP_PKEY* dh = nullptr);
  EVP_PKEY* release();
#else
#error "Unsupported crypto backend"
#endif

  enum class CheckResult {
    NONE,
    P_NOT_PRIME = 0x01,
    P_NOT_SAFE_PRIME = 0x02,
    UNABLE_TO_CHECK_GENERATOR = 0x04,
    NOT_SUITABLE_GENERATOR = 0x08,
    Q_NOT_PRIME = 0x10,
    INVALID_Q = 0x20,
#ifndef OPENSSL_IS_BORINGSSL
    // BoringSSL does not define DH_CHECK_INVALID_J_VALUE.
    INVALID_J = 0x40,
    MODULUS_TOO_SMALL = 0x80,
    MODULUS_TOO_LARGE = 0x100,
#endif
    CHECK_FAILED = 512,
  };
  CheckResult check();

  enum class CheckPublicKeyResult {
    NONE,
    TOO_SMALL,
    TOO_LARGE,
    INVALID,
    CHECK_FAILED = 512,
  };
  // Check to see if the given public key is suitable for this DH instance.
  CheckPublicKeyResult checkPublicKey(const BignumPointer& pub_key);

  DataPointer getPrime() const;
  size_t getPrimeBits() const;
  DataPointer getGenerator() const;
  DataPointer getPublicKey() const;
  DataPointer getPrivateKey() const;
  bool hasPrivateKey() const;
  DataPointer generateKeys();
  DataPointer computeSecret(const BignumPointer& peer) const;

  bool setPublicKey(BignumPointer&& key);
  bool setPrivateKey(BignumPointer&& key);

  size_t size() const;

  static DataPointer stateless(const EVPKeyPointer& ourKey,
                               const EVPKeyPointer& theirKey);

 private:
#if NCRYPTO_USE_OPENSSL_PROVIDER
  DeleteFnPtr<EVP_PKEY, EVP_PKEY_free> dh_;
  BignumPointer p_;
  BignumPointer g_;
  BignumPointer pub_key_;
  BignumPointer pvt_key_;
  const char* group_name_ = nullptr;
#elif NCRYPTO_USE_BORINGSSL
  DeleteFnPtr<DH, DH_free> dh_;
#else
#error "Unsupported crypto backend"
#endif
};

struct StackOfX509Deleter {
  void operator()(STACK_OF(X509) * p) const { sk_X509_pop_free(p, X509_free); }
};
using StackOfX509 = std::unique_ptr<STACK_OF(X509), StackOfX509Deleter>;

class SSLCtxPointer final {
 public:
  SSLCtxPointer() = default;
  explicit SSLCtxPointer(SSL_CTX* ctx);
  SSLCtxPointer(SSLCtxPointer&& other) noexcept;
  SSLCtxPointer& operator=(SSLCtxPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(SSLCtxPointer)
  ~SSLCtxPointer();

  inline bool operator==(std::nullptr_t) const noexcept {
    return ctx_ == nullptr;
  }
  inline operator bool() const { return ctx_ != nullptr; }
  inline SSL_CTX* get() const { return ctx_.get(); }
  void reset(SSL_CTX* ctx = nullptr);
  void reset(const SSL_METHOD* method);
  SSL_CTX* release();

  bool setGroups(const char* groups);
  void setStatusCallback(auto callback) {
    if (!ctx_) return;
    SSL_CTX_set_tlsext_status_cb(get(), callback);
    SSL_CTX_set_tlsext_status_arg(get(), nullptr);
  }

  bool setCipherSuites(const char* ciphers);

  static SSLCtxPointer NewServer();
  static SSLCtxPointer NewClient();
  static SSLCtxPointer New(const SSL_METHOD* method = TLS_method());

 private:
  DeleteFnPtr<SSL_CTX, SSL_CTX_free> ctx_;
};

class SSLPointer final {
 public:
  SSLPointer() = default;
  explicit SSLPointer(SSL* ssl);
  SSLPointer(SSLPointer&& other) noexcept;
  SSLPointer& operator=(SSLPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(SSLPointer)
  ~SSLPointer();

  inline bool operator==(std::nullptr_t) noexcept { return ssl_ == nullptr; }
  inline operator bool() const { return ssl_ != nullptr; }
  inline SSL* get() const { return ssl_.get(); }
  inline operator SSL*() const { return ssl_.get(); }
  void reset(SSL* ssl = nullptr);
  SSL* release();

  bool setSession(const SSLSessionPointer& session);
  bool setSniContext(const SSLCtxPointer& ctx) const;

  std::optional<const std::string_view> getServerName() const;
  X509View getCertificate() const;
  EVPKeyPointer getPeerTempKey() const;
  std::optional<std::string_view> getNegotiatedGroup() const;
  const SSL_CIPHER* getCipher() const;
  bool isServer() const;

  std::optional<std::string_view> getCipherName() const;
  std::optional<std::string_view> getCipherStandardName() const;
  std::optional<std::string_view> getCipherVersion() const;

  std::optional<uint32_t> verifyPeerCertificate() const;

  static std::optional<int> getSecurityLevel();

  void getCiphers(std::function<void(const char*)> cb) const;

  static SSLPointer New(const SSLCtxPointer& ctx);
  static std::optional<const std::string_view> GetServerName(const SSL* ssl);

 private:
  DeleteFnPtr<SSL, SSL_free> ssl_;
};

class X509Name final {
 public:
  // A missing Entry means the name entry could not be inspected. A missing
  // value marks an AttributeValue that the legacy UTF-8 view cannot represent.
  struct Entry {
    std::string key;
    std::optional<std::string> value;
  };

  X509Name();
  explicit X509Name(const X509_NAME* name);
  NCRYPTO_DISALLOW_COPY_AND_MOVE(X509Name)

  inline operator const X509_NAME*() const { return name_; }
  inline operator bool() const { return name_ != nullptr; }
  inline const X509_NAME* get() const { return name_; }
  inline size_t size() const { return total_; }

  class Iterator final {
   public:
    Iterator(const X509Name& name, int pos);
    Iterator(const Iterator& other) = default;
    Iterator(Iterator&& other) = default;
    Iterator& operator=(const Iterator& other) = delete;
    Iterator& operator=(Iterator&& other) = delete;
    Iterator& operator++();
    operator bool() const;
    bool operator==(const Iterator& other) const;
    bool operator!=(const Iterator& other) const;
    std::optional<Entry> operator*() const;

   private:
    const X509Name& name_;
    int loc_;
  };

  inline Iterator begin() const { return Iterator(*this, 0); }
  inline Iterator end() const { return Iterator(*this, total_); }

 private:
  const X509_NAME* name_;
  int total_;
};

struct X509NameAttribute final {
  int set = 0;
  std::string oid;
  std::optional<std::string> value;
};

using X509NameInspection = std::vector<X509NameAttribute>;

struct X509RawExtension final {
  std::string oid;
  bool critical = false;
  std::vector<unsigned char> value;
};

class X509View final {
 public:
  static X509View From(const SSLPointer& ssl);
  static X509View From(const SSLCtxPointer& ctx);

  X509View() = default;
  inline explicit X509View(const X509* cert) : cert_(cert) {}
  X509View(const X509View& other) = default;
  X509View& operator=(const X509View& other) = default;
  NCRYPTO_DISALLOW_MOVE(X509View)

  inline X509* get() const { return const_cast<X509*>(cert_); }
  inline operator X509*() const { return const_cast<X509*>(cert_); }
  inline operator const X509*() const { return cert_; }

  inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; }
  inline operator bool() const { return cert_ != nullptr; }

  BIOPointer toPEM() const;
  BIOPointer toDER() const;

  const X509Name getSubjectName() const;
  const X509Name getIssuerName() const;
  BIOPointer getSubject() const;
  BIOPointer getSubjectAltName(bool* has_extension = nullptr) const;
  BIOPointer getIssuer() const;
  BIOPointer getInfoAccess() const;
  BIOPointer getValidFrom() const;
  BIOPointer getValidTo() const;
  std::optional<std::string_view> getSignatureAlgorithm() const;
  std::optional<std::string> getSignatureAlgorithmOID() const;
  std::optional<int64_t> getValidFromTime() const;
  std::optional<int64_t> getValidToTime() const;
  DataPointer getSerialNumber() const;
  Result<EVPKeyPointer, int> getPublicKey() const;
  StackOfASN1 getKeyUsage() const;

  std::optional<int64_t> getVersion() const;
  std::optional<X509NameInspection> inspectSubjectName() const;
  std::optional<X509NameInspection> inspectIssuerName() const;
  std::optional<std::vector<X509RawExtension>> inspectExtensions() const;

  bool isCA() const;
  bool isIssuedBy(const X509View& other) const;
  bool checkPrivateKey(const EVPKeyPointer& pkey) const;
  bool checkPublicKey(const EVPKeyPointer& pkey) const;

  std::optional<std::string> getFingerprint(const Digest& method) const;

  X509Pointer clone() const;

  enum class CheckMatch {
    NO_MATCH,
    MATCH,
    INVALID_NAME,
    OPERATION_FAILED,
  };
  CheckMatch checkHost(std::string_view host,
                       int flags,
                       DataPointer* peerName = nullptr) const;
  CheckMatch checkEmail(std::string_view email, int flags) const;
  CheckMatch checkIp(const Buffer<const unsigned char>& ip, int flags) const;

  using UsageCallback = std::function<void(const char*)>;
  bool enumUsages(UsageCallback callback) const;

  template <typename T>
  using KeyCallback = std::function<bool(const T& t)>;
  bool ifRsa(KeyCallback<Rsa> callback) const;
  bool ifEc(KeyCallback<Ec> callback) const;

 private:
  const X509* cert_ = nullptr;
};

class X509Pointer final {
 public:
  static Result<X509Pointer, int> Parse(Buffer<const unsigned char> buffer);
  static X509Pointer IssuerFrom(const SSLPointer& ssl, const X509View& view);
  static X509Pointer IssuerFrom(const SSL_CTX* ctx, const X509View& view);
  static X509Pointer PeerFrom(const SSLPointer& ssl);

  X509Pointer() = default;
  explicit X509Pointer(X509* cert);
  X509Pointer(X509Pointer&& other) noexcept;
  X509Pointer& operator=(X509Pointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(X509Pointer)
  ~X509Pointer();

  inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; }
  inline operator bool() const { return cert_ != nullptr; }
  inline X509* get() const { return cert_.get(); }
  inline operator X509*() const { return cert_.get(); }
  inline operator const X509*() const { return cert_.get(); }
  void reset(X509* cert = nullptr);
  X509* release();

  X509View view() const;
  operator X509View() const { return view(); }

  static const char* ErrorCode(int32_t err);
  static std::optional<const char*> ErrorReason(int32_t err);

 private:
  DeleteFnPtr<X509, X509_free> cert_;
};

class ECDSASigPointer final {
 public:
  explicit ECDSASigPointer();
  explicit ECDSASigPointer(ECDSA_SIG* sig);
  ECDSASigPointer(ECDSASigPointer&& other) noexcept;
  ECDSASigPointer& operator=(ECDSASigPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(ECDSASigPointer)
  ~ECDSASigPointer();

  inline bool operator==(std::nullptr_t) noexcept { return sig_ == nullptr; }
  inline operator bool() const { return sig_ != nullptr; }
  inline ECDSA_SIG* get() const { return sig_.get(); }
  inline operator ECDSA_SIG*() const { return sig_.get(); }
  void reset(ECDSA_SIG* sig = nullptr);
  ECDSA_SIG* release();

  static ECDSASigPointer New();
  static ECDSASigPointer Parse(const Buffer<const unsigned char>& buffer);

  inline const BIGNUM* r() const { return pr_; }
  inline const BIGNUM* s() const { return ps_; }

  bool setParams(BignumPointer&& r, BignumPointer&& s);

  Buffer<unsigned char> encode() const;

 private:
  DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free> sig_;
  const BIGNUM* pr_ = nullptr;
  const BIGNUM* ps_ = nullptr;
};

class ECGroupPointer final {
 public:
  explicit ECGroupPointer();
  explicit ECGroupPointer(EC_GROUP* group);
  ECGroupPointer(ECGroupPointer&& other) noexcept;
  ECGroupPointer& operator=(ECGroupPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(ECGroupPointer)
  ~ECGroupPointer();

  inline bool operator==(std::nullptr_t) noexcept { return group_ == nullptr; }
  inline operator bool() const { return group_ != nullptr; }
  inline EC_GROUP* get() const { return group_.get(); }
  inline operator EC_GROUP*() const { return group_.get(); }
  void reset(EC_GROUP* group = nullptr);
  EC_GROUP* release();

  static ECGroupPointer NewByCurveName(int nid);

 private:
  DeleteFnPtr<EC_GROUP, EC_GROUP_free> group_;
};

class ECPointPointer final {
 public:
  ECPointPointer();
  explicit ECPointPointer(EC_POINT* point);
  ECPointPointer(ECPointPointer&& other) noexcept;
  ECPointPointer& operator=(ECPointPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(ECPointPointer)
  ~ECPointPointer();

  inline bool operator==(std::nullptr_t) noexcept { return point_ == nullptr; }
  inline operator bool() const { return point_ != nullptr; }
  inline EC_POINT* get() const { return point_.get(); }
  inline operator EC_POINT*() const { return point_.get(); }
  void reset(EC_POINT* point = nullptr);
  EC_POINT* release();

  bool setFromBuffer(const Buffer<const unsigned char>& buffer,
                     const EC_GROUP* group);
  bool mul(const EC_GROUP* group, const BIGNUM* priv_key);

  static ECPointPointer New(const EC_GROUP* group);

 private:
  DeleteFnPtr<EC_POINT, EC_POINT_free> point_;
};

class ECKeyPointer final {
  friend class EVPKeyPointer;

 public:
  ECKeyPointer();
  explicit ECKeyPointer(const EVPKeyPointer& key);
#if NCRYPTO_USE_LEGACY_KEY_TYPES
  explicit ECKeyPointer(EC_KEY* key);
#endif
  ECKeyPointer(ECKeyPointer&& other) noexcept;
  ECKeyPointer& operator=(ECKeyPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(ECKeyPointer)
  ~ECKeyPointer();

#if NCRYPTO_USE_OPENSSL_PROVIDER
  inline bool operator==(std::nullptr_t) noexcept {
    return group_ == nullptr;
  }
  inline operator bool() const {
    return group_ != nullptr;
  }
#elif NCRYPTO_USE_BORINGSSL
  inline bool operator==(std::nullptr_t) noexcept { return key_ == nullptr; }
  inline operator bool() const { return key_ != nullptr; }
#else
#error "Unsupported crypto backend"
#endif
#if NCRYPTO_USE_BORINGSSL
  inline EC_KEY* get() const { return key_.get(); }
  inline operator EC_KEY*() const { return key_.get(); }
  void reset(EC_KEY* key = nullptr);
  EC_KEY* release();
#elif NCRYPTO_USE_OPENSSL_PROVIDER
  void reset();
#else
#error "Unsupported crypto backend"
#endif

  ECKeyPointer clone() const;
  bool setPrivateKey(const BignumPointer& priv);
  bool setPublicKey(const ECPointPointer& pub);
  bool setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y);
  bool generate();
  bool checkKey() const;
  bool checkPrivateKey() const;
  DataPointer computeSecret(const ECPointPointer& peer) const;

  const EC_GROUP* getGroup() const;
  const BIGNUM* getPrivateKey() const;
  const EC_POINT* getPublicKey() const;

  static ECKeyPointer New(const EC_GROUP* group);
  static ECKeyPointer NewByCurveName(int nid);

#if NCRYPTO_USE_LEGACY_KEY_TYPES
  static const EC_POINT* GetPublicKey(const EC_KEY* key);
  static const BIGNUM* GetPrivateKey(const EC_KEY* key);
  static const EC_GROUP* GetGroup(const EC_KEY* key);
  static int GetGroupName(const EC_KEY* key);
  static bool Check(const EC_KEY* key);
#endif

 private:
#if NCRYPTO_USE_OPENSSL_PROVIDER
  DeleteFnPtr<EC_GROUP, EC_GROUP_free> group_;
  DeleteFnPtr<EC_POINT, EC_POINT_free> pub_;
  DeleteFnPtr<BIGNUM, BN_clear_free> priv_;
#elif NCRYPTO_USE_BORINGSSL
  DeleteFnPtr<EC_KEY, EC_KEY_free> key_;
#else
#error "Unsupported crypto backend"
#endif
};

class EVPMDCtxPointer final {
 public:
  EVPMDCtxPointer();
  explicit EVPMDCtxPointer(EVP_MD_CTX* ctx);
  EVPMDCtxPointer(EVPMDCtxPointer&& other) noexcept;
  EVPMDCtxPointer& operator=(EVPMDCtxPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(EVPMDCtxPointer)
  ~EVPMDCtxPointer();

  inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; }
  inline operator bool() const { return ctx_ != nullptr; }
  inline EVP_MD_CTX* get() const { return ctx_.get(); }
  inline operator EVP_MD_CTX*() const { return ctx_.get(); }
  void reset(EVP_MD_CTX* ctx = nullptr);
  EVP_MD_CTX* release();

  bool digestInit(const EVP_MD* digest);
  inline bool digestInit(const Digest& digest) {
    return digestInit(digest.get());
  }
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 0)
  bool digestInit(const EVP_MD* digest, const OSSL_PARAM* params);
  inline bool digestInit(const Digest& digest, const OSSL_PARAM* params) {
    return digestInit(digest.get(), params);
  }
#endif
  bool digestUpdate(const Buffer<const void>& in);
  DataPointer digestFinal(size_t length);
  bool digestFinalInto(Buffer<void>* buf);
  size_t getExpectedSize();

  std::optional<EVP_PKEY_CTX*> signInit(const EVPKeyPointer& key,
                                        const Digest& digest);
  std::optional<EVP_PKEY_CTX*> verifyInit(const EVPKeyPointer& key,
                                          const Digest& digest);

  std::optional<EVP_PKEY_CTX*> signInitWithContext(
      const EVPKeyPointer& key,
      const Digest& digest,
      const Buffer<const unsigned char>& context_string);
  std::optional<EVP_PKEY_CTX*> verifyInitWithContext(
      const EVPKeyPointer& key,
      const Digest& digest,
      const Buffer<const unsigned char>& context_string);

  DataPointer signOneShot(const Buffer<const unsigned char>& buf) const;
  DataPointer sign(const Buffer<const unsigned char>& buf) const;
  bool verify(const Buffer<const unsigned char>& buf,
              const Buffer<const unsigned char>& sig) const;
  // Unlike verify(), preserves EVP_DigestVerify()'s three-way result.
  int verifyOneShot(const Buffer<const unsigned char>& buf,
                    const Buffer<const unsigned char>& sig) const;

  const EVP_MD* getDigest() const;
  size_t getDigestSize() const;
  bool hasXofFlag() const;

  bool copyTo(const EVPMDCtxPointer& other) const;

  static EVPMDCtxPointer New();

 private:
  DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free> ctx_;
};

#if NCRYPTO_USE_BORINGSSL
class HMACCtxPointer final {
 public:
  HMACCtxPointer();
  explicit HMACCtxPointer(HMAC_CTX* ctx);
  HMACCtxPointer(HMACCtxPointer&& other) noexcept;
  HMACCtxPointer& operator=(HMACCtxPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(HMACCtxPointer)
  ~HMACCtxPointer();

  inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; }
  inline operator bool() const { return ctx_ != nullptr; }
  inline HMAC_CTX* get() const { return ctx_.get(); }
  inline operator HMAC_CTX*() const { return ctx_.get(); }
  void reset(HMAC_CTX* ctx = nullptr);
  HMAC_CTX* release();

  bool init(const Buffer<const void>& buf, const Digest& md);
  bool update(const Buffer<const void>& buf);
  DataPointer digest();
  bool digestInto(Buffer<void>* buf);

  static HMACCtxPointer New();

 private:
  DeleteFnPtr<HMAC_CTX, HMAC_CTX_free> ctx_;
};
#endif  // NCRYPTO_USE_BORINGSSL

#if OPENSSL_WITH_EVP_MAC
class EVPMacPointer final {
 public:
  EVPMacPointer() = default;
  explicit EVPMacPointer(EVP_MAC* mac);
  EVPMacPointer(EVPMacPointer&& other) noexcept;
  EVPMacPointer& operator=(EVPMacPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(EVPMacPointer)
  ~EVPMacPointer();

  inline bool operator==(std::nullptr_t) noexcept { return mac_ == nullptr; }
  inline operator bool() const { return mac_ != nullptr; }
  inline EVP_MAC* get() const { return mac_.get(); }
  inline operator EVP_MAC*() const { return mac_.get(); }
  void reset(EVP_MAC* mac = nullptr);
  EVP_MAC* release();

  static EVPMacPointer Fetch(const char* algorithm);

 private:
  DeleteFnPtr<EVP_MAC, EVP_MAC_free> mac_;
};

enum class MacKind : uint8_t {
  kOther,
  kHmac,
  kCmac,
  kGmac,
};

class MacCache final {
 public:
  struct Result {
    // Borrowed from the cache and valid until the cache is reset. Creating an
    // EVP_MAC_CTX takes an independent reference to the method.
    EVP_MAC* mac = nullptr;
    int32_t id = -1;
    MacKind kind = MacKind::kOther;
  };

  using AliasMap = std::unordered_map<std::string,
                                      int32_t,
                                      CaseInsensitiveNameHash,
                                      CaseInsensitiveNameEqual>;

  MacCache() = default;
  NCRYPTO_DISALLOW_COPY_AND_MOVE(MacCache)

  Result lookup(const char* name, uint64_t generation) const;
  inline Result lookup(int32_t id, uint64_t generation) const {
    if (generation_ != generation || id == -1) return {};
    const uint32_t unsigned_id = static_cast<uint32_t>(id);
    if (unsigned_id < first_id_) return {};
    const size_t index = unsigned_id - first_id_;
    if (index >= macs_.size()) return {};
    return {macs_[index].mac.get(), id, macs_[index].kind};
  }
  Result insert(const char* name, EVPMacPointer&& mac, uint64_t generation);
  void reset(uint64_t generation);
  const AliasMap& aliases() const;
  static MacKind GetKind(EVP_MAC* mac);

 private:
  struct Entry {
    EVPMacPointer mac;
    MacKind kind;
  };

  uint64_t generation_ = 0;

  // IDs are not reused across generations because JavaScript may cache them
  // independently in each Realm.
  uint32_t first_id_ = 0;
  uint32_t next_id_ = 0;
  std::vector<Entry> macs_;
  AliasMap aliases_;
};

class EVPMacCtxPointer final {
 public:
  EVPMacCtxPointer() = default;
  explicit EVPMacCtxPointer(EVP_MAC_CTX* ctx);
  EVPMacCtxPointer(EVPMacCtxPointer&& other) noexcept;
  EVPMacCtxPointer& operator=(EVPMacCtxPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(EVPMacCtxPointer)
  ~EVPMacCtxPointer();

  inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; }
  inline operator bool() const { return ctx_ != nullptr; }
  inline EVP_MAC_CTX* get() const { return ctx_.get(); }
  inline operator EVP_MAC_CTX*() const { return ctx_.get(); }
  void reset(EVP_MAC_CTX* ctx = nullptr);
  EVP_MAC_CTX* release();

  bool init(const Buffer<const void>& key, const OSSL_PARAM* params = nullptr);
  bool update(const Buffer<const void>& data);
  size_t getSize() const;
  const OSSL_PARAM* getSettableParams() const;
  DataPointer final(size_t length);

  static EVPMacCtxPointer New(EVP_MAC* mac);

 private:
  DeleteFnPtr<EVP_MAC_CTX, EVP_MAC_CTX_free> ctx_;
};

class HMACCtxPointer final {
 public:
  HMACCtxPointer();
  HMACCtxPointer(HMACCtxPointer&& other) noexcept;
  HMACCtxPointer& operator=(HMACCtxPointer&& other) noexcept;
  NCRYPTO_DISALLOW_COPY(HMACCtxPointer)
  ~HMACCtxPointer();

  inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; }
  inline operator bool() const { return ctx_ != nullptr; }
  void reset();

  bool init(const Buffer<const void>& buf, const Digest& md);
  bool update(const Buffer<const void>& buf);
  DataPointer digest();
  bool digestInto(Buffer<void>* buf);
  HMACCtxPointer clone() const;

  static HMACCtxPointer New();
  // Creates a context that already uses md, so that init() does not have to
  // fetch md again when it is called with the same digest.
  static HMACCtxPointer New(const Digest& md);

 private:
  HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx);

  EVPMacPointer mac_;
  EVPMacCtxPointer ctx_;
  const EVP_MD* md_ = nullptr;
  size_t md_size_ = 0;
};
#endif  // OPENSSL_WITH_EVP_MAC

#if NCRYPTO_USE_BORINGSSL
class MacCache final {
 public:
  MacCache() = default;
  NCRYPTO_DISALLOW_COPY_AND_MOVE(MacCache)
};
#endif

// ============================================================================
// FIPS
bool isFipsEnabled();

// Configure seed-preserving PQC private-key encoding when the backend supports
// it.
void ConfigurePqcEncoding();

bool setFipsEnabled(bool enabled, CryptoErrorList* errors);

uint64_t getFipsStateGeneration();

bool testFipsEnabled();

// ============================================================================
// Various utilities

bool CSPRNG(void* buffer, size_t length) NCRYPTO_MUST_USE_RESULT;

// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
// for the OpenSSL CLI, but works poorly for some environments like Node.js
// because it involves synchronous interaction with the controlling terminal,
// something we never want, and use this function to avoid it.
int NoPasswordCallback(char* buf, int size, int rwflag, void* u);

int PasswordCallback(char* buf, int size, int rwflag, void* u);

bool SafeX509SubjectAltNamePrint(const BIOPointer& out,
                                 const X509_EXTENSION* ext);
bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext);

// ============================================================================
// SPKAC

bool VerifySpkac(const char* input, size_t length);
BIOPointer ExportPublicKey(const char* input, size_t length);

// The caller takes ownership of the returned Buffer<char>
Buffer<char> ExportChallenge(const char* input, size_t length);

// ============================================================================
// KDF

#if NCRYPTO_USE_OPENSSL_PROVIDER
class KDF final {
 public:
  KDF() = default;
  KDF(KDF&&) noexcept = default;
  KDF& operator=(KDF&&) noexcept = default;
  NCRYPTO_DISALLOW_COPY(KDF)

  inline operator bool() const { return kdf_ != nullptr; }

  static KDF Fetch(const char* algorithm, OSSL_LIB_CTX* libctx = nullptr);

  // Each derivation uses a fresh context. A null output can be used for
  // parameter validation by KDFs that support it, such as scrypt.
  bool derive(const Buffer<unsigned char>& out, const OSSL_PARAM* params) const;

 private:
  explicit KDF(EVP_KDF* kdf);
  DeleteFnPtr<EVP_KDF, EVP_KDF_free> kdf_;
};
#endif

const EVP_MD* getDigestByName(const char* name);

// Verify that the specified HKDF output length is valid for the given digest.
// The maximum length for HKDF output for a given digest is 255 times the
// hash size for the given digest algorithm.
bool checkHkdfLength(const Digest& digest, size_t length);

bool extractP1363(const Buffer<const unsigned char>& buf,
                  unsigned char* dest,
                  size_t n);

DataPointer hkdf(const Digest& md,
                 const Buffer<const unsigned char>& key,
                 const Buffer<const unsigned char>& info,
                 const Buffer<const unsigned char>& salt,
                 size_t length);

bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem);

DataPointer scrypt(const Buffer<const char>& pass,
                   const Buffer<const unsigned char>& salt,
                   uint64_t N,
                   uint64_t r,
                   uint64_t p,
                   uint64_t maxmem,
                   size_t length);

DataPointer pbkdf2(const Digest& md,
                   const Buffer<const char>& pass,
                   const Buffer<const unsigned char>& salt,
                   uint32_t iterations,
                   size_t length);

#if OPENSSL_WITH_ARGON2
enum class Argon2Type { ARGON2D, ARGON2I, ARGON2ID };

DataPointer argon2(const Buffer<const char>& pass,
                   const Buffer<const unsigned char>& salt,
                   uint32_t lanes,
                   size_t length,
                   uint32_t memcost,
                   uint32_t iter,
                   uint32_t version,
                   const Buffer<const unsigned char>& secret,
                   const Buffer<const unsigned char>& ad,
                   Argon2Type type);
#endif

// ============================================================================
// KEM (Key Encapsulation Mechanism)
#if OPENSSL_WITH_KEM

class KEM final {
 public:
  struct EncapsulateResult {
    DataPointer ciphertext;
    DataPointer shared_key;

    EncapsulateResult() = default;
    EncapsulateResult(DataPointer ct, DataPointer sk)
        : ciphertext(std::move(ct)), shared_key(std::move(sk)) {}
  };

  // Encapsulate a shared secret using KEM with a public key.
  // Returns both the ciphertext and shared secret.
  static std::optional<EncapsulateResult> Encapsulate(
      const EVPKeyPointer& public_key);

  // Decapsulate a shared secret using KEM with a private key and ciphertext.
  // Returns the shared secret.
  static DataPointer Decapsulate(const EVPKeyPointer& private_key,
                                 const Buffer<const void>& ciphertext);

 private:
#if NCRYPTO_USE_OPENSSL_PROVIDER
  static bool SetOperationParameter(EVP_PKEY_CTX* ctx,
                                    const EVPKeyPointer& key);
#endif
};

#endif  // OPENSSL_WITH_KEM

// ============================================================================
// Version metadata
#define NCRYPTO_VERSION "0.0.1"

enum {
  NCRYPTO_VERSION_MAJOR = 0,
  NCRYPTO_VERSION_MINOR = 0,
  NCRYPTO_VERSION_REVISION = 1,
};

}  // namespace ncrypto