Skip to content

Commit cd86c6a

Browse files
committed
crypto: wire ML-DSA and ML-KEM for use when using BoringSSL
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent cc7f5ea commit cd86c6a

32 files changed

Lines changed: 629 additions & 506 deletions

benchmark/crypto/create-keyobject.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const keyFixtures = {
2626

2727
if (hasOpenSSL(3, 5)) {
2828
keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private');
29+
} else if (process.features.openssl_is_boringssl) {
30+
keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private_seed_only');
2931
}
3032

3133
const bench = common.createBenchmark(main, {

benchmark/crypto/kem.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ if (hasOpenSSL(3, 5)) {
2424
keyFixtures['ml-kem-512'] = readKeyPair('ml_kem_512_public', 'ml_kem_512_private');
2525
keyFixtures['ml-kem-768'] = readKeyPair('ml_kem_768_public', 'ml_kem_768_private');
2626
keyFixtures['ml-kem-1024'] = readKeyPair('ml_kem_1024_public', 'ml_kem_1024_private');
27+
} else if (process.features.openssl_is_boringssl) {
28+
keyFixtures['ml-kem-768'] = readKeyPair('ml_kem_768_public', 'ml_kem_768_private_seed_only');
29+
keyFixtures['ml-kem-1024'] = readKeyPair('ml_kem_1024_public', 'ml_kem_1024_private_seed_only');
2730
}
2831
if (hasOpenSSL(3, 2)) {
2932
keyFixtures['p-256'] = readKeyPair('ec_p256_public', 'ec_p256_private');

benchmark/crypto/oneshot-sign.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const keyFixtures = {
1919

2020
if (hasOpenSSL(3, 5)) {
2121
keyFixtures['ml-dsa-44'] = readKey('ml_dsa_44_private');
22+
} else if (process.features.openssl_is_boringssl) {
23+
keyFixtures['ml-dsa-44'] = readKey('ml_dsa_44_private_seed_only');
2224
}
2325

2426
const data = crypto.randomBytes(256);

benchmark/crypto/oneshot-verify.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const keyFixtures = {
2626

2727
if (hasOpenSSL(3, 5)) {
2828
keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private');
29+
} else if (process.features.openssl_is_boringssl) {
30+
keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private_seed_only');
2931
}
3032

3133
const data = crypto.randomBytes(256);

deps/ncrypto/ncrypto.cc

Lines changed: 116 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ constexpr static PQCMapping pqc_mappings[] = {
3434
{"ML-DSA-44", EVP_PKEY_ML_DSA_44},
3535
{"ML-DSA-65", EVP_PKEY_ML_DSA_65},
3636
{"ML-DSA-87", EVP_PKEY_ML_DSA_87},
37-
{"ML-KEM-512", EVP_PKEY_ML_KEM_512},
3837
{"ML-KEM-768", EVP_PKEY_ML_KEM_768},
3938
{"ML-KEM-1024", EVP_PKEY_ML_KEM_1024},
39+
40+
#if OPENSSL_WITH_PQC_ML_KEM_512
41+
{"ML-KEM-512", EVP_PKEY_ML_KEM_512},
42+
#endif
43+
#if OPENSSL_WITH_PQC_SLH_DSA
4044
{"SLH-DSA-SHA2-128f", EVP_PKEY_SLH_DSA_SHA2_128F},
4145
{"SLH-DSA-SHA2-128s", EVP_PKEY_SLH_DSA_SHA2_128S},
4246
{"SLH-DSA-SHA2-192f", EVP_PKEY_SLH_DSA_SHA2_192F},
@@ -49,6 +53,7 @@ constexpr static PQCMapping pqc_mappings[] = {
4953
{"SLH-DSA-SHAKE-192s", EVP_PKEY_SLH_DSA_SHAKE_192S},
5054
{"SLH-DSA-SHAKE-256f", EVP_PKEY_SLH_DSA_SHAKE_256F},
5155
{"SLH-DSA-SHAKE-256s", EVP_PKEY_SLH_DSA_SHAKE_256S},
56+
#endif
5257
};
5358

5459
#endif
@@ -2095,27 +2100,99 @@ EVPKeyPointer EVPKeyPointer::NewRawPrivate(
20952100
}
20962101

20972102
#if OPENSSL_WITH_PQC
2098-
EVPKeyPointer EVPKeyPointer::NewRawSeed(
2099-
int id, const Buffer<const unsigned char>& data) {
2100-
if (id == 0) return {};
2103+
namespace {
2104+
constexpr size_t kPqcMlDsaSeedSize = 32;
2105+
constexpr size_t kPqcMlKemSeedSize = 64;
2106+
2107+
size_t GetPqcSeedSize(int id) {
2108+
switch (id) {
2109+
case EVP_PKEY_ML_DSA_44:
2110+
case EVP_PKEY_ML_DSA_65:
2111+
case EVP_PKEY_ML_DSA_87:
2112+
return kPqcMlDsaSeedSize;
2113+
#if OPENSSL_WITH_PQC_ML_KEM_512
2114+
case EVP_PKEY_ML_KEM_512:
2115+
#endif
2116+
case EVP_PKEY_ML_KEM_768:
2117+
case EVP_PKEY_ML_KEM_1024:
2118+
return kPqcMlKemSeedSize;
2119+
default:
2120+
unreachable();
2121+
}
2122+
}
2123+
2124+
#if OPENSSL_WITH_BORINGSSL_PQC
2125+
const EVP_PKEY_ALG* GetPqcSeedAlg(int id) {
2126+
switch (id) {
2127+
case EVP_PKEY_ML_DSA_44:
2128+
return EVP_pkey_ml_dsa_44();
2129+
case EVP_PKEY_ML_DSA_65:
2130+
return EVP_pkey_ml_dsa_65();
2131+
case EVP_PKEY_ML_DSA_87:
2132+
return EVP_pkey_ml_dsa_87();
2133+
case EVP_PKEY_ML_KEM_768:
2134+
return EVP_pkey_ml_kem_768();
2135+
case EVP_PKEY_ML_KEM_1024:
2136+
return EVP_pkey_ml_kem_1024();
2137+
default:
2138+
unreachable();
2139+
}
2140+
}
2141+
#else
2142+
const char* GetPqcSeedParamName(int id) {
2143+
switch (id) {
2144+
case EVP_PKEY_ML_DSA_44:
2145+
case EVP_PKEY_ML_DSA_65:
2146+
case EVP_PKEY_ML_DSA_87:
2147+
return OSSL_PKEY_PARAM_ML_DSA_SEED;
2148+
case EVP_PKEY_ML_KEM_512:
2149+
case EVP_PKEY_ML_KEM_768:
2150+
case EVP_PKEY_ML_KEM_1024:
2151+
return OSSL_PKEY_PARAM_ML_KEM_SEED;
2152+
default:
2153+
unreachable();
2154+
}
2155+
}
2156+
#endif
21012157

2158+
EVPKeyPointer NewPqcKeyFromSeed(int id,
2159+
const Buffer<const unsigned char>& data) {
2160+
#if OPENSSL_WITH_BORINGSSL_PQC
2161+
return EVPKeyPointer(
2162+
EVP_PKEY_from_private_seed(GetPqcSeedAlg(id), data.data, data.len));
2163+
#else
21022164
OSSL_PARAM params[] = {
2103-
OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED,
2165+
OSSL_PARAM_construct_octet_string(GetPqcSeedParamName(id),
21042166
const_cast<unsigned char*>(data.data),
21052167
data.len),
21062168
OSSL_PARAM_END};
21072169

2108-
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(id, nullptr);
2109-
if (ctx == nullptr) return {};
2170+
auto ctx = EVPKeyCtxPointer::NewFromID(id);
2171+
if (!ctx) return {};
21102172

21112173
EVP_PKEY* pkey = nullptr;
2112-
if (ctx == nullptr || EVP_PKEY_fromdata_init(ctx) <= 0 ||
2113-
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
2114-
EVP_PKEY_CTX_free(ctx);
2174+
if (EVP_PKEY_fromdata_init(ctx.get()) <= 0 ||
2175+
EVP_PKEY_fromdata(ctx.get(), &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
21152176
return {};
21162177
}
2117-
21182178
return EVPKeyPointer(pkey);
2179+
#endif
2180+
}
2181+
2182+
bool GetPqcSeed(EVP_PKEY* pkey, int id, const Buffer<unsigned char>& out) {
2183+
size_t len = out.len;
2184+
#if OPENSSL_WITH_BORINGSSL_PQC
2185+
return EVP_PKEY_get_private_seed(pkey, out.data, &len) == 1;
2186+
#else
2187+
return EVP_PKEY_get_octet_string_param(
2188+
pkey, GetPqcSeedParamName(id), out.data, out.len, &len) == 1;
2189+
#endif
2190+
}
2191+
} // namespace
2192+
2193+
EVPKeyPointer EVPKeyPointer::NewRawSeed(
2194+
int id, const Buffer<const unsigned char>& data) {
2195+
return NewPqcKeyFromSeed(id, data);
21192196
}
21202197
#endif
21212198

@@ -2165,7 +2242,7 @@ EVP_PKEY* EVPKeyPointer::release() {
21652242
int EVPKeyPointer::id(const EVP_PKEY* key) {
21662243
if (key == nullptr) return 0;
21672244
int type = EVP_PKEY_id(key);
2168-
#if OPENSSL_WITH_PQC
2245+
#if OPENSSL_WITH_OPENSSL_PQC
21692246
// EVP_PKEY_id returns -1 when EVP_PKEY_* is only implemented in a provider
21702247
// which is the case for all post-quantum NIST algorithms
21712248
// one suggested way would be to use a chain of `EVP_PKEY_is_a`
@@ -2243,34 +2320,11 @@ DataPointer EVPKeyPointer::rawPublicKey() const {
22432320
DataPointer EVPKeyPointer::rawSeed() const {
22442321
if (!pkey_) return {};
22452322

2246-
// Determine seed length and parameter name based on key type
2247-
size_t seed_len;
2248-
const char* param_name;
2249-
2250-
switch (id()) {
2251-
case EVP_PKEY_ML_DSA_44:
2252-
case EVP_PKEY_ML_DSA_65:
2253-
case EVP_PKEY_ML_DSA_87:
2254-
seed_len = 32; // ML-DSA uses 32-byte seeds
2255-
param_name = OSSL_PKEY_PARAM_ML_DSA_SEED;
2256-
break;
2257-
case EVP_PKEY_ML_KEM_512:
2258-
case EVP_PKEY_ML_KEM_768:
2259-
case EVP_PKEY_ML_KEM_1024:
2260-
seed_len = 64; // ML-KEM uses 64-byte seeds
2261-
param_name = OSSL_PKEY_PARAM_ML_KEM_SEED;
2262-
break;
2263-
default:
2264-
unreachable();
2265-
}
2323+
const size_t seed_len = GetPqcSeedSize(id());
22662324

22672325
if (auto data = DataPointer::Alloc(seed_len)) {
22682326
const Buffer<unsigned char> buf = data;
2269-
size_t len = data.size();
2270-
2271-
if (EVP_PKEY_get_octet_string_param(
2272-
get(), param_name, buf.data, len, &seed_len) != 1)
2273-
return {};
2327+
if (!GetPqcSeed(get(), id(), buf)) return {};
22742328
return data;
22752329
}
22762330
return {};
@@ -2312,6 +2366,7 @@ EVPKeyPointer::operator const EC_KEY*() const {
23122366
}
23132367

23142368
namespace {
2369+
23152370
EVPKeyPointer::ParseKeyResult TryParsePublicKeyInner(const BIOPointer& bp,
23162371
const char* name,
23172372
auto&& parse) {
@@ -2739,6 +2794,7 @@ bool EVPKeyPointer::isOneShotVariant() const {
27392794
case EVP_PKEY_ML_DSA_44:
27402795
case EVP_PKEY_ML_DSA_65:
27412796
case EVP_PKEY_ML_DSA_87:
2797+
#if OPENSSL_WITH_PQC_SLH_DSA
27422798
case EVP_PKEY_SLH_DSA_SHA2_128F:
27432799
case EVP_PKEY_SLH_DSA_SHA2_128S:
27442800
case EVP_PKEY_SLH_DSA_SHA2_192F:
@@ -2751,6 +2807,7 @@ bool EVPKeyPointer::isOneShotVariant() const {
27512807
case EVP_PKEY_SLH_DSA_SHAKE_192S:
27522808
case EVP_PKEY_SLH_DSA_SHAKE_256F:
27532809
case EVP_PKEY_SLH_DSA_SHAKE_256S:
2810+
#endif
27542811
#endif
27552812
return true;
27562813
default:
@@ -4401,7 +4458,17 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::signInitWithContext(
44014458
const EVPKeyPointer& key,
44024459
const Digest& digest,
44034460
const Buffer<const unsigned char>& context_string) {
4404-
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
4461+
#ifdef OPENSSL_IS_BORINGSSL
4462+
EVP_PKEY_CTX* ctx = nullptr;
4463+
if (!EVP_DigestSignInit(ctx_.get(), &ctx, digest, nullptr, key.get())) {
4464+
return std::nullopt;
4465+
}
4466+
if (EVP_PKEY_CTX_set1_signature_context_string(
4467+
ctx, context_string.data, context_string.len) <= 0) {
4468+
return std::nullopt;
4469+
}
4470+
return ctx;
4471+
#elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING)
44054472
EVP_PKEY_CTX* ctx = nullptr;
44064473

44074474
#ifdef OSSL_SIGNATURE_PARAM_INSTANCE
@@ -4446,7 +4513,17 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInitWithContext(
44464513
const EVPKeyPointer& key,
44474514
const Digest& digest,
44484515
const Buffer<const unsigned char>& context_string) {
4449-
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
4516+
#ifdef OPENSSL_IS_BORINGSSL
4517+
EVP_PKEY_CTX* ctx = nullptr;
4518+
if (!EVP_DigestVerifyInit(ctx_.get(), &ctx, digest, nullptr, key.get())) {
4519+
return std::nullopt;
4520+
}
4521+
if (EVP_PKEY_CTX_set1_signature_context_string(
4522+
ctx, context_string.data, context_string.len) <= 0) {
4523+
return std::nullopt;
4524+
}
4525+
return ctx;
4526+
#elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING)
44504527
EVP_PKEY_CTX* ctx = nullptr;
44514528

44524529
#ifdef OSSL_SIGNATURE_PARAM_INSTANCE

deps/ncrypto/ncrypto.h

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
#define OPENSSL_WITH_ARGON2 0
5959
#endif
6060

61-
#if OPENSSL_VERSION_PREREQ(3, 0)
61+
#if OPENSSL_VERSION_PREREQ(3, 0) || defined(OPENSSL_IS_BORINGSSL)
6262
#define OPENSSL_WITH_KEM 1
6363
#else
6464
#define OPENSSL_WITH_KEM 0
@@ -70,7 +70,7 @@
7070
#define OPENSSL_WITH_KMAC 0
7171
#endif
7272

73-
#if OPENSSL_VERSION_PREREQ(3, 2)
73+
#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_PREREQ(3, 2)
7474
#define OPENSSL_WITH_SIGNATURE_CONTEXT_STRING 1
7575
#else
7676
#define OPENSSL_WITH_SIGNATURE_CONTEXT_STRING 0
@@ -82,24 +82,40 @@
8282
#define OPENSSL_WITH_OPENSSL_DHKEM 0
8383
#endif
8484

85-
#if OPENSSL_WITH_KEM && !OPENSSL_VERSION_PREREQ(3, 5)
85+
#if OPENSSL_WITH_KEM && !defined(OPENSSL_IS_BORINGSSL) && \
86+
!OPENSSL_VERSION_PREREQ(3, 5)
8687
#define OPENSSL_WITH_KEM_OPERATION_PARAM 1
8788
#else
8889
#define OPENSSL_WITH_KEM_OPERATION_PARAM 0
8990
#endif
9091

91-
// Define OPENSSL_WITH_PQC for post-quantum cryptography support.
92-
#if OPENSSL_VERSION_PREREQ(3, 5)
93-
#define OPENSSL_WITH_PQC 1
92+
// Post-quantum cryptography support. Keep these explicit so code can
93+
// distinguish provider API shape from the available algorithm set.
94+
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 5)
95+
#define OPENSSL_WITH_OPENSSL_PQC 1
9496
#else
95-
#define OPENSSL_WITH_PQC 0
97+
#define OPENSSL_WITH_OPENSSL_PQC 0
9698
#endif
9799

98-
#if OPENSSL_WITH_PQC
100+
#ifdef OPENSSL_IS_BORINGSSL
101+
#define OPENSSL_WITH_BORINGSSL_PQC 1
102+
#else
103+
#define OPENSSL_WITH_BORINGSSL_PQC 0
104+
#endif
105+
106+
#define OPENSSL_WITH_PQC \
107+
(OPENSSL_WITH_OPENSSL_PQC || OPENSSL_WITH_BORINGSSL_PQC)
108+
#define OPENSSL_WITH_PQC_ML_KEM_512 OPENSSL_WITH_OPENSSL_PQC
109+
#define OPENSSL_WITH_PQC_SLH_DSA OPENSSL_WITH_OPENSSL_PQC
110+
111+
#if OPENSSL_WITH_OPENSSL_PQC
99112
#define EVP_PKEY_ML_KEM_512 NID_ML_KEM_512
100113
#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768
101114
#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024
102115
#include <openssl/core_names.h>
116+
#elif OPENSSL_WITH_BORINGSSL_PQC
117+
#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768
118+
#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024
103119
#endif
104120

105121
#if OPENSSL_VERSION_PREREQ(3, 0)

lib/internal/crypto/webidl.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -588,14 +588,18 @@ converters.ContextParams = createDictionaryConverter(
588588
key: 'context',
589589
converter: converters.BufferSource,
590590
validator(V, dict) {
591-
let { 0: major, 1: minor } = process.versions.openssl.split('.');
592-
major = NumberParseInt(major, 10);
593-
minor = NumberParseInt(minor, 10);
594-
if (major > 3 || (major === 3 && minor >= 2)) {
591+
if (process.features.openssl_is_boringssl) {
595592
this.validator = undefined;
596593
} else {
597-
this.validator = validateZeroLength('ContextParams.context');
598-
this.validator(V, dict);
594+
let { 0: major, 1: minor } = process.versions.openssl.split('.');
595+
major = NumberParseInt(major, 10);
596+
minor = NumberParseInt(minor, 10);
597+
if (major > 3 || (major === 3 && minor >= 2)) {
598+
this.validator = undefined;
599+
} else {
600+
this.validator = validateZeroLength('ContextParams.context');
601+
this.validator(V, dict);
602+
}
599603
}
600604
},
601605
},

0 commit comments

Comments
 (0)