diff --git a/BUILD.bazel b/BUILD.bazel index dfd6c1a..c6ea9e2 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -5,6 +5,8 @@ cc_library( includes = ["include"], visibility = ["//visibility:public"], deps = [ - "@ssl" + "@ssl//:ssl", + "@ssl//:crypto", + "@ssl//:decrepit" ] ) diff --git a/WORKSPACE b/WORKSPACE index d4bfe6d..036ab5b 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -8,4 +8,8 @@ http_archive( strip_prefix = "boringssl-0.20250818.0", type = "tgz", urls = ["https://github.com/google/boringssl/archive/refs/tags/0.20250818.0.tar.gz"], + patches = [ + "@ncrypto//:patches/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch" + ], + patch_strip = 1 ) diff --git a/include/ncrypto.h b/include/ncrypto.h index 4e27d0f..49dbed3 100644 --- a/include/ncrypto.h +++ b/include/ncrypto.h @@ -28,6 +28,15 @@ #include #endif // OPENSSL_FIPS +// Define OPENSSL_WITH_PQC for post-quantum cryptography support +#if OPENSSL_VERSION_NUMBER >= 0x30500000L +#define OPENSSL_WITH_PQC 1 +#define EVP_PKEY_ML_KEM_512 NID_ML_KEM_512 +#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768 +#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 +#include +#endif + #if OPENSSL_VERSION_MAJOR >= 3 #define OSSL3_CONST const #else @@ -226,6 +235,8 @@ class DataPointer; class DHPointer; class ECKeyPointer; class EVPKeyPointer; +class EVPMacCtxPointer; +class EVPMacPointer; class EVPMDCtxPointer; class SSLCtxPointer; class SSLPointer; @@ -253,11 +264,59 @@ struct Buffer { 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; +}; + +// Computes a fixed-length digest. DataPointer hashDigest(const Buffer& data, const EVP_MD* md); +// Computes a variable-length digest for XOF algorithms (e.g. SHAKE128). +DataPointer xofHashDigest(const Buffer& data, + const EVP_MD* md, + size_t length); 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 + // FIXME: These constants are not available in all OpenSSL/BoringSSL versions + // 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; @@ -278,17 +337,59 @@ class Cipher final { int getKeyLength() const; int getBlockSize() const; std::string_view getModeLabel() const; - std::string_view getName() 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; - static const Cipher FromName(std::string_view name); + 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; + 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; - const EVP_MD* digest; + Digest digest; const Buffer label; }; @@ -477,6 +578,11 @@ class Ec final { 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); + inline const BignumPointer& getX() const { return x_; } inline const BignumPointer& getY() const { return y_; } inline const BignumPointer& getD() const { return d_; } @@ -496,9 +602,31 @@ class DataPointer final { 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); - explicit DataPointer(const Buffer& buffer); + 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) @@ -534,9 +662,12 @@ class DataPointer final { }; } + bool isSecure() const { return secure_; } + private: void* data_ = nullptr; size_t len_ = 0; + bool secure_ = false; }; class BIOPointer final { @@ -546,7 +677,7 @@ class BIOPointer final { 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(std::string_view filename, std::string_view mode); + static BIOPointer NewFile(const char* filename, const char* mode); static BIOPointer NewFp(FILE* fd, int flags); template @@ -612,7 +743,8 @@ class CipherCtxPointer final { void reset(EVP_CIPHER_CTX* ctx = nullptr); EVP_CIPHER_CTX* release(); - void setFlags(int flags); + void setAllowWrap(); + bool setKeyLength(size_t length); bool setIvLength(size_t length); bool setAeadTag(const Buffer& tag); @@ -627,6 +759,12 @@ class CipherCtxPointer final { int getMode() const; int getNid() const; + bool isGcmMode() const; + bool isOcbMode() const; + bool isCcmMode() const; + bool isWrapMode() const; + bool isChaCha20Poly1305() const; + bool update(const Buffer& in, unsigned char* out, int* out_len, @@ -662,13 +800,13 @@ class EVPKeyCtxPointer final { bool setDsaParameters(uint32_t bits, std::optional q_bits); bool setEcParameters(int curve, int encoding); - bool setRsaOaepMd(const EVP_MD* md); - bool setRsaMgf1Md(const EVP_MD* md); + 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 EVP_MD* md); - bool setRsaPssKeygenMgf1Md(const EVP_MD* md); + bool setRsaPssKeygenMd(const Digest& md); + bool setRsaPssKeygenMgf1Md(const Digest& md); bool setRsaPssSaltlen(int salt_len); bool setRsaImplicitRejection(); bool setRsaOaepLabel(DataPointer&& data); @@ -712,6 +850,10 @@ class EVPKeyPointer final { const Buffer& data); static EVPKeyPointer NewRawPrivate(int id, const Buffer& data); +#if OPENSSL_WITH_PQC + static EVPKeyPointer NewRawSeed(int id, + const Buffer& data); +#endif static EVPKeyPointer NewDH(DHPointer&& dh); static EVPKeyPointer NewRSA(RSAPointer&& rsa); @@ -805,6 +947,10 @@ class EVPKeyPointer final { DataPointer rawPrivateKey() const; BIOPointer derPublicKey() const; +#if OPENSSL_WITH_PQC + DataPointer rawSeed() const; +#endif + Result writePrivateKey( const PrivateKeyEncodingConfig& config) const; Result writePublicKey( @@ -844,9 +990,8 @@ class DHPointer final { static BignumPointer GetStandardGenerator(); static BignumPointer FindGroup( - const std::string_view name, - FindGroupOption option = FindGroupOption::NONE); - static DHPointer FromGroup(const std::string_view name, + 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); @@ -945,6 +1090,8 @@ class SSLCtxPointer final { 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()); @@ -972,8 +1119,8 @@ class SSLPointer final { bool setSession(const SSLSessionPointer& session); bool setSniContext(const SSLCtxPointer& ctx) const; - const std::string_view getClientHelloAlpn() const; - const std::string_view getClientHelloServerName() const; + const char* getClientHelloAlpn() const; + const char* getClientHelloServerName() const; std::optional getServerName() const; X509View getCertificate() const; @@ -987,7 +1134,9 @@ class SSLPointer final { std::optional verifyPeerCertificate() const; - void getCiphers(std::function cb) const; + static std::optional getSecurityLevel(); + + void getCiphers(std::function cb) const; static SSLPointer New(const SSLCtxPointer& ctx); static std::optional GetServerName(const SSL* ssl); @@ -1062,6 +1211,8 @@ class X509View final { BIOPointer getInfoAccess() const; BIOPointer getValidFrom() const; BIOPointer getValidTo() const; + std::optional getSignatureAlgorithm() const; + std::optional getSignatureAlgorithmOID() const; int64_t getValidFromTime() const; int64_t getValidToTime() const; DataPointer getSerialNumber() const; @@ -1073,7 +1224,7 @@ class X509View final { bool checkPrivateKey(const EVPKeyPointer& pkey) const; bool checkPublicKey(const EVPKeyPointer& pkey) const; - std::optional getFingerprint(const EVP_MD* method) const; + std::optional getFingerprint(const Digest& method) const; X509Pointer clone() const; @@ -1083,13 +1234,13 @@ class X509View final { INVALID_NAME, OPERATION_FAILED, }; - CheckMatch checkHost(const std::string_view host, + CheckMatch checkHost(std::string_view host, int flags, DataPointer* peerName = nullptr) const; - CheckMatch checkEmail(const std::string_view email, int flags) const; - CheckMatch checkIp(const std::string_view ip, int flags) const; + CheckMatch checkEmail(std::string_view email, int flags) const; + CheckMatch checkIp(std::string_view ip, int flags) const; - using UsageCallback = std::function; + using UsageCallback = std::function; bool enumUsages(UsageCallback callback) const; template @@ -1126,8 +1277,8 @@ class X509Pointer final { X509View view() const; operator X509View() const { return view(); } - static std::string_view ErrorCode(int32_t err); - static std::optional ErrorReason(int32_t err); + static const char* ErrorCode(int32_t err); + static std::optional ErrorReason(int32_t err); private: DeleteFnPtr cert_; @@ -1269,16 +1420,25 @@ class EVPMDCtxPointer final { void reset(EVP_MD_CTX* ctx = nullptr); EVP_MD_CTX* release(); - bool digestInit(const EVP_MD* digest); + 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 EVP_MD* digest); + const Digest& digest); std::optional verifyInit(const EVPKeyPointer& key, - const EVP_MD* digest); + const Digest& digest); + + std::optional signInitWithContext( + const EVPKeyPointer& key, + const Digest& digest, + const Buffer& context_string); + std::optional verifyInitWithContext( + const EVPKeyPointer& key, + const Digest& digest, + const Buffer& context_string); DataPointer signOneShot(const Buffer& buf) const; DataPointer sign(const Buffer& buf) const; @@ -1313,7 +1473,7 @@ class HMACCtxPointer final { void reset(HMAC_CTX* ctx = nullptr); HMAC_CTX* release(); - bool init(const Buffer& buf, const EVP_MD* md); + bool init(const Buffer& buf, const Digest& md); bool update(const Buffer& buf); DataPointer digest(); bool digestInto(Buffer* buf); @@ -1324,6 +1484,56 @@ class HMACCtxPointer final { DeleteFnPtr ctx_; }; +#if OPENSSL_VERSION_MAJOR >= 3 +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 mac_; +}; + +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& key, const OSSL_PARAM* params = nullptr); + bool update(const Buffer& data); + DataPointer final(size_t length); + + static EVPMacCtxPointer New(EVP_MAC* mac); + + private: + DeleteFnPtr ctx_; +}; +#endif // OPENSSL_VERSION_MAJOR >= 3 + #ifndef OPENSSL_NO_ENGINE class EnginePointer final { public: @@ -1343,7 +1553,7 @@ class EnginePointer final { bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr); bool init(bool finish_on_exit = false); - EVPKeyPointer loadPrivateKey(const std::string_view key_name); + EVPKeyPointer loadPrivateKey(const char* key_name); // Release ownership of the ENGINE* pointer. ENGINE* release(); @@ -1351,7 +1561,7 @@ class EnginePointer final { // 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 std::string_view name, + static EnginePointer getEngineByName(const char* name, CryptoErrorList* errors = nullptr); // Call once when initializing OpenSSL at startup for the process. @@ -1408,26 +1618,26 @@ DataPointer ExportChallenge(const Buffer& buf); // ============================================================================ // KDF -const EVP_MD* getDigestByName(const std::string_view name); -const EVP_CIPHER* getCipherByName(const std::string_view name); +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 EVP_MD* md, size_t length); +bool checkHkdfLength(const Digest& digest, size_t length); bool extractP1363(const Buffer& buf, unsigned char* dest, size_t n); -bool hkdfInfo(const EVP_MD* md, +bool hkdfInfo(const Digest& md, const Buffer& key, const Buffer& info, const Buffer& salt, size_t length, Buffer* out); -DataPointer hkdf(const EVP_MD* md, +DataPointer hkdf(const Digest& md, const Buffer& key, const Buffer& info, const Buffer& salt, @@ -1452,19 +1662,70 @@ DataPointer scrypt(const Buffer& pass, uint64_t maxmem, size_t length); -bool pbkdf2Into(const EVP_MD* md, +bool pbkdf2Into(const Digest& md, const Buffer& pass, const Buffer& salt, uint32_t iterations, size_t length, Buffer* out); -DataPointer pbkdf2(const EVP_MD* md, +DataPointer pbkdf2(const Digest& md, const Buffer& pass, const Buffer& salt, uint32_t iterations, size_t length); +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#ifndef OPENSSL_NO_ARGON2 +enum class Argon2Type { ARGON2D, ARGON2I, ARGON2ID }; + +DataPointer argon2(const Buffer& pass, + const Buffer& salt, + uint32_t lanes, + size_t length, + uint32_t memcost, + uint32_t iter, + uint32_t version, + const Buffer& secret, + const Buffer& ad, + Argon2Type type); +#endif +#endif + +// ============================================================================ +// KEM (Key Encapsulation Mechanism) +#if OPENSSL_VERSION_MAJOR >= 3 + +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 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& ciphertext); + + private: +#if !OPENSSL_VERSION_PREREQ(3, 5) + static bool SetOperationParameter(EVP_PKEY_CTX* ctx, + const EVPKeyPointer& key); +#endif +}; + +#endif // OPENSSL_VERSION_MAJOR >= 3 + // ============================================================================ // Version metadata #define NCRYPTO_VERSION "0.0.1" diff --git a/patches/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch b/patches/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch new file mode 100644 index 0000000..e058724 --- /dev/null +++ b/patches/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch @@ -0,0 +1,28 @@ +From feda75fa93bb54bc697f1f771a0aec573ab87ab7 Mon Sep 17 00:00:00 2001 +From: Nicholas Paun +Date: Fri, 26 Sep 2025 14:57:57 -0700 +Subject: [PATCH] Expose libdecrepit so NodeJS can use it for ncrypto + +--- + BUILD.bazel | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/BUILD.bazel b/BUILD.bazel +index b7dc35932..7d214716c 100644 +--- a/BUILD.bazel ++++ b/BUILD.bazel +@@ -155,10 +155,10 @@ bssl_cc_binary( + ], + ) + +-# Build, but do not export libdecrepit. + bssl_cc_library( + name = "decrepit", + srcs = decrepit_sources, ++ visibility = ["//visibility:public"], + copts = ["-DBORINGSSL_IMPLEMENTATION"], + internal_hdrs = decrepit_internal_headers, + deps = [ +-- +2.50.1 (Apple Git-155) + diff --git a/src/engine.cpp b/src/engine.cpp index 1eae89d..1845cfc 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -44,15 +44,15 @@ ENGINE* EnginePointer::release() { return ret; } -EnginePointer EnginePointer::getEngineByName(const std::string_view name, +EnginePointer EnginePointer::getEngineByName(const char* name, CryptoErrorList* errors) { MarkPopErrorOnReturn mark_pop_error_on_return(errors); - EnginePointer engine(ENGINE_by_id(name.data())); + EnginePointer engine(ENGINE_by_id(name)); if (!engine) { // Engine not found, try loading dynamically. engine = EnginePointer(ENGINE_by_id("dynamic")); if (engine) { - if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", name.data(), 0) || + if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", name, 0) || !ENGINE_ctrl_cmd_string(engine.get(), "LOAD", nullptr, 0)) { engine.reset(); } @@ -73,10 +73,10 @@ bool EnginePointer::init(bool finish_on_exit) { return ENGINE_init(engine) == 1; } -EVPKeyPointer EnginePointer::loadPrivateKey(const std::string_view key_name) { +EVPKeyPointer EnginePointer::loadPrivateKey(const char* key_name) { if (engine == nullptr) return EVPKeyPointer(); return EVPKeyPointer( - ENGINE_load_private_key(engine, key_name.data(), nullptr, nullptr)); + ENGINE_load_private_key(engine, key_name, nullptr, nullptr)); } void EnginePointer::initEnginesOnce() { diff --git a/src/ncrypto.cpp b/src/ncrypto.cpp index 4b28ca8..75a28bb 100644 --- a/src/ncrypto.cpp +++ b/src/ncrypto.cpp @@ -16,9 +16,44 @@ #endif #include +#include #include +#include #if OPENSSL_VERSION_MAJOR >= 3 +#include +#include #include +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#include +#endif +#endif +#if OPENSSL_WITH_PQC +struct PQCMapping { + const char* name; + int nid; +}; + +constexpr static PQCMapping pqc_mappings[] = { + {"ML-DSA-44", EVP_PKEY_ML_DSA_44}, + {"ML-DSA-65", EVP_PKEY_ML_DSA_65}, + {"ML-DSA-87", EVP_PKEY_ML_DSA_87}, + {"ML-KEM-512", EVP_PKEY_ML_KEM_512}, + {"ML-KEM-768", EVP_PKEY_ML_KEM_768}, + {"ML-KEM-1024", EVP_PKEY_ML_KEM_1024}, + {"SLH-DSA-SHA2-128f", EVP_PKEY_SLH_DSA_SHA2_128F}, + {"SLH-DSA-SHA2-128s", EVP_PKEY_SLH_DSA_SHA2_128S}, + {"SLH-DSA-SHA2-192f", EVP_PKEY_SLH_DSA_SHA2_192F}, + {"SLH-DSA-SHA2-192s", EVP_PKEY_SLH_DSA_SHA2_192S}, + {"SLH-DSA-SHA2-256f", EVP_PKEY_SLH_DSA_SHA2_256F}, + {"SLH-DSA-SHA2-256s", EVP_PKEY_SLH_DSA_SHA2_256S}, + {"SLH-DSA-SHAKE-128f", EVP_PKEY_SLH_DSA_SHAKE_128F}, + {"SLH-DSA-SHAKE-128s", EVP_PKEY_SLH_DSA_SHAKE_128S}, + {"SLH-DSA-SHAKE-192f", EVP_PKEY_SLH_DSA_SHAKE_192F}, + {"SLH-DSA-SHAKE-192s", EVP_PKEY_SLH_DSA_SHAKE_192S}, + {"SLH-DSA-SHAKE-256f", EVP_PKEY_SLH_DSA_SHAKE_256F}, + {"SLH-DSA-SHAKE-256s", EVP_PKEY_SLH_DSA_SHAKE_256S}, +}; + #endif // EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. @@ -118,20 +153,64 @@ DataPointer DataPointer::Alloc(size_t len) { #endif } +DataPointer DataPointer::SecureAlloc(size_t len) { +#ifndef OPENSSL_IS_BORINGSSL + auto ptr = OPENSSL_secure_zalloc(len); + if (ptr == nullptr) return {}; + return DataPointer(ptr, len, true); +#else + // BoringSSL does not implement the OPENSSL_secure_zalloc API. + auto ptr = OPENSSL_malloc(len); + if (ptr == nullptr) return {}; + memset(ptr, 0, len); + return DataPointer(ptr, len); +#endif +} + +size_t DataPointer::GetSecureHeapUsed() { +#ifndef OPENSSL_IS_BORINGSSL + return CRYPTO_secure_malloc_initialized() ? CRYPTO_secure_used() : 0; +#else + // BoringSSL does not have the secure heap and therefore + // will always return 0. + return 0; +#endif +} + +DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount, + size_t min) { +#ifndef OPENSSL_IS_BORINGSSL + switch (CRYPTO_secure_malloc_init(amount, min)) { + case 0: + return InitSecureHeapResult::FAILED; + case 2: + return InitSecureHeapResult::UNABLE_TO_MEMORY_MAP; + case 1: + return InitSecureHeapResult::OK; + default: + return InitSecureHeapResult::FAILED; + } +#else + // BoringSSL does not actually support the secure heap + return InitSecureHeapResult::FAILED; +#endif +} + DataPointer DataPointer::Copy(const Buffer& buffer) { return DataPointer(OPENSSL_memdup(buffer.data, buffer.len), buffer.len); } -DataPointer::DataPointer(void* data, size_t length) - : data_(data), len_(length) {} +DataPointer::DataPointer(void* data, size_t length, bool secure) + : data_(data), len_(length), secure_(secure) {} -DataPointer::DataPointer(const Buffer& buffer) - : data_(buffer.data), len_(buffer.len) {} +DataPointer::DataPointer(const Buffer& buffer, bool secure) + : data_(buffer.data), len_(buffer.len), secure_(secure) {} DataPointer::DataPointer(DataPointer&& other) noexcept - : data_(other.data_), len_(other.len_) { + : data_(other.data_), len_(other.len_), secure_(other.secure_) { other.data_ = nullptr; other.len_ = 0; + other.secure_ = false; } DataPointer& DataPointer::operator=(DataPointer&& other) noexcept { @@ -151,7 +230,11 @@ void DataPointer::zero() { void DataPointer::reset(void* data, size_t length) { if (data_ != nullptr) { - OPENSSL_clear_free(data_, len_); + if (secure_) { + OPENSSL_secure_clear_free(data_, len_); + } else { + OPENSSL_clear_free(data_, len_); + } } data_ = data; len_ = length; @@ -182,6 +265,7 @@ DataPointer DataPointer::resize(size_t len) { // ============================================================================ bool isFipsEnabled() { + ClearErrorOnReturn clear_error_on_return; #if OPENSSL_VERSION_MAJOR >= 3 return EVP_default_properties_is_fips_enabled(nullptr) == 1; #else @@ -193,30 +277,31 @@ bool setFipsEnabled(bool enable, CryptoErrorList* errors) { if (isFipsEnabled() == enable) return true; ClearErrorOnReturn clearErrorOnReturn(errors); #if OPENSSL_VERSION_MAJOR >= 3 - return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1; + return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1 && + EVP_default_properties_is_fips_enabled(nullptr); #else return FIPS_mode_set(enable ? 1 : 0) == 1; #endif } bool testFipsEnabled() { + ClearErrorOnReturn clear_error_on_return; #if OPENSSL_VERSION_MAJOR >= 3 OSSL_PROVIDER* fips_provider = nullptr; if (OSSL_PROVIDER_available(nullptr, "fips")) { fips_provider = OSSL_PROVIDER_load(nullptr, "fips"); } - const auto enabled = fips_provider == nullptr ? 0 - : OSSL_PROVIDER_self_test(fips_provider) ? 1 - : 0; + if (fips_provider == nullptr) return false; + int result = OSSL_PROVIDER_self_test(fips_provider); + OSSL_PROVIDER_unload(fips_provider); + return result; #else #ifdef OPENSSL_FIPS - const auto enabled = FIPS_selftest() ? 1 : 0; + return FIPS_selftest(); #else // OPENSSL_FIPS - const auto enabled = 0; + return false; #endif // OPENSSL_FIPS #endif - - return enabled; } // ============================================================================ @@ -1072,6 +1157,29 @@ BIOPointer X509View::getValidTo() const { return bio; } +std::optional X509View::getSignatureAlgorithm() const { + if (cert_ == nullptr) return std::nullopt; + int nid = X509_get_signature_nid(cert_); + if (nid == NID_undef) return std::nullopt; + const char* ln = OBJ_nid2ln(nid); + if (ln == nullptr) return std::nullopt; + return std::string_view(ln); +} + +std::optional X509View::getSignatureAlgorithmOID() const { + if (cert_ == nullptr) return std::nullopt; + const X509_ALGOR* alg = nullptr; + X509_get0_signature(nullptr, &alg, cert_); + if (alg == nullptr) return std::nullopt; + const ASN1_OBJECT* obj = nullptr; + X509_ALGOR_get0(&obj, nullptr, nullptr, alg); + if (obj == nullptr) return std::nullopt; + std::array buf{}; + int len = OBJ_obj2txt(buf.data(), buf.size(), obj, 1); + if (len < 0 || static_cast(len) >= buf.size()) return std::nullopt; + return std::string(buf.data(), static_cast(len)); +} + int64_t X509View::getValidToTime() const { #ifdef OPENSSL_IS_BORINGSSL #ifndef NCRYPTO_NO_ASN1_TIME @@ -1234,7 +1342,7 @@ X509View X509View::From(const SSLCtxPointer& ctx) { } std::optional X509View::getFingerprint( - const EVP_MD* method) const { + const Digest& method) const { unsigned int md_size; unsigned char md[EVP_MAX_MD_SIZE]; static constexpr char hex[] = "0123456789ABCDEF"; @@ -1360,7 +1468,7 @@ X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) { // When adding or removing errors below, please also update the list in the API // documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md // Also *please* update the respective section in doc/api/tls.md as well -std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) +const char* X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) #define CASE(CODE) \ case X509_V_ERR_##CODE: \ return #CODE; @@ -1398,7 +1506,7 @@ std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) return "UNSPECIFIED"; } -std::optional X509Pointer::ErrorReason(int32_t err) { +std::optional X509Pointer::ErrorReason(int32_t err) { if (err == X509_V_OK) return std::nullopt; return X509_verify_cert_error_string(err); } @@ -1454,9 +1562,8 @@ BIOPointer BIOPointer::New(const void* data, size_t len) { return BIOPointer(BIO_new_mem_buf(data, len)); } -BIOPointer BIOPointer::NewFile(std::string_view filename, - std::string_view mode) { - return BIOPointer(BIO_new_file(filename.data(), mode.data())); +BIOPointer BIOPointer::NewFile(const char* filename, const char* mode) { + return BIOPointer(BIO_new_file(filename, mode)); } BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) { @@ -1738,30 +1845,30 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, // ============================================================================ // KDF -const EVP_MD* getDigestByName(const std::string_view name) { +const EVP_MD* getDigestByName(const char* name) { // Historically, "dss1" and "DSS1" were DSA aliases for SHA-1 // exposed through the public API. - if (name == "dss1" || name == "DSS1") [[unlikely]] { + if (strcmp(name, "dss1") == 0 || strcmp(name, "DSS1") == 0) [[unlikely]] { return EVP_sha1(); } - return EVP_get_digestbyname(name.data()); + return EVP_get_digestbyname(name); } -const EVP_CIPHER* getCipherByName(const std::string_view name) { - return EVP_get_cipherbyname(name.data()); +const EVP_CIPHER* getCipherByName(const char* name) { + return EVP_get_cipherbyname(name); } -bool checkHkdfLength(const EVP_MD* md, size_t length) { +bool checkHkdfLength(const Digest& md, size_t length) { // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as // the output of the hash function. 255 is a hard limit because HKDF appends // an 8-bit counter to each HMAC'd message, starting at 1. static constexpr size_t kMaxDigestMultiplier = 255; - size_t max_length = EVP_MD_size(md) * kMaxDigestMultiplier; + size_t max_length = md.size() * kMaxDigestMultiplier; if (length > max_length) return false; return true; } -bool hkdfInfo(const EVP_MD* md, +bool hkdfInfo(const Digest& md, const Buffer& key, const Buffer& info, const Buffer& salt, @@ -1779,13 +1886,16 @@ bool hkdfInfo(const EVP_MD* md, if (salt.len > 0) { actual_salt = {reinterpret_cast(salt.data), salt.len}; } else { - actual_salt = {default_salt, static_cast(EVP_MD_size(md))}; + actual_salt = {default_salt, static_cast(md.size())}; } #ifndef NCRYPTO_NO_KDF_H auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_HKDF); + // OpenSSL < 3.0.0 accepted only a void* as the argument of + // EVP_PKEY_CTX_set_hkdf_md. + const EVP_MD* md_ptr = md; if (!ctx || !EVP_PKEY_derive_init(ctx.get()) || - !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) || + !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md_ptr) || !EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) { return false; } @@ -1830,7 +1940,7 @@ bool hkdfInfo(const EVP_MD* md, #endif } -DataPointer hkdf(const EVP_MD* md, +DataPointer hkdf(const Digest& md, const Buffer& key, const Buffer& info, const Buffer& salt, @@ -1895,7 +2005,7 @@ DataPointer scrypt(const Buffer& pass, return {}; } -bool pbkdf2Into(const EVP_MD* md, +bool pbkdf2Into(const Digest& md, const Buffer& pass, const Buffer& salt, uint32_t iterations, @@ -1908,12 +2018,13 @@ bool pbkdf2Into(const EVP_MD* md, return false; } + const EVP_MD* md_ptr = md; if (PKCS5_PBKDF2_HMAC(pass.data, pass.len, salt.data, salt.len, iterations, - md, + md_ptr, length, out->data)) { return true; @@ -1922,7 +2033,7 @@ bool pbkdf2Into(const EVP_MD* md, return false; } -DataPointer pbkdf2(const EVP_MD* md, +DataPointer pbkdf2(const Digest& md, const Buffer& pass, const Buffer& salt, uint32_t iterations, @@ -1937,6 +2048,102 @@ DataPointer pbkdf2(const EVP_MD* md, return {}; } +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#ifndef OPENSSL_NO_ARGON2 +DataPointer argon2(const Buffer& pass, + const Buffer& salt, + uint32_t lanes, + size_t length, + uint32_t memcost, + uint32_t iter, + uint32_t version, + const Buffer& secret, + const Buffer& ad, + Argon2Type type) { + ClearErrorOnReturn clearErrorOnReturn; + + std::string_view algorithm; + switch (type) { + case Argon2Type::ARGON2I: + algorithm = "ARGON2I"; + break; + case Argon2Type::ARGON2D: + algorithm = "ARGON2D"; + break; + case Argon2Type::ARGON2ID: + algorithm = "ARGON2ID"; + break; + default: + // Invalid Argon2 type + return {}; + } + + // creates a new library context to avoid locking when running concurrently + auto ctx = DeleteFnPtr{OSSL_LIB_CTX_new()}; + if (!ctx) { + return {}; + } + + // required if threads > 1 + if (lanes > 1 && OSSL_set_max_threads(ctx.get(), lanes) != 1) { + return {}; + } + + auto kdf = DeleteFnPtr{ + EVP_KDF_fetch(ctx.get(), algorithm.data(), nullptr)}; + if (!kdf) { + return {}; + } + + auto kctx = + DeleteFnPtr{EVP_KDF_CTX_new(kdf.get())}; + if (!kctx) { + return {}; + } + + std::vector params; + params.reserve(9); + + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_PASSWORD, + const_cast(pass.len > 0 ? pass.data : ""), + pass.len)); + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_SALT, const_cast(salt.data), salt.len)); + params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &lanes)); + params.push_back( + OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes)); + params.push_back( + OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost)); + params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); + + if (ad.len != 0) { + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_ARGON2_AD, const_cast(ad.data), ad.len)); + } + + if (secret.len != 0) { + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_SECRET, + const_cast(secret.data), + secret.len)); + } + + params.push_back(OSSL_PARAM_construct_end()); + + auto dp = DataPointer::Alloc(length); + if (dp && EVP_KDF_derive(kctx.get(), + reinterpret_cast(dp.get()), + length, + params.data()) == 1) { + return dp; + } + + return {}; +} +#endif +#endif + // ============================================================================ EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( @@ -1982,6 +2189,31 @@ EVPKeyPointer EVPKeyPointer::NewRawPrivate( EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); } +#if OPENSSL_WITH_PQC +EVPKeyPointer EVPKeyPointer::NewRawSeed( + int id, const Buffer& data) { + if (id == 0) return {}; + + OSSL_PARAM params[] = { + OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, + const_cast(data.data), + data.len), + OSSL_PARAM_END}; + + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(id, nullptr); + if (ctx == nullptr) return {}; + + EVP_PKEY* pkey = nullptr; + if (ctx == nullptr || EVP_PKEY_fromdata_init(ctx) <= 0 || + EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { + EVP_PKEY_CTX_free(ctx); + return {}; + } + + return EVPKeyPointer(pkey); +} +#endif + EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { #ifndef NCRYPTO_NO_EVP_DH if (!dh) return {}; @@ -2039,7 +2271,26 @@ EVP_PKEY* EVPKeyPointer::release() { int EVPKeyPointer::id(const EVP_PKEY* key) { if (key == nullptr) return 0; - return EVP_PKEY_id(key); + int type = EVP_PKEY_id(key); +#if OPENSSL_WITH_PQC + // EVP_PKEY_id returns -1 when EVP_PKEY_* is only implemented in a provider + // which is the case for all post-quantum NIST algorithms + // one suggested way would be to use a chain of `EVP_PKEY_is_a` + // https://github.com/openssl/openssl/issues/27738#issuecomment-3013215870 + // or, this way there are less calls to the OpenSSL provider, just + // getting the name once + if (type == -1) { + const char* type_name = EVP_PKEY_get0_type_name(key); + if (type_name == nullptr) return -1; + + for (const auto& mapping : pqc_mappings) { + if (strcmp(type_name, mapping.name) == 0) { + return mapping.nid; + } + } + } +#endif + return type; } int EVPKeyPointer::base_id(const EVP_PKEY* key) { @@ -2095,6 +2346,44 @@ DataPointer EVPKeyPointer::rawPublicKey() const { return {}; } +#if OPENSSL_WITH_PQC +DataPointer EVPKeyPointer::rawSeed() const { + if (!pkey_) return {}; + + // Determine seed length and parameter name based on key type + size_t seed_len; + const char* param_name; + + switch (id()) { + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + seed_len = 32; // ML-DSA uses 32-byte seeds + param_name = OSSL_PKEY_PARAM_ML_DSA_SEED; + break; + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: + seed_len = 64; // ML-KEM uses 64-byte seeds + param_name = OSSL_PKEY_PARAM_ML_KEM_SEED; + break; + default: + unreachable(); + } + + if (auto data = DataPointer::Alloc(seed_len)) { + const Buffer buf = data; + size_t len = data.size(); + + if (EVP_PKEY_get_octet_string_param( + get(), param_name, buf.data, len, &seed_len) != 1) + return {}; + return data; + } + return {}; +} +#endif + DataPointer EVPKeyPointer::rawPrivateKey() const { if (!pkey_) return {}; if (auto data = DataPointer::Alloc(rawPrivateKeySize())) { @@ -2545,7 +2834,30 @@ bool EVPKeyPointer::isRsaVariant() const { bool EVPKeyPointer::isOneShotVariant() const { if (!pkey_) return false; int type = id(); - return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448; + switch (type) { + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: +#if OPENSSL_WITH_PQC + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: +#endif + return true; + default: + return false; + } } bool EVPKeyPointer::isSigVariant() const { @@ -2660,8 +2972,7 @@ SSLPointer SSLPointer::New(const SSLCtxPointer& ctx) { return SSLPointer(SSL_new(ctx.get())); } -void SSLPointer::getCiphers( - std::function cb) const { +void SSLPointer::getCiphers(std::function cb) const { if (!ssl_) return; STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(get()); @@ -2726,7 +3037,7 @@ std::optional SSLPointer::verifyPeerCertificate() const { return std::nullopt; } -const std::string_view SSLPointer::getClientHelloAlpn() const { +const char* SSLPointer::getClientHelloAlpn() const { if (ssl_ == nullptr) return {}; #ifndef OPENSSL_IS_BORINGSSL const unsigned char* buf; @@ -2751,7 +3062,7 @@ const std::string_view SSLPointer::getClientHelloAlpn() const { #endif } -const std::string_view SSLPointer::getClientHelloServerName() const { +const char* SSLPointer::getClientHelloServerName() const { if (ssl_ == nullptr) return {}; #ifndef OPENSSL_IS_BORINGSSL const unsigned char* buf; @@ -2836,6 +3147,22 @@ std::optional SSLPointer::getCipherVersion() const { return SSL_CIPHER_get_version(cipher); } +std::optional SSLPointer::getSecurityLevel() { +#ifndef OPENSSL_IS_BORINGSSL + auto ctx = SSLCtxPointer::New(); + if (!ctx) return std::nullopt; + + auto ssl = SSLPointer::New(ctx); + if (!ssl) return std::nullopt; + + return SSL_get_security_level(ssl); +#else + // OPENSSL_TLS_SECURITY_LEVEL is not defined in BoringSSL + // so assume it is the default OPENSSL_TLS_SECURITY_LEVEL value. + return 1; +#endif // OPENSSL_IS_BORINGSSL +} + SSLCtxPointer::SSLCtxPointer(SSL_CTX* ctx) : ctx_(ctx) {} SSLCtxPointer::SSLCtxPointer(SSLCtxPointer&& other) noexcept @@ -2885,10 +3212,21 @@ bool SSLCtxPointer::setGroups(const char* groups) { #endif } +bool SSLCtxPointer::setCipherSuites(const char* ciphers) { +#ifndef OPENSSL_IS_BORINGSSL + if (!ctx_) return false; + return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers); +#else + // BoringSSL does not allow API config of TLS 1.3 cipher suites. + // We treat this as a non-op. + return true; +#endif +} + // ============================================================================ -const Cipher Cipher::FromName(std::string_view name) { - return Cipher(EVP_get_cipherbyname(name.data())); +const Cipher Cipher::FromName(const char* name) { + return Cipher(EVP_get_cipherbyname(name)); } const Cipher Cipher::FromNid(int nid) { @@ -2899,6 +3237,63 @@ const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { return Cipher(EVP_CIPHER_CTX_cipher(ctx.get())); } +const Cipher Cipher::EMPTY = Cipher(); +const Cipher Cipher::AES_128_CBC = Cipher::FromNid(NID_aes_128_cbc); +const Cipher Cipher::AES_192_CBC = Cipher::FromNid(NID_aes_192_cbc); +const Cipher Cipher::AES_256_CBC = Cipher::FromNid(NID_aes_256_cbc); +const Cipher Cipher::AES_128_CTR = Cipher::FromNid(NID_aes_128_ctr); +const Cipher Cipher::AES_192_CTR = Cipher::FromNid(NID_aes_192_ctr); +const Cipher Cipher::AES_256_CTR = Cipher::FromNid(NID_aes_256_ctr); +const Cipher Cipher::AES_128_GCM = Cipher::FromNid(NID_aes_128_gcm); +const Cipher Cipher::AES_192_GCM = Cipher::FromNid(NID_aes_192_gcm); +const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm); +const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap); +const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap); +const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap); + +#ifndef OPENSSL_IS_BORINGSSL +const Cipher Cipher::AES_128_OCB = Cipher::FromNid(NID_aes_128_ocb); +const Cipher Cipher::AES_192_OCB = Cipher::FromNid(NID_aes_192_ocb); +const Cipher Cipher::AES_256_OCB = Cipher::FromNid(NID_aes_256_ocb); +#endif + +const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305); + +bool Cipher::isGcmMode() const { + if (!cipher_) return false; + return getMode() == EVP_CIPH_GCM_MODE; +} + +bool Cipher::isWrapMode() const { + if (!cipher_) return false; + return getMode() == EVP_CIPH_WRAP_MODE; +} + +bool Cipher::isCtrMode() const { + if (!cipher_) return false; + return getMode() == EVP_CIPH_CTR_MODE; +} + +bool Cipher::isCcmMode() const { + if (!cipher_) return false; + return getMode() == EVP_CIPH_CCM_MODE; +} + +bool Cipher::isOcbMode() const { + if (!cipher_) return false; + return getMode() == EVP_CIPH_OCB_MODE; +} + +bool Cipher::isStreamMode() const { + if (!cipher_) return false; + return getMode() == EVP_CIPH_STREAM_CIPHER; +} + +bool Cipher::isChaCha20Poly1305() const { + if (!cipher_) return false; + return getNid() == NID_chacha20_poly1305; +} + int Cipher::getMode() const { if (!cipher_) return 0; return EVP_CIPHER_mode(cipher_); @@ -2953,7 +3348,7 @@ std::string_view Cipher::getModeLabel() const { return "{unknown}"; } -std::string_view Cipher::getName() const { +const char* Cipher::getName() const { if (!cipher_) return {}; // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. @@ -2975,6 +3370,82 @@ bool Cipher::isSupportedAuthenticatedMode() const { } } +int Cipher::bytesToKey(const Digest& digest, + const Buffer& input, + unsigned char* key, + unsigned char* iv) const { + return EVP_BytesToKey( + *this, Digest::MD5, nullptr, input.data, input.len, 1, key, iv); +} + +namespace { +struct CipherCallbackContext { + Cipher::CipherNameCallback cb; + void operator()(const char* name) { cb(name); } +}; + +#if OPENSSL_VERSION_MAJOR >= 3 +template +void array_push_back(const TypeName* evp_ref, + const char* from, + const char* to, + void* arg) { + if (from == nullptr) return; + + const TypeName* real_instance = getbyname(from); + if (!real_instance) return; + + const char* real_name = getname(real_instance); + if (!real_name) return; + + // EVP_*_fetch() does not support alias names, so we need to pass it the + // real/original algorithm name. + // We use EVP_*_fetch() as a filter here because it will only return an + // instance if the algorithm is supported by the public OpenSSL APIs (some + // algorithms are used internally by OpenSSL and are also passed to this + // callback). + TypeName* fetched = fetch_type(nullptr, real_name, nullptr); + if (fetched == nullptr) return; + + free_type(fetched); + auto& cb = *(static_cast(arg)); + cb(from); +} +#else +template +void array_push_back(const TypeName* evp_ref, + const char* from, + const char* to, + void* arg) { + if (!from) return; + auto& cb = *(static_cast(arg)); + cb(from); +} +#endif +} // namespace + +void Cipher::ForEach(Cipher::CipherNameCallback callback) { + ClearErrorOnReturn clearErrorOnReturn; + CipherCallbackContext context; + context.cb = std::move(callback); + + EVP_CIPHER_do_all_sorted( +#if OPENSSL_VERSION_MAJOR >= 3 + array_push_back, +#else + array_push_back, +#endif + &context); +} + // ============================================================================ CipherCtxPointer CipherCtxPointer::New() { @@ -3008,9 +3479,9 @@ EVP_CIPHER_CTX* CipherCtxPointer::release() { return ctx_.release(); } -void CipherCtxPointer::setFlags(int flags) { +void CipherCtxPointer::setAllowWrap() { if (!ctx_) return; - EVP_CIPHER_CTX_set_flags(ctx_.get(), flags); + EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); } bool CipherCtxPointer::setKeyLength(size_t length) { @@ -3056,6 +3527,31 @@ int CipherCtxPointer::getNid() const { return EVP_CIPHER_CTX_nid(ctx_.get()); } +bool CipherCtxPointer::isGcmMode() const { + if (!ctx_) return false; + return getMode() == EVP_CIPH_GCM_MODE; +} + +bool CipherCtxPointer::isOcbMode() const { + if (!ctx_) return false; + return getMode() == EVP_CIPH_OCB_MODE; +} + +bool CipherCtxPointer::isCcmMode() const { + if (!ctx_) return false; + return getMode() == EVP_CIPH_CCM_MODE; +} + +bool CipherCtxPointer::isWrapMode() const { + if (!ctx_) return false; + return getMode() == EVP_CIPH_WRAP_MODE; +} + +bool CipherCtxPointer::isChaCha20Poly1305() const { + if (!ctx_) return false; + return getNid() == NID_chacha20_poly1305; +} + bool CipherCtxPointer::init(const Cipher& cipher, bool encrypt, const unsigned char* key, @@ -3355,6 +3851,10 @@ EVPKeyCtxPointer EVPKeyCtxPointer::New(const EVPKeyPointer& key) { } EVPKeyCtxPointer EVPKeyCtxPointer::NewFromID(int id) { +#ifdef OPENSSL_IS_BORINGSSL + // DSA keys are not supported with BoringSSL + if (id == EVP_PKEY_DSA) return {}; +#endif return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id, nullptr)); } @@ -3420,14 +3920,16 @@ bool EVPKeyCtxPointer::setEcParameters(int curve, int encoding) { EVP_PKEY_CTX_set_ec_param_enc(ctx_.get(), encoding) == 1; } -bool EVPKeyCtxPointer::setRsaOaepMd(const EVP_MD* md) { - if (md == nullptr || !ctx_) return false; - return EVP_PKEY_CTX_set_rsa_oaep_md(ctx_.get(), md) > 0; +bool EVPKeyCtxPointer::setRsaOaepMd(const Digest& md) { + if (!md || !ctx_) return false; + const EVP_MD* md_ptr = md; + return EVP_PKEY_CTX_set_rsa_oaep_md(ctx_.get(), md_ptr) > 0; } -bool EVPKeyCtxPointer::setRsaMgf1Md(const EVP_MD* md) { - if (md == nullptr || !ctx_) return false; - return EVP_PKEY_CTX_set_rsa_mgf1_md(ctx_.get(), md) > 0; +bool EVPKeyCtxPointer::setRsaMgf1Md(const Digest& md) { + if (!md || !ctx_) return false; + const EVP_MD* md_ptr = md; + return EVP_PKEY_CTX_set_rsa_mgf1_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaPadding(int padding) { @@ -3462,14 +3964,17 @@ bool EVPKeyCtxPointer::setRsaKeygenPubExp(BignumPointer&& e) { return false; } -bool EVPKeyCtxPointer::setRsaPssKeygenMd(const EVP_MD* md) { - if (md == nullptr || !ctx_) return false; - return EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx_.get(), md) > 0; +bool EVPKeyCtxPointer::setRsaPssKeygenMd(const Digest& md) { + if (!md || !ctx_) return false; + // OpenSSL < 3 accepts a void* for the md parameter. + const EVP_MD* md_ptr = md; + return EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx_.get(), md_ptr) > 0; } -bool EVPKeyCtxPointer::setRsaPssKeygenMgf1Md(const EVP_MD* md) { - if (md == nullptr || !ctx_) return false; - return EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx_.get(), md) > 0; +bool EVPKeyCtxPointer::setRsaPssKeygenMgf1Md(const Digest& md) { + if (!md || !ctx_) return false; + const EVP_MD* md_ptr = md; + return EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaPssSaltlen(int salt_len) { @@ -3547,7 +4052,6 @@ EVPKeyPointer EVPKeyCtxPointer::paramgen() const { bool EVPKeyCtxPointer::publicCheck() const { if (!ctx_) return false; #ifndef OPENSSL_IS_BORINGSSL - return EVP_PKEY_public_check(ctx_.get()) == 1; #if OPENSSL_VERSION_MAJOR >= 3 return EVP_PKEY_public_check_quick(ctx_.get()) == 1; #else @@ -3861,6 +4365,26 @@ int Ec::getCurve() const { return EC_GROUP_get_curve_name(getGroup()); } +int Ec::GetCurveIdFromName(const char* name) { + int nid = EC_curve_nist2nid(name); + if (nid == NID_undef) { + nid = OBJ_sn2nid(name); + } + return nid; +} + +bool Ec::GetCurves(Ec::GetCurveCallback callback) { + const size_t count = EC_get_builtin_curves(nullptr, 0); + std::vector curves(count); + if (EC_get_builtin_curves(curves.data(), count) != count) { + return false; + } + for (auto curve : curves) { + if (!callback(OBJ_nid2sn(curve.nid))) return false; + } + return true; +} + uint32_t Ec::getDegree() const { return EC_GROUP_get_degree(getGroup()); } @@ -3903,7 +4427,7 @@ EVP_MD_CTX* EVPMDCtxPointer::release() { return ctx_.release(); } -bool EVPMDCtxPointer::digestInit(const EVP_MD* digest) { +bool EVPMDCtxPointer::digestInit(const Digest& digest) { if (!ctx_) return false; return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0; } @@ -3969,7 +4493,7 @@ bool EVPMDCtxPointer::copyTo(const EVPMDCtxPointer& other) const { } std::optional EVPMDCtxPointer::signInit(const EVPKeyPointer& key, - const EVP_MD* digest) { + const Digest& digest) { EVP_PKEY_CTX* ctx = nullptr; if (!EVP_DigestSignInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { return std::nullopt; @@ -3978,7 +4502,7 @@ std::optional EVPMDCtxPointer::signInit(const EVPKeyPointer& key, } std::optional EVPMDCtxPointer::verifyInit( - const EVPKeyPointer& key, const EVP_MD* digest) { + const EVPKeyPointer& key, const Digest& digest) { EVP_PKEY_CTX* ctx = nullptr; if (!EVP_DigestVerifyInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { return std::nullopt; @@ -3986,6 +4510,54 @@ std::optional EVPMDCtxPointer::verifyInit( return ctx; } +std::optional EVPMDCtxPointer::signInitWithContext( + const EVPKeyPointer& key, + const Digest& digest, + const Buffer& context_string) { +#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING + EVP_PKEY_CTX* ctx = nullptr; + + const OSSL_PARAM params[] = { + OSSL_PARAM_construct_octet_string( + OSSL_SIGNATURE_PARAM_CONTEXT_STRING, + const_cast(context_string.data), + context_string.len), + OSSL_PARAM_END}; + + if (!EVP_DigestSignInit_ex( + ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) { + return std::nullopt; + } + return ctx; +#else + return std::nullopt; +#endif +} + +std::optional EVPMDCtxPointer::verifyInitWithContext( + const EVPKeyPointer& key, + const Digest& digest, + const Buffer& context_string) { +#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING + EVP_PKEY_CTX* ctx = nullptr; + + const OSSL_PARAM params[] = { + OSSL_PARAM_construct_octet_string( + OSSL_SIGNATURE_PARAM_CONTEXT_STRING, + const_cast(context_string.data), + context_string.len), + OSSL_PARAM_END}; + + if (!EVP_DigestVerifyInit_ex( + ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) { + return std::nullopt; + } + return ctx; +#else + return std::nullopt; +#endif +} + DataPointer EVPMDCtxPointer::signOneShot( const Buffer& buf) const { if (!ctx_) return {}; @@ -4075,9 +4647,10 @@ HMAC_CTX* HMACCtxPointer::release() { return ctx_.release(); } -bool HMACCtxPointer::init(const Buffer& buf, const EVP_MD* md) { +bool HMACCtxPointer::init(const Buffer& buf, const Digest& md) { if (!ctx_) return false; - return HMAC_Init_ex(ctx_.get(), buf.data, buf.len, md, nullptr) == 1; + const EVP_MD* md_ptr = md; + return HMAC_Init_ex(ctx_.get(), buf.data, buf.len, md_ptr, nullptr) == 1; } bool HMACCtxPointer::update(const Buffer& buf) { @@ -4110,6 +4683,96 @@ HMACCtxPointer HMACCtxPointer::New() { return HMACCtxPointer(HMAC_CTX_new()); } +#if OPENSSL_VERSION_MAJOR >= 3 +EVPMacPointer::EVPMacPointer(EVP_MAC* mac) : mac_(mac) {} + +EVPMacPointer::EVPMacPointer(EVPMacPointer&& other) noexcept + : mac_(std::move(other.mac_)) {} + +EVPMacPointer& EVPMacPointer::operator=(EVPMacPointer&& other) noexcept { + if (this == &other) return *this; + mac_ = std::move(other.mac_); + return *this; +} + +EVPMacPointer::~EVPMacPointer() { + mac_.reset(); +} + +void EVPMacPointer::reset(EVP_MAC* mac) { + mac_.reset(mac); +} + +EVP_MAC* EVPMacPointer::release() { + return mac_.release(); +} + +EVPMacPointer EVPMacPointer::Fetch(const char* algorithm) { + return EVPMacPointer(EVP_MAC_fetch(nullptr, algorithm, nullptr)); +} + +EVPMacCtxPointer::EVPMacCtxPointer(EVP_MAC_CTX* ctx) : ctx_(ctx) {} + +EVPMacCtxPointer::EVPMacCtxPointer(EVPMacCtxPointer&& other) noexcept + : ctx_(std::move(other.ctx_)) {} + +EVPMacCtxPointer& EVPMacCtxPointer::operator=( + EVPMacCtxPointer&& other) noexcept { + if (this == &other) return *this; + ctx_ = std::move(other.ctx_); + return *this; +} + +EVPMacCtxPointer::~EVPMacCtxPointer() { + ctx_.reset(); +} + +void EVPMacCtxPointer::reset(EVP_MAC_CTX* ctx) { + ctx_.reset(ctx); +} + +EVP_MAC_CTX* EVPMacCtxPointer::release() { + return ctx_.release(); +} + +bool EVPMacCtxPointer::init(const Buffer& key, + const OSSL_PARAM* params) { + if (!ctx_) return false; + return EVP_MAC_init(ctx_.get(), + static_cast(key.data), + key.len, + params) == 1; +} + +bool EVPMacCtxPointer::update(const Buffer& data) { + if (!ctx_) return false; + return EVP_MAC_update(ctx_.get(), + static_cast(data.data), + data.len) == 1; +} + +DataPointer EVPMacCtxPointer::final(size_t length) { + if (!ctx_) return {}; + auto buf = DataPointer::Alloc(length); + if (!buf) return {}; + + size_t result_len = length; + if (EVP_MAC_final(ctx_.get(), + static_cast(buf.get()), + &result_len, + length) != 1) { + return {}; + } + + return buf; +} + +EVPMacCtxPointer EVPMacCtxPointer::New(EVP_MAC* mac) { + if (!mac) return EVPMacCtxPointer(); + return EVPMacCtxPointer(EVP_MAC_CTX_new(mac)); +} +#endif // OPENSSL_VERSION_MAJOR >= 3 + DataPointer hashDigest(const Buffer& buf, const EVP_MD* md) { if (md == nullptr) return {}; @@ -4130,6 +4793,22 @@ DataPointer hashDigest(const Buffer& buf, return data.resize(result_size); } +DataPointer xofHashDigest(const Buffer& buf, + const EVP_MD* md, + size_t output_length) { + if (md == nullptr) return {}; + + EVPMDCtxPointer ctx = EVPMDCtxPointer::New(); + if (!ctx) return {}; + if (ctx.digestInit(md) != 1) { + return {}; + } + if (ctx.digestUpdate(reinterpret_cast&>(buf)) != 1) { + return {}; + } + return ctx.digestFinal(output_length); +} + // ============================================================================ X509Name::X509Name() : name_(nullptr), total_(0) {} @@ -4218,6 +4897,145 @@ size_t Dsa::getDivisorLength() const { if (dsa_ == nullptr) return 0; return BignumPointer::GetBitCount(getQ()); } + +// ============================================================================ + +size_t Digest::size() const { + if (md_ == nullptr) return 0; + return EVP_MD_size(md_); +} + +const Digest Digest::MD5 = Digest(EVP_md5()); +const Digest Digest::SHA1 = Digest(EVP_sha1()); +const Digest Digest::SHA256 = Digest(EVP_sha256()); +const Digest Digest::SHA384 = Digest(EVP_sha384()); +const Digest Digest::SHA512 = Digest(EVP_sha512()); + +const Digest Digest::FromName(const char* name) { + return ncrypto::getDigestByName(name); +} + +// ============================================================================ +// KEM Implementation +#if OPENSSL_VERSION_MAJOR >= 3 +#if !OPENSSL_VERSION_PREREQ(3, 5) +bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) { + const char* operation = nullptr; + + switch (EVP_PKEY_id(key.get())) { + case EVP_PKEY_RSA: + operation = OSSL_KEM_PARAM_OPERATION_RSASVE; + break; +#if OPENSSL_VERSION_PREREQ(3, 2) + case EVP_PKEY_EC: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: + operation = OSSL_KEM_PARAM_OPERATION_DHKEM; + break; +#endif + default: + unreachable(); + } + + if (operation != nullptr) { + OSSL_PARAM params[] = { + OSSL_PARAM_utf8_string( + OSSL_KEM_PARAM_OPERATION, const_cast(operation), 0), + OSSL_PARAM_END}; + + if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) { + return false; + } + } + + return true; +} +#endif + +std::optional KEM::Encapsulate( + const EVPKeyPointer& public_key) { + ClearErrorOnReturn clear_error_on_return; + + auto ctx = public_key.newCtx(); + if (!ctx) return std::nullopt; + + if (EVP_PKEY_encapsulate_init(ctx.get(), nullptr) <= 0) { + return std::nullopt; + } + +#if !OPENSSL_VERSION_PREREQ(3, 5) + if (!SetOperationParameter(ctx.get(), public_key)) { + return std::nullopt; + } +#endif + + // Determine output buffer sizes + size_t ciphertext_len = 0; + size_t shared_key_len = 0; + + if (EVP_PKEY_encapsulate( + ctx.get(), nullptr, &ciphertext_len, nullptr, &shared_key_len) <= 0) { + return std::nullopt; + } + + auto ciphertext = DataPointer::Alloc(ciphertext_len); + auto shared_key = DataPointer::Alloc(shared_key_len); + if (!ciphertext || !shared_key) return std::nullopt; + + if (EVP_PKEY_encapsulate(ctx.get(), + static_cast(ciphertext.get()), + &ciphertext_len, + static_cast(shared_key.get()), + &shared_key_len) <= 0) { + return std::nullopt; + } + + return EncapsulateResult(std::move(ciphertext), std::move(shared_key)); +} + +DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key, + const Buffer& ciphertext) { + ClearErrorOnReturn clear_error_on_return; + + auto ctx = private_key.newCtx(); + if (!ctx) return {}; + + if (EVP_PKEY_decapsulate_init(ctx.get(), nullptr) <= 0) { + return {}; + } + +#if !OPENSSL_VERSION_PREREQ(3, 5) + if (!SetOperationParameter(ctx.get(), private_key)) { + return {}; + } +#endif + + // First pass: determine shared secret size + size_t shared_key_len = 0; + if (EVP_PKEY_decapsulate(ctx.get(), + nullptr, + &shared_key_len, + static_cast(ciphertext.data), + ciphertext.len) <= 0) { + return {}; + } + + auto shared_key = DataPointer::Alloc(shared_key_len); + if (!shared_key) return {}; + + if (EVP_PKEY_decapsulate(ctx.get(), + static_cast(shared_key.get()), + &shared_key_len, + static_cast(ciphertext.data), + ciphertext.len) <= 0) { + return {}; + } + + return shared_key; +} + +#endif // OPENSSL_VERSION_MAJOR >= 3 + } // namespace ncrypto // ===========================================================================