crypto: add CFRG curves to Web Crypto API

PR-URL: https://github.com/nodejs/node/pull/42507
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
Filip Skokan 2022-06-04 08:20:27 +02:00 committed by GitHub
parent dfa896f926
commit 7e5da97d15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1900 additions and 1336 deletions

View File

@ -1,5 +1,21 @@
# Web Crypto API
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'`
algorithms.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Removed proprietary `'NODE-ED25519'` and `'NODE-ED448'`
algorithms.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Removed proprietary `'NODE-X25519'` and `'NODE-X448'` named
curves from the `'ECDH'` algorithm.
-->
<!-- introduced_in=v15.0.0 -->
> Stability: 1 - Experimental
@ -69,22 +85,22 @@ async function generateEcKey(namedCurve = 'P-521') {
}
```
#### ED25519/ED448/X25519/X448 key pairs
#### Ed25519/Ed448/X25519/X448 key pairs
> Stability: 1 - Experimental
```js
const { subtle } = require('node:crypto').webcrypto;
async function generateEd25519Key() {
return subtle.generateKey({
name: 'NODE-ED25519',
namedCurve: 'NODE-ED25519',
name: 'Ed25519',
}, true, ['sign', 'verify']);
}
async function generateX25519Key() {
return subtle.generateKey({
name: 'ECDH',
namedCurve: 'NODE-X25519',
name: 'X25519',
}, true, ['deriveKey']);
}
```
@ -315,7 +331,11 @@ implementation and the APIs supported for each:
| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
| `'RSA-OAEP'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | |
| `'ECDSA'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
| `'Ed25519'`[^2] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
| `'Ed448'`[^2] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
| `'ECDH'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | |
| `'X25519'`[^2] | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | |
| `'X448'`[^2] | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | |
| `'AES-CTR'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | |
| `'AES-CBC'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | |
| `'AES-GCM'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | |
@ -329,8 +349,6 @@ implementation and the APIs supported for each:
| `'SHA-512'` | | | | | | | | | | | | ✔ |
| `'NODE-DSA'`[^1] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
| `'NODE-DH'`[^1] | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | |
| `'NODE-ED25519'`[^1] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
| `'NODE-ED448'`[^1] | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
## Class: `Crypto`
@ -394,7 +412,7 @@ added: v15.0.0
<!--lint disable maximum-line-length remark-lint-->
* Type: {AesKeyGenParams|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams|NodeEdKeyGenParams}
* Type: {AesKeyGenParams|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams}
<!--lint enable maximum-line-length remark-lint-->
@ -459,7 +477,11 @@ Valid key usages depend on the key algorithm (identified by
| `'AES-GCM'` | ✔ | ✔ | | | | | ✔ | ✔ |
| `'AES-KW'` | | | | | | | ✔ | ✔ |
| `'ECDH'` | | | | | ✔ | ✔ | | |
| `'X25519'`[^2] | | | | | ✔ | ✔ | | |
| `'X448'`[^2] | | | | | ✔ | ✔ | | |
| `'ECDSA'` | | | ✔ | ✔ | | | | |
| `'Ed25519'`[^2] | | | ✔ | ✔ | | | | |
| `'Ed448'`[^2] | | | ✔ | ✔ | | | | |
| `'HDKF'` | | | | | ✔ | ✔ | | |
| `'HMAC'` | | | ✔ | ✔ | | | | |
| `'PBKDF2'` | | | | | ✔ | ✔ | | |
@ -469,8 +491,6 @@ Valid key usages depend on the key algorithm (identified by
| `'NODE-DSA'`[^1] | | | ✔ | ✔ | | | | |
| `'NODE-DH'`[^1] | | | | | ✔ | ✔ | | |
| `'NODE-SCRYPT'`[^1] | | | | | ✔ | ✔ | | |
| `'NODE-ED25519'`[^1] | | | ✔ | ✔ | | | | |
| `'NODE-ED448'`[^1] | | | ✔ | ✔ | | | | |
## Class: `CryptoKeyPair`
@ -530,11 +550,15 @@ The algorithms currently supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Added `'X25519'`, and `'X448'` algorithms.
-->
<!--lint disable maximum-line-length remark-lint-->
* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams}
* `algorithm`: {AlgorithmIdentifier|EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams}
* `baseKey`: {CryptoKey}
* `length`: {number}
* Returns: {Promise} containing {ArrayBuffer}
@ -559,11 +583,15 @@ The algorithms currently supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Added `'X25519'`, and `'X448'` algorithms.
-->
<!--lint disable maximum-line-length remark-lint-->
* `algorithm`: {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams}
* `algorithm`: {AlgorithmIdentifier|EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|NodeDhDeriveBitsParams|NodeScryptParams}
* `baseKey`: {CryptoKey}
* `derivedKeyAlgorithm`: {HmacKeyGenParams|AesKeyGenParams}
* `extractable`: {boolean}
@ -640,6 +668,10 @@ The algorithms currently supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'`
algorithms.
- version: v15.9.0
pr-url: https://github.com/nodejs/node/pull/37203
description: Removed `'NODE-DSA'` JWK export.
@ -674,6 +706,8 @@ extension that allows converting a {CryptoKey} into a Node.js {KeyObject}.
| `'AES-KW'` | | | ✔ | ✔ |
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
| `'Ed25519'`[^2] | ✔ | ✔ | ✔ | ✔ |
| `'Ed448'`[^2] | ✔ | ✔ | ✔ | ✔ |
| `'HDKF'` | | | | |
| `'HMAC'` | | | ✔ | ✔ |
| `'PBKDF2'` | | | | |
@ -683,8 +717,6 @@ extension that allows converting a {CryptoKey} into a Node.js {KeyObject}.
| `'NODE-DSA'`[^1] | ✔ | ✔ | | |
| `'NODE-DH'`[^1] | ✔ | ✔ | | |
| `'NODE-SCRYPT'`[^1] | | | | |
| `'NODE-ED25519'`[^1] | ✔ | ✔ | ✔ | ✔ |
| `'NODE-ED448'`[^1] | ✔ | ✔ | ✔ | ✔ |
### `subtle.generateKey(algorithm, extractable, keyUsages)`
@ -694,7 +726,7 @@ added: v15.0.0
<!--lint disable maximum-line-length remark-lint-->
* `algorithm`: {RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams|NodeEdKeyGenParams}
* `algorithm`: {AlgorithmIdentifier|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|NodeDsaKeyGenParams|NodeDhKeyGenParams}
<!--lint enable maximum-line-length remark-lint-->
@ -713,11 +745,13 @@ include:
* `'RSA-PSS'`
* `'RSA-OAEP'`
* `'ECDSA'`
* `'Ed25519'`[^2]
* `'Ed448'`[^2]
* `'ECDH'`
* `'X25519'`[^2]
* `'X448'`[^2]
* `'NODE-DSA'`[^1]
* `'NODE-DH'`[^1]
* `'NODE-ED25519'`[^1]
* `'NODE-ED448'`[^1]
The {CryptoKey} (secret key) generating algorithms supported include:
@ -732,6 +766,10 @@ The {CryptoKey} (secret key) generating algorithms supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Added `'Ed25519'`, `'Ed448'`, `'X25519'`, and `'X448'`
algorithms.
- version: v15.9.0
pr-url: https://github.com/nodejs/node/pull/37203
description: Removed `'NODE-DSA'` JWK import.
@ -743,7 +781,7 @@ changes:
<!--lint disable maximum-line-length remark-lint-->
* `algorithm`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams|Pbkdf2ImportParams|NodeDsaImportParams|NodeDhImportParams|NodeScryptImportParams|NodeEdKeyImportParams}
* `algorithm`: {AlgorithmIdentifier|RsaHashedImportParams|EcKeyImportParams|HmacImportParams|NodeDsaImportParams}
<!--lint enable maximum-line-length remark-lint-->
@ -770,7 +808,11 @@ The algorithms currently supported include:
| `'AES-GCM'` | | | ✔ | ✔ |
| `'AES-KW'` | | | ✔ | ✔ |
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
| `'X25519'`[^2] | ✔ | ✔ | ✔ | ✔ |
| `'X448'`[^2] | ✔ | ✔ | ✔ | ✔ |
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
| `'Ed25519'`[^2] | ✔ | ✔ | ✔ | ✔ |
| `'Ed448'`[^2] | ✔ | ✔ | ✔ | ✔ |
| `'HDKF'` | | | | ✔ |
| `'HMAC'` | | | ✔ | ✔ |
| `'PBKDF2'` | | | | ✔ |
@ -780,18 +822,20 @@ The algorithms currently supported include:
| `'NODE-DSA'`[^1] | ✔ | ✔ | | |
| `'NODE-DH'`[^1] | ✔ | ✔ | | |
| `'NODE-SCRYPT'`[^1] | | | | ✔ |
| `'NODE-ED25519'`[^1] | ✔ | ✔ | ✔ | ✔ |
| `'NODE-ED448'`[^1] | ✔ | ✔ | ✔ | ✔ |
### `subtle.sign(algorithm, key, data)`
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Added `'Ed25519'`, and `'Ed448'` algorithms.
-->
<!--lint disable maximum-line-length remark-lint-->
* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|NodeDsaSignParams}
* `algorithm`: {AlgorithmIdentifier|RsaPssParams|EcdsaParams|Ed448Params}
* `key`: {CryptoKey}
* `data`: {ArrayBuffer|TypedArray|DataView|Buffer}
* Returns: {Promise} containing {ArrayBuffer}
@ -808,10 +852,10 @@ The algorithms currently supported include:
* `'RSASSA-PKCS1-v1_5'`
* `'RSA-PSS'`
* `'ECDSA'`
* `'Ed25519'`[^2]
* `'Ed448'`[^2]
* `'HMAC'`
* `'NODE-DSA'`[^1]
* `'NODE-ED25519'`[^1]
* `'NODE-ED448'`[^1]
### `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)`
@ -825,8 +869,8 @@ added: v15.0.0
<!--lint disable maximum-line-length remark-lint-->
* `unwrapAlgo`: {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams|AesKwParams}
* `unwrappedKeyAlgo`: {RsaHashedImportParams|EcKeyImportParams|HmacImportParams|AesImportParams}
* `unwrapAlgo`: {AlgorithmIdentifier|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams}
* `unwrappedKeyAlgo`: {AlgorithmIdentifier|RsaHashedImportParams|EcKeyImportParams|HmacImportParams}
<!--lint enable maximum-line-length remark-lint-->
@ -870,11 +914,15 @@ The unwrapped key algorithms supported include:
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42507
description: Added `'Ed25519'`, and `'Ed448'` algorithms.
-->
<!--lint disable maximum-line-length remark-lint-->
* `algorithm`: {RsaSignParams|RsaPssParams|EcdsaParams|HmacParams|NodeDsaSignParams}
* `algorithm`: {AlgorithmIdentifier|RsaPssParams|EcdsaParams|Ed448Params}
* `key`: {CryptoKey}
* `signature`: {ArrayBuffer|TypedArray|DataView|Buffer}
* `data`: {ArrayBuffer|TypedArray|DataView|Buffer}
@ -892,10 +940,10 @@ The algorithms currently supported include:
* `'RSASSA-PKCS1-v1_5'`
* `'RSA-PSS'`
* `'ECDSA'`
* `'Ed25519'`[^2]
* `'Ed448'`[^2]
* `'HMAC'`
* `'NODE-DSA'`[^1]
* `'NODE-ED25519'`[^1]
* `'NODE-ED448'`[^1]
### `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)`
@ -903,12 +951,16 @@ The algorithms currently supported include:
added: v15.0.0
-->
<!--lint disable maximum-line-length remark-lint-->
* `format`: {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`.
* `key`: {CryptoKey}
* `wrappingKey`: {CryptoKey}
* `wrapAlgo`: {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams|AesKwParams}
* `wrapAlgo`: {AlgorithmIdentifier|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams}
* Returns: {Promise} containing {ArrayBuffer}
<!--lint enable maximum-line-length remark-lint-->
In cryptography, "wrapping a key" refers to exporting and then encrypting the
keying material. The `subtle.wrapKey()` method exports the keying material into
the format identified by `format`, then encrypts it using the method and
@ -933,6 +985,20 @@ The algorithm parameter objects define the methods and parameters used by
the various {SubtleCrypto} methods. While described here as "classes", they
are simple JavaScript dictionary objects.
### Class: `AlgorithmIdentifier`
<!-- YAML
added: REPLACEME
-->
#### `algorithmIdentifier.name`
<!-- YAML
added: REPLACEME
-->
* Type: {string}
### Class: `AesCbcParams`
<!-- YAML
@ -1047,21 +1113,6 @@ added: v15.0.0
This values must be one of `32`, `64`, `96`, `104`, `112`, `120`, or
`128`. **Default:** `128`.
### Class: `AesImportParams`
<!-- YAML
added: v15.0.0
-->
#### `aesImportParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be one of `'AES-CTR'`, `'AES-CBC'`, `'AES-GCM'`, or
`'AES-KW'`.
### Class: `AesKeyGenParams`
<!-- YAML
@ -1088,20 +1139,6 @@ added: v15.0.0
* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or
`'AES-KW'`
### Class: `AesKwParams`
<!-- YAML
added: v15.0.0
-->
#### `aesKwParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'AES-KW'`.
### Class: `EcdhKeyDeriveParams`
<!-- YAML
@ -1114,7 +1151,7 @@ added: v15.0.0
added: v15.0.0
-->
* Type: {string} Must be `'ECDH'`.
* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`.
#### `ecdhKeyDeriveParams.public`
@ -1181,8 +1218,7 @@ added: v15.0.0
added: v15.0.0
-->
* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`,
`'NODE-ED25519'`, `'NODE-ED448'`, `'NODE-X25519'`, or `'NODE-X448'`.
* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`.
### Class: `EcKeyImportParams`
@ -1204,8 +1240,34 @@ added: v15.0.0
added: v15.0.0
-->
* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`,
`'NODE-ED25519'`, `'NODE-ED448'`, `'NODE-X25519'`, or `'NODE-X448'`.
* Type: {string} Must be one of `'P-256'`, `'P-384'`, `'P-521'`.
### Class: `Ed448Params`
<!-- YAML
added: v15.0.0
-->
#### `ed448Params.name`
<!-- YAML
added: REPLACEME
-->
* Type: {string} Must be `'Ed448'`.
#### `ed448Params.context`
<!-- YAML
added: REPLACEME
-->
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
The `context` member represents the optional context data to associate with
the message.
The Node.js Web Crypto API implementation only supports zero-length context
which is equivalent to not providing context at all.
### Class: `HkdfParams`
@ -1350,34 +1412,6 @@ added: v15.0.0
* Type: {string} Must be `'HMAC'`.
### Class: `HmacParams`
<!-- YAML
added: v15.0.0
-->
#### `hmacParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'HMAC'`.
### Class: `Pbkdf2ImportParams`
<!-- YAML
added: v15.0.0
-->
#### `pbkdf2ImportParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'PBKDF2'`
### Class: `Pbkdf2Params`
<!-- YAML
@ -1572,20 +1606,6 @@ added: v15.0.0
The length (in bytes) of the random salt to use.
### Class: `RsaSignParams`
<!-- YAML
added: v15.0.0
-->
#### `rsaSignParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'RSASSA-PKCS1-v1_5'`
## Node.js-specific extensions
The Node.js Web Crypto API extends various aspects of the Web Crypto API.
@ -1607,20 +1627,6 @@ added: v15.0.0
The `NODE-DH` algorithm is the common implementation of Diffie-Hellman
key agreement.
#### Class: `NodeDhImportParams`
<!-- YAML
added: v15.0.0
-->
##### `nodeDhImportParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'NODE-DH'`.
#### Class: `NodeDhKeyGenParams`
<!-- YAML
@ -1767,85 +1773,6 @@ added: v15.0.0
* Type: {string} Must be `'NODE-DSA'`.
#### Class: `NodeDsaSignParams`
<!-- YAML
added: v15.0.0
-->
##### `nodeDsaSignParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'NODE-DSA'`
### `NODE-ED25519` and `NODE-ED448` Algorithms
<!-- YAML
added: v15.8.0
-->
#### Class: `NodeEdKeyGenParams`
<!-- YAML
added: v15.8.0
-->
##### `nodeEdKeyGenParams.name`
<!-- YAML
added: v15.8.0
-->
* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'` or `'ECDH'`.
##### `nodeEdKeyGenParams.namedCurve`
<!-- YAML
added: v15.8.0
-->
* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'`,
`'NODE-X25519'`, or `'NODE-X448'`.
#### Class: `NodeEdKeyImportParams`
<!-- YAML
added: v15.8.0
-->
##### `nodeEdKeyImportParams.name`
<!-- YAML
added: v15.8.0
-->
* 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: v15.8.0
-->
* Type: {string} Must be one of `'NODE-ED25519'`, `'NODE-ED448'`,
`'NODE-X25519'`, or `'NODE-X448'`.
##### `nodeEdKeyImportParams.public`
<!-- YAML
added: v15.8.0
-->
* Type: {boolean}
The `public` parameter is used to specify that the `'raw'` format key is to be
interpreted as a public key. **Default:** `false`.
### `NODE-SCRYPT` Algorithm
<!-- YAML
@ -1855,20 +1782,6 @@ added: v15.0.0
The `NODE-SCRYPT` algorithm is the common implementation of the scrypt key
derivation algorithm.
#### Class: `NodeScryptImportParams`
<!-- YAML
added: v15.0.0
-->
##### `nodeScryptImportParams.name`
<!-- YAML
added: v15.0.0
-->
* Type: {string} Must be `'NODE-SCRYPT'`.
#### Class: `NodeScryptParams`
<!-- YAML
@ -1927,8 +1840,12 @@ added: v15.0.0
[^1]: Non-standard Node.js-specific extension
[^2]: An experimental implementation of
[Secure Curves in the Web Cryptography API][] as of 05 May 2022
[JSON Web Key]: https://tools.ietf.org/html/rfc7517
[Key usages]: #cryptokeyusages
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
[Secure Curves in the Web Cryptography API]: https://wicg.github.io/webcrypto-secure-curves/
[Web Crypto API]: https://www.w3.org/TR/WebCryptoAPI/

369
lib/internal/crypto/cfrg.js Normal file
View File

@ -0,0 +1,369 @@
'use strict';
const {
Promise,
SafeSet,
} = primordials;
const { Buffer } = require('buffer');
const {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
codes: {
ERR_INVALID_ARG_TYPE,
},
} = require('internal/errors');
const {
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
validateKeyOps,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
emitExperimentalWarning,
lazyDOMException,
} = require('internal/util');
const {
generateKeyPair,
} = require('internal/crypto/keygen');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
isKeyObject,
} = require('internal/crypto/keys');
function verifyAcceptableCfrgKeyUse(name, type, usages) {
let checkSet;
switch (name) {
case 'X25519':
// Fall through
case 'X448':
checkSet = ['deriveKey', 'deriveBits'];
break;
case 'Ed25519':
// Fall through
case 'Ed448':
switch (type) {
case 'private':
checkSet = ['sign'];
break;
case 'public':
checkSet = ['verify'];
break;
}
}
if (hasAnyNotIn(usages, checkSet)) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
}
function createECPublicKeyRaw(name, keyData) {
const handle = new KeyObjectHandle();
keyData = getArrayBufferOrView(keyData, 'keyData');
if (handle.initECRaw(name.toLowerCase(), keyData))
return new PublicKeyObject(handle);
}
function createCFRGRawKey(name, keyData, isPublic) {
const handle = new KeyObjectHandle();
keyData = getArrayBufferOrView(keyData, 'keyData');
switch (name) {
case 'Ed25519':
case 'X25519':
if (keyData.byteLength !== 32) {
throw lazyDOMException(
`${name} raw keys must be exactly 32-bytes`);
}
break;
case 'Ed448':
if (keyData.byteLength !== 57) {
throw lazyDOMException(
`${name} raw keys must be exactly 57-bytes`);
}
break;
case 'X448':
if (keyData.byteLength !== 56) {
throw lazyDOMException(
`${name} raw keys must be exactly 56-bytes`);
}
break;
}
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
if (!handle.initEDRaw(name, keyData, keyType)) {
throw lazyDOMException('Failure to generate key object');
}
return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
}
async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
const { name } = algorithm;
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
const usageSet = new SafeSet(keyUsages);
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
break;
case 'X25519':
// Fall through
case 'X448':
if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
break;
}
return new Promise((resolve, reject) => {
let genKeyType;
switch (name) {
case 'Ed25519':
genKeyType = 'ed25519';
break;
case 'Ed448':
genKeyType = 'ed448';
break;
case 'X25519':
genKeyType = 'x25519';
break;
case 'X448':
genKeyType = 'x448';
break;
}
generateKeyPair(genKeyType, undefined, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}
const algorithm = { name };
let publicUsages;
let privateUsages;
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
break;
case 'X25519':
// Fall through
case 'X448':
publicUsages = [];
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
break;
}
const publicKey =
new InternalCryptoKey(
pubKey,
algorithm,
publicUsages,
true);
const privateKey =
new InternalCryptoKey(
privKey,
algorithm,
privateUsages,
extractable);
resolve({ publicKey, privateKey });
});
});
}
function cfrgExportKey(key, format) {
emitExperimentalWarning(`The ${key.algorithm.name} Web Crypto API algorithm`);
return jobPromise(new ECKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}
async function cfrgImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { name } = algorithm;
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
let keyObject;
const usagesSet = new SafeSet(keyUsages);
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
verifyAcceptableCfrgKeyUse(name, keyData.type, usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableCfrgKeyUse(name, 'public', usagesSet);
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki'
});
break;
}
case 'pkcs8': {
verifyAcceptableCfrgKeyUse(name, 'private', usagesSet);
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8'
});
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (keyData.kty !== 'OKP')
throw lazyDOMException('Invalid key type', 'DataError');
const isPublic = keyData.d === undefined;
if (usagesSet.size > 0 && keyData.use !== undefined) {
let checkUse;
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
checkUse = 'sig';
break;
case 'X25519':
// Fall through
case 'X448':
checkUse = 'enc';
break;
}
if (keyData.use !== checkUse)
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 (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
if (
(name === 'Ed25519' || name === 'Ed448') &&
keyData.alg !== 'EdDSA'
) {
throw lazyDOMException('Invalid alg', 'DataError');
}
}
verifyAcceptableCfrgKeyUse(
name,
isPublic ? 'public' : 'private',
usagesSet);
keyObject = createCFRGRawKey(
name,
Buffer.from(
isPublic ? keyData.x : keyData.d,
'base64'),
isPublic);
break;
}
case 'raw': {
verifyAcceptableCfrgKeyUse(name, 'public', usagesSet);
keyObject = createECPublicKeyRaw(name, keyData);
if (keyObject === undefined)
throw lazyDOMException('Unable to import CFRG key', 'OperationError');
break;
}
}
if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
throw lazyDOMException('Invalid key type', 'DataError');
}
return new InternalCryptoKey(
keyObject,
{ name },
keyUsages,
extractable);
}
function eddsaSignVerify(key, data, { name, context }, signature) {
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
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');
if (name === 'Ed448' && context !== undefined) {
context =
getArrayBufferOrView(context, 'algorithm.context');
if (context.byteLength !== 0) {
throw lazyDOMException(
'Non zero-length context is not yet supported.', 'NotSupportedError');
}
}
return jobPromise(new SignJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
undefined,
undefined,
undefined,
data,
undefined,
undefined,
undefined,
undefined,
signature));
}
module.exports = {
cfrgExportKey,
cfrgImportKey,
cfrgGenerateKey,
eddsaSignVerify,
};

View File

@ -447,8 +447,12 @@ async function asyncDeriveBitsECDH(algorithm, baseKey, length) {
'baseKey must be a private key', 'InvalidAccessError');
}
if (key.algorithm.name !== 'ECDH') {
throw lazyDOMException('Keys must be ECDH keys', 'InvalidAccessError');
if (
key.algorithm.name !== 'ECDH' &&
key.algorithm.name !== 'X25519' &&
key.algorithm.name !== 'X448'
) {
throw lazyDOMException('Keys must be ECDH, X25519, or X448 keys', 'InvalidAccessError');
}
if (key.algorithm.name !== baseKey.algorithm.name) {
@ -457,12 +461,16 @@ async function asyncDeriveBitsECDH(algorithm, baseKey, length) {
'InvalidAccessError');
}
if (key.algorithm.namedCurve !== baseKey.algorithm.namedCurve)
if (
key.algorithm.name === 'ECDH' &&
key.algorithm.namedCurve !== baseKey.algorithm.namedCurve
) {
throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
}
const bits = await new Promise((resolve, reject) => {
deriveBitsECDH(
baseKey.algorithm.namedCurve,
key.algorithm.name === 'ECDH' ? baseKey.algorithm.namedCurve : baseKey.algorithm.name,
key[kKeyObject][kHandle],
baseKey[kKeyObject][kHandle], (err, bits) => {
if (err) return reject(err);

View File

@ -6,22 +6,18 @@ const {
SafeSet,
} = primordials;
const { Buffer } = require('buffer');
const {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
kSigEncP1363,
} = internalBinding('crypto');
const {
validateBoolean,
validateOneOf,
validateString,
} = require('internal/validators');
@ -68,10 +64,6 @@ 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':
@ -96,40 +88,6 @@ 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;
}
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
if (!handle.initEDRaw(namedCurve, keyData, keyType)) {
throw lazyDOMException('Failure to generate key object');
}
return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
}
async function ecGenerateKey(algorithm, extractable, keyUsages) {
const { name, namedCurve } = algorithm;
validateString(namedCurve, 'algorithm.namedCurve');
@ -141,16 +99,6 @@ 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',
@ -163,33 +111,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) => {
let genKeyType;
let genOpts;
switch (namedCurve) {
case 'NODE-ED25519':
genKeyType = 'ed25519';
break;
case 'NODE-ED448':
genKeyType = 'ed448';
break;
case 'NODE-X25519':
genKeyType = 'x25519';
break;
case 'NODE-X448':
genKeyType = 'x448';
break;
default:
genKeyType = 'ec';
genOpts = { namedCurve };
break;
}
generateKeyPair(genKeyType, genOpts, (err, pubKey, privKey) => {
generateKeyPair('ec', { namedCurve }, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
@ -201,10 +126,6 @@ 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');
@ -256,29 +177,12 @@ async function ecImportKey(
ObjectKeys(kNamedCurveAliases));
let keyObject;
const usagesSet = new SafeSet(keyUsages);
let checkNamedCurve = true;
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
switch (namedCurve) {
case 'NODE-X25519':
// Fall through
case 'NODE-X448':
if (algorithm.name !== 'ECDH')
throw lazyDOMException('Invalid algorithm name.', 'DataError');
break;
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
if (algorithm.name !== namedCurve)
throw lazyDOMException('Invalid algorithm name.', 'DataError');
break;
}
verifyAcceptableEcKeyUse(name, keyData.type, usagesSet);
keyObject = keyData;
break;
@ -305,119 +209,54 @@ async function ecImportKey(
let curve;
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
switch (keyData.kty) {
case 'OKP': {
const isPublic = keyData.d === undefined;
if (keyData.kty !== 'EC')
throw lazyDOMException('Invalid key type', 'DataError');
let type;
switch (namedCurve) {
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
type = `NODE-${keyData.crv.toUpperCase()}`;
break;
case 'NODE-X25519':
// Fall through
case 'NODE-X448':
type = 'ECDH';
break;
}
if (algorithm.name !== type)
throw lazyDOMException('Invalid algorithm name.', 'DataError');
verifyAcceptableEcKeyUse(
type,
isPublic ? 'public' : 'private',
usagesSet);
keyObject = createECRawKey(
namedCurve,
Buffer.from(
isPublic ? keyData.x : 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 (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);
break;
}
case 'raw': {
switch (namedCurve) {
case 'NODE-X25519':
// Fall through
case 'NODE-X448':
if (algorithm.public !== undefined)
validateBoolean(algorithm.public, 'algorithm.public');
if (algorithm.name !== 'ECDH')
throw lazyDOMException('Invalid algorithm name.', 'DataError');
verifyAcceptableEcKeyUse(
algorithm.name,
algorithm.public === true ? 'public' : 'private',
usagesSet);
keyObject = createECRawKey(namedCurve, keyData, algorithm.public);
break;
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
if (algorithm.public !== undefined)
validateBoolean(algorithm.public, 'algorithm.public');
if (algorithm.name !== namedCurve)
throw lazyDOMException('Invalid algorithm name.', 'DataError');
verifyAcceptableEcKeyUse(
algorithm.name,
algorithm.public === true ? 'public' : 'private',
usagesSet);
keyObject = createECRawKey(namedCurve, keyData, algorithm.public);
break;
default:
verifyAcceptableEcKeyUse(name, 'public', usagesSet);
keyObject = createECPublicKeyRaw(namedCurve, keyData);
}
verifyAcceptableEcKeyUse(name, 'public', usagesSet);
keyObject = createECPublicKeyRaw(namedCurve, keyData);
if (keyObject === undefined)
throw lazyDOMException('Unable to import EC key', 'OperationError');
break;
@ -426,41 +265,18 @@ async function ecImportKey(
switch (algorithm.name) {
case 'ECDSA':
// Fall through
case 'ECDH':
if (keyObject.asymmetricKeyType !== 'ec')
throw lazyDOMException('Invalid key type', 'DataError');
break;
case 'ECDH':
if (algorithm.namedCurve === 'NODE-X25519') {
if (keyObject.asymmetricKeyType !== 'x25519')
throw lazyDOMException('Invalid key type', 'DataError');
checkNamedCurve = false;
} else if (algorithm.namedCurve === 'NODE-X448') {
if (keyObject.asymmetricKeyType !== 'x448')
throw lazyDOMException('Invalid key type', 'DataError');
checkNamedCurve = false;
} else if (keyObject.asymmetricKeyType !== 'ec') {
throw lazyDOMException('Invalid key type', 'DataError');
}
break;
case 'NODE-ED25519':
if (keyObject.asymmetricKeyType !== 'ed25519')
throw lazyDOMException('Invalid key type', 'DataError');
checkNamedCurve = false;
break;
case 'NODE-ED448':
if (keyObject.asymmetricKeyType !== 'ed448')
throw lazyDOMException('Invalid key type', 'DataError');
checkNamedCurve = false;
break;
}
if (checkNamedCurve) {
const {
namedCurve: checkNamedCurve
} = keyObject[kHandle].keyDetail({});
if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
}
const {
namedCurve: checkNamedCurve
} = keyObject[kHandle].keyDetail({});
if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
return new InternalCryptoKey(
keyObject,
@ -476,19 +292,9 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) {
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 lazyDOMException(`Hash is not permitted for ${name}`);
break;
default:
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
hashname = normalizeHashName(hash.name);
}
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const hashname = normalizeHashName(hash.name);
return jobPromise(new SignJob(
kCryptoJobAsync,

View File

@ -448,7 +448,7 @@ function getKeyObjectHandleFromJwk(key, ctx) {
const handle = new KeyObjectHandle();
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
if (!handle.initEDRaw(`NODE-${key.crv.toUpperCase()}`, keyData, keyType)) {
if (!handle.initEDRaw(key.crv, keyData, keyType)) {
throw new ERR_CRYPTO_INVALID_JWK();
}

View File

@ -143,10 +143,6 @@ 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];
@ -170,13 +166,15 @@ const kAlgorithms = {
'sha-512': 'SHA-512',
'hkdf': 'HKDF',
'pbkdf2': 'PBKDF2',
'ed25519': 'Ed25519',
'ed448': 'Ed448',
'x25519': 'X25519',
'x448': 'X448',
// Following here are Node.js specific extensions. All
// should be prefixed with 'node-'
'node-dsa': 'NODE-DSA',
'node-dh': 'NODE-DH',
'node-scrypt': 'NODE-SCRYPT',
'node-ed25519': 'NODE-ED25519',
'node-ed448': 'NODE-ED448',
};
const kAlgorithmsKeys = ObjectKeys(kAlgorithms);

View File

@ -98,10 +98,15 @@ async function generateKey(
case 'RSA-OAEP':
return lazyRequire('internal/crypto/rsa')
.rsaKeyGenerate(algorithm, extractable, keyUsages);
case 'NODE-ED25519':
case 'Ed25519':
// Fall through
case 'NODE-ED448':
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
return lazyRequire('internal/crypto/cfrg')
.cfrgGenerateKey(algorithm, extractable, keyUsages);
case 'ECDSA':
// Fall through
case 'ECDH':
@ -146,6 +151,10 @@ async function deriveBits(algorithm, baseKey, length) {
if (baseKey.algorithm.name !== algorithm.name)
throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
switch (algorithm.name) {
case 'X25519':
// Fall through
case 'X448':
// Fall through
case 'ECDH':
return lazyRequire('internal/crypto/diffiehellman')
.asyncDeriveBitsECDH(algorithm, baseKey, length);
@ -227,6 +236,10 @@ async function deriveKey(
const length = getKeyLength(derivedKeyAlgorithm);
let bits;
switch (algorithm.name) {
case 'X25519':
// Fall through
case 'X448':
// Fall through
case 'ECDH':
bits = await lazyRequire('internal/crypto/diffiehellman')
.asyncDeriveBitsECDH(algorithm, baseKey, length);
@ -270,10 +283,6 @@ async function exportKeySpki(key) {
.rsaExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
// Fall through
case 'ECDSA':
// Fall through
case 'ECDH':
@ -282,6 +291,18 @@ async function exportKeySpki(key) {
.ecExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
if (key.type === 'public') {
return lazyRequire('internal/crypto/cfrg')
.cfrgExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
case 'NODE-DSA':
if (key.type === 'public') {
return lazyRequire('internal/crypto/dsa')
@ -313,10 +334,6 @@ async function exportKeyPkcs8(key) {
.rsaExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
// Fall through
case 'ECDSA':
// Fall through
case 'ECDH':
@ -325,6 +342,18 @@ async function exportKeyPkcs8(key) {
.ecExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
if (key.type === 'private') {
return lazyRequire('internal/crypto/cfrg')
.cfrgExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
case 'NODE-DSA':
if (key.type === 'private') {
return lazyRequire('internal/crypto/dsa')
@ -346,14 +375,6 @@ async function exportKeyPkcs8(key) {
async function exportKeyRaw(key) {
switch (key.algorithm.name) {
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
if (key.type === 'public') {
return lazyRequire('internal/crypto/ec')
.ecExportKey(key, kWebCryptoKeyFormatRaw);
}
break;
case 'ECDSA':
// Fall through
case 'ECDH':
@ -362,6 +383,18 @@ async function exportKeyRaw(key) {
.ecExportKey(key, kWebCryptoKeyFormatRaw);
}
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
if (key.type === 'public') {
return lazyRequire('internal/crypto/cfrg')
.cfrgExportKey(key, kWebCryptoKeyFormatRaw);
}
break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
@ -405,6 +438,17 @@ async function exportKeyJWK(key) {
case 'ECDH':
jwk.crv ||= key.algorithm.namedCurve;
return jwk;
case 'X25519':
// Fall through
case 'X448':
jwk.crv ||= key.algorithm.name;
return jwk;
case 'Ed25519':
// Fall through
case 'Ed448':
jwk.crv ||= key.algorithm.name;
jwk.alg = 'EdDSA';
return jwk;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
@ -420,10 +464,6 @@ async function exportKeyJWK(key) {
key.algorithm.hash.name,
normalizeHashName.kContextJwkHmac);
return jwk;
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
return jwk;
default:
// Fall through
}
@ -530,15 +570,20 @@ 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':
return lazyRequire('internal/crypto/ec')
.ecImportKey(format, keyData, algorithm, extractable, keyUsages);
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
return lazyRequire('internal/crypto/cfrg')
.cfrgImportKey(format, keyData, algorithm, extractable, keyUsages);
case 'HMAC':
return lazyRequire('internal/crypto/mac')
.hmacImportKey(format, keyData, algorithm, extractable, keyUsages);
@ -659,13 +704,15 @@ 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);
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
return lazyRequire('internal/crypto/cfrg')
.eddsaSignVerify(key, data, algorithm, signature);
case 'HMAC':
return lazyRequire('internal/crypto/mac')
.hmacSignVerify(key, data, algorithm, signature);

View File

@ -45,13 +45,13 @@ int GetCurveFromName(const char* name) {
int GetOKPCurveFromName(const char* name) {
int nid;
if (strcmp(name, "NODE-ED25519") == 0) {
if (strcmp(name, "Ed25519") == 0) {
nid = EVP_PKEY_ED25519;
} else if (strcmp(name, "NODE-ED448") == 0) {
} else if (strcmp(name, "Ed448") == 0) {
nid = EVP_PKEY_ED448;
} else if (strcmp(name, "NODE-X25519") == 0) {
} else if (strcmp(name, "X25519") == 0) {
nid = EVP_PKEY_X25519;
} else if (strcmp(name, "NODE-X448") == 0) {
} else if (strcmp(name, "X448") == 0) {
nid = EVP_PKEY_X448;
} else {
nid = NID_undef;

51
test/fixtures/crypto/eddsa.js vendored Normal file
View File

@ -0,0 +1,51 @@
'use strict';
module.exports = function() {
const pkcs8 = {
'Ed25519': Buffer.from(
'302e020100300506032b657004220420f3c8f4c48df878146e8cd3bf6df4e50e389b' +
'a7074e15c2352dcd5d308d4ca81f', 'hex'),
'Ed448': Buffer.from(
'3047020100300506032b6571043b04390eff03458c28e0179c521de312c969b78343' +
'48ecab991a60e3b2e9a79e4cd9e480ef291712d2c83d047272d5c9f428664f696d26' +
'70458f1d2e', 'hex')
}
const spki = {
'Ed25519': Buffer.from(
'302a300506032b6570032100d8e18963d809d487d9549accaec6742e7eeba24d8a0d' +
'3b14b7e3caea06893dcc', 'hex'),
'Ed448': Buffer.from(
'3043300506032b6571033a00ab4bb885fd7d2c5af24e83710cffa0c74a57e274801d' +
'b2057b0bdc5ea032b6fe6bc78b8045365aeb26e86e1f14fd349d07c48495f5a46a5a' +
'80', 'hex')
}
const data = Buffer.from(
'2b7ed0bc7795694ab4acd35903fe8cd7d80f6a1c8688a6c3414409457514a1457855bb' +
'b219e30a1beea8fe869082d99fc8282f9050d024e59eaf0730ba9db70a', 'hex');
// For verification tests.
const signatures = {
'Ed25519': Buffer.from(
'3d90de5e5743dfc28225bfadb341b116cbf8a3f1ceedbf4adc350ef5d3471843a418' +
'614dcb6e614862614cf7af1496f9340b3c844ea4dceab1d3d155eb7ecc00', 'hex'),
'Ed448': Buffer.from(
'76897e8c50ac6b1132735c09c55f506c0149d2677c75664f8bc10b826fbd9df0a03c' +
'd986bce8339e64c7d1720ea9361784dc73837765ac2980c0dac0814a8bc187d1c9c9' +
'07c5dcc07956f85b70930fe42de764177217cb2d52bab7c1debe0ca89ccecbcd63f7' +
'025a2a5a572b9d23b0642f00', 'hex')
}
const algorithms = ['Ed25519', 'Ed448'];
const vectors = algorithms.map((algorithm) => ({
publicKeyBuffer: spki[algorithm],
privateKeyBuffer: pkcs8[algorithm],
name: algorithm,
data,
signature: signatures[algorithm],
}));
return vectors;
}

View File

@ -1715,22 +1715,6 @@ generateKeyPair('rsa', {
assert.strictEqual(typeof privateKey, 'string');
}));
{
// Proprietary Web Cryptography API ECDH/ECDSA namedCurve parameters
// should not be recognized in this API.
// See https://github.com/nodejs/node/issues/37055
const curves = ['NODE-ED25519', 'NODE-ED448', 'NODE-X25519', 'NODE-X448'];
for (const namedCurve of curves) {
assert.throws(
() => generateKeyPair('ec', { namedCurve }, common.mustNotCall()),
{
name: 'TypeError',
message: 'Invalid EC curve name'
}
);
}
}
{
// This test creates EC key pairs on curves without associated OIDs.
// Specifying a key encoding should not crash.

View File

@ -0,0 +1,213 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { webcrypto } = require('crypto');
const { subtle } = webcrypto;
const kTests = [
{
name: 'X25519',
size: 32,
pkcs8: '302e020100300506032b656e04220420c8838e76d057dfb7d8c95a69e138160ad' +
'd6373fd71a4d276bb56e3a81b64ff61',
spki: '302a300506032b656e0321001cf2b1e6022ec537371ed7f53e54fa1154d83e98eb' +
'64ea51fae5b3307cfe9706',
result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008'
},
{
name: 'X448',
size: 56,
pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' +
'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' +
'd0cc53bf26929e',
spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' +
'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' +
'ce6f',
result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' +
'726e0f6dcf810eb594dca97b4882bd44c43ea7dc67f49a4e',
},
];
async function prepareKeys() {
const keys = {};
await Promise.all(
kTests.map(async ({ name, size, pkcs8, spki, result }) => {
const [
privateKey,
publicKey,
] = await Promise.all([
subtle.importKey(
'pkcs8',
Buffer.from(pkcs8, 'hex'),
{ name },
true,
['deriveKey', 'deriveBits']),
subtle.importKey(
'spki',
Buffer.from(spki, 'hex'),
{ name },
true,
['deriveKey', 'deriveBits']),
]);
keys[name] = {
privateKey,
publicKey,
size,
result,
};
}));
return keys;
}
(async function() {
const keys = await prepareKeys();
await Promise.all(
Object.keys(keys).map(async (name) => {
const { size, result, privateKey, publicKey } = keys[name];
{
// Good parameters
const bits = await subtle.deriveBits({
name,
public: publicKey
}, privateKey, 8 * size);
assert(bits instanceof ArrayBuffer);
assert.strictEqual(Buffer.from(bits).toString('hex'), result);
}
{
// Case insensitivity
const bits = await subtle.deriveBits({
name: name.toLowerCase(),
public: publicKey
}, privateKey, 8 * size);
assert.strictEqual(Buffer.from(bits).toString('hex'), result);
}
{
// Null length
const bits = await subtle.deriveBits({
name,
public: publicKey
}, privateKey, null);
assert.strictEqual(Buffer.from(bits).toString('hex'), result);
}
{
// Short Result
const bits = await subtle.deriveBits({
name,
public: publicKey
}, privateKey, 8 * size - 32);
assert.strictEqual(
Buffer.from(bits).toString('hex'),
result.slice(0, -8));
}
{
// Too long result
await assert.rejects(subtle.deriveBits({
name,
public: publicKey
}, privateKey, 8 * size + 8), {
message: /derived bit length is too small/
});
}
{
// Non-multiple of 8
const bits = await subtle.deriveBits({
name,
public: publicKey
}, privateKey, 8 * size - 11);
assert.strictEqual(
Buffer.from(bits).toString('hex'),
result.slice(0, -4));
}
}));
// Error tests
{
// Missing public property
await assert.rejects(
subtle.deriveBits(
{ name: 'X448' },
keys.X448.privateKey,
8 * keys.X448.size),
{ code: 'ERR_INVALID_ARG_TYPE' });
}
{
// The public property is not a CryptoKey
await assert.rejects(
subtle.deriveBits(
{
name: 'X448',
public: { message: 'Not a CryptoKey' }
},
keys.X448.privateKey,
8 * keys.X448.size),
{ code: 'ERR_INVALID_ARG_TYPE' });
}
{
// Mismatched types
await assert.rejects(
subtle.deriveBits(
{
name: 'X448',
public: keys.X25519.publicKey
},
keys.X448.privateKey,
8 * keys.X448.size),
{ message: 'The public and private keys must be of the same type' });
}
{
// Base key is not a private key
await assert.rejects(subtle.deriveBits({
name: 'X448',
public: keys.X448.publicKey
}, keys.X448.publicKey, null), {
message: /baseKey must be a private key/
});
}
{
// Base key is not a private key
await assert.rejects(subtle.deriveBits({
name: 'X448',
public: keys.X448.privateKey
}, keys.X448.publicKey, null), {
message: /algorithm\.public must be a public key/
});
}
{
// Public is a secret key
const keyData = webcrypto.getRandomValues(new Uint8Array(32));
const key = await subtle.importKey(
'raw',
keyData,
{ name: 'AES-CBC', length: 256 },
false, ['encrypt']);
await assert.rejects(subtle.deriveBits({
name: 'X448',
public: key
}, keys.X448.publicKey, null), {
message: /algorithm\.public must be a public key/
});
}
})().then(common.mustCall());

View File

@ -207,7 +207,7 @@ async function prepareKeys() {
name: 'ECDH',
public: publicKey
}, keys['P-521'].privateKey, null), {
message: /Keys must be ECDH keys/
message: /Keys must be ECDH, X25519, or X448 keys/
});
}

View File

@ -132,3 +132,29 @@ if (typeof internalBinding('crypto').ScryptJob === 'function') {
tests.then(common.mustCall());
}
// Test X25519 and X448 bit derivation
{
async function test(name) {
const [alice, bob] = await Promise.all([
subtle.generateKey({ name }, true, ['deriveBits']),
subtle.generateKey({ name }, true, ['deriveBits']),
]);
const [secret1, secret2] = await Promise.all([
subtle.deriveBits({
name, public: alice.publicKey
}, bob.privateKey, 128),
subtle.deriveBits({
name, public: bob.publicKey
}, alice.privateKey, 128),
]);
assert(secret1 instanceof ArrayBuffer);
assert(secret2 instanceof ArrayBuffer);
assert.deepStrictEqual(secret1, secret2);
}
test('X25519').then(common.mustCall());
test('X448').then(common.mustCall());
}

View File

@ -0,0 +1,188 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { webcrypto } = require('crypto');
const { subtle } = webcrypto;
const kTests = [
{
name: 'X25519',
size: 32,
pkcs8: '302e020100300506032b656e04220420c8838e76d057dfb7d8c95a69e138160ad' +
'd6373fd71a4d276bb56e3a81b64ff61',
spki: '302a300506032b656e0321001cf2b1e6022ec537371ed7f53e54fa1154d83e98eb' +
'64ea51fae5b3307cfe9706',
result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008'
},
{
name: 'X448',
size: 56,
pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' +
'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' +
'd0cc53bf26929e',
spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' +
'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' +
'ce6f',
result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b'
},
];
async function prepareKeys() {
const keys = {};
await Promise.all(
kTests.map(async ({ name, size, pkcs8, spki, result }) => {
const [
privateKey,
publicKey,
] = await Promise.all([
subtle.importKey(
'pkcs8',
Buffer.from(pkcs8, 'hex'),
{ name },
true,
['deriveKey', 'deriveBits']),
subtle.importKey(
'spki',
Buffer.from(spki, 'hex'),
{ name },
true,
['deriveKey', 'deriveBits']),
]);
keys[name] = {
privateKey,
publicKey,
size,
result,
};
}));
return keys;
}
(async function() {
const keys = await prepareKeys();
const otherArgs = [
{ name: 'HMAC', hash: 'SHA-256', length: 256 },
true,
['sign', 'verify']];
await Promise.all(
Object.keys(keys).map(async (name) => {
const { result, privateKey, publicKey } = keys[name];
{
// Good parameters
const key = await subtle.deriveKey({
name,
public: publicKey
}, privateKey, ...otherArgs);
const raw = await subtle.exportKey('raw', key);
assert.strictEqual(Buffer.from(raw).toString('hex'), result);
}
{
// Case insensitivity
const key = await subtle.deriveKey({
name: name.toLowerCase(),
public: publicKey
}, privateKey, {
name: 'HmAc',
hash: 'SHA-256',
length: 256
}, true, ['sign', 'verify']);
const raw = await subtle.exportKey('raw', key);
assert.strictEqual(Buffer.from(raw).toString('hex'), result);
}
}));
// Error tests
{
// Missing public property
await assert.rejects(
subtle.deriveKey(
{ name: 'X448' },
keys.X448.privateKey,
...otherArgs),
{ code: 'ERR_INVALID_ARG_TYPE' });
}
{
// The public property is not a CryptoKey
await assert.rejects(
subtle.deriveKey(
{
name: 'X448',
public: { message: 'Not a CryptoKey' }
},
keys.X448.privateKey,
...otherArgs),
{ code: 'ERR_INVALID_ARG_TYPE' });
}
{
// Mismatched named curves
await assert.rejects(
subtle.deriveKey(
{
name: 'X448',
public: keys.X25519.publicKey
},
keys.X448.privateKey,
...otherArgs),
{ message: 'The public and private keys must be of the same type' });
}
{
// Base key is not a private key
await assert.rejects(
subtle.deriveKey(
{
name: 'X448',
public: keys.X448.publicKey
},
keys.X448.publicKey,
...otherArgs),
{ message: /baseKey must be a private key/ });
}
{
// Base key is not a private key
await assert.rejects(
subtle.deriveKey(
{
name: 'X448',
public: keys.X448.privateKey
},
keys.X448.publicKey,
...otherArgs),
{ message: /algorithm\.public must be a public key/ });
}
{
// Public is a secret key
const keyData = webcrypto.getRandomValues(new Uint8Array(32));
const key = await subtle.importKey(
'raw',
keyData,
{ name: 'AES-CBC', length: 256 },
false, ['encrypt']);
await assert.rejects(
subtle.deriveKey(
{
name: 'X448',
public: key
},
keys.X448.publicKey,
...otherArgs),
{ message: /algorithm\.public must be a public key/ });
}
})().then(common.mustCall());

View File

@ -175,7 +175,7 @@ async function prepareKeys() {
},
keys['P-521'].privateKey,
...otherArgs),
{ message: /Keys must be ECDH keys/ });
{ message: /Keys must be ECDH, X25519, or X448 keys/ });
}
{

View File

@ -14,7 +14,7 @@ const { internalBinding } = require('internal/test/binding');
// This is only a partial test. The WebCrypto Web Platform Tests
// will provide much greater coverage.
// Test ECDH bit derivation
// Test ECDH key derivation
{
async function test(namedCurve) {
const [alice, bob] = await Promise.all([
@ -48,7 +48,7 @@ const { internalBinding } = require('internal/test/binding');
test('P-521').then(common.mustCall());
}
// Test HKDF bit derivation
// Test HKDF key derivation
{
async function test(pass, info, salt, hash, expected) {
const ec = new TextEncoder();
@ -85,7 +85,7 @@ const { internalBinding } = require('internal/test/binding');
tests.then(common.mustCall());
}
// Test PBKDF2 bit derivation
// Test PBKDF2 key derivation
{
async function test(pass, salt, iterations, hash, expected) {
const ec = new TextEncoder();
@ -121,7 +121,7 @@ const { internalBinding } = require('internal/test/binding');
tests.then(common.mustCall());
}
// Test Scrypt bit derivation
// Test Scrypt key derivation
if (typeof internalBinding('crypto').ScryptJob === 'function') {
async function test(pass, salt, expected) {
const ec = new TextEncoder();
@ -184,3 +184,38 @@ if (typeof internalBinding('crypto').ScryptJob === 'function') {
}
})().then(common.mustCall());
}
// Test X25519 and X448 key derivation
{
async function test(name) {
const [alice, bob] = await Promise.all([
subtle.generateKey({ name }, true, ['deriveKey']),
subtle.generateKey({ name }, true, ['deriveKey']),
]);
const [secret1, secret2] = await Promise.all([
subtle.deriveKey({
name, public: alice.publicKey
}, bob.privateKey, {
name: 'AES-CBC',
length: 256
}, true, ['encrypt']),
subtle.deriveKey({
name, public: bob.publicKey
}, alice.privateKey, {
name: 'AES-CBC',
length: 256
}, true, ['encrypt']),
]);
const [raw1, raw2] = await Promise.all([
subtle.exportKey('raw', secret1),
subtle.exportKey('raw', secret2),
]);
assert.deepStrictEqual(raw1, raw2);
}
test('X25519').then(common.mustCall());
test('X448').then(common.mustCall());
}

View File

@ -1,481 +0,0 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const {
generateKeyPairSync,
webcrypto: { subtle }
} = require('crypto');
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/
}).then(common.mustCall());
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/
}).then(common.mustCall());
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 rawPublicKey = await subtle.exportKey('raw', publicKey);
assert.deepStrictEqual(Buffer.from(rawPublicKey), vector.publicKey);
assert.rejects(subtle.exportKey('raw', privateKey), {
message: new RegExp(`Unable to export a raw ${namedCurve} private key`)
}).then(common.mustCall());
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/
}).then(common.mustCall());
assert.rejects(
subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'NODE-X448'
},
true,
['sign', 'verify']),
{
message: /Unsupported named curves for ECDSA/
}).then(common.mustCall());
assert.rejects(
subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'NODE-ED25519'
},
true,
['sign', 'verify']),
{
message: /Unsupported named curves for ECDSA/
}).then(common.mustCall());
assert.rejects(
subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'NODE-ED448'
},
true,
['sign', 'verify']),
{
message: /Unsupported named curves for ECDSA/
}).then(common.mustCall());
{
for (const asymmetricKeyType of ['ed25519', 'ed448']) {
const { publicKey, privateKey } = generateKeyPairSync(asymmetricKeyType);
for (const keyObject of [publicKey, privateKey]) {
const namedCurve = `NODE-${asymmetricKeyType.toUpperCase()}`;
subtle.importKey(
'node.keyObject',
keyObject,
{ name: namedCurve, namedCurve },
true,
keyObject.type === 'private' ? ['sign'] : ['verify'],
).then((cryptoKey) => {
assert.strictEqual(cryptoKey.type, keyObject.type);
assert.strictEqual(cryptoKey.algorithm.name, namedCurve);
}, common.mustNotCall());
subtle.importKey(
keyObject.type === 'private' ? 'pkcs8' : 'spki',
keyObject.export({
format: 'der',
type: keyObject.type === 'private' ? 'pkcs8' : 'spki',
}),
{ name: namedCurve, namedCurve },
true,
keyObject.type === 'private' ? ['sign'] : ['verify'],
).then((cryptoKey) => {
assert.strictEqual(cryptoKey.type, keyObject.type);
assert.strictEqual(cryptoKey.algorithm.name, namedCurve);
assert.strictEqual(cryptoKey.algorithm.namedCurve, namedCurve);
}, common.mustNotCall());
assert.rejects(
subtle.importKey(
'node.keyObject',
keyObject,
{
name: 'ECDSA',
namedCurve,
},
true,
keyObject.type === 'private' ? ['sign'] : ['verify']
),
{
message: /Invalid algorithm name/
}).then(common.mustCall());
assert.rejects(
subtle.importKey(
'node.keyObject',
keyObject,
{
name: 'ECDH',
namedCurve,
},
true,
keyObject.type === 'private' ? ['deriveBits', 'deriveKey'] : [],
),
{
message: /Invalid algorithm name/
}).then(common.mustCall());
}
}
}
{
// See: https://github.com/nodejs/node/pull/40300
for (const namedCurve of ['NODE-ED25519', 'NODE-ED448']) {
assert.rejects(
(async () => {
const { privateKey } = await generateKey(namedCurve);
return subtle.sign(
{
name: namedCurve,
hash: 'SHA-256'
},
privateKey,
Buffer.from('abc')
);
})(),
(err) => {
assert.strictEqual(err.message, `Hash is not permitted for ${namedCurve}`);
assert(err instanceof DOMException);
return true;
}).then(common.mustCall());
assert.rejects(
(async () => {
const { publicKey, privateKey } = await generateKey(namedCurve);
const signature = await subtle.sign(
{
name: namedCurve,
},
privateKey,
Buffer.from('abc')
).catch(common.mustNotCall());
return subtle.verify(
{
name: namedCurve,
hash: 'SHA-256',
},
publicKey,
signature,
Buffer.from('abc')
);
})(),
(err) => {
assert.strictEqual(err.message, `Hash is not permitted for ${namedCurve}`);
assert(err instanceof DOMException);
return true;
}).then(common.mustCall());
}
}

View File

@ -0,0 +1,308 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const crypto = require('crypto');
const { subtle } = crypto.webcrypto;
const keyData = {
'Ed25519': {
jwsAlg: 'EdDSA',
spki: Buffer.from(
'302a300506032b6570032100a054b618c12b26c8d43595a5c38dd2b0140b944a' +
'151f75003278c2b6c58ec08f', 'hex'),
pkcs8: Buffer.from(
'302e020100300506032b657004220420d53150bdcd17b4d4b21ae756d4965639' +
'd75b28f56ff9111b1f88326913e445bc', 'hex'),
jwk: {
kty: 'OKP',
crv: 'Ed25519',
x: 'oFS2GMErJsjUNZWlw43SsBQLlEoVH3UAMnjCtsWOwI8',
d: '1TFQvc0XtNSyGudW1JZWOddbKPVv-REbH4gyaRPkRbw'
}
},
'Ed448': {
jwsAlg: 'EdDSA',
spki: Buffer.from(
'3043300506032b6571033a0008cc38160c85bca5656ac4924af7ea97a9161b20' +
'2528273dcb84afd2eeb99ac912a401b34ef15ef4d9486406a6eecc31e5909219' +
'bd54866800', 'hex'),
pkcs8: Buffer.from(
'3047020100300506032b6571043b0439afd05b2fbb153b47c18dfa66baaed0de' +
'fb4e88c651487cdee0fafc40fa3d048fe1cd145a44143243c0468166b5bc161a' +
'82e3b904f3e2fcaaf9', 'hex'),
jwk: {
kty: 'OKP',
crv: 'Ed448',
x: 'CMw4FgyFvKVlasSSSvfql6kWGyAlKCc9y4Sv0u65mskSpAGzTvFe9NlIZAam7' +
'swx5ZCSGb1UhmgA',
d: 'r9BbL7sVO0fBjfpmuq7Q3vtOiMZRSHze4Pr8QPo9BI_hzRRaRBQyQ8BGgWa1v' +
'BYaguO5BPPi_Kr5'
}
},
'X25519': {
jwsAlg: 'ECDH-ES',
spki: Buffer.from(
'302a300506032b656e032100f38d9f4e621a44e0428176a4c8a534b34f07f8db' +
'30152f9ca0167aabf598fe65', 'hex'),
pkcs8: Buffer.from(
'302e020100300506032b656e04220420a8327850317b4b03a5a8b4e923413b1d' +
'a4a642e0d6f7a72cf4d16a549e628a5f', 'hex'),
jwk: {
kty: 'OKP',
crv: 'X25519',
x: '842fTmIaROBCgXakyKU0s08H-NswFS-coBZ6q_WY_mU',
d: 'qDJ4UDF7SwOlqLTpI0E7HaSmQuDW96cs9NFqVJ5iil8'
}
},
'X448': {
jwsAlg: 'ECDH-ES',
spki: Buffer.from(
'3042300506032b656f0339001d451c8c0c369a42eadfc2875cd44953caeb46c4' +
'66dc86568280bfdbbb01f4709a1b0b1e0dd66cf7b11c84119ddc98890db72891' +
'29e30da4', 'hex'),
pkcs8: Buffer.from(
'3046020100300506032b656f043a0438fc818f6546a81f963c27765dc1c05bfd' +
'b169667e5e0cf45318ed1cb93872217ab0d9004e0c7dd0dcb00192f72039cc1a' +
'1dff750ec31c8afb', 'hex'),
jwk: {
kty: 'OKP',
crv: 'X448',
x: 'HUUcjAw2mkLq38KHXNRJU8rrRsRm3IZWgoC_27sB9HCaGwseDdZs97EchBGd3' +
'JiJDbcokSnjDaQ',
d: '_IGPZUaoH5Y8J3ZdwcBb_bFpZn5eDPRTGO0cuThyIXqw2QBODH3Q3LABkvcgO' +
'cwaHf91DsMcivs'
}
}
};
const testVectors = [
{
name: 'Ed25519',
privateUsages: ['sign'],
publicUsages: ['verify']
},
{
name: 'Ed448',
privateUsages: ['sign'],
publicUsages: ['verify']
},
{
name: 'X25519',
privateUsages: ['deriveKey', 'deriveBits'],
publicUsages: []
},
{
name: 'X448',
privateUsages: ['deriveKey', 'deriveBits'],
publicUsages: []
},
];
async function testImportSpki({ name, publicUsages }, extractable) {
const key = await subtle.importKey(
'spki',
keyData[name].spki,
{ name },
extractable,
publicUsages);
assert.strictEqual(key.type, 'public');
assert.strictEqual(key.extractable, extractable);
assert.deepStrictEqual(key.usages, publicUsages);
assert.deepStrictEqual(key.algorithm.name, name);
if (extractable) {
// Test the roundtrip
const spki = await subtle.exportKey('spki', key);
assert.strictEqual(
Buffer.from(spki).toString('hex'),
keyData[name].spki.toString('hex'));
} else {
await assert.rejects(
subtle.exportKey('spki', key), {
message: /key is not extractable/
});
}
// Bad usage
await assert.rejects(
subtle.importKey(
'spki',
keyData[name].spki,
{ name },
extractable,
['wrapKey']),
{ message: /Unsupported key usage/ });
}
async function testImportPkcs8({ name, privateUsages }, extractable) {
const key = await subtle.importKey(
'pkcs8',
keyData[name].pkcs8,
{ name },
extractable,
privateUsages);
assert.strictEqual(key.type, 'private');
assert.strictEqual(key.extractable, extractable);
assert.deepStrictEqual(key.usages, privateUsages);
assert.deepStrictEqual(key.algorithm.name, name);
if (extractable) {
// Test the roundtrip
const pkcs8 = await subtle.exportKey('pkcs8', key);
assert.strictEqual(
Buffer.from(pkcs8).toString('hex'),
keyData[name].pkcs8.toString('hex'));
} else {
await assert.rejects(
subtle.exportKey('pkcs8', key), {
message: /key is not extractable/
});
}
}
async function testImportJwk({ name, publicUsages, privateUsages }, extractable) {
const jwk = keyData[name].jwk;
const [
publicKey,
privateKey,
] = await Promise.all([
subtle.importKey(
'jwk',
{
kty: jwk.kty,
crv: jwk.crv,
x: jwk.x,
},
{ name },
extractable, publicUsages),
subtle.importKey(
'jwk',
jwk,
{ name },
extractable,
privateUsages),
subtle.importKey(
'jwk',
{
alg: keyData[name].jwsAlg,
kty: jwk.kty,
crv: jwk.crv,
x: jwk.x,
},
{ name },
extractable, publicUsages),
subtle.importKey(
'jwk',
{
...jwk,
alg: keyData[name].jwsAlg,
},
{ name },
extractable,
privateUsages),
]);
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(publicKey.extractable, extractable);
assert.strictEqual(privateKey.extractable, extractable);
assert.deepStrictEqual(publicKey.usages, publicUsages);
assert.deepStrictEqual(privateKey.usages, privateUsages);
assert.strictEqual(publicKey.algorithm.name, name);
assert.strictEqual(privateKey.algorithm.name, name);
if (extractable) {
// Test the round trip
const [
pubJwk,
pvtJwk,
] = await Promise.all([
subtle.exportKey('jwk', publicKey),
subtle.exportKey('jwk', privateKey),
]);
assert.deepStrictEqual(pubJwk.key_ops, publicUsages);
assert.strictEqual(pubJwk.ext, true);
assert.strictEqual(pubJwk.kty, 'OKP');
assert.strictEqual(pubJwk.x, jwk.x);
assert.strictEqual(pubJwk.crv, jwk.crv);
assert.deepStrictEqual(pvtJwk.key_ops, privateUsages);
assert.strictEqual(pvtJwk.ext, true);
assert.strictEqual(pvtJwk.kty, 'OKP');
assert.strictEqual(pvtJwk.x, jwk.x);
assert.strictEqual(pvtJwk.crv, jwk.crv);
assert.strictEqual(pvtJwk.d, jwk.d);
if (jwk.crv.startsWith('Ed')) {
assert.strictEqual(pubJwk.alg, 'EdDSA');
assert.strictEqual(pvtJwk.alg, 'EdDSA');
} else {
assert.strictEqual(pubJwk.alg, undefined);
assert.strictEqual(pvtJwk.alg, undefined);
}
} else {
await assert.rejects(
subtle.exportKey('jwk', publicKey), {
message: /key is not extractable/
});
await assert.rejects(
subtle.exportKey('jwk', privateKey), {
message: /key is not extractable/
});
}
}
(async function() {
const tests = [];
testVectors.forEach((vector) => {
[true, false].forEach((extractable) => {
tests.push(testImportSpki(vector, extractable));
tests.push(testImportPkcs8(vector, extractable));
tests.push(testImportJwk(vector, extractable));
});
});
await Promise.all(tests);
})().then(common.mustCall());
{
const rsaPublic = crypto.createPublicKey(
fixtures.readKey('rsa_public_2048.pem'));
const rsaPrivate = crypto.createPrivateKey(
fixtures.readKey('rsa_private_2048.pem'));
for (const [name, [publicUsage, privateUsage]] of Object.entries({
'Ed25519': ['verify', 'sign'],
'X448': ['deriveBits', 'deriveBits'],
})) {
assert.rejects(subtle.importKey(
'node.keyObject',
rsaPublic,
{ name },
true, [publicUsage]), { message: /Invalid key type/ });
assert.rejects(subtle.importKey(
'node.keyObject',
rsaPrivate,
{ name },
true, [privateUsage]), { message: /Invalid key type/ });
assert.rejects(subtle.importKey(
'spki',
rsaPublic.export({ format: 'der', type: 'spki' }),
{ name },
true, [publicUsage]), { message: /Invalid key type/ });
assert.rejects(subtle.importKey(
'pkcs8',
rsaPrivate.export({ format: 'der', type: 'pkcs8' }),
{ name },
true, [privateUsage]), { message: /Invalid key type/ });
}
}

View File

@ -129,6 +129,40 @@ const vectors = {
'deriveBits',
]
},
'Ed25519': {
usages: [
'sign',
'verify',
],
mandatoryUsages: ['sign']
},
'Ed448': {
usages: [
'sign',
'verify',
],
mandatoryUsages: ['sign']
},
'X25519': {
usages: [
'deriveKey',
'deriveBits',
],
mandatoryUsages: [
'deriveKey',
'deriveBits',
]
},
'X448': {
usages: [
'deriveKey',
'deriveBits',
],
mandatoryUsages: [
'deriveKey',
'deriveBits',
]
},
'NODE-DSA': {
algorithm: { modulusLength: 1024, hash: 'SHA-256' },
usages: [
@ -665,3 +699,63 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' });
assert(!isCryptoKey(buffer));
assert(!isCryptoKey(keyObject));
}
// Test OKP Key Generation
{
async function test(
name,
privateUsages,
publicUsages = privateUsages) {
let usages = privateUsages;
if (publicUsages !== privateUsages)
usages = usages.concat(publicUsages);
const { publicKey, privateKey } = await subtle.generateKey({
name,
}, true, usages);
assert(publicKey);
assert(privateKey);
assert(isCryptoKey(publicKey));
assert(isCryptoKey(privateKey));
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(publicKey.extractable, true);
assert.strictEqual(privateKey.extractable, true);
assert.deepStrictEqual(publicKey.usages, publicUsages);
assert.deepStrictEqual(privateKey.usages, privateUsages);
assert.strictEqual(publicKey.algorithm.name, name);
assert.strictEqual(privateKey.algorithm.name, name);
}
const kTests = [
[
'Ed25519',
['sign'],
['verify'],
],
[
'Ed448',
['sign'],
['verify'],
],
[
'X25519',
['deriveKey', 'deriveBits'],
[],
],
[
'X448',
['deriveKey', 'deriveBits'],
[],
],
];
const tests = kTests.map((args) => test(...args));
// Test bad parameters
Promise.all(tests).then(common.mustCall());
}

View File

@ -23,6 +23,7 @@ async function testVerify({ name,
privateKey,
hmacKey,
rsaKeys,
okpKeys,
] = await Promise.all([
subtle.importKey(
'spki',
@ -55,6 +56,12 @@ async function testVerify({ name,
},
false,
['sign']),
subtle.generateKey(
{
name: 'Ed25519',
},
false,
['sign']),
]);
assert(await subtle.verify({ name, hash }, publicKey, signature, plaintext));
@ -89,6 +96,11 @@ async function testVerify({ name,
message: /Unable to use this key to verify/
});
await assert.rejects(
subtle.verify({ name, hash }, okpKeys.publicKey, signature, plaintext), {
message: /Unable to use this key to verify/
});
// Test failure when signature is altered
{
const copy = Buffer.from(signature);
@ -140,6 +152,7 @@ async function testSign({ name,
privateKey,
hmacKey,
rsaKeys,
okpKeys,
] = await Promise.all([
subtle.importKey(
'spki',
@ -172,6 +185,12 @@ async function testSign({ name,
},
false,
['sign']),
subtle.generateKey(
{
name: 'Ed25519',
},
false,
['sign']),
]);
{
@ -210,6 +229,11 @@ async function testSign({ name,
subtle.sign({ name, hash }, rsaKeys.privateKey, plaintext), {
message: /Unable to use this key to sign/
});
await assert.rejects(
subtle.sign({ name, hash }, okpKeys.privateKey, plaintext), {
message: /Unable to use this key to sign/
});
}
(async function() {

View File

@ -0,0 +1,263 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { subtle } = require('crypto').webcrypto;
const vectors = require('../fixtures/crypto/eddsa')();
async function testVerify({ name,
publicKeyBuffer,
privateKeyBuffer,
signature,
data }) {
const [
publicKey,
noVerifyPublicKey,
privateKey,
hmacKey,
rsaKeys,
ecKeys,
] = await Promise.all([
subtle.importKey(
'spki',
publicKeyBuffer,
{ name },
false,
['verify']),
subtle.importKey(
'spki',
publicKeyBuffer,
{ name },
false,
[ /* No usages */ ]),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name },
false,
['sign']),
subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']),
subtle.generateKey(
{
name: 'RSA-PSS',
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
false,
['sign']),
subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-256'
},
false,
['sign']),
]);
assert(await subtle.verify({ name }, publicKey, signature, data));
// Test verification with altered buffers
const copy = Buffer.from(data);
const sigcopy = Buffer.from(signature);
const p = subtle.verify({ name }, publicKey, sigcopy, copy);
copy[0] = 255 - copy[0];
sigcopy[0] = 255 - sigcopy[0];
assert(await p);
// Test failure when using wrong key
await assert.rejects(
subtle.verify({ name }, privateKey, signature, data), {
message: /Unable to use this key to verify/
});
await assert.rejects(
subtle.verify({ name }, noVerifyPublicKey, signature, data), {
message: /Unable to use this key to verify/
});
// Test failure when using the wrong algorithms
await assert.rejects(
subtle.verify({ name }, hmacKey, signature, data), {
message: /Unable to use this key to verify/
});
await assert.rejects(
subtle.verify({ name }, rsaKeys.publicKey, signature, data), {
message: /Unable to use this key to verify/
});
await assert.rejects(
subtle.verify({ name }, ecKeys.publicKey, signature, data), {
message: /Unable to use this key to verify/
});
// Test failure when signature is altered
{
const copy = Buffer.from(signature);
copy[0] = 255 - copy[0];
assert(!(await subtle.verify(
{ name },
publicKey,
copy,
data)));
assert(!(await subtle.verify(
{ name },
publicKey,
copy.slice(1),
data)));
}
// Test failure when data is altered
{
const copy = Buffer.from(data);
copy[0] = 255 - copy[0];
assert(!(await subtle.verify({ name }, publicKey, signature, copy)));
}
}
async function testSign({ name,
publicKeyBuffer,
privateKeyBuffer,
signature,
data }) {
const [
publicKey,
noSignPrivateKey,
privateKey,
hmacKey,
rsaKeys,
ecKeys,
] = await Promise.all([
subtle.importKey(
'spki',
publicKeyBuffer,
{ name },
false,
['verify']),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name },
false,
[ /* No usages */ ]),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name },
false,
['sign']),
subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']),
subtle.generateKey(
{
name: 'RSA-PSS',
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
false,
['sign']),
subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-256'
},
false,
['sign']),
]);
{
const sig = await subtle.sign({ name }, privateKey, data);
assert.strictEqual(sig.byteLength, signature.byteLength);
assert(await subtle.verify({ name }, publicKey, sig, data));
}
{
const copy = Buffer.from(data);
const p = subtle.sign({ name }, privateKey, copy);
copy[0] = 255 - copy[0];
const sig = await p;
assert(await subtle.verify({ name }, publicKey, sig, data));
}
// Test failure when using wrong key
await assert.rejects(
subtle.sign({ name }, publicKey, data), {
message: /Unable to use this key to sign/
});
// Test failure when no sign usage
await assert.rejects(
subtle.sign({ name }, noSignPrivateKey, data), {
message: /Unable to use this key to sign/
});
// Test failure when using the wrong algorithms
await assert.rejects(
subtle.sign({ name }, hmacKey, data), {
message: /Unable to use this key to sign/
});
await assert.rejects(
subtle.sign({ name }, rsaKeys.privateKey, data), {
message: /Unable to use this key to sign/
});
await assert.rejects(
subtle.sign({ name }, ecKeys.privateKey, data), {
message: /Unable to use this key to sign/
});
}
(async function() {
const variations = [];
vectors.forEach((vector) => {
variations.push(testVerify(vector));
variations.push(testSign(vector));
});
await Promise.all(variations);
})().then(common.mustCall());
// Ed448 context
{
const vector = vectors.find(({ name }) => name === 'Ed448');
Promise.all([
subtle.importKey(
'pkcs8',
vector.privateKeyBuffer,
{ name: 'Ed448' },
false,
['sign']),
subtle.importKey(
'spki',
vector.publicKeyBuffer,
{ name: 'Ed448' },
false,
['verify']),
]).then(async ([privateKey, publicKey]) => {
const sig = await subtle.sign({ name: 'Ed448', context: Buffer.alloc(0) }, privateKey, vector.data);
assert.deepStrictEqual(Buffer.from(sig), vector.signature);
assert.strictEqual(
await subtle.verify({ name: 'Ed448', context: Buffer.alloc(0) }, publicKey, sig, vector.data), true);
await assert.rejects(subtle.sign({ name: 'Ed448', context: Buffer.alloc(1) }, privateKey, vector.data), {
message: /Non zero-length context is not yet supported/
});
await assert.rejects(subtle.verify({ name: 'Ed448', context: Buffer.alloc(1) }, publicKey, sig, vector.data), {
message: /Non zero-length context is not yet supported/
});
}).then(common.mustCall());
}

View File

@ -104,3 +104,43 @@ const { subtle } = require('crypto').webcrypto;
test('hello world').then(common.mustCall());
}
// Test Sign/Verify Ed25519
{
async function test(data) {
const ec = new TextEncoder();
const { publicKey, privateKey } = await subtle.generateKey({
name: 'Ed25519',
}, true, ['sign', 'verify']);
const signature = await subtle.sign({
name: 'Ed25519',
}, privateKey, ec.encode(data));
assert(await subtle.verify({
name: 'Ed25519',
}, publicKey, signature, ec.encode(data)));
}
test('hello world').then(common.mustCall());
}
// Test Sign/Verify Ed448
{
async function test(data) {
const ec = new TextEncoder();
const { publicKey, privateKey } = await subtle.generateKey({
name: 'Ed448',
}, true, ['sign', 'verify']);
const signature = await subtle.sign({
name: 'Ed448',
}, privateKey, ec.encode(data));
assert(await subtle.verify({
name: 'Ed448',
}, publicKey, signature, ec.encode(data)));
}
test('hello world').then(common.mustCall());
}

View File

@ -1,315 +0,0 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const {
generateKeyPairSync,
webcrypto: { subtle }
} = require('crypto');
// 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/
}).then(common.mustCall());
assert.rejects(importKey('NODE-X448', Buffer.alloc(10), true), {
message: /NODE-X448 raw keys must be exactly 56-bytes/
}).then(common.mustCall());
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/
}).then(common.mustCall());
assert.rejects(
subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'NODE-ED448'
},
true,
['deriveBits']),
{
message: /Unsupported named curves for ECDH/
}).then(common.mustCall());
{
// Private JWK import
subtle.importKey(
'jwk',
{
crv: 'X25519',
d: '8CE-XY7cvbR-Pu7mILHq8YZ4hLGAA2-RD01he5q2wUA',
x: '42IbTo34ZYANub5o42547vB6OxdEd44ztwZewoRch0Q',
kty: 'OKP'
},
{
name: 'ECDH',
namedCurve: 'NODE-X25519'
},
true,
['deriveBits']).then(common.mustCall(), common.mustNotCall());
// Public JWK import
subtle.importKey(
'jwk',
{
crv: 'X25519',
x: '42IbTo34ZYANub5o42547vB6OxdEd44ztwZewoRch0Q',
kty: 'OKP'
},
{
name: 'ECDH',
namedCurve: 'NODE-X25519'
},
true,
[]).then(common.mustCall(), common.mustNotCall());
for (const asymmetricKeyType of ['x25519', 'x448']) {
const { publicKey, privateKey } = generateKeyPairSync(asymmetricKeyType);
for (const keyObject of [publicKey, privateKey]) {
const namedCurve = `NODE-${asymmetricKeyType.toUpperCase()}`;
subtle.importKey(
'node.keyObject',
keyObject,
{ name: 'ECDH', namedCurve },
true,
keyObject.type === 'private' ? ['deriveBits', 'deriveKey'] : [],
).then((cryptoKey) => {
assert.strictEqual(cryptoKey.type, keyObject.type);
assert.strictEqual(cryptoKey.algorithm.name, 'ECDH');
}, common.mustNotCall());
subtle.importKey(
keyObject.type === 'private' ? 'pkcs8' : 'spki',
keyObject.export({
format: 'der',
type: keyObject.type === 'private' ? 'pkcs8' : 'spki',
}),
{ name: 'ECDH', namedCurve },
true,
keyObject.type === 'private' ? ['deriveBits'] : [],
).then((cryptoKey) => {
assert.strictEqual(cryptoKey.type, keyObject.type);
assert.strictEqual(cryptoKey.algorithm.name, 'ECDH');
assert.strictEqual(cryptoKey.algorithm.namedCurve, namedCurve);
}, common.mustNotCall());
}
}
}

View File

@ -84,10 +84,10 @@ const customTypesMap = {
'Crypto': 'webcrypto.html#class-crypto',
'SubtleCrypto': 'webcrypto.html#class-subtlecrypto',
'RsaOaepParams': 'webcrypto.html#class-rsaoaepparams',
'AlgorithmIdentifier': 'webcrypto.html#class-algorithmidentifier',
'AesCtrParams': 'webcrypto.html#class-aesctrparams',
'AesCbcParams': 'webcrypto.html#class-aescbcparams',
'AesGcmParams': 'webcrypto.html#class-aesgcmparams',
'AesKwParams': 'webcrypto.html#class-aeskwparams',
'EcdhKeyDeriveParams': 'webcrypto.html#class-ecdhkeyderiveparams',
'HkdfParams': 'webcrypto.html#class-hkdfparams',
'Pbkdf2Params': 'webcrypto.html#class-pbkdf2params',
@ -100,26 +100,15 @@ const customTypesMap = {
'webcrypto.html#class-rsahashedimportparams',
'EcKeyImportParams': 'webcrypto.html#class-eckeyimportparams',
'HmacImportParams': 'webcrypto.html#class-hmacimportparams',
'AesImportParams': 'webcrypto.html#class-aesimportparams',
'Pbkdf2ImportParams': 'webcrypto.html#class-pbkdf2importparams',
'HmacParams': 'webcrypto.html#class-hmacparams',
'EcdsaParams': 'webcrypto.html#class-ecdsaparams',
'RsaPssParams': 'webcrypto.html#class-rsapssparams',
'RsaSignParams': 'webcrypto.html#class-rsasignparams',
'NodeDhImportParams': 'webcrypto.html#class-nodedhimportparams',
'Ed448Params': 'webcrypto.html#class-ed448params',
'NodeDhKeyGenParams': 'webcrypto.html#class-nodedhkeygenparams',
'NodeDhDeriveBitsParams':
'webcrypto.html#class-nodedhderivebitsparams',
'NodeDsaImportParams': 'webcrypto.html#class-nodedsaimportparams',
'NodeDsaKeyGenParams': 'webcrypto.html#class-nodedsakeygenparams',
'NodeDsaSignParams': 'webcrypto.html#class-nodedsasignparams',
'NodeScryptImportParams':
'webcrypto.html#class-nodescryptimportparams',
'NodeScryptParams': 'webcrypto.html#class-nodescryptparams',
'NodeEdKeyImportParams':
'webcrypto.html#class-nodeedkeyimportparams',
'NodeEdKeyGenParams':
'webcrypto.html#class-nodeedkeygenparams',
'dgram.Socket': 'dgram.html#class-dgramsocket',