Skip to content

Commit 8d768f2

Browse files
panvaanonrig
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 bcfc7dd commit 8d768f2

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

include/ncrypto.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,40 @@ DataPointer argon2(const Buffer<const char>& pass,
16251625
#endif
16261626
#endif
16271627

1628+
// ============================================================================
1629+
// KEM (Key Encapsulation Mechanism)
1630+
#if OPENSSL_VERSION_MAJOR >= 3
1631+
1632+
class KEM final {
1633+
public:
1634+
struct EncapsulateResult {
1635+
DataPointer ciphertext;
1636+
DataPointer shared_key;
1637+
1638+
EncapsulateResult() = default;
1639+
EncapsulateResult(DataPointer ct, DataPointer sk)
1640+
: ciphertext(std::move(ct)), shared_key(std::move(sk)) {}
1641+
};
1642+
1643+
// Encapsulate a shared secret using KEM with a public key.
1644+
// Returns both the ciphertext and shared secret.
1645+
static std::optional<EncapsulateResult> Encapsulate(
1646+
const EVPKeyPointer& public_key);
1647+
1648+
// Decapsulate a shared secret using KEM with a private key and ciphertext.
1649+
// Returns the shared secret.
1650+
static DataPointer Decapsulate(const EVPKeyPointer& private_key,
1651+
const Buffer<const void>& ciphertext);
1652+
1653+
private:
1654+
#if !OPENSSL_VERSION_PREREQ(3, 5)
1655+
static bool SetOperationParameter(EVP_PKEY_CTX* ctx,
1656+
const EVPKeyPointer& key);
1657+
#endif
1658+
};
1659+
1660+
#endif // OPENSSL_VERSION_MAJOR >= 3
1661+
16281662
// ============================================================================
16291663
// Version metadata
16301664
#define NCRYPTO_VERSION "0.0.1"

src/ncrypto.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4703,6 +4703,127 @@ const Digest Digest::FromName(const char* name) {
47034703
return ncrypto::getDigestByName(name);
47044704
}
47054705

4706+
// ============================================================================
4707+
// KEM Implementation
4708+
#if OPENSSL_VERSION_MAJOR >= 3
4709+
#if !OPENSSL_VERSION_PREREQ(3, 5)
4710+
bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) {
4711+
const char* operation = nullptr;
4712+
4713+
switch (EVP_PKEY_id(key.get())) {
4714+
case EVP_PKEY_RSA:
4715+
operation = OSSL_KEM_PARAM_OPERATION_RSASVE;
4716+
break;
4717+
#if OPENSSL_VERSION_PREREQ(3, 2)
4718+
case EVP_PKEY_EC:
4719+
case EVP_PKEY_X25519:
4720+
case EVP_PKEY_X448:
4721+
operation = OSSL_KEM_PARAM_OPERATION_DHKEM;
4722+
break;
4723+
#endif
4724+
default:
4725+
unreachable();
4726+
}
4727+
4728+
if (operation != nullptr) {
4729+
OSSL_PARAM params[] = {
4730+
OSSL_PARAM_utf8_string(
4731+
OSSL_KEM_PARAM_OPERATION, const_cast<char*>(operation), 0),
4732+
OSSL_PARAM_END};
4733+
4734+
if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) {
4735+
return false;
4736+
}
4737+
}
4738+
4739+
return true;
4740+
}
4741+
#endif
4742+
4743+
std::optional<KEM::EncapsulateResult> KEM::Encapsulate(
4744+
const EVPKeyPointer& public_key) {
4745+
ClearErrorOnReturn clear_error_on_return;
4746+
4747+
auto ctx = public_key.newCtx();
4748+
if (!ctx) return std::nullopt;
4749+
4750+
if (EVP_PKEY_encapsulate_init(ctx.get(), nullptr) <= 0) {
4751+
return std::nullopt;
4752+
}
4753+
4754+
#if !OPENSSL_VERSION_PREREQ(3, 5)
4755+
if (!SetOperationParameter(ctx.get(), public_key)) {
4756+
return std::nullopt;
4757+
}
4758+
#endif
4759+
4760+
// Determine output buffer sizes
4761+
size_t ciphertext_len = 0;
4762+
size_t shared_key_len = 0;
4763+
4764+
if (EVP_PKEY_encapsulate(
4765+
ctx.get(), nullptr, &ciphertext_len, nullptr, &shared_key_len) <= 0) {
4766+
return std::nullopt;
4767+
}
4768+
4769+
auto ciphertext = DataPointer::Alloc(ciphertext_len);
4770+
auto shared_key = DataPointer::Alloc(shared_key_len);
4771+
if (!ciphertext || !shared_key) return std::nullopt;
4772+
4773+
if (EVP_PKEY_encapsulate(ctx.get(),
4774+
static_cast<unsigned char*>(ciphertext.get()),
4775+
&ciphertext_len,
4776+
static_cast<unsigned char*>(shared_key.get()),
4777+
&shared_key_len) <= 0) {
4778+
return std::nullopt;
4779+
}
4780+
4781+
return EncapsulateResult(std::move(ciphertext), std::move(shared_key));
4782+
}
4783+
4784+
DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key,
4785+
const Buffer<const void>& ciphertext) {
4786+
ClearErrorOnReturn clear_error_on_return;
4787+
4788+
auto ctx = private_key.newCtx();
4789+
if (!ctx) return {};
4790+
4791+
if (EVP_PKEY_decapsulate_init(ctx.get(), nullptr) <= 0) {
4792+
return {};
4793+
}
4794+
4795+
#if !OPENSSL_VERSION_PREREQ(3, 5)
4796+
if (!SetOperationParameter(ctx.get(), private_key)) {
4797+
return {};
4798+
}
4799+
#endif
4800+
4801+
// First pass: determine shared secret size
4802+
size_t shared_key_len = 0;
4803+
if (EVP_PKEY_decapsulate(ctx.get(),
4804+
nullptr,
4805+
&shared_key_len,
4806+
static_cast<const unsigned char*>(ciphertext.data),
4807+
ciphertext.len) <= 0) {
4808+
return {};
4809+
}
4810+
4811+
auto shared_key = DataPointer::Alloc(shared_key_len);
4812+
if (!shared_key) return {};
4813+
4814+
if (EVP_PKEY_decapsulate(ctx.get(),
4815+
static_cast<unsigned char*>(shared_key.get()),
4816+
&shared_key_len,
4817+
static_cast<const unsigned char*>(ciphertext.data),
4818+
ciphertext.len) <= 0) {
4819+
return {};
4820+
}
4821+
4822+
return shared_key;
4823+
}
4824+
4825+
#endif // OPENSSL_VERSION_MAJOR >= 3
4826+
47064827
} // namespace ncrypto
47074828

47084829
// ===========================================================================

0 commit comments

Comments
 (0)