Skip to content

Commit 08f8d80

Browse files
panvanpaun
authored andcommitted
crypto: support ML-KEM, DHKEM, and RSASVE key encapsulation mechanisms
PR-URL: nodejs/node#59491 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent df0927a commit 08f8d80

2 files changed

Lines changed: 173 additions & 0 deletions

File tree

include/ncrypto.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,6 +1644,40 @@ DataPointer argon2(const Buffer<const char>& pass,
16441644
#endif
16451645
#endif
16461646

1647+
// ============================================================================
1648+
// KEM (Key Encapsulation Mechanism)
1649+
#if OPENSSL_VERSION_MAJOR >= 3
1650+
1651+
class KEM final {
1652+
public:
1653+
struct EncapsulateResult {
1654+
DataPointer ciphertext;
1655+
DataPointer shared_key;
1656+
1657+
EncapsulateResult() = default;
1658+
EncapsulateResult(DataPointer ct, DataPointer sk)
1659+
: ciphertext(std::move(ct)), shared_key(std::move(sk)) {}
1660+
};
1661+
1662+
// Encapsulate a shared secret using KEM with a public key.
1663+
// Returns both the ciphertext and shared secret.
1664+
static std::optional<EncapsulateResult> Encapsulate(
1665+
const EVPKeyPointer& public_key);
1666+
1667+
// Decapsulate a shared secret using KEM with a private key and ciphertext.
1668+
// Returns the shared secret.
1669+
static DataPointer Decapsulate(const EVPKeyPointer& private_key,
1670+
const Buffer<const void>& ciphertext);
1671+
1672+
private:
1673+
#if !OPENSSL_VERSION_PREREQ(3, 5)
1674+
static bool SetOperationParameter(EVP_PKEY_CTX* ctx,
1675+
const EVPKeyPointer& key);
1676+
#endif
1677+
};
1678+
1679+
#endif // OPENSSL_VERSION_MAJOR >= 3
1680+
16471681
// ============================================================================
16481682
// Version metadata
16491683
#define NCRYPTO_VERSION "0.0.1"

src/ncrypto.cpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4727,6 +4727,145 @@ size_t Dsa::getDivisorLength() const {
47274727
}
47284728
} // namespace ncrypto
47294729

4730+
// ============================================================================
4731+
4732+
size_t Digest::size() const {
4733+
if (md_ == nullptr) return 0;
4734+
return EVP_MD_size(md_);
4735+
}
4736+
4737+
const Digest Digest::MD5 = Digest(EVP_md5());
4738+
const Digest Digest::SHA1 = Digest(EVP_sha1());
4739+
const Digest Digest::SHA256 = Digest(EVP_sha256());
4740+
const Digest Digest::SHA384 = Digest(EVP_sha384());
4741+
const Digest Digest::SHA512 = Digest(EVP_sha512());
4742+
4743+
const Digest Digest::FromName(const char* name) {
4744+
return ncrypto::getDigestByName(name);
4745+
}
4746+
4747+
// ============================================================================
4748+
// KEM Implementation
4749+
#if OPENSSL_VERSION_MAJOR >= 3
4750+
#if !OPENSSL_VERSION_PREREQ(3, 5)
4751+
bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) {
4752+
const char* operation = nullptr;
4753+
4754+
switch (EVP_PKEY_id(key.get())) {
4755+
case EVP_PKEY_RSA:
4756+
operation = OSSL_KEM_PARAM_OPERATION_RSASVE;
4757+
break;
4758+
#if OPENSSL_VERSION_PREREQ(3, 2)
4759+
case EVP_PKEY_EC:
4760+
case EVP_PKEY_X25519:
4761+
case EVP_PKEY_X448:
4762+
operation = OSSL_KEM_PARAM_OPERATION_DHKEM;
4763+
break;
4764+
#endif
4765+
default:
4766+
unreachable();
4767+
}
4768+
4769+
if (operation != nullptr) {
4770+
OSSL_PARAM params[] = {
4771+
OSSL_PARAM_utf8_string(
4772+
OSSL_KEM_PARAM_OPERATION, const_cast<char*>(operation), 0),
4773+
OSSL_PARAM_END};
4774+
4775+
if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) {
4776+
return false;
4777+
}
4778+
}
4779+
4780+
return true;
4781+
}
4782+
#endif
4783+
4784+
std::optional<KEM::EncapsulateResult> KEM::Encapsulate(
4785+
const EVPKeyPointer& public_key) {
4786+
ClearErrorOnReturn clear_error_on_return;
4787+
4788+
auto ctx = public_key.newCtx();
4789+
if (!ctx) return std::nullopt;
4790+
4791+
if (EVP_PKEY_encapsulate_init(ctx.get(), nullptr) <= 0) {
4792+
return std::nullopt;
4793+
}
4794+
4795+
#if !OPENSSL_VERSION_PREREQ(3, 5)
4796+
if (!SetOperationParameter(ctx.get(), public_key)) {
4797+
return std::nullopt;
4798+
}
4799+
#endif
4800+
4801+
// Determine output buffer sizes
4802+
size_t ciphertext_len = 0;
4803+
size_t shared_key_len = 0;
4804+
4805+
if (EVP_PKEY_encapsulate(
4806+
ctx.get(), nullptr, &ciphertext_len, nullptr, &shared_key_len) <= 0) {
4807+
return std::nullopt;
4808+
}
4809+
4810+
auto ciphertext = DataPointer::Alloc(ciphertext_len);
4811+
auto shared_key = DataPointer::Alloc(shared_key_len);
4812+
if (!ciphertext || !shared_key) return std::nullopt;
4813+
4814+
if (EVP_PKEY_encapsulate(ctx.get(),
4815+
static_cast<unsigned char*>(ciphertext.get()),
4816+
&ciphertext_len,
4817+
static_cast<unsigned char*>(shared_key.get()),
4818+
&shared_key_len) <= 0) {
4819+
return std::nullopt;
4820+
}
4821+
4822+
return EncapsulateResult(std::move(ciphertext), std::move(shared_key));
4823+
}
4824+
4825+
DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key,
4826+
const Buffer<const void>& ciphertext) {
4827+
ClearErrorOnReturn clear_error_on_return;
4828+
4829+
auto ctx = private_key.newCtx();
4830+
if (!ctx) return {};
4831+
4832+
if (EVP_PKEY_decapsulate_init(ctx.get(), nullptr) <= 0) {
4833+
return {};
4834+
}
4835+
4836+
#if !OPENSSL_VERSION_PREREQ(3, 5)
4837+
if (!SetOperationParameter(ctx.get(), private_key)) {
4838+
return {};
4839+
}
4840+
#endif
4841+
4842+
// First pass: determine shared secret size
4843+
size_t shared_key_len = 0;
4844+
if (EVP_PKEY_decapsulate(ctx.get(),
4845+
nullptr,
4846+
&shared_key_len,
4847+
static_cast<const unsigned char*>(ciphertext.data),
4848+
ciphertext.len) <= 0) {
4849+
return {};
4850+
}
4851+
4852+
auto shared_key = DataPointer::Alloc(shared_key_len);
4853+
if (!shared_key) return {};
4854+
4855+
if (EVP_PKEY_decapsulate(ctx.get(),
4856+
static_cast<unsigned char*>(shared_key.get()),
4857+
&shared_key_len,
4858+
static_cast<const unsigned char*>(ciphertext.data),
4859+
ciphertext.len) <= 0) {
4860+
return {};
4861+
}
4862+
4863+
return shared_key;
4864+
}
4865+
4866+
#endif // OPENSSL_VERSION_MAJOR >= 3
4867+
4868+
} // namespace ncrypto
47304869
// ===========================================================================
47314870
#ifdef NCRYPTO_BSSL_NEEDS_DH_PRIMES
47324871
// While newer versions of BoringSSL have these primes, older versions do not,

0 commit comments

Comments
 (0)