Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2021-01-10 18:53:01 +0300
committerMichaël Zasso <targos@protonmail.com>2021-02-02 12:41:54 +0300
commit53a0bdff47699382e098ff99f656da5d85eb05dd (patch)
tree82614eac0009c2997712277728cadb118d873df9
parente1379a78eaedc656d4f2c8e2a723e54a2a12ac86 (diff)
crypto: experimental (Ed/X)25519/(Ed/X)448 support
Implements initial experimental support for Curve25519 and Curve448 support for both ECDH and sign/verify in Web Crypto. Introduced as a Node.js-specific extension to Web Crypto. Signed-off-by: James M Snell <jasnell@gmail.com> Fixes: https://github.com/nodejs/node/issues/36076 PR-URL: https://github.com/nodejs/node/pull/36879 Reviewed-By: Filip Skokan <panva.ip@gmail.com>
-rw-r--r--doc/api/webcrypto.md101
-rw-r--r--lib/internal/crypto/diffiehellman.js3
-rw-r--r--lib/internal/crypto/ec.js233
-rw-r--r--lib/internal/crypto/util.js8
-rw-r--r--lib/internal/crypto/webcrypto.js31
-rw-r--r--src/crypto/crypto_ecdh.cc308
-rw-r--r--src/crypto/crypto_ecdh.h15
-rw-r--r--src/crypto/crypto_keygen.h3
-rw-r--r--src/crypto/crypto_keys.cc50
-rw-r--r--src/crypto/crypto_keys.h1
-rw-r--r--src/crypto/crypto_sig.cc82
-rw-r--r--src/env.h2
-rw-r--r--test/parallel/test-webcrypto-ed25519-ed448.js367
-rw-r--r--test/parallel/test-webcrypto-x25519-x448.js246
-rw-r--r--tools/doc/type-parser.js4
15 files changed, 1287 insertions, 167 deletions
diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md
index ce9999a744f..c238d32fd71 100644
--- a/doc/api/webcrypto.md
+++ b/doc/api/webcrypto.md
@@ -65,6 +65,26 @@ async function generateEcKey(namedCurve = 'P-521') {
}
```
+#### ED25519/ED448/X25519/X448 Elliptic curve key pairs
+
+```js
+const { subtle } = require('crypto').webcrypto;
+
+async function generateEd25519Key() {
+ return subtle.generateKey({
+ name: 'NODE-ED25519',
+ namedCurve: 'NODE-ED25519',
+ }, true, ['sign', 'verify']);
+}
+
+async function generateX25519Key() {
+ return subtle.generateKey({
+ name: 'ECDH',
+ namedCurve: 'NODE-X25519',
+ }, true, ['deriveKey']);
+}
+```
+
#### HMAC keys
```js
@@ -305,6 +325,8 @@ implementation and the APIs supported for each:
| `'SHA-512'` | | | | | | | | | | | | ✔ |
| `'NODE-DSA'`<sup>1</sup> | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
| `'NODE-DH'`<sup>1</sup> | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | |
+| `'NODE-ED25519'`<sup>1</sup> | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
+| `'NODE-ED448'`<sup>1</sup> | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
<sup>1</sup> Node.js-specific extension
@@ -420,6 +442,8 @@ Valid key usages depend on the key algorithm (identified by
| `'NODE-DSA'` <sup>1</sup> | | | ✔ | ✔ | | | | |
| `'NODE-DH'` <sup>1</sup> | | | | | ✔ | ✔ | | |
| `'NODE-SCRYPT'` <sup>1</sup> | | | | | ✔ | ✔ | | |
+| `'NODE-ED25519'` <sup>1</sup> | | | ✔ | ✔ | | | | |
+| `'NODE-ED448'` <sup>1</sup> | | | ✔ | ✔ | | | | |
<sup>1</sup> Node.js-specific extension.
@@ -620,6 +644,8 @@ extension that allows converting a {CryptoKey} into a Node.js {KeyObject}.
| `'NODE-DSA'` <sup>1</sup> | ✔ | ✔ | ✔ | |
| `'NODE-DH'` <sup>1</sup> | ✔ | ✔ | | |
| `'NODE-SCRYPT'` <sup>1</sup> | | | | |
+| `'NODE-ED25519'` <sup>1</sup> | ✔ | ✔ | ✔ | ✔ |
+| `'NODE-ED448'` <sup>1</sup> | ✔ | ✔ | ✔ | ✔ |
<sup>1</sup> Node.js-specific extension
@@ -629,7 +655,7 @@ added: v15.0.0
-->
<!--lint disable maximum-line-length remark-lint-->
-* `algorithm`: {RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams}
+* `algorithm`: {RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams|NodeEdKeyGenParams}
<!--lint enable maximum-line-length remark-lint-->
* `extractable`: {boolean}
* `keyUsages`: {string[]} See [Key usages][].
@@ -649,6 +675,8 @@ include:
* `'ECDH'`
* `'NODE-DSA'` <sup>1</sup>
* `'NODE-DH'` <sup>1</sup>
+* `'NODE-ED25519'` <sup>1</sup>
+* `'NODE-ED448'` <sup>1</sup>
The {CryptoKey} (secret key) generating algorithms supported include:
@@ -669,7 +697,7 @@ added: v15.0.0
`node.keyObject`.
* `keyData`: {ArrayBuffer|TypedArray|DataView|Buffer|KeyObject}
<!--lint disable maximum-line-length remark-lint-->
-* `algorithm`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams|Pbkdf2ImportParams|NodeDsaImportParams|NodeDhImportParams|NodeScryptImportParams}
+* `algorithm`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams|Pbkdf2ImportParams|NodeDsaImportParams|NodeDhImportParams|NodeScryptImportParams|NodeEdKeyImportParams}
<!--lint enable maximum-line-length remark-lint-->
* `extractable`: {boolean}
* `keyUsages`: {string[]} See [Key usages][].
@@ -704,6 +732,8 @@ The algorithms currently supported include:
| `'NODE-DSA'` <sup>1</sup> | ✔ | ✔ | ✔ | |
| `'NODE-DH'` <sup>1</sup> | ✔ | ✔ | | |
| `'NODE-SCRYPT'` <sup>1</sup> | | | | ✔ |
+| `'NODE-ED25519'` <sup>1</sup> | ✔ | ✔ | ✔ | ✔ |
+| `'NODE-ED448'` <sup>1</sup> | ✔ | ✔ | ✔ | ✔ |
<sup>1</sup> Node.js-specific extension
@@ -731,6 +761,8 @@ The algorithms currently supported include:
* `'ECDSA'`
* `'HMAC'`
* `'NODE-DSA'`<sup>1</sup>
+* `'NODE-ED25519'`<sup>1</sup>
+* `'NODE-ED448'`<sup>1</sup>
<sup>1</sup> Non-standadrd Node.js extension
@@ -809,6 +841,8 @@ The algorithms currently supported include:
* `'ECDSA'`
* `'HMAC'`
* `'NODE-DSA'`<sup>1</sup>
+* `'NODE-ED25519'`<sup>1</sup>
+* `'NODE-ED448'`<sup>1</sup>
<sup>1</sup> Non-standard Node.js extension
@@ -1062,7 +1096,8 @@ added: v15.0.0
added: v15.0.0
-->
-* Type: {string} Must be one of `'P-256'`, `'P-384'` or `'P-521'`.
+* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`,
+ `'NODE-ED25519'`, `'NODE-ED448'`, `'NODE-X25519'`, or `'NODE-X448'`.
### Class: `EcKeyImportParams`
<!-- YAML
@@ -1081,7 +1116,8 @@ added: v15.0.0
added: v15.0.0
-->
-* Type: {string} Must be one of `'P-256'`, `'P-384'` or `'P-521'`.
+* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`,
+ `'NODE-ED25519'`, `'NODE-ED448'`, `'NODE-X25519'`, or `'NODE-X448'`.
### Class: `HkdfParams`
<!-- YAML
@@ -1598,6 +1634,63 @@ added: v15.0.0
* Type: {string} Must be `'NODE-DSA'`
+### `NODE-ED25519` and `NODE-ED448` Algorithms
+<!-- YAML
+added: REPLACEME
+-->
+
+#### Class: `NodeEdKeyGenParams`
+<!-- YAML
+added: REPLACEME
+-->
+
+##### `nodeEdKeyGenParams.name`
+<!-- YAML
+added: REPLACEME
+-->
+
+* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'` or `'ECDH'`.
+
+##### `nodeEdKeyGenParams.namedCurve`
+<!-- YAML
+added: REPLACEME
+-->
+
+* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'`,
+ `'NODE-X25519'`, or `'NODE-X448'`.
+
+#### Class: `NodeEdKeyImportParams`
+<!-- YAML
+added: REPLACEME
+-->
+
+##### `nodeEdKeyImportParams.name`
+<!-- YAML
+added: REPLACEME
+-->
+
+* Type: {string} Must be one of `'NODE-ED25519'` or `'NODE-ED448'`
+ if importing an `Ed25519` or `Ed448` key, or `'ECDH'` if importing
+ an `X25519` or `X448` key.
+
+##### `nodeEdKeyImportParams.namedCurve`
+<!-- YAML
+added: REPLACEME
+-->
+
+* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'`,
+ `'NODE-X25519'`, or `'NODE-X448'`.
+
+##### `nodeEdKeyImportParams.public`
+<!-- YAML
+added: REPLACEME
+-->
+
+* Type: {boolean}
+
+The `public` parameter is used to specify that the key is to be interpreted
+as a public key.
+
### `NODE-SCRYPT` Algorithm
<!-- YAML
added: v15.0.0
diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js
index a28d61369b1..a82c03eb76d 100644
--- a/lib/internal/crypto/diffiehellman.js
+++ b/lib/internal/crypto/diffiehellman.js
@@ -70,7 +70,6 @@ const {
toBuf,
kHandle,
kKeyObject,
- kNamedCurveAliases,
} = require('internal/crypto/util');
const {
@@ -451,7 +450,7 @@ async function asyncDeriveBitsECDH(algorithm, baseKey, length) {
const bits = await new Promise((resolve, reject) => {
deriveBitsECDH(
- kNamedCurveAliases[baseKey.algorithm.namedCurve],
+ baseKey.algorithm.namedCurve,
key[kKeyObject][kHandle],
baseKey[kKeyObject][kHandle], (err, bits) => {
if (err) return reject(err);
diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js
index b14bddd4911..248bba57ad2 100644
--- a/lib/internal/crypto/ec.js
+++ b/lib/internal/crypto/ec.js
@@ -6,17 +6,21 @@ const {
SafeSet,
} = primordials;
+const { Buffer } = require('buffer');
+
const {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
+ kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
+ validateBoolean,
validateOneOf,
validateString,
} = require('internal/validators');
@@ -60,6 +64,10 @@ function verifyAcceptableEcKeyUse(name, type, usages) {
case 'ECDH':
checkSet = ['deriveKey', 'deriveBits'];
break;
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ // Fall through
case 'ECDSA':
switch (type) {
case 'private':
@@ -84,6 +92,41 @@ function createECPublicKeyRaw(namedCurve, keyData) {
return new PublicKeyObject(handle);
}
+function createECRawKey(namedCurve, keyData, isPublic) {
+ const handle = new KeyObjectHandle();
+ keyData = getArrayBufferOrView(keyData, 'keyData');
+
+ switch (namedCurve) {
+ case 'NODE-ED25519':
+ case 'NODE-X25519':
+ if (keyData.byteLength !== 32) {
+ throw lazyDOMException(
+ `${namedCurve} raw keys must be exactly 32-bytes`);
+ }
+ break;
+ case 'NODE-ED448':
+ if (keyData.byteLength !== 57) {
+ throw lazyDOMException(
+ `${namedCurve} raw keys must be exactly 57-bytes`);
+ }
+ break;
+ case 'NODE-X448':
+ if (keyData.byteLength !== 56) {
+ throw lazyDOMException(
+ `${namedCurve} raw keys must be exactly 56-bytes`);
+ }
+ break;
+ }
+
+ if (isPublic) {
+ handle.initEDRaw(namedCurve, keyData, kKeyTypePublic);
+ return new PublicKeyObject(handle);
+ }
+
+ handle.initEDRaw(namedCurve, keyData, kKeyTypePrivate);
+ return new PrivateKeyObject(handle);
+}
+
async function ecGenerateKey(algorithm, extractable, keyUsages) {
const { name, namedCurve } = algorithm;
validateString(namedCurve, 'algorithm.namedCurve');
@@ -95,6 +138,16 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) {
const usageSet = new SafeSet(keyUsages);
switch (name) {
case 'ECDSA':
+ if (namedCurve === 'NODE-ED25519' ||
+ namedCurve === 'NODE-ED448' ||
+ namedCurve === 'NODE-X25519' ||
+ namedCurve === 'NODE-X448') {
+ throw lazyDOMException('Unsupported named curves for ECDSA');
+ }
+ // Fall through
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
if (hasAnyNotIn(usageSet, 'sign', 'verify')) {
throw lazyDOMException(
'Unsupported key usage for an ECDSA key',
@@ -107,6 +160,10 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) {
'Unsupported key usage for an ECDH key',
'SyntaxError');
}
+ if (namedCurve === 'NODE-ED25519' || namedCurve === 'NODE-ED448') {
+ throw lazyDOMException('Unsupported named curves for ECDH');
+ }
+ // Fall through
}
return new Promise((resolve, reject) => {
generateKeyPair('ec', { namedCurve }, (err, pubKey, privKey) => {
@@ -121,6 +178,10 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) {
let publicUsages;
let privateUsages;
switch (name) {
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ // Fall through
case 'ECDSA':
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
@@ -170,9 +231,13 @@ async function ecImportKey(
namedCurve,
'algorithm.namedCurve',
ObjectKeys(kNamedCurveAliases));
-
+ // Only used for NODE-EDnnnn key variants to distinguish between
+ // importing a raw public key or raw private key.
+ if (algorithm.public !== undefined)
+ validateBoolean(algorithm.public, 'algorithm.public');
let keyObject;
const usagesSet = new SafeSet(keyUsages);
+ let checkNamedCurve = true;
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
@@ -205,68 +270,111 @@ async function ecImportKey(
let curve;
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
-
- if (keyData.kty !== 'EC')
- throw lazyDOMException('Invalid key type', 'DataError');
-
- if (keyData.d !== undefined) {
- verifyAcceptableEcKeyUse(name, 'private', usagesSet);
- } else {
- verifyAcceptableEcKeyUse(name, 'public', usagesSet);
- }
-
- if (usagesSet.size > 0 && keyData.use !== undefined) {
- if (algorithm.name === 'ECDSA' && keyData.use !== 'sig')
- throw lazyDOMException('Invalid use type', 'DataError');
- if (algorithm.name === 'ECDH' && keyData.use !== 'enc')
- throw lazyDOMException('Invalid use type', 'DataError');
- }
-
- validateKeyOps(keyData.key_ops, usagesSet);
-
- if (keyData.ext !== undefined &&
- keyData.ext === false &&
- extractable === true) {
- throw lazyDOMException('JWK is not extractable', 'DataError');
- }
-
- if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
- if (typeof keyData.alg !== 'string')
- throw lazyDOMException('Invalid alg', 'DataError');
- switch (keyData.alg) {
- case 'ES256': curve = 'P-256'; break;
- case 'ES384': curve = 'P-384'; break;
- case 'ES512': curve = 'P-521'; break;
+ switch (keyData.kty) {
+ case 'OKP': {
+ checkNamedCurve = false;
+ const isPublic = keyData.d === undefined;
+ const type =
+ namedCurve === 'NODE-X25519' || 'NODE-X448' ? 'ECDH' : 'ECDSA';
+ verifyAcceptableEcKeyUse(
+ type,
+ isPublic ? 'public' : 'private',
+ usagesSet);
+ keyObject = createECRawKey(
+ namedCurve,
+ Buffer.from(
+ isPublic ? keyData.k : keyData.d,
+ 'base64'),
+ isPublic);
+ break;
+ }
+ default: {
+ if (keyData.kty !== 'EC')
+ throw lazyDOMException('Invalid key type', 'DataError');
+
+ if (keyData.d !== undefined) {
+ verifyAcceptableEcKeyUse(name, 'private', usagesSet);
+ } else {
+ verifyAcceptableEcKeyUse(name, 'public', usagesSet);
+ }
+
+ if (usagesSet.size > 0 && keyData.use !== undefined) {
+ if (algorithm.name === 'ECDSA' && keyData.use !== 'sig')
+ throw lazyDOMException('Invalid use type', 'DataError');
+ if (algorithm.name === 'ECDH' && keyData.use !== 'enc')
+ throw lazyDOMException('Invalid use type', 'DataError');
+ }
+
+ validateKeyOps(keyData.key_ops, usagesSet);
+
+ if (keyData.ext !== undefined &&
+ keyData.ext === false &&
+ extractable === true) {
+ throw lazyDOMException('JWK is not extractable', 'DataError');
+ }
+
+ if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
+ if (typeof keyData.alg !== 'string')
+ throw lazyDOMException('Invalid alg', 'DataError');
+ switch (keyData.alg) {
+ case 'ES256': curve = 'P-256'; break;
+ case 'ES384': curve = 'P-384'; break;
+ case 'ES512': curve = 'P-521'; break;
+ }
+ if (curve !== namedCurve)
+ throw lazyDOMException('Named curve mismatch', 'DataError');
+ }
+
+ const handle = new KeyObjectHandle();
+ const type = handle.initJwk(keyData, namedCurve);
+ if (type === undefined)
+ throw lazyDOMException('Invalid JWK keyData', 'DataError');
+ keyObject = type === kKeyTypePrivate ?
+ new PrivateKeyObject(handle) :
+ new PublicKeyObject(handle);
}
- if (curve !== namedCurve)
- throw lazyDOMException('Named curve mismatch', 'DataError');
}
-
- const handle = new KeyObjectHandle();
- const type = handle.initJwk(keyData, namedCurve);
- if (type === undefined)
- throw lazyDOMException('Invalid JWK keyData', 'DataError');
-
- keyObject = type === kKeyTypePrivate ?
- new PrivateKeyObject(handle) :
- new PublicKeyObject(handle);
-
break;
}
case 'raw': {
- verifyAcceptableEcKeyUse(name, 'public', usagesSet);
- keyObject = createECPublicKeyRaw(namedCurve, keyData);
+ switch (namedCurve) {
+ case 'NODE-X25519':
+ // Fall through
+ case 'NODE-X448':
+ checkNamedCurve = false;
+ verifyAcceptableEcKeyUse(
+ 'ECDH',
+ algorithm.public === true ? 'public' : 'private',
+ usagesSet);
+ keyObject = createECRawKey(namedCurve, keyData, algorithm.public);
+ break;
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ checkNamedCurve = false;
+ verifyAcceptableEcKeyUse(
+ 'ECDSA',
+ algorithm.public === true ? 'public' : 'private',
+ usagesSet);
+ keyObject = createECRawKey(namedCurve, keyData, algorithm.public);
+ break;
+ default:
+ verifyAcceptableEcKeyUse(name, 'public', usagesSet);
+ keyObject = createECPublicKeyRaw(namedCurve, keyData);
+ }
if (keyObject === undefined)
throw lazyDOMException('Unable to import EC key', 'OperationError');
break;
}
}
- const {
- namedCurve: checkNamedCurve
- } = keyObject[kHandle].keyDetail({});
- if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
- throw lazyDOMException('Named curve mismatch', 'DataError');
+ if (checkNamedCurve) {
+ const {
+ namedCurve: checkNamedCurve
+ } = keyObject[kHandle].keyDetail({});
+ if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
+ throw lazyDOMException('Named curve mismatch', 'DataError');
+ }
return new InternalCryptoKey(
keyObject,
@@ -275,22 +383,33 @@ async function ecImportKey(
extractable);
}
-function ecdsaSignVerify(key, data, { hash }, signature) {
- if (hash === undefined)
- throw new ERR_MISSING_OPTION('algorithm.hash');
-
+function ecdsaSignVerify(key, data, { name, hash }, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
+ let hashname;
+ switch (name) {
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ if (hash !== undefined)
+ throw new lazyDOMException(`Hash is not permitted for ${name}`);
+ break;
+ default:
+ if (hash === undefined)
+ throw new ERR_MISSING_OPTION('algorithm.hash');
+ hashname = normalizeHashName(hash.name);
+ }
+
return jobPromise(new SignJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
- normalizeHashName(hash.name),
+ hashname,
undefined, // Salt length, not used with ECDSA
undefined, // PSS Padding, not used with ECDSA
signature));
diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js
index 9d2fee2b2dd..d08249559a8 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -148,6 +148,10 @@ const kNamedCurveAliases = {
'P-256': 'prime256v1',
'P-384': 'secp384r1',
'P-521': 'secp521r1',
+ 'NODE-ED25519': 'ed25519',
+ 'NODE-ED448': 'ed448',
+ 'NODE-X25519': 'x25519',
+ 'NODE-X448': 'x448',
};
const kAesKeyLengths = [128, 192, 256];
@@ -175,7 +179,9 @@ const kAlgorithms = [
// should be prefixed with 'node-'
'node-dsa',
'node-dh',
- 'node-scrypt'
+ 'node-scrypt',
+ 'node-ed25519',
+ 'node-ed448',
];
// These are the only export and import formats we currently
diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index 7bd487972eb..2e14fcc90c3 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -78,6 +78,10 @@ async function generateKey(
case 'RSA-OAEP':
return lazyRequire('internal/crypto/rsa')
.rsaKeyGenerate(algorithm, extractable, keyUsages);
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ // Fall through
case 'ECDSA':
// Fall through
case 'ECDH':
@@ -203,6 +207,10 @@ async function exportKeySpki(key) {
.rsaExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ // Fall through
case 'ECDSA':
// Fall through
case 'ECDH':
@@ -242,6 +250,10 @@ async function exportKeyPkcs8(key) {
.rsaExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ // Fall through
case 'ECDSA':
// Fall through
case 'ECDH':
@@ -271,6 +283,11 @@ async function exportKeyPkcs8(key) {
async function exportKeyRaw(key) {
switch (key.algorithm.name) {
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ return lazyRequire('internal/crypto/ec')
+ .ecExportKey(key, kWebCryptoKeyFormatRaw);
case 'ECDSA':
// Fall through
case 'ECDH':
@@ -320,7 +337,7 @@ async function exportKeyJWK(key) {
case 'ECDSA':
// Fall through
case 'ECDH':
- jwk.crv = key.algorithm.namedCurve;
+ jwk.crv ||= key.algorithm.namedCurve;
return jwk;
case 'AES-CTR':
// Fall through
@@ -342,6 +359,10 @@ async function exportKeyJWK(key) {
key.algorithm.hash.name,
normalizeHashName.kContextJwkDsa);
return jwk;
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ return jwk;
default:
// Fall through
}
@@ -445,6 +466,10 @@ async function importKey(
case 'RSA-OAEP':
return lazyRequire('internal/crypto/rsa')
.rsaImportKey(format, keyData, algorithm, extractable, keyUsages);
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ // Fall through
case 'ECDSA':
// Fall through
case 'ECDH':
@@ -564,6 +589,10 @@ function signVerify(algorithm, key, data, signature) {
case 'RSASSA-PKCS1-V1_5':
return lazyRequire('internal/crypto/rsa')
.rsaSignVerify(key, data, algorithm, signature);
+ case 'NODE-ED25519':
+ // Fall through
+ case 'NODE-ED448':
+ // Fall through
case 'ECDSA':
return lazyRequire('internal/crypto/ec')
.ecdsaSignVerify(key, data, algorithm, signature);
diff --git a/src/crypto/crypto_ecdh.cc b/src/crypto/crypto_ecdh.cc
index efeb08b908e..6d3ee26fc70 100644
--- a/src/crypto/crypto_ecdh.cc
+++ b/src/crypto/crypto_ecdh.cc
@@ -7,6 +7,7 @@
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
+#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
@@ -30,14 +31,25 @@ using v8::Uint32;
using v8::Value;
namespace crypto {
-namespace {
+
int GetCurveFromName(const char* name) {
int nid = EC_curve_nist2nid(name);
if (nid == NID_undef)
nid = OBJ_sn2nid(name);
+ // If there is still no match, check manually for known curves
+ if (nid == NID_undef) {
+ if (strcmp(name, "NODE-ED25519") == 0) {
+ nid = EVP_PKEY_ED25519;
+ } else if (strcmp(name, "NODE-ED448") == 0) {
+ nid = EVP_PKEY_ED448;
+ } else if (strcmp(name, "NODE-X25519") == 0) {
+ nid = EVP_PKEY_X25519;
+ } else if (strcmp(name, "NODE-X448") == 0) {
+ nid = EVP_PKEY_X448;
+ }
+ }
return nid;
}
-} // namespace
void ECDH::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
@@ -392,6 +404,11 @@ void ECDH::ConvertKey(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(buf);
}
+void ECDHBitsConfig::MemoryInfo(MemoryTracker* tracker) const {
+ tracker->TrackField("public", public_);
+ tracker->TrackField("private", private_);
+}
+
Maybe<bool> ECDHBitsTraits::EncodeOutput(
Environment* env,
const ECDHBitsConfig& params,
@@ -416,6 +433,7 @@ Maybe<bool> ECDHBitsTraits::AdditionalConfig(
KeyObjectHandle* public_key;
Utf8Value name(env->isolate(), args[offset]);
+
ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset + 1], Nothing<bool>());
ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 2], Nothing<bool>());
@@ -425,23 +443,9 @@ Maybe<bool> ECDHBitsTraits::AdditionalConfig(
return Nothing<bool>();
}
- params->private_key = ECKeyPointer(
- EC_KEY_dup(
- EVP_PKEY_get1_EC_KEY(private_key->Data()->GetAsymmetricKey().get())));
- if (!params->private_key) {
- THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
- return Nothing<bool>();
- }
-
- params->public_key = ECKeyPointer(
- EC_KEY_dup(
- EVP_PKEY_get1_EC_KEY(public_key->Data()->GetAsymmetricKey().get())));
- if (!params->public_key) {
- THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
- return Nothing<bool>();
- }
-
- params->group = EC_KEY_get0_group(params->private_key.get());
+ params->id_ = GetCurveFromName(*name);
+ params->private_ = private_key->Data();
+ params->public_ = public_key->Data();
return Just(true);
}
@@ -450,44 +454,102 @@ bool ECDHBitsTraits::DeriveBits(
Environment* env,
const ECDHBitsConfig& params,
ByteSource* out) {
- if (params.group == nullptr)
- return false;
- CHECK_EQ(EC_KEY_check_key(params.private_key.get()), 1);
- CHECK_EQ(EC_KEY_check_key(params.public_key.get()), 1);
- const EC_POINT* pub = EC_KEY_get0_public_key(params.public_key.get());
- int field_size = EC_GROUP_get_degree(params.group);
- size_t len = (field_size + 7) / 8;
- char* data = MallocOpenSSL<char>(len);
- ByteSource buf = ByteSource::Allocated(data, len);
- if (ECDH_compute_key(
- data,
- len,
- pub,
- params.private_key.get(),
- nullptr) <= 0) {
- return false;
+
+ char* data = nullptr;
+ size_t len = 0;
+
+ switch (params.id_) {
+ case EVP_PKEY_X25519:
+ // Fall through
+ case EVP_PKEY_X448: {
+ EVPKeyCtxPointer ctx(
+ EVP_PKEY_CTX_new(
+ params.private_->GetAsymmetricKey().get(),
+ nullptr));
+ if (EVP_PKEY_derive_init(ctx.get()) <= 0 ||
+ EVP_PKEY_derive_set_peer(
+ ctx.get(),
+ params.public_->GetAsymmetricKey().get()) <= 0 ||
+ EVP_PKEY_derive(ctx.get(), nullptr, &len) <= 0) {
+ return false;
+ }
+
+ data = MallocOpenSSL<char>(len);
+
+ if (EVP_PKEY_derive(
+ ctx.get(),
+ reinterpret_cast<unsigned char*>(data),
+ &len) <= 0) {
+ return false;
+ }
+
+ break;
+ }
+ default: {
+ const EC_KEY* private_key =
+ EVP_PKEY_get0_EC_KEY(params.private_->GetAsymmetricKey().get());
+ const EC_KEY* public_key =
+ EVP_PKEY_get0_EC_KEY(params.public_->GetAsymmetricKey().get());
+
+ const EC_GROUP* group = EC_KEY_get0_group(private_key);
+ if (group == nullptr)
+ return false;
+
+ CHECK_EQ(EC_KEY_check_key(private_key), 1);
+ CHECK_EQ(EC_KEY_check_key(public_key), 1);
+ const EC_POINT* pub = EC_KEY_get0_public_key(public_key);
+ int field_size = EC_GROUP_get_degree(group);
+ len = (field_size + 7) / 8;
+ data = MallocOpenSSL<char>(len);
+ CHECK_NOT_NULL(data);
+ CHECK_NOT_NULL(pub);
+ CHECK_NOT_NULL(private_key);
+ if (ECDH_compute_key(
+ data,
+ len,
+ pub,
+ private_key,
+ nullptr) <= 0) {
+ return false;
+ }
+ }
}
+ ByteSource buf = ByteSource::Allocated(data, len);
*out = std::move(buf);
return true;
}
EVPKeyCtxPointer EcKeyGenTraits::Setup(EcKeyPairGenConfig* params) {
- EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr));
- EVP_PKEY* raw_params = nullptr;
- if (!param_ctx ||
- EVP_PKEY_paramgen_init(param_ctx.get()) <= 0 ||
- EVP_PKEY_CTX_set_ec_paramgen_curve_nid(
- param_ctx.get(), params->params.curve_nid) <= 0 ||
- EVP_PKEY_CTX_set_ec_param_enc(
- param_ctx.get(), params->params.param_encoding) <= 0 ||
- EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0) {
- return EVPKeyCtxPointer();
- }
- EVPKeyPointer key_params(raw_params);
- EVPKeyCtxPointer key_ctx(EVP_PKEY_CTX_new(key_params.get(), nullptr));
-
- if (!key_ctx || EVP_PKEY_keygen_init(key_ctx.get()) <= 0)
- return EVPKeyCtxPointer();
+ EVPKeyCtxPointer key_ctx;
+ switch (params->params.curve_nid) {
+ case EVP_PKEY_ED25519:
+ // Fall through
+ case EVP_PKEY_ED448:
+ // Fall through
+ case EVP_PKEY_X25519:
+ // Fall through
+ case EVP_PKEY_X448:
+ key_ctx.reset(EVP_PKEY_CTX_new_id(params->params.curve_nid, nullptr));
+ break;
+ default: {
+ EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr));
+ EVP_PKEY* raw_params = nullptr;
+ if (!param_ctx ||
+ EVP_PKEY_paramgen_init(param_ctx.get()) <= 0 ||
+ EVP_PKEY_CTX_set_ec_paramgen_curve_nid(
+ param_ctx.get(), params->params.curve_nid) <= 0 ||
+ EVP_PKEY_CTX_set_ec_param_enc(
+ param_ctx.get(), params->params.param_encoding) <= 0 ||
+ EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0) {
+ return EVPKeyCtxPointer();
+ }
+ EVPKeyPointer key_params(raw_params);
+ key_ctx.reset(EVP_PKEY_CTX_new(key_params.get(), nullptr));
+ }
+ }
+
+ if (key_ctx && EVP_PKEY_keygen_init(key_ctx.get()) <= 0)
+ key_ctx.reset();
return key_ctx;
}
@@ -538,23 +600,49 @@ WebCryptoKeyExportStatus EC_Raw_Export(
CHECK(key_data->GetAsymmetricKey());
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key_data->GetAsymmetricKey().get());
- CHECK_NOT_NULL(ec_key);
- const EC_GROUP* group = EC_KEY_get0_group(ec_key);
- const EC_POINT* point = EC_KEY_get0_public_key(ec_key);
- point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
-
- // Get the allocated data size...
- size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
- if (len == 0)
- return WebCryptoKeyExportStatus::FAILED;
-
- unsigned char* data = MallocOpenSSL<unsigned char>(len);
- size_t check_len = EC_POINT_point2oct(group, point, form, data, len, nullptr);
- if (check_len == 0)
- return WebCryptoKeyExportStatus::FAILED;
-
- CHECK_EQ(len, check_len);
+ unsigned char* data;
+ size_t len = 0;
+
+ if (ec_key == nullptr) {
+ typedef int (*export_fn)(const EVP_PKEY*, unsigned char*, size_t* len);
+ export_fn fn = nullptr;
+ switch (key_data->GetKeyType()) {
+ case kKeyTypePrivate:
+ fn = EVP_PKEY_get_raw_private_key;
+ break;
+ case kKeyTypePublic:
+ fn = EVP_PKEY_get_raw_public_key;
+ break;
+ case kKeyTypeSecret:
+ UNREACHABLE();
+ }
+ CHECK_NOT_NULL(fn);
+ // Get the size of the raw key data
+ if (fn(key_data->GetAsymmetricKey().get(), nullptr, &len) == 0)
+ return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
+ data = MallocOpenSSL<unsigned char>(len);
+ if (fn(key_data->GetAsymmetricKey().get(), data, &len) == 0)
+ return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
+ } else {
+ if (key_data->GetKeyType() != kKeyTypePublic)
+ return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
+ const EC_GROUP* group = EC_KEY_get0_group(ec_key);
+ const EC_POINT* point = EC_KEY_get0_public_key(ec_key);
+ point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
+
+ // Get the allocated data size...
+ len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
+ if (len == 0)
+ return WebCryptoKeyExportStatus::FAILED;
+ data = MallocOpenSSL<unsigned char>(len);
+ size_t check_len =
+ EC_POINT_point2oct(group, point, form, data, len, nullptr);
+ if (check_len == 0)
+ return WebCryptoKeyExportStatus::FAILED;
+
+ CHECK_EQ(len, check_len);
+ }
*out = ByteSource::Allocated(reinterpret_cast<char*>(data), len);
@@ -578,8 +666,6 @@ WebCryptoKeyExportStatus ECKeyExportTraits::DoExport(
switch (format) {
case kWebCryptoKeyFormatRaw:
- if (key_data->GetKeyType() != kKeyTypePublic)
- return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return EC_Raw_Export(key_data.get(), params, out);
case kWebCryptoKeyFormatPKCS8:
if (key_data->GetKeyType() != kKeyTypePrivate)
@@ -651,6 +737,90 @@ Maybe<bool> ExportJWKEcKey(
return Just(true);
}
+Maybe<bool> ExportJWKEdKey(
+ Environment* env,
+ std::shared_ptr<KeyObjectData> key,
+ Local<Object> target) {
+ ManagedEVPPKey pkey = key->GetAsymmetricKey();
+
+ const char* curve = nullptr;
+ switch (EVP_PKEY_id(pkey.get())) {
+ case EVP_PKEY_ED25519:
+ curve = "Ed25519";
+ break;
+ case EVP_PKEY_ED448:
+ curve = "Ed448";
+ break;
+ case EVP_PKEY_X25519:
+ curve = "X25519";
+ break;
+ case EVP_PKEY_X448:
+ curve = "X448";
+ break;
+ default:
+ UNREACHABLE();
+ }
+ if (target->Set(
+ env->context(),
+ env->jwk_crv_string(),
+ OneByteString(env->isolate(), curve)).IsNothing()) {
+ return Nothing<bool>();
+ }
+
+ size_t len = 0;
+ Local<Value> encoded;
+ Local<Value> error;
+
+ if (!EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &len))
+ return Nothing<bool>();
+
+ unsigned char* data = MallocOpenSSL<unsigned char>(len);
+ ByteSource out = ByteSource::Allocated(reinterpret_cast<char*>(data), len);
+
+ if (key->GetKeyType() == kKeyTypePrivate) {
+ if (!EVP_PKEY_get_raw_private_key(pkey.get(), data, &len) ||
+ !StringBytes::Encode(
+ env->isolate(),
+ reinterpret_cast<const char*>(data),
+ len,
+ BASE64URL,
+ &error).ToLocal(&encoded) ||
+ !target->Set(
+ env->context(),
+ env->jwk_d_string(),
+ encoded).IsJust()) {
+ if (!error.IsEmpty())
+ env->isolate()->ThrowException(error);
+ return Nothing<bool>();
+ }
+ }
+
+ if (!EVP_PKEY_get_raw_public_key(pkey.get(), data, &len) ||
+ !StringBytes::Encode(
+ env->isolate(),
+ reinterpret_cast<const char*>(data),
+ len,
+ BASE64URL,
+ &error).ToLocal(&encoded) ||
+ !target->Set(
+ env->context(),
+ env->jwk_x_string(),
+ encoded).IsJust()) {
+ if (!error.IsEmpty())
+ env->isolate()->ThrowException(error);
+ return Nothing<bool>();
+ }
+
+ if (target->Set(
+ env->context(),
+ env->jwk_kty_string(),
+ env->jwk_okp_string()).IsNothing()) {
+ return Nothing<bool>();
+ }
+
+ return Just(true);
+}
+
std::shared_ptr<KeyObjectData> ImportJWKEcKey(
Environment* env,
Local<Object> jwk,
diff --git a/src/crypto/crypto_ecdh.h b/src/crypto/crypto_ecdh.h
index c83818d16be..b2ae9e5b2be 100644
--- a/src/crypto/crypto_ecdh.h
+++ b/src/crypto/crypto_ecdh.h
@@ -16,6 +16,8 @@
namespace node {
namespace crypto {
+int GetCurveFromName(const char* name);
+
class ECDH final : public BaseObject {
public:
~ECDH() override;
@@ -52,11 +54,11 @@ class ECDH final : public BaseObject {
};
struct ECDHBitsConfig final : public MemoryRetainer {
- ECKeyPointer private_key;
- ECKeyPointer public_key;
- const EC_GROUP* group = nullptr;
+ int id_;
+ std::shared_ptr<KeyObjectData> private_;
+ std::shared_ptr<KeyObjectData> public_;
- SET_NO_MEMORY_INFO();
+ void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ECDHBitsConfig);
SET_SELF_SIZE(ECDHBitsConfig);
};
@@ -144,6 +146,11 @@ v8::Maybe<bool> ExportJWKEcKey(
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
+v8::Maybe<bool> ExportJWKEdKey(
+ Environment* env,
+ std::shared_ptr<KeyObjectData> key,
+ v8::Local<v8::Object> target);
+
std::shared_ptr<KeyObjectData> ImportJWKEcKey(
Environment* env,
v8::Local<v8::Object> jwk,
diff --git a/src/crypto/crypto_keygen.h b/src/crypto/crypto_keygen.h
index c4197c6eaed..58e3b8211d6 100644
--- a/src/crypto/crypto_keygen.h
+++ b/src/crypto/crypto_keygen.h
@@ -161,7 +161,8 @@ struct KeyPairGenTraits final {
Environment* env,
AdditionalParameters* params) {
EVPKeyCtxPointer ctx = KeyPairAlgorithmTraits::Setup(params);
- if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0)
+
+ if (!ctx)
return KeyGenJobStatus::FAILED;
// Generate the key
diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc
index f80a39ce5de..4457141f461 100644
--- a/src/crypto/crypto_keys.cc
+++ b/src/crypto/crypto_keys.cc
@@ -492,6 +492,13 @@ Maybe<bool> ExportJWKAsymmetricKey(
case EVP_PKEY_RSA_PSS: return ExportJWKRsaKey(env, key, target);
case EVP_PKEY_DSA: return ExportJWKDsaKey(env, key, target);
case EVP_PKEY_EC: return ExportJWKEcKey(env, key, target);
+ case EVP_PKEY_ED25519:
+ // Fall through
+ case EVP_PKEY_ED448:
+ // Fall through
+ case EVP_PKEY_X25519:
+ // Fall through
+ case EVP_PKEY_X448: return ExportJWKEdKey(env, key, target);
}
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Just(false);
@@ -873,6 +880,7 @@ v8::Local<v8::Function> KeyObjectHandle::Initialize(Environment* env) {
env->SetProtoMethod(t, "export", Export);
env->SetProtoMethod(t, "exportJwk", ExportJWK);
env->SetProtoMethod(t, "initECRaw", InitECRaw);
+ env->SetProtoMethod(t, "initEDRaw", InitEDRaw);
env->SetProtoMethod(t, "initJwk", InitJWK);
env->SetProtoMethod(t, "keyDetail", GetKeyDetail);
@@ -1032,6 +1040,48 @@ void KeyObjectHandle::InitECRaw(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(true);
}
+void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ KeyObjectHandle* key;
+ ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
+
+ CHECK(args[0]->IsString());
+ Utf8Value name(env->isolate(), args[0]);
+
+ ArrayBufferOrViewContents<unsigned char> key_data(args[1]);
+ KeyType type = static_cast<KeyType>(args[2].As<Int32>()->Value());
+
+ MarkPopErrorOnReturn mark_pop_error_on_return;
+
+ typedef EVP_PKEY* (*new_key_fn)(int, ENGINE*, const unsigned char*, size_t);
+ new_key_fn fn = type == kKeyTypePrivate
+ ? EVP_PKEY_new_raw_private_key
+ : EVP_PKEY_new_raw_public_key;
+
+ int id = GetCurveFromName(*name);
+
+ switch (id) {
+ case EVP_PKEY_X25519:
+ case EVP_PKEY_X448:
+ case EVP_PKEY_ED25519:
+ case EVP_PKEY_ED448: {
+ EVPKeyPointer pkey(fn(id, nullptr, key_data.data(), key_data.size()));
+ if (!pkey)
+ return args.GetReturnValue().Set(false);
+ key->data_ =
+ KeyObjectData::CreateAsymmetric(
+ type,
+ ManagedEVPPKey(std::move(pkey)));
+ CHECK(key->data_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ args.GetReturnValue().Set(true);
+}
+
void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h
index 8938a203eb1..64bf45a1dba 100644
--- a/src/crypto/crypto_keys.h
+++ b/src/crypto/crypto_keys.h
@@ -185,6 +185,7 @@ class KeyObjectHandle : public BaseObject {
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitECRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetKeyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc
index a5a95878a9e..455d1bfdd6c 100644
--- a/src/crypto/crypto_sig.cc
+++ b/src/crypto/crypto_sig.cc
@@ -223,6 +223,16 @@ void CheckThrow(Environment* env, SignBase::Error error) {
return;
}
}
+
+bool IsOneShot(const ManagedEVPPKey& key) {
+ switch (EVP_PKEY_id(key.get())) {
+ case EVP_PKEY_ED25519:
+ case EVP_PKEY_ED448:
+ return true;
+ default:
+ return false;
+ }
+}
} // namespace
SignBase::Error SignBase::Init(const char* sign_type) {
@@ -807,26 +817,47 @@ bool SignTraits::DeriveBits(
switch (params.mode) {
case SignConfiguration::kSign: {
size_t len;
- if (!EVP_DigestSignUpdate(
- context.get(),
- params.data.data<unsigned char>(),
- params.data.size()) ||
- !EVP_DigestSignFinal(context.get(), nullptr, &len)) {
- return false;
- }
- char* data = MallocOpenSSL<char>(len);
- ByteSource buf = ByteSource::Allocated(data, len);
- unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
- if (!EVP_DigestSignFinal(context.get(), ptr, &len))
- return false;
-
- // If this is an EC key (assuming ECDSA) we have to
- // convert the signature in to the proper format.
- if (EVP_PKEY_id(params.key->GetAsymmetricKey().get()) == EVP_PKEY_EC) {
- *out = ConvertToWebCryptoSignature(params.key->GetAsymmetricKey(), buf);
- } else {
- buf.Resize(len);
+ unsigned char* data = nullptr;
+ if (IsOneShot(params.key->GetAsymmetricKey())) {
+ EVP_DigestSign(
+ context.get(),
+ nullptr,
+ &len,
+ params.data.data<unsigned char>(),
+ params.data.size());
+ data = MallocOpenSSL<unsigned char>(len);
+ EVP_DigestSign(
+ context.get(),
+ data,
+ &len,
+ params.data.data<unsigned char>(),
+ params.data.size());
+ ByteSource buf =
+ ByteSource::Allocated(reinterpret_cast<char*>(data), len);
*out = std::move(buf);
+ } else {
+ if (!EVP_DigestSignUpdate(
+ context.get(),
+ params.data.data<unsigned char>(),
+ params.data.size()) ||
+ !EVP_DigestSignFinal(context.get(), nullptr, &len)) {
+ return false;
+ }
+ data = MallocOpenSSL<unsigned char>(len);
+ ByteSource buf =
+ ByteSource::Allocated(reinterpret_cast<char*>(data), len);
+ if (!EVP_DigestSignFinal(context.get(), data, &len))
+ return false;
+
+ // If this is an EC key (assuming ECDSA) we have to
+ // convert the signature in to the proper format.
+ if (EVP_PKEY_id(params.key->GetAsymmetricKey().get()) == EVP_PKEY_EC) {
+ *out = ConvertToWebCryptoSignature(
+ params.key->GetAsymmetricKey(), buf);
+ } else {
+ buf.Resize(len);
+ *out = std::move(buf);
+ }
}
break;
}
@@ -834,17 +865,12 @@ bool SignTraits::DeriveBits(
char* data = MallocOpenSSL<char>(1);
data[0] = 0;
*out = ByteSource::Allocated(data, 1);
- if (!EVP_DigestVerifyUpdate(
- context.get(),
- params.data.data<unsigned char>(),
- params.data.size())) {
- return false;
- }
-
- if (EVP_DigestVerifyFinal(
+ if (EVP_DigestVerify(
context.get(),
params.signature.data<unsigned char>(),
- params.signature.size()) == 1) {
+ params.signature.size(),
+ params.data.data<unsigned char>(),
+ params.data.size()) == 1) {
data[0] = 1;
}
}
diff --git a/src/env.h b/src/env.h
index 6be3cdb22fc..1268cb071ee 100644
--- a/src/env.h
+++ b/src/env.h
@@ -280,6 +280,7 @@ constexpr size_t kFsStatsBufferLength =
V(isclosing_string, "isClosing") \
V(issuer_string, "issuer") \
V(issuercert_string, "issuerCertificate") \
+ V(jwk_crv_string, "crv") \
V(jwk_d_string, "d") \
V(jwk_dp_string, "dp") \
V(jwk_dq_string, "dq") \
@@ -294,6 +295,7 @@ constexpr size_t kFsStatsBufferLength =
V(jwk_kty_string, "kty") \
V(jwk_n_string, "n") \
V(jwk_oct_string, "oct") \
+ V(jwk_okp_string, "OKP") \
V(jwk_rsa_string, "RSA") \
V(jwk_x_string, "x") \
V(jwk_y_string, "y") \
diff --git a/test/parallel/test-webcrypto-ed25519-ed448.js b/test/parallel/test-webcrypto-ed25519-ed448.js
new file mode 100644
index 00000000000..b06de24c78f
--- /dev/null
+++ b/test/parallel/test-webcrypto-ed25519-ed448.js
@@ -0,0 +1,367 @@
+'use strict';
+
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const { subtle } = require('crypto').webcrypto;
+
+async function generateKey(namedCurve) {
+ return subtle.generateKey(
+ {
+ name: namedCurve,
+ namedCurve
+ },
+ true,
+ ['sign', 'verify']);
+}
+
+async function test1(namedCurve) {
+ const {
+ publicKey,
+ privateKey,
+ } = await generateKey(namedCurve);
+
+ const data = Buffer.from('hello world');
+
+ assert(publicKey);
+ assert(privateKey);
+
+ const sig = await subtle.sign(
+ { name: namedCurve },
+ privateKey,
+ data
+ );
+
+ assert(sig);
+
+ assert(await subtle.verify(
+ { name: namedCurve },
+ publicKey,
+ sig,
+ data
+ ));
+}
+
+Promise.all([
+ test1('NODE-ED25519'),
+ test1('NODE-ED448')
+]).then(common.mustCall());
+
+assert.rejects(
+ subtle.importKey(
+ 'raw',
+ Buffer.alloc(10),
+ {
+ name: 'NODE-ED25519',
+ namedCurve: 'NODE-ED25519'
+ },
+ false,
+ ['sign']),
+ {
+ message: /NODE-ED25519 raw keys must be exactly 32-bytes/
+ });
+
+assert.rejects(
+ subtle.importKey(
+ 'raw',
+ Buffer.alloc(10),
+ {
+ name: 'NODE-ED448',
+ namedCurve: 'NODE-ED448'
+ },
+ false,
+ ['sign']),
+ {
+ message: /NODE-ED448 raw keys must be exactly 57-bytes/
+ });
+
+const testVectors = {
+ 'NODE-ED25519': [
+ {
+ privateKey:
+ Buffer.from(
+ '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a',
+ 'hex'),
+ message: Buffer.alloc(0),
+ sig:
+ Buffer.from(
+ 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155' +
+ '5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b',
+ 'hex'),
+ crv: 'Ed25519',
+ },
+ {
+ privateKey:
+ Buffer.from(
+ '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c',
+ 'hex'),
+ message: Buffer.from('72', 'hex'),
+ sig:
+ Buffer.from(
+ '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da' +
+ '085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00',
+ 'hex'),
+ crv: 'Ed25519',
+ },
+ {
+ privateKey:
+ Buffer.from(
+ 'c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ 'fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025',
+ 'hex'),
+ message: Buffer.from('af82', 'hex'),
+ sig:
+ Buffer.from(
+ '6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac' +
+ '18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a',
+ 'hex'),
+ crv: 'Ed25519',
+ },
+ {
+ privateKey:
+ Buffer.from(
+ 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ '278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e',
+ 'hex'),
+ message: Buffer.from(
+ '08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98' +
+ 'fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d8' +
+ '79de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d' +
+ '658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc' +
+ '1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4fe' +
+ 'ba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e' +
+ '06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbef' +
+ 'efd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7' +
+ 'aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed1' +
+ '85ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2' +
+ 'd17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24' +
+ '554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f270' +
+ '88d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc' +
+ '2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b07' +
+ '07e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128ba' +
+ 'b27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51a' +
+ 'ddd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429e' +
+ 'c96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb7' +
+ '51fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c' +
+ '42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8' +
+ 'ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34df' +
+ 'f7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08' +
+ 'd78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649' +
+ 'de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e4' +
+ '88acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a3' +
+ '2ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e' +
+ '6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5f' +
+ 'b93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b5' +
+ '0d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1' +
+ '369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380d' +
+ 'b2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c' +
+ '0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0',
+ 'hex'),
+ sig: Buffer.from(
+ '0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350' +
+ 'aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03',
+ 'hex'),
+ crv: 'Ed25519',
+ }
+ ],
+ 'NODE-ED448': [
+ {
+ privateKey:
+ Buffer.from(
+ '6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3' +
+ '528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b', 'hex'),
+ publicKey:
+ Buffer.from(
+ '5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778' +
+ 'edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180', 'hex'),
+ message: Buffer.alloc(0),
+ sig:
+ Buffer.from(
+ '533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f' +
+ '2b233f034f628281f2fd7a22ddd47d7828c59bd0a21bfd3980ff0d2028d4b18a' +
+ '9df63e006c5d1c2d345b925d8dc00b4104852db99ac5c7cdda8530a113a0f4db' +
+ 'b61149f05a7363268c71d95808ff2e652600', 'hex'),
+ crv: 'Ed448',
+ },
+ {
+ privateKey:
+ Buffer.from(
+ 'c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463a' +
+ 'fbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e', 'hex'),
+ publicKey:
+ Buffer.from(
+ '43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c086' +
+ '6aea01eb00742802b8438ea4cb82169c235160627b4c3a9480', 'hex'),
+ message: Buffer.from('03', 'hex'),
+ sig:
+ Buffer.from(
+ '26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f435' +
+ '2541b143c4b981b7e18f62de8ccdf633fc1bf037ab7cd779805e0dbcc0aae1cb' +
+ 'cee1afb2e027df36bc04dcecbf154336c19f0af7e0a6472905e799f1953d2a0f' +
+ 'f3348ab21aa4adafd1d234441cf807c03a00', 'hex'),
+ crv: 'Ed448',
+ },
+ {
+ privateKey:
+ Buffer.from(
+ 'cd23d24f714274e744343237b93290f511f6425f98e64459ff203e8985083ffd' +
+ 'f60500553abc0e05cd02184bdb89c4ccd67e187951267eb328', 'hex'),
+ publicKey:
+ Buffer.from(
+ 'dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e3' +
+ '65fce161d71ce7131a543ea4cb5f7e9f1d8b00696447001400', 'hex'),
+ message: Buffer.from('0c3e544074ec63b0265e0c', 'hex'),
+ sig:
+ Buffer.from(
+ '1f0a8888ce25e8d458a21130879b840a9089d999aaba039eaf3e3afa090a09d3' +
+ '89dba82c4ff2ae8ac5cdfb7c55e94d5d961a29fe0109941e00b8dbdeea6d3b05' +
+ '1068df7254c0cdc129cbe62db2dc957dbb47b51fd3f213fb8698f064774250a5' +
+ '028961c9bf8ffd973fe5d5c206492b140e00', 'hex'),
+ crv: 'Ed448',
+ }
+ ]
+};
+
+async function test2(namedCurve) {
+ const vectors = testVectors[namedCurve];
+ await Promise.all(vectors.map(async (vector) => {
+ const [
+ privateKey,
+ publicKey,
+ ] = await Promise.all([
+ subtle.importKey(
+ 'raw',
+ vector.privateKey,
+ {
+ name: namedCurve,
+ namedCurve
+ },
+ true, ['sign']),
+ subtle.importKey(
+ 'raw',
+ vector.publicKey,
+ {
+ name: namedCurve,
+ namedCurve,
+ public: true
+ },
+ true, ['verify'])
+ ]);
+
+ const [
+ rawKey1,
+ rawKey2
+ ] = await Promise.all([
+ subtle.exportKey('raw', privateKey),
+ subtle.exportKey('raw', publicKey)
+ ]);
+ assert.deepStrictEqual(Buffer.from(rawKey1), vector.privateKey);
+ assert.deepStrictEqual(Buffer.from(rawKey2), vector.publicKey);
+
+ const sig = await subtle.sign(
+ { name: namedCurve },
+ privateKey,
+ vector.message
+ );
+
+ assert(sig);
+
+ assert(await subtle.verify(
+ { name: namedCurve },
+ publicKey,
+ vector.sig,
+ vector.message
+ ));
+
+ const [
+ publicKeyJwk,
+ privateKeyJwk
+ ] = await Promise.all([
+ subtle.exportKey('jwk', publicKey),
+ subtle.exportKey('jwk', privateKey)
+ ]);
+ assert.strictEqual(publicKeyJwk.kty, 'OKP');
+ assert.strictEqual(privateKeyJwk.kty, 'OKP');
+ assert.strictEqual(publicKeyJwk.crv, vector.crv);
+ assert.strictEqual(privateKeyJwk.crv, vector.crv);
+ assert.deepStrictEqual(
+ Buffer.from(publicKeyJwk.x, 'base64'),
+ vector.publicKey);
+ assert.deepStrictEqual(
+ Buffer.from(privateKeyJwk.x, 'base64'),
+ vector.publicKey);
+ assert.deepStrictEqual(
+ Buffer.from(privateKeyJwk.d, 'base64'),
+ vector.privateKey);
+ }));
+}
+
+Promise.all([
+ test2('NODE-ED25519'),
+ test2('NODE-ED448')
+]).then(common.mustCall());
+
+assert.rejects(
+ subtle.generateKey(
+ {
+ name: 'ECDSA',
+ namedCurve: 'NODE-X25519'
+ },
+ true,
+ ['sign', 'verify']),
+ {
+ message: /Unsupported named curves for ECDSA/
+ });
+
+assert.rejects(
+ subtle.generateKey(
+ {
+ name: 'ECDSA',
+ namedCurve: 'NODE-X448'
+ },
+ true,
+ ['sign', 'verify']),
+ {
+ message: /Unsupported named curves for ECDSA/
+ });
+
+assert.rejects(
+ subtle.generateKey(
+ {
+ name: 'ECDSA',
+ namedCurve: 'NODE-ED25519'
+ },
+ true,
+ ['sign', 'verify']),
+ {
+ message: /Unsupported named curves for ECDSA/
+ });
+
+assert.rejects(
+ subtle.generateKey(
+ {
+ name: 'ECDSA',
+ namedCurve: 'NODE-ED448'
+ },
+ true,
+ ['sign', 'verify']),
+ {
+ message: /Unsupported named curves for ECDSA/
+ });
diff --git a/test/parallel/test-webcrypto-x25519-x448.js b/test/parallel/test-webcrypto-x25519-x448.js
new file mode 100644
index 00000000000..94794840630
--- /dev/null
+++ b/test/parallel/test-webcrypto-x25519-x448.js
@@ -0,0 +1,246 @@
+'use strict';
+
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const { subtle } = require('crypto').webcrypto;
+
+// X25519 and X448 are ECDH named curves that should work
+// with the existing ECDH mechanisms with no additional
+// changes.
+
+async function generateKeys(namedCurve, ...usages) {
+ return subtle.generateKey(
+ {
+ name: 'ECDH',
+ namedCurve
+ },
+ true,
+ usages);
+}
+
+async function deriveKey(publicKey, privateKey, length = 256) {
+ return subtle.deriveKey(
+ {
+ name: 'ECDH',
+ public: publicKey,
+ },
+ privateKey,
+ {
+ name: 'HMAC',
+ length,
+ hash: 'SHA-512',
+ },
+ true,
+ ['sign', 'verify']
+ );
+}
+
+async function exportKey(secretKey) {
+ return subtle.exportKey('raw', secretKey);
+}
+
+async function importKey(namedCurve, keyData, isPublic = false) {
+ return subtle.importKey(
+ 'raw',
+ keyData,
+ { name: 'ECDH', namedCurve, public: isPublic },
+ true,
+ ['deriveKey']
+ );
+}
+
+assert.rejects(importKey('NODE-X25519', Buffer.alloc(10), true), {
+ message: /NODE-X25519 raw keys must be exactly 32-bytes/
+});
+assert.rejects(importKey('NODE-X448', Buffer.alloc(10), true), {
+ message: /NODE-X448 raw keys must be exactly 56-bytes/
+});
+
+async function test1(namedCurve) {
+ const {
+ publicKey: publicKey1,
+ privateKey: privateKey1,
+ } = await generateKeys(namedCurve, 'deriveKey', 'deriveBits');
+
+ const {
+ publicKey: publicKey2,
+ privateKey: privateKey2,
+ } = await generateKeys(namedCurve, 'deriveKey', 'deriveBits');
+
+ assert(publicKey1);
+ assert(privateKey1);
+ assert(publicKey2);
+ assert(privateKey2);
+
+ assert.strictEqual(publicKey1.algorithm.namedCurve, namedCurve);
+ assert.strictEqual(privateKey1.algorithm.namedCurve, namedCurve);
+ assert.strictEqual(publicKey2.algorithm.namedCurve, namedCurve);
+ assert.strictEqual(privateKey2.algorithm.namedCurve, namedCurve);
+
+ const [key1, key2] = await Promise.all([
+ deriveKey(publicKey1, privateKey2),
+ deriveKey(publicKey2, privateKey1),
+ ]);
+
+ assert(key1);
+ assert(key2);
+
+ const [secret1, secret2] = await Promise.all([
+ exportKey(key1),
+ exportKey(key2),
+ ]);
+
+ assert.deepStrictEqual(secret1, secret2);
+}
+
+Promise.all([
+ test1('NODE-X25519'),
+ test1('NODE-X448'),
+]).then(common.mustCall());
+
+const testVectors = {
+ 'NODE-X25519': {
+ alice: {
+ privateKey:
+ Buffer.from(
+ '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a',
+ 'hex'),
+ },
+ bob: {
+ privateKey:
+ Buffer.from(
+ '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f',
+ 'hex'),
+ },
+ sharedSecret:
+ Buffer.from(
+ '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742',
+ 'hex'),
+ },
+ 'NODE-X448': {
+ alice: {
+ privateKey:
+ Buffer.from(
+ '9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d' +
+ 'd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ '9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c' +
+ '22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0',
+ 'hex'),
+ },
+ bob: {
+ privateKey:
+ Buffer.from(
+ '1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d' +
+ '6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d',
+ 'hex'),
+ publicKey:
+ Buffer.from(
+ '3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b430' +
+ '27d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609',
+ 'hex'),
+ },
+ sharedSecret:
+ Buffer.from(
+ '07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282b' +
+ 'b60c0b56fd2464c335543936521c24403085d59a449a5037514a879d',
+ 'hex'),
+ },
+};
+
+async function test2(namedCurve, length) {
+ const [
+ publicKey1,
+ publicKey2,
+ privateKey1,
+ privateKey2,
+ ] = await Promise.all([
+ importKey(namedCurve, testVectors[namedCurve].alice.publicKey, true),
+ importKey(namedCurve, testVectors[namedCurve].bob.publicKey, true),
+ importKey(namedCurve, testVectors[namedCurve].alice.privateKey),
+ importKey(namedCurve, testVectors[namedCurve].bob.privateKey),
+ ]);
+
+ const [key1, key2] = await Promise.all([
+ deriveKey(publicKey1, privateKey2, length),
+ deriveKey(publicKey2, privateKey1, length),
+ ]);
+
+ assert(key1);
+ assert(key2);
+
+ const [secret1, secret2] = await Promise.all([
+ exportKey(key1),
+ exportKey(key2),
+ ]);
+
+ assert.deepStrictEqual(secret1, secret2);
+
+ assert.deepStrictEqual(
+ Buffer.from(secret1),
+ testVectors[namedCurve].sharedSecret);
+
+ const [
+ publicKeyJwk,
+ privateKeyJwk
+ ] = await Promise.all([
+ subtle.exportKey('jwk', publicKey1),
+ subtle.exportKey('jwk', privateKey1)
+ ]);
+ assert.strictEqual(publicKeyJwk.kty, 'OKP');
+ assert.strictEqual(privateKeyJwk.kty, 'OKP');
+ assert.strictEqual(publicKeyJwk.crv, namedCurve.slice(5));
+ assert.strictEqual(privateKeyJwk.crv, namedCurve.slice(5));
+ assert.deepStrictEqual(
+ Buffer.from(publicKeyJwk.x, 'base64'),
+ testVectors[namedCurve].alice.publicKey);
+ assert.deepStrictEqual(
+ Buffer.from(privateKeyJwk.x, 'base64'),
+ testVectors[namedCurve].alice.publicKey);
+ assert.deepStrictEqual(
+ Buffer.from(privateKeyJwk.d, 'base64'),
+ testVectors[namedCurve].alice.privateKey);
+}
+
+Promise.all([
+ test2('NODE-X25519', 256),
+ test2('NODE-X448', 448),
+]).then(common.mustCall());
+
+assert.rejects(
+ subtle.generateKey(
+ {
+ name: 'ECDH',
+ namedCurve: 'NODE-ED25519'
+ },
+ true,
+ ['deriveBits']),
+ {
+ message: /Unsupported named curves for ECDH/
+ });
+
+assert.rejects(
+ subtle.generateKey(
+ {
+ name: 'ECDH',
+ namedCurve: 'NODE-ED448'
+ },
+ true,
+ ['deriveBits']),
+ {
+ message: /Unsupported named curves for ECDH/
+ });
diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js
index 5a91b1556c5..41e6086bf00 100644
--- a/tools/doc/type-parser.js
+++ b/tools/doc/type-parser.js
@@ -114,6 +114,10 @@ const customTypesMap = {
'NodeScryptImportParams':
'webcrypto.html#webcrypto_class_nodescryptimportparams',
'NodeScryptParams': 'webcrypto.html#webcrypto_class_nodescryptparams',
+ 'NodeEdKeyImportParams':
+ 'webcrypto.html#webcrypto_class_nodeedkeyimportparams',
+ 'NodeEdKeyGenParams':
+ 'webcrypto.html#webcrypto_class_nodeedkeygenparams',
'dgram.Socket': 'dgram.html#dgram_class_dgram_socket',