#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef OPENSSL_NO_ENGINE #include #endif // !OPENSSL_NO_ENGINE // The FIPS-related functions are only available // when the OpenSSL itself was compiled with FIPS support. #if defined(OPENSSL_FIPS) && OPENSSL_VERSION_MAJOR < 3 #include #endif // OPENSSL_FIPS #if OPENSSL_VERSION_MAJOR >= 3 #define OSSL3_CONST const #else #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 pop_back(); std::optional pop_front(); private: std::list 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() int peekError(); 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() int peekError(); private: CryptoErrorList* errors_; }; // TODO(@jasnell): Eventually replace with std::expected when we are able to // bump up to c++23. template struct Result final { const bool has_value; T value; std::optional error = std::nullopt; std::optional openssl_error = std::nullopt; Result(T&& value) : has_value(true), value(std::move(value)) {} Result(E&& error, std::optional 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 struct FunctionDeleter { void operator()(T* pointer) const { function(pointer); } typedef std::unique_ptr Pointer; }; template using DeleteFnPtr = typename FunctionDeleter::Pointer; using PKCS8Pointer = DeleteFnPtr; using RSAPointer = DeleteFnPtr; using SSLSessionPointer = DeleteFnPtr; class BIOPointer; class BignumPointer; class CipherCtxPointer; class DataPointer; class DHPointer; class ECKeyPointer; class EVPKeyPointer; 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; // An unowned, unmanaged pointer to a buffer of data. template 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&) = default; Digest& operator=(const Digest&) = default; inline Digest& operator=(const EVP_MD* md) { 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); private: const EVP_MD* md_ = nullptr; }; DataPointer hashDigest(const Buffer& data, const EVP_MD* md); 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 && EVP_CCM_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH && EVP_CHACHAPOLY_TLS_TAG_LEN <= MAX_AUTH_TAG_LENGTH); Cipher() = default; Cipher(const EVP_CIPHER* cipher) : cipher_(cipher) {} Cipher(const Cipher&) = default; Cipher& operator=(const Cipher&) = default; inline Cipher& operator=(const EVP_CIPHER* cipher) { 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 isOcbMode() const; bool isStreamMode() const; bool isChaCha20Poly1305() const; bool isSupportedAuthenticatedMode() const; int bytesToKey(const Digest& digest, const Buffer& input, unsigned char* key, unsigned char* iv) const; static const Cipher FromName(const char* name); static const Cipher FromNid(int nid); static const Cipher FromCtx(const CipherCtxPointer& ctx); using CipherNameCallback = std::function; // Iterates the known ciphers if the underlying implementation // is able to do so. static void ForEach(CipherNameCallback callback); // Utilities to get various ciphers by type. If the underlying // implementation does not support the requested cipher, then // the result will be an empty Cipher object whose bool operator // will return false. static const Cipher 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; struct CipherParams { int padding; Digest digest; const Buffer label; }; static DataPointer encrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer in); static DataPointer decrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer in); static DataPointer sign(const EVPKeyPointer& key, const CipherParams& params, const Buffer in); static DataPointer recover(const EVPKeyPointer& key, const CipherParams& params, const Buffer 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; }; // ============================================================================ // DSA class Dsa final { public: Dsa(); Dsa(OSSL3_CONST DSA* dsa); NCRYPTO_DISALLOW_COPY_AND_MOVE(Dsa) inline operator bool() const { return dsa_ != nullptr; } inline operator OSSL3_CONST DSA*() const { return dsa_; } const BIGNUM* getP() const; const BIGNUM* getQ() const; size_t getModulusLength() const; size_t getDivisorLength() const; private: OSSL3_CONST DSA* dsa_; }; // ============================================================================ // RSA class Rsa final { public: Rsa(); Rsa(OSSL3_CONST RSA* rsa); NCRYPTO_DISALLOW_COPY_AND_MOVE(Rsa) inline operator bool() const { return rsa_ != nullptr; } inline operator OSSL3_CONST RSA*() const { return rsa_; } 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 PssParams { std::string_view digest = "sha1"; std::optional mgf1_digest = "sha1"; int64_t salt_length = 20; }; const PublicKey getPublicKey() const; const PrivateKey getPrivateKey() const; const std::optional getPssParams() const; bool setPublicKey(BignumPointer&& n, BignumPointer&& e); bool setPrivateKey(BignumPointer&& d, BignumPointer&& q, BignumPointer&& p, BignumPointer&& dp, BignumPointer&& dq, BignumPointer&& qi); using CipherParams = Cipher::CipherParams; static DataPointer encrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer in); static DataPointer decrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer in); private: OSSL3_CONST RSA* rsa_; }; class Ec final { public: Ec(); Ec(OSSL3_CONST EC_KEY* key); NCRYPTO_DISALLOW_COPY_AND_MOVE(Ec) const EC_GROUP* getGroup() const; int getCurve() const; inline operator bool() const { return ec_ != nullptr; } inline operator OSSL3_CONST EC_KEY*() const { return ec_; } static int GetCurveIdFromName(const char* name); using GetCurveCallback = std::function; static bool GetCurves(GetCurveCallback callback); private: OSSL3_CONST EC_KEY* ec_ = nullptr; }; // 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& 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& 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 inline T* get() const noexcept { return static_cast(data_); } inline size_t size() const noexcept { return len_; } void reset(void* data = nullptr, size_t len = 0); void reset(const Buffer& 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 release(); // Returns a Buffer struct that is a view of the underlying data. template inline operator const Buffer() const { return { .data = static_cast(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 static BIOPointer New(const Buffer& 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); template static void Printf(BIOPointer* bio, const char* format, Args... args) { if (bio == nullptr || !*bio) return; BIO_printf(bio->get(), format, std::forward(args...)); } private: mutable DeleteFnPtr 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) 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; 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 unsigned long GetWord(const BIGNUM* bn); // NOLINT(runtime/int) static const BIGNUM* One(); BignumPointer clone(); private: DeleteFnPtr 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& tag); bool setAeadTagLength(size_t length); bool setPadding(bool padding); 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 isCcmMode() const; bool isWrapMode() const; bool isChaCha20Poly1305() const; bool update(const Buffer& in, unsigned char* out, int* out_len, bool finalize = false); bool getAeadTag(size_t len, unsigned char* out); private: DeleteFnPtr ctx_; }; 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 q_bits); bool setEcParameters(int curve, 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& sig, const Buffer& data); DataPointer sign(const Buffer& data); bool signInto(const Buffer& data, Buffer* sig); static constexpr int kDefaultRsaExponent = 0x10001; static bool setRsaPadding(EVP_PKEY_CTX* ctx, int padding, std::optional 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 NewFromID(int id); private: DeleteFnPtr ctx_; }; class EVPKeyPointer final { public: static EVPKeyPointer New(); static EVPKeyPointer NewRawPublic(int id, const Buffer& data); static EVPKeyPointer NewRawPrivate(int id, const Buffer& data); static EVPKeyPointer NewDH(DHPointer&& dh); static EVPKeyPointer NewRSA(RSAPointer&& rsa); 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, }; enum class PKParseError { NOT_RECOGNIZED, NEED_PASSPHRASE, FAILED }; using ParseKeyResult = Result; struct AsymmetricKeyEncodingConfig { bool output_key_object = false; PKFormatType format = PKFormatType::DER; PKEncodingType type = PKEncodingType::PKCS8; 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 { const EVP_CIPHER* cipher = nullptr; std::optional 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&); }; static ParseKeyResult TryParsePublicKey( const PublicKeyEncodingConfig& config, const Buffer& buffer); static ParseKeyResult TryParsePublicKeyPEM( const Buffer& buffer); static ParseKeyResult TryParsePrivateKey( const PrivateKeyEncodingConfig& config, const Buffer& buffer); 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); operator const EC_KEY*() const; 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 int id(const EVP_PKEY* key); static int base_id(const EVP_PKEY* key); int id() const; int base_id() 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; Result writePrivateKey( const PrivateKeyEncodingConfig& config) const; Result writePublicKey( const PublicKeyEncodingConfig& config) const; EVPKeyCtxPointer newCtx() const; static bool IsRSAPrivateKey(const Buffer& buffer); std::optional getBytesOfRS() const; int getDefaultSignPadding() const; operator Rsa() const; operator Dsa() const; bool isRsaVariant() const; bool isOneShotVariant() const; bool isSigVariant() const; bool validateDsaParameters() const; private: DeleteFnPtr 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; explicit DHPointer(DH* dh); DHPointer(DHPointer&& other) noexcept; DHPointer& operator=(DHPointer&& other) noexcept; NCRYPTO_DISALLOW_COPY(DHPointer) ~DHPointer(); inline bool operator==(std::nullptr_t) noexcept { return dh_ == nullptr; } inline operator bool() const { return dh_ != nullptr; } inline DH* get() const { return dh_.get(); } void reset(DH* dh = nullptr); DH* release(); enum class CheckResult { NONE, P_NOT_PRIME = DH_CHECK_P_NOT_PRIME, P_NOT_SAFE_PRIME = DH_CHECK_P_NOT_SAFE_PRIME, UNABLE_TO_CHECK_GENERATOR = DH_UNABLE_TO_CHECK_GENERATOR, NOT_SUITABLE_GENERATOR = DH_NOT_SUITABLE_GENERATOR, Q_NOT_PRIME = DH_CHECK_Q_NOT_PRIME, #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not define the DH_CHECK_INVALID_[Q or J]_VALUE INVALID_Q = DH_CHECK_INVALID_Q_VALUE, INVALID_J = DH_CHECK_INVALID_J_VALUE, #endif CHECK_FAILED = 512, }; CheckResult check(); enum class CheckPublicKeyResult { NONE, #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not define DH_R_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE TOO_SMALL = DH_R_CHECK_PUBKEY_TOO_SMALL, TOO_LARGE = DH_R_CHECK_PUBKEY_TOO_LARGE, INVALID = DH_R_CHECK_PUBKEY_INVALID, #else INVALID = DH_R_INVALID_PUBKEY, #endif 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; DataPointer getGenerator() const; DataPointer getPublicKey() const; DataPointer getPrivateKey() const; DataPointer generateKeys() const; 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: DeleteFnPtr dh_; }; struct StackOfX509Deleter { void operator()(STACK_OF(X509) * p) const { sk_X509_pop_free(p, X509_free); } }; using StackOfX509 = std::unique_ptr; 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 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; const char* getClientHelloAlpn() const; const char* getClientHelloServerName() const; std::optional getServerName() const; X509View getCertificate() const; EVPKeyPointer getPeerTempKey() const; const SSL_CIPHER* getCipher() const; bool isServer() const; std::optional getCipherName() const; std::optional getCipherStandardName() const; std::optional getCipherVersion() const; std::optional verifyPeerCertificate() const; static std::optional getSecurityLevel(); void getCiphers(std::function cb) const; static SSLPointer New(const SSLCtxPointer& ctx); static std::optional GetServerName(const SSL* ssl); private: DeleteFnPtr ssl_; }; class X509Name final { public: 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::pair 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_; }; 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(cert_); } inline operator X509*() const { return const_cast(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() const; BIOPointer getIssuer() const; BIOPointer getInfoAccess() const; BIOPointer getValidFrom() const; BIOPointer getValidTo() const; int64_t getValidFromTime() const; int64_t getValidToTime() const; DataPointer getSerialNumber() const; Result getPublicKey() const; StackOfASN1 getKeyUsage() const; bool isCA() const; bool isIssuedBy(const X509View& other) const; bool checkPrivateKey(const EVPKeyPointer& pkey) const; bool checkPublicKey(const EVPKeyPointer& pkey) const; std::optional 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(std::string_view ip, int flags) const; using UsageCallback = std::function; bool enumUsages(UsageCallback callback) const; template using KeyCallback = std::function; bool ifRsa(KeyCallback callback) const; bool ifEc(KeyCallback callback) const; private: const X509* cert_ = nullptr; }; class X509Pointer final { public: static Result Parse(Buffer 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 ErrorReason(int32_t err); private: DeleteFnPtr 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& buffer); inline const BIGNUM* r() const { return pr_; } inline const BIGNUM* s() const { return ps_; } bool setParams(BignumPointer&& r, BignumPointer&& s); Buffer encode() const; private: DeleteFnPtr 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 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& buffer, const EC_GROUP* group); bool mul(const EC_GROUP* group, const BIGNUM* priv_key); static ECPointPointer New(const EC_GROUP* group); private: DeleteFnPtr point_; }; class ECKeyPointer final { public: ECKeyPointer(); explicit ECKeyPointer(EC_KEY* key); ECKeyPointer(ECKeyPointer&& other) noexcept; ECKeyPointer& operator=(ECKeyPointer&& other) noexcept; NCRYPTO_DISALLOW_COPY(ECKeyPointer) ~ECKeyPointer(); inline bool operator==(std::nullptr_t) noexcept { return key_ == nullptr; } inline operator bool() const { return key_ != nullptr; } 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(); 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; 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); 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); private: DeleteFnPtr key_; }; 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 Digest& digest); bool digestUpdate(const Buffer& in); DataPointer digestFinal(size_t length); bool digestFinalInto(Buffer* buf); size_t getExpectedSize(); std::optional signInit(const EVPKeyPointer& key, const Digest& digest); std::optional verifyInit(const EVPKeyPointer& key, const Digest& digest); DataPointer signOneShot(const Buffer& buf) const; DataPointer sign(const Buffer& buf) const; bool verify(const Buffer& buf, const Buffer& 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 ctx_; }; 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& buf, const Digest& md); bool update(const Buffer& buf); DataPointer digest(); bool digestInto(Buffer* buf); static HMACCtxPointer New(); private: DeleteFnPtr ctx_; }; #ifndef OPENSSL_NO_ENGINE class EnginePointer final { public: EnginePointer() = default; explicit EnginePointer(ENGINE* engine_, bool finish_on_exit = false); EnginePointer(EnginePointer&& other) noexcept; EnginePointer& operator=(EnginePointer&& other) noexcept; NCRYPTO_DISALLOW_COPY(EnginePointer) ~EnginePointer(); inline operator bool() const { return engine != nullptr; } inline ENGINE* get() { return engine; } inline void setFinishOnExit() { finish_on_exit = true; } void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false); bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr); bool init(bool finish_on_exit = false); EVPKeyPointer loadPrivateKey(const char* key_name); // Release ownership of the ENGINE* pointer. ENGINE* release(); // Retrieve an OpenSSL Engine instance by name. If the name does not // identify a valid named engine, the returned EnginePointer will be // empty. static EnginePointer getEngineByName(const char* name, CryptoErrorList* errors = nullptr); // Call once when initializing OpenSSL at startup for the process. static void initEnginesOnce(); private: ENGINE* engine = nullptr; bool finish_on_exit = false; }; #endif // !OPENSSL_NO_ENGINE // ============================================================================ // FIPS bool isFipsEnabled(); bool setFipsEnabled(bool enabled, CryptoErrorList* errors); 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, X509_EXTENSION* ext); bool SafeX509InfoAccessPrint(const BIOPointer& out, 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 Buffer ExportChallenge(const char* input, size_t length); // ============================================================================ // KDF const EVP_MD* getDigestByName(const char* name); const EVP_CIPHER* getCipherByName(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& buf, unsigned char* dest, size_t n); DataPointer hkdf(const Digest& md, const Buffer& key, const Buffer& info, const Buffer& salt, size_t length); bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem); DataPointer scrypt(const Buffer& pass, const Buffer& salt, uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem, size_t length); DataPointer pbkdf2(const Digest& md, const Buffer& pass, const Buffer& salt, uint32_t iterations, size_t length); // ============================================================================ // Version metadata #define NCRYPTO_VERSION "0.0.1" enum { NCRYPTO_VERSION_MAJOR = 0, NCRYPTO_VERSION_MINOR = 0, NCRYPTO_VERSION_REVISION = 1, }; } // namespace ncrypto