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:
parent
dfa896f926
commit
7e5da97d15
@ -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
369
lib/internal/crypto/cfrg.js
Normal 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,
|
||||
};
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
51
test/fixtures/crypto/eddsa.js
vendored
Normal 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;
|
||||
}
|
@ -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.
|
||||
|
213
test/parallel/test-webcrypto-derivebits-cfrg.js
Normal file
213
test/parallel/test-webcrypto-derivebits-cfrg.js
Normal 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());
|
@ -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/
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
188
test/parallel/test-webcrypto-derivekey-cfrg.js
Normal file
188
test/parallel/test-webcrypto-derivekey-cfrg.js
Normal 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());
|
@ -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/ });
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
308
test/parallel/test-webcrypto-export-import-cfrg.js
Normal file
308
test/parallel/test-webcrypto-export-import-cfrg.js
Normal 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/ });
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
@ -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() {
|
||||
|
263
test/parallel/test-webcrypto-sign-verify-eddsa.js
Normal file
263
test/parallel/test-webcrypto-sign-verify-eddsa.js
Normal 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());
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user