Skip to content

Commit 06229f2

Browse files
committed
crypto: support Ed448 and ML-DSA context parameter in Web Cryptography
1 parent 589ef79 commit 06229f2

File tree

21 files changed

+365
-207
lines changed

21 files changed

+365
-207
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4256,6 +4256,56 @@ std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInit(
42564256
return ctx;
42574257
}
42584258

4259+
std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::signInitWithContext(
4260+
const EVPKeyPointer& key,
4261+
const Digest& digest,
4262+
const Buffer<const unsigned char>& context_string) {
4263+
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
4264+
EVP_PKEY_CTX* ctx = nullptr;
4265+
4266+
const OSSL_PARAM params[] = {
4267+
OSSL_PARAM_construct_octet_string(
4268+
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
4269+
const_cast<unsigned char*>(context_string.data),
4270+
context_string.len),
4271+
OSSL_PARAM_END};
4272+
4273+
const char* digest_name = digest ? EVP_MD_get0_name(digest) : nullptr;
4274+
if (!EVP_DigestSignInit_ex(
4275+
ctx_.get(), &ctx, digest_name, nullptr, nullptr, key.get(), params)) {
4276+
return std::nullopt;
4277+
}
4278+
return ctx;
4279+
#else
4280+
return std::nullopt;
4281+
#endif
4282+
}
4283+
4284+
std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInitWithContext(
4285+
const EVPKeyPointer& key,
4286+
const Digest& digest,
4287+
const Buffer<const unsigned char>& context_string) {
4288+
#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING
4289+
EVP_PKEY_CTX* ctx = nullptr;
4290+
4291+
const OSSL_PARAM params[] = {
4292+
OSSL_PARAM_construct_octet_string(
4293+
OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
4294+
const_cast<unsigned char*>(context_string.data),
4295+
context_string.len),
4296+
OSSL_PARAM_END};
4297+
4298+
const char* digest_name = digest ? EVP_MD_get0_name(digest) : nullptr;
4299+
if (!EVP_DigestVerifyInit_ex(
4300+
ctx_.get(), &ctx, digest_name, nullptr, nullptr, key.get(), params)) {
4301+
return std::nullopt;
4302+
}
4303+
return ctx;
4304+
#else
4305+
return std::nullopt;
4306+
#endif
4307+
}
4308+
42594309
DataPointer EVPMDCtxPointer::signOneShot(
42604310
const Buffer<const unsigned char>& buf) const {
42614311
if (!ctx_) return {};

deps/ncrypto/ncrypto.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,15 @@ class EVPMDCtxPointer final {
14031403
std::optional<EVP_PKEY_CTX*> verifyInit(const EVPKeyPointer& key,
14041404
const Digest& digest);
14051405

1406+
std::optional<EVP_PKEY_CTX*> signInitWithContext(
1407+
const EVPKeyPointer& key,
1408+
const Digest& digest,
1409+
const Buffer<const unsigned char>& context_string);
1410+
std::optional<EVP_PKEY_CTX*> verifyInitWithContext(
1411+
const EVPKeyPointer& key,
1412+
const Digest& digest,
1413+
const Buffer<const unsigned char>& context_string);
1414+
14061415
DataPointer signOneShot(const Buffer<const unsigned char>& buf) const;
14071416
DataPointer sign(const Buffer<const unsigned char>& buf) const;
14081417
bool verify(const Buffer<const unsigned char>& buf,

doc/api/webcrypto.md

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,7 +1265,7 @@ changes:
12651265
12661266
<!--lint disable maximum-line-length remark-lint-->
12671267
1268-
* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams}
1268+
* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|ContextParams}
12691269
* `key` {CryptoKey}
12701270
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
12711271
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
@@ -1374,7 +1374,7 @@ changes:
13741374
13751375
<!--lint disable maximum-line-length remark-lint-->
13761376
1377-
* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams}
1377+
* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|ContextParams}
13781378
* `key` {CryptoKey}
13791379
* `signature` {ArrayBuffer|TypedArray|DataView|Buffer}
13801380
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
@@ -1652,20 +1652,23 @@ added: REPLACEME
16521652
added: REPLACEME
16531653
-->
16541654
1655-
* Type: {string} Must be `'ML-DSA-44'`[^modern-algos], `'ML-DSA-65'`[^modern-algos], or `'ML-DSA-87'`[^modern-algos].
1655+
* Type: {string} Must be `Ed448`[^secure-curves], `'ML-DSA-44'`[^modern-algos],
1656+
`'ML-DSA-65'`[^modern-algos], or `'ML-DSA-87'`[^modern-algos].
16561657
16571658
#### `contextParams.context`
16581659
16591660
<!-- YAML
16601661
added: REPLACEME
1662+
changes:
1663+
- version: REPLACEME
1664+
pr-url: https://github.com/nodejs/node/pull/59570
1665+
description: Non-empty context is now supported.
16611666
-->
16621667
16631668
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
16641669
16651670
The `context` member represents the optional context data to associate with
16661671
the message.
1667-
The Node.js Web Crypto API implementation only supports zero-length context
1668-
which is equivalent to not providing context at all.
16691672
16701673
### Class: `CShakeParams`
16711674
@@ -1846,37 +1849,6 @@ added: v15.0.0
18461849
18471850
* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`.
18481851
1849-
### Class: `Ed448Params`
1850-
1851-
<!-- YAML
1852-
added: v15.0.0
1853-
-->
1854-
1855-
#### `ed448Params.name`
1856-
1857-
<!-- YAML
1858-
added:
1859-
- v18.4.0
1860-
- v16.17.0
1861-
-->
1862-
1863-
* Type: {string} Must be `'Ed448'`[^secure-curves].
1864-
1865-
#### `ed448Params.context`
1866-
1867-
<!-- YAML
1868-
added:
1869-
- v18.4.0
1870-
- v16.17.0
1871-
-->
1872-
1873-
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
1874-
1875-
The `context` member represents the optional context data to associate with
1876-
the message.
1877-
The Node.js Web Crypto API implementation only supports zero-length context
1878-
which is equivalent to not providing context at all.
1879-
18801852
### Class: `EncapsulatedBits`
18811853
18821854
<!-- YAML

lib/internal/crypto/cfrg.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ function eddsaSignVerify(key, data, algorithm, signature) {
359359
undefined,
360360
undefined,
361361
undefined,
362+
algorithm.name === 'Ed448' ? algorithm.context : undefined,
362363
signature));
363364
}
364365

lib/internal/crypto/ec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) {
302302
undefined, // Salt length, not used with ECDSA
303303
undefined, // PSS Padding, not used with ECDSA
304304
kSigEncP1363,
305+
undefined,
305306
signature));
306307
}
307308

lib/internal/crypto/ml_dsa.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ function mlDsaSignVerify(key, data, algorithm, signature) {
306306
undefined,
307307
undefined,
308308
undefined,
309+
algorithm.context,
309310
signature));
310311
}
311312

lib/internal/crypto/rsa.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ function rsaSignVerify(key, data, { saltLength }, signature) {
355355
saltLength,
356356
key[kAlgorithm].name === 'RSA-PSS' ? RSA_PKCS1_PSS_PADDING : undefined,
357357
undefined,
358+
undefined,
358359
signature);
359360
});
360361
}

lib/internal/crypto/sig.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ function signOneShot(algorithm, data, key, callback) {
171171
algorithm,
172172
pssSaltLength,
173173
rsaPadding,
174-
dsaSigEnc);
174+
dsaSigEnc,
175+
undefined,
176+
undefined);
175177

176178
if (!callback) {
177179
const { 0: err, 1: signature } = job.run();
@@ -276,6 +278,7 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
276278
pssSaltLength,
277279
rsaPadding,
278280
dsaSigEnc,
281+
undefined,
279282
signature);
280283

281284
if (!callback) {

lib/internal/crypto/util.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ const kAlgorithmDefinitions = {
242242
'generateKey': null,
243243
'exportKey': null,
244244
'importKey': null,
245-
'sign': 'Ed448Params',
246-
'verify': 'Ed448Params',
245+
'sign': 'ContextParams',
246+
'verify': 'ContextParams',
247247
},
248248
'HKDF': {
249249
'importKey': null,
@@ -440,7 +440,6 @@ const simpleAlgorithmDictionaries = {
440440
salt: 'BufferSource',
441441
info: 'BufferSource',
442442
},
443-
Ed448Params: { context: 'BufferSource' },
444443
ContextParams: { context: 'BufferSource' },
445444
Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' },
446445
RsaOaepParams: { label: 'BufferSource' },

lib/internal/crypto/webidl.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const {
1919
MathTrunc,
2020
Number,
2121
NumberIsFinite,
22+
NumberParseInt,
2223
ObjectPrototypeHasOwnProperty,
2324
ObjectPrototypeIsPrototypeOf,
2425
SafeArrayIterator,
@@ -304,13 +305,12 @@ function createDictionaryConverter(name, dictionaries) {
304305
const context = `'${key}' of '${name}'${
305306
opts.context ? ` (${opts.context})` : ''
306307
}`;
307-
const { converter, validator } = member;
308-
const idlMemberValue = converter(esMemberValue, {
308+
const idlMemberValue = member.converter(esMemberValue, {
309309
__proto__: null,
310310
...opts,
311311
context,
312312
});
313-
validator?.(idlMemberValue, esDict);
313+
member.validator?.(idlMemberValue, esDict);
314314
setOwnProperty(idlDict, key, idlMemberValue);
315315
} else if (member.required) {
316316
throw makeException(
@@ -779,17 +779,25 @@ converters.EcdhKeyDeriveParams = createDictionaryConverter(
779779
},
780780
]);
781781

782-
for (const name of ['Ed448Params', 'ContextParams']) {
783-
converters[name] = createDictionaryConverter(
784-
name, [
785-
...new SafeArrayIterator(dictAlgorithm),
786-
{
787-
key: 'context',
788-
converter: converters.BufferSource,
789-
validator: validateZeroLength(`${name}.context`),
782+
converters.ContextParams = createDictionaryConverter(
783+
'ContextParams', [
784+
...new SafeArrayIterator(dictAlgorithm),
785+
{
786+
key: 'context',
787+
converter: converters.BufferSource,
788+
validator(V, dict) {
789+
let { 0: major, 1: minor } = process.versions.openssl.split('.');
790+
major = NumberParseInt(major, 10);
791+
minor = NumberParseInt(minor, 10);
792+
if (major > 3 || (major === 3 && minor >= 2)) {
793+
this.validator = undefined;
794+
} else {
795+
this.validator = validateZeroLength('ContextParams.context');
796+
this.validator(V, dict);
797+
}
790798
},
791-
]);
792-
}
799+
},
800+
]);
793801

794802
module.exports = {
795803
converters,

0 commit comments

Comments
 (0)