GeezerVison is showing github.com — crypto: support ML-DSA KeyObject, sign, and verify · nodejs/node@38aedfb
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: benchmark/crypto/create-keyobject.js +5-4Lines changed: 5 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -18,13 +18,14 @@ function readKeyPair(publicKeyName, privateKeyName) { | 18 | 18 | } | 19 | 19 |
| 20 | 20 | const keyFixtures = { | 21 | | - ec: readKeyPair('ec_p256_public', 'ec_p256_private'), | 22 | | - rsa: readKeyPair('rsa_public_2048', 'rsa_private_2048'), | 23 | | - ed25519: readKeyPair('ed25519_public', 'ed25519_private'), | | 21 | + 'ec': readKeyPair('ec_p256_public', 'ec_p256_private'), | | 22 | + 'rsa': readKeyPair('rsa_public_2048', 'rsa_private_2048'), | | 23 | + 'ed25519': readKeyPair('ed25519_public', 'ed25519_private'), | | 24 | + 'ml-dsa-44': readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'), | 24 | 25 | }; | 25 | 26 |
| 26 | 27 | const bench = common.createBenchmark(main, { | 27 | | - keyType: ['rsa', 'ec', 'ed25519'], | | 28 | + keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], | 28 | 29 | keyFormat: ['pkcs8', 'spki', 'der-pkcs8', 'der-spki', 'jwk-public', 'jwk-private'], | 29 | 30 | n: [1e3], | 30 | 31 | }); | |
Collapse file Copy file name to clipboardExpand all lines: benchmark/crypto/oneshot-sign.js +10-4Lines changed: 10 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -6,10 +6,15 @@ const fs = require('fs'); | 6 | 6 | const path = require('path'); | 7 | 7 | const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/'); | 8 | 8 |
| | 9 | +function readKey(name) { | | 10 | + return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8'); | | 11 | +} | | 12 | + | 9 | 13 | const keyFixtures = { | 10 | | - ec: fs.readFileSync(`${fixtures_keydir}/ec_p256_private.pem`, 'utf-8'), | 11 | | - rsa: fs.readFileSync(`${fixtures_keydir}/rsa_private_2048.pem`, 'utf-8'), | 12 | | - ed25519: fs.readFileSync(`${fixtures_keydir}/ed25519_private.pem`, 'utf-8'), | | 14 | + 'ec': readKey('ec_p256_private'), | | 15 | + 'rsa': readKey('rsa_private_2048'), | | 16 | + 'ed25519': readKey('ed25519_private'), | | 17 | + 'ml-dsa-44': readKey('ml_dsa_44_private'), | 13 | 18 | }; | 14 | 19 |
| 15 | 20 | const data = crypto.randomBytes(256); | @@ -18,7 +23,7 @@ let pems; | 18 | 23 | let keyObjects; | 19 | 24 |
| 20 | 25 | const bench = common.createBenchmark(main, { | 21 | | - keyType: ['rsa', 'ec', 'ed25519'], | | 26 | + keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], | 22 | 27 | mode: ['sync', 'async', 'async-parallel'], | 23 | 28 | keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], | 24 | 29 | n: [1e3], | @@ -90,6 +95,7 @@ function main({ n, mode, keyFormat, keyType }) { | 90 | 95 | digest = 'sha256'; | 91 | 96 | break; | 92 | 97 | case 'ed25519': | | 98 | + case 'ml-dsa-44': | 93 | 99 | break; | 94 | 100 | default: | 95 | 101 | throw new Error('not implemented'); | |
Collapse file Copy file name to clipboardExpand all lines: benchmark/crypto/oneshot-verify.js +6-4Lines changed: 6 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -18,9 +18,10 @@ function readKeyPair(publicKeyName, privateKeyName) { | 18 | 18 | } | 19 | 19 |
| 20 | 20 | const keyFixtures = { | 21 | | - ec: readKeyPair('ec_p256_public', 'ec_p256_private'), | 22 | | - rsa: readKeyPair('rsa_public_2048', 'rsa_private_2048'), | 23 | | - ed25519: readKeyPair('ed25519_public', 'ed25519_private'), | | 21 | + 'ec': readKeyPair('ec_p256_public', 'ec_p256_private'), | | 22 | + 'rsa': readKeyPair('rsa_public_2048', 'rsa_private_2048'), | | 23 | + 'ed25519': readKeyPair('ed25519_public', 'ed25519_private'), | | 24 | + 'ml-dsa-44': readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'), | 24 | 25 | }; | 25 | 26 |
| 26 | 27 | const data = crypto.randomBytes(256); | @@ -29,7 +30,7 @@ let pems; | 29 | 30 | let keyObjects; | 30 | 31 |
| 31 | 32 | const bench = common.createBenchmark(main, { | 32 | | - keyType: ['rsa', 'ec', 'ed25519'], | | 33 | + keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], | 33 | 34 | mode: ['sync', 'async', 'async-parallel'], | 34 | 35 | keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], | 35 | 36 | n: [1e3], | @@ -104,6 +105,7 @@ function main({ n, mode, keyFormat, keyType }) { | 104 | 105 | digest = 'sha256'; | 105 | 106 | break; | 106 | 107 | case 'ed25519': | | 108 | + case 'ml-dsa-44': | 107 | 109 | break; | 108 | 110 | default: | 109 | 111 | throw new Error('not implemented'); | |
Collapse file Copy file name to clipboardExpand all lines: deps/ncrypto/ncrypto.cc +72-2Lines changed: 72 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1897,6 +1897,31 @@ EVPKeyPointer EVPKeyPointer::NewRawPrivate( | 1897 | 1897 | EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); | 1898 | 1898 | } | 1899 | 1899 |
| | 1900 | +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 | | 1901 | +EVPKeyPointer EVPKeyPointer::NewRawSeed( | | 1902 | + int id, const Buffer<const unsigned char>& data) { | | 1903 | + if (id == 0) return {}; | | 1904 | + | | 1905 | + OSSL_PARAM params[] = { | | 1906 | + OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, | | 1907 | + const_cast<unsigned char*>(data.data), | | 1908 | + data.len), | | 1909 | + OSSL_PARAM_END}; | | 1910 | + | | 1911 | + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(id, nullptr); | | 1912 | + if (ctx == nullptr) return {}; | | 1913 | + | | 1914 | + EVP_PKEY* pkey = nullptr; | | 1915 | + if (ctx == nullptr || EVP_PKEY_fromdata_init(ctx) <= 0 || | | 1916 | + EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { | | 1917 | + EVP_PKEY_CTX_free(ctx); | | 1918 | + return {}; | | 1919 | + } | | 1920 | + | | 1921 | + return EVPKeyPointer(pkey); | | 1922 | +} | | 1923 | +#endif | | 1924 | + | 1900 | 1925 | EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { | 1901 | 1926 | if (!dh) return {}; | 1902 | 1927 | auto key = New(); | @@ -1942,7 +1967,16 @@ EVP_PKEY* EVPKeyPointer::release() { | 1942 | 1967 |
| 1943 | 1968 | int EVPKeyPointer::id(const EVP_PKEY* key) { | 1944 | 1969 | if (key == nullptr) return 0; | 1945 | | - return EVP_PKEY_id(key); | | 1970 | + int type = EVP_PKEY_id(key); | | 1971 | +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 | | 1972 | + // https://github.com/openssl/openssl/issues/27738#issuecomment-3013215870 | | 1973 | + if (type == -1) { | | 1974 | + if (EVP_PKEY_is_a(key, "ML-DSA-44")) return EVP_PKEY_ML_DSA_44; | | 1975 | + if (EVP_PKEY_is_a(key, "ML-DSA-65")) return EVP_PKEY_ML_DSA_65; | | 1976 | + if (EVP_PKEY_is_a(key, "ML-DSA-87")) return EVP_PKEY_ML_DSA_87; | | 1977 | + } | | 1978 | +#endif | | 1979 | + return type; | 1946 | 1980 | } | 1947 | 1981 |
| 1948 | 1982 | int EVPKeyPointer::base_id(const EVP_PKEY* key) { | @@ -1998,6 +2032,31 @@ DataPointer EVPKeyPointer::rawPublicKey() const { | 1998 | 2032 | return {}; | 1999 | 2033 | } | 2000 | 2034 |
| | 2035 | +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 | | 2036 | +DataPointer EVPKeyPointer::rawSeed() const { | | 2037 | + if (!pkey_) return {}; | | 2038 | + switch (id()) { | | 2039 | + case EVP_PKEY_ML_DSA_44: | | 2040 | + case EVP_PKEY_ML_DSA_65: | | 2041 | + case EVP_PKEY_ML_DSA_87: | | 2042 | + break; | | 2043 | + default: | | 2044 | + unreachable(); | | 2045 | + } | | 2046 | + | | 2047 | + size_t seed_len = 32; | | 2048 | + if (auto data = DataPointer::Alloc(seed_len)) { | | 2049 | + const Buffer<unsigned char> buf = data; | | 2050 | + size_t len = data.size(); | | 2051 | + if (EVP_PKEY_get_octet_string_param( | | 2052 | + get(), OSSL_PKEY_PARAM_ML_DSA_SEED, buf.data, len, &seed_len) != 1) | | 2053 | + return {}; | | 2054 | + return data; | | 2055 | + } | | 2056 | + return {}; | | 2057 | +} | | 2058 | +#endif | | 2059 | + | 2001 | 2060 | DataPointer EVPKeyPointer::rawPrivateKey() const { | 2002 | 2061 | if (!pkey_) return {}; | 2003 | 2062 | if (auto data = DataPointer::Alloc(rawPrivateKeySize())) { | @@ -2453,7 +2512,18 @@ bool EVPKeyPointer::isRsaVariant() const { | 2453 | 2512 | bool EVPKeyPointer::isOneShotVariant() const { | 2454 | 2513 | if (!pkey_) return false; | 2455 | 2514 | int type = id(); | 2456 | | - return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448; | | 2515 | + switch (type) { | | 2516 | + case EVP_PKEY_ED25519: | | 2517 | + case EVP_PKEY_ED448: | | 2518 | +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 | | 2519 | + case EVP_PKEY_ML_DSA_44: | | 2520 | + case EVP_PKEY_ML_DSA_65: | | 2521 | + case EVP_PKEY_ML_DSA_87: | | 2522 | +#endif | | 2523 | + return true; | | 2524 | + default: | | 2525 | + return false; | | 2526 | + } | 2457 | 2527 | } | 2458 | 2528 |
| 2459 | 2529 | bool EVPKeyPointer::isSigVariant() const { | |
Collapse file Copy file name to clipboardExpand all lines: deps/ncrypto/ncrypto.h +11Lines changed: 11 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 30 | 30 |
| 31 | 31 | #if OPENSSL_VERSION_MAJOR >= 3 | 32 | 32 | #define OSSL3_CONST const | | 33 | +#if OPENSSL_VERSION_MINOR >= 5 | | 34 | +#include <openssl/core_names.h> | | 35 | +#endif | 33 | 36 | #else | 34 | 37 | #define OSSL3_CONST | 35 | 38 | #endif | @@ -817,6 +820,10 @@ class EVPKeyPointer final { | 817 | 820 | const Buffer<const unsigned char>& data); | 818 | 821 | static EVPKeyPointer NewRawPrivate(int id, | 819 | 822 | const Buffer<const unsigned char>& data); | | 823 | +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 | | 824 | + static EVPKeyPointer NewRawSeed(int id, | | 825 | + const Buffer<const unsigned char>& data); | | 826 | +#endif | 820 | 827 | static EVPKeyPointer NewDH(DHPointer&& dh); | 821 | 828 | static EVPKeyPointer NewRSA(RSAPointer&& rsa); | 822 | 829 |
| @@ -910,6 +917,10 @@ class EVPKeyPointer final { | 910 | 917 | DataPointer rawPrivateKey() const; | 911 | 918 | BIOPointer derPublicKey() const; | 912 | 919 |
| | 920 | +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 | | 921 | + DataPointer rawSeed() const; | | 922 | +#endif | | 923 | + | 913 | 924 | Result<BIOPointer, bool> writePrivateKey( | 914 | 925 | const PrivateKeyEncodingConfig& config) const; | 915 | 926 | Result<BIOPointer, bool> writePublicKey( | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/crypto.md +42-5Lines changed: 42 additions & 5 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1916,6 +1916,9 @@ This can be called many times with new data as it is streamed. | 1916 | 1916 | <!-- YAML | 1917 | 1917 | added: v11.6.0 | 1918 | 1918 | changes: | | 1919 | + - version: REPLACEME | | 1920 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 1921 | + description: Add support for ML-DSA keys. | 1919 | 1922 | - version: | 1920 | 1923 | - v14.5.0 | 1921 | 1924 | - v12.19.0 | @@ -2021,6 +2024,9 @@ Other key details might be exposed via this API using additional attributes. | 2021 | 2024 | <!-- YAML | 2022 | 2025 | added: v11.6.0 | 2023 | 2026 | changes: | | 2027 | + - version: REPLACEME | | 2028 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 2029 | + description: Add support for ML-DSA keys. | 2024 | 2030 | - version: | 2025 | 2031 | - v13.9.0 | 2026 | 2032 | - v12.17.0 | @@ -2055,6 +2061,9 @@ types are: | 2055 | 2061 | * `'ed25519'` (OID 1.3.101.112) | 2056 | 2062 | * `'ed448'` (OID 1.3.101.113) | 2057 | 2063 | * `'dh'` (OID 1.2.840.113549.1.3.1) | | 2064 | +* `'ml-dsa-44'`[^openssl35] (OID 2.16.840.1.101.3.4.3.17) | | 2065 | +* `'ml-dsa-65'`[^openssl35] (OID 2.16.840.1.101.3.4.3.18) | | 2066 | +* `'ml-dsa-87'`[^openssl35] (OID 2.16.840.1.101.3.4.3.19) | 2058 | 2067 |
| 2059 | 2068 | This property is `undefined` for unrecognized `KeyObject` types and symmetric | 2060 | 2069 | keys. | @@ -3403,6 +3412,9 @@ input.on('readable', () => { | 3403 | 3412 | <!-- YAML | 3404 | 3413 | added: v11.6.0 | 3405 | 3414 | changes: | | 3415 | + - version: REPLACEME | | 3416 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 3417 | + description: Add support for ML-DSA keys. | 3406 | 3418 | - version: v15.12.0 | 3407 | 3419 | pr-url: https://github.com/nodejs/node/pull/37254 | 3408 | 3420 | description: The key can also be a JWK object. | @@ -3439,6 +3451,9 @@ of the passphrase is limited to 1024 bytes. | 3439 | 3451 | <!-- YAML | 3440 | 3452 | added: v11.6.0 | 3441 | 3453 | changes: | | 3454 | + - version: REPLACEME | | 3455 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 3456 | + description: Add support for ML-DSA keys. | 3442 | 3457 | - version: v15.12.0 | 3443 | 3458 | pr-url: https://github.com/nodejs/node/pull/37254 | 3444 | 3459 | description: The key can also be a JWK object. | @@ -3648,6 +3663,9 @@ underlying hash function. See [`crypto.createHmac()`][] for more information. | 3648 | 3663 | <!-- YAML | 3649 | 3664 | added: v10.12.0 | 3650 | 3665 | changes: | | 3666 | + - version: REPLACEME | | 3667 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 3668 | + description: Add support for ML-DSA key pairs. | 3651 | 3669 | - version: v18.0.0 | 3652 | 3670 | pr-url: https://github.com/nodejs/node/pull/41678 | 3653 | 3671 | description: Passing an invalid callback to the `callback` argument | @@ -3678,7 +3696,8 @@ changes: | 3678 | 3696 | --> | 3679 | 3697 |
| 3680 | 3698 | * `type` {string} Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, | 3681 | | - `'ed448'`, `'x25519'`, `'x448'`, or `'dh'`. | | 3699 | + `'ed448'`, `'x25519'`, `'x448'`, `'dh'`, `'ml-dsa-44'`[^openssl35], | | 3700 | + `'ml-dsa-65'`[^openssl35], or `'ml-dsa-87'`[^openssl35]. | 3682 | 3701 | * `options` {Object} | 3683 | 3702 | * `modulusLength` {number} Key size in bits (RSA, DSA). | 3684 | 3703 | * `publicExponent` {number} Public exponent (RSA). **Default:** `0x10001`. | @@ -3767,6 +3786,9 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties. | 3767 | 3786 | <!-- YAML | 3768 | 3787 | added: v10.12.0 | 3769 | 3788 | changes: | | 3789 | + - version: REPLACEME | | 3790 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 3791 | + description: Add support for ML-DSA key pairs. | 3770 | 3792 | - version: v16.10.0 | 3771 | 3793 | pr-url: https://github.com/nodejs/node/pull/39927 | 3772 | 3794 | description: Add ability to define `RSASSA-PSS-params` sequence parameters | @@ -3792,7 +3814,8 @@ changes: | 3792 | 3814 | --> | 3793 | 3815 |
| 3794 | 3816 | * `type` {string} Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, | 3795 | | - `'ed448'`, `'x25519'`, `'x448'`, or `'dh'`. | | 3817 | + `'ed448'`, `'x25519'`, `'x448'`, `'dh'`, `'ml-dsa-44'`[^openssl35], | | 3818 | + `'ml-dsa-65'`[^openssl35], or `'ml-dsa-87'`[^openssl35]. | 3796 | 3819 | * `options` {Object} | 3797 | 3820 | * `modulusLength` {number} Key size in bits (RSA, DSA). | 3798 | 3821 | * `publicExponent` {number} Public exponent (RSA). **Default:** `0x10001`. | @@ -3816,7 +3839,7 @@ changes: | 3816 | 3839 | * `privateKey` {string | Buffer | KeyObject} | 3817 | 3840 |
| 3818 | 3841 | Generates a new asymmetric key pair of the given `type`. RSA, RSA-PSS, DSA, EC, | 3819 | | -Ed25519, Ed448, X25519, X448, and DH are currently supported. | | 3842 | +Ed25519, Ed448, X25519, X448, DH, and ML-DSA[^openssl35] are currently supported. | 3820 | 3843 |
| 3821 | 3844 | If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function | 3822 | 3845 | behaves as if [`keyObject.export()`][] had been called on its result. Otherwise, | @@ -5416,6 +5439,9 @@ Throws an error if FIPS mode is not available. | 5416 | 5439 | <!-- YAML | 5417 | 5440 | added: v12.0.0 | 5418 | 5441 | changes: | | 5442 | + - version: REPLACEME | | 5443 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 5444 | + description: Add support for ML-DSA signing. | 5419 | 5445 | - version: v18.0.0 | 5420 | 5446 | pr-url: https://github.com/nodejs/node/pull/41678 | 5421 | 5447 | description: Passing an invalid callback to the `callback` argument | @@ -5445,7 +5471,10 @@ changes: | 5445 | 5471 |
| 5446 | 5472 | Calculates and returns the signature for `data` using the given private key and | 5447 | 5473 | algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is | 5448 | | -dependent upon the key type (especially Ed25519 and Ed448). | | 5474 | +dependent upon the key type. | | 5475 | + | | 5476 | +`algorithm` is required to be `null` or `undefined` for Ed25519, Ed448, and | | 5477 | +ML-DSA. | 5449 | 5478 |
| 5450 | 5479 | If `key` is not a [`KeyObject`][], this function behaves as if `key` had been | 5451 | 5480 | passed to [`crypto.createPrivateKey()`][]. If it is an object, the following | @@ -5526,6 +5555,9 @@ not introduce timing vulnerabilities. | 5526 | 5555 | <!-- YAML | 5527 | 5556 | added: v12.0.0 | 5528 | 5557 | changes: | | 5558 | + - version: REPLACEME | | 5559 | + pr-url: https://github.com/nodejs/node/pull/59259 | | 5560 | + description: Add support for ML-DSA signature verification. | 5529 | 5561 | - version: v18.0.0 | 5530 | 5562 | pr-url: https://github.com/nodejs/node/pull/41678 | 5531 | 5563 | description: Passing an invalid callback to the `callback` argument | @@ -5561,7 +5593,10 @@ changes: | 5561 | 5593 |
| 5562 | 5594 | Verifies the given signature for `data` using the given key and algorithm. If | 5563 | 5595 | `algorithm` is `null` or `undefined`, then the algorithm is dependent upon the | 5564 | | -key type (especially Ed25519 and Ed448). | | 5596 | +key type. | | 5597 | + | | 5598 | +`algorithm` is required to be `null` or `undefined` for Ed25519, Ed448, and | | 5599 | +ML-DSA. | 5565 | 5600 |
| 5566 | 5601 | If `key` is not a [`KeyObject`][], this function behaves as if `key` had been | 5567 | 5602 | passed to [`crypto.createPublicKey()`][]. If it is an object, the following | @@ -6150,6 +6185,8 @@ See the [list of SSL OP Flags][] for details. | 6150 | 6185 | </tr> | 6151 | 6186 | </table> | 6152 | 6187 |
| | 6188 | +[^openssl35]: Requires OpenSSL >= 3.5 | | 6189 | + | 6153 | 6190 | [AEAD algorithms]: https://en.wikipedia.org/wiki/Authenticated_encryption | 6154 | 6191 | [CCM mode]: #ccm-mode | 6155 | 6192 | [CVE-2021-44532]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44532 | |
|