diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 290c3a816fe310..c6ef072e051a9a 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -2927,6 +2927,7 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm); const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap); const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap); const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap); +const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305); bool Cipher::isGcmMode() const { if (!cipher_) return false; diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 3278d5612eb11b..42f9cf6aa2a246 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -373,6 +373,7 @@ class Cipher final { static const Cipher AES_128_KW; static const Cipher AES_192_KW; static const Cipher AES_256_KW; + static const Cipher CHACHA20_POLY1305; struct CipherParams { int padding; diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index b2506dee18c3c3..4eade7ec621962 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -2,6 +2,18 @@ +### Static method: `SubtleCrypto.supports(operation, algorithm[, lengthOrAdditionalAlgorithm])` + + + +> Stability: 1.1 - Active development + + + +* `operation` {string} "encrypt", "decrypt", "sign", "verify", "digest", "generateKey", "deriveKey", "deriveBits", "importKey", "exportKey", "getPublicKey", "wrapKey", or "unwrapKey" +* `algorithm` {string|Algorithm} +* `lengthOrAdditionalAlgorithm` {null|number|string|Algorithm|undefined} Depending on the operation this is either ignored, the value of the length argument when operation is "deriveBits", the algorithm of key to be derived when operation is "deriveKey", the algorithm of key to be exported before wrapping when operation is "wrapKey", or the algorithm of key to be imported after unwrapping when operation is "unwrapKey". **Default:** `null` when operation is "deriveBits", `undefined` otherwise. +* Returns: {boolean} Indicating whether the implementation supports the given operation + + + +Allows feature detection in Web Crypto API, +which can be used to detect whether a given algorithm identifier +(including its parameters) is supported for the given operation. + ### `subtle.decrypt(algorithm, key, data)` -* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * `key` {CryptoKey} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -568,10 +732,11 @@ an {ArrayBuffer} containing the plaintext result. The algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` ### `subtle.deriveBits(algorithm, baseKey[, length])` @@ -606,7 +771,7 @@ material provided by `baseKey`, `subtle.deriveBits()` attempts to generate `length` bits. When `length` is not provided or `null` the maximum number of bits for a given -algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'` +algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'`[^secure-curves] algorithms, for other algorithms `length` is required to be a number. If successful, the returned promise will be resolved with an {ArrayBuffer} @@ -615,10 +780,10 @@ containing the generated data. The algorithms currently supported include: * `'ECDH'` -* `'X25519'` -* `'X448'` [^1] * `'HKDF'` * `'PBKDF2'` +* `'X25519'` +* `'X448'`[^secure-curves] ### `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)` @@ -655,18 +820,25 @@ generate raw keying material, then passing the result into the The algorithms currently supported include: * `'ECDH'` -* `'X25519'` -* `'X448'` [^1] * `'HKDF'` * `'PBKDF2'` +* `'X25519'` +* `'X448'`[^secure-curves] ### `subtle.digest(algorithm, data)` -* `algorithm` {string|Algorithm} +* `algorithm` {string|Algorithm|CShakeParams} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -676,10 +848,15 @@ with an {ArrayBuffer} containing the computed digest. If `algorithm` is provided as a {string}, it must be one of: +* `'cSHAKE128'`[^modern-algos] +* `'cSHAKE256'`[^modern-algos] * `'SHA-1'` * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If `algorithm` is provided as an {Object}, it must have a `name` property whose value is one of the above. @@ -688,9 +865,13 @@ whose value is one of the above. -* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * `key` {CryptoKey} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -702,16 +883,23 @@ containing the encrypted result. The algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` ### `subtle.exportKey(format, key)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `key` {CryptoKey} * Returns: {Promise} Fulfills with an {ArrayBuffer|Object} upon success. @@ -739,25 +928,50 @@ When `format` is `'jwk'` and the export is successful, the returned promise will be resolved with a JavaScript object conforming to the [JSON Web Key][] specification. -| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | -| ------------------------------------------------------- | -------- | --------- | ------- | ------- | -| `'AES-CBC'` | | | ✔ | ✔ | -| `'AES-CTR'` | | | ✔ | ✔ | -| `'AES-GCM'` | | | ✔ | ✔ | -| `'AES-KW'` | | | ✔ | ✔ | -| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | -| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed448'` [^1] | ✔ | ✔ | ✔ | ✔ | -| `'HMAC'` | | | ✔ | ✔ | -| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | -| `'RSA-PSS'` | ✔ | ✔ | ✔ | | -| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | +| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` | +| ------------------------------------ | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ | +| `'AES-CBC'` | | | ✔ | ✔ | ✔ | | | +| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | | +| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | | +| `'AES-KW'` | | | ✔ | ✔ | ✔ | | | +| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | | +| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'HMAC'` | | | ✔ | ✔ | ✔ | | | +| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | | +| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | | +| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | | + +### `subtle.getPublicKey(key, keyUsages)` + + + +> Stability: 1.1 - Active development + +* `key` {CryptoKey} A private key from which to derive the corresponding public key. +* `keyUsages` {string\[]} See [Key usages][]. +* Returns: {Promise} Fulfills with a {CryptoKey} upon success. + +Derives the public key from a given private key. ### `subtle.generateKey(algorithm, extractable, keyUsages)` @@ -777,29 +991,39 @@ may generate either a single {CryptoKey} or a {CryptoKeyPair}. The {CryptoKeyPair} (public and private key) generating algorithms supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` -* `'RSA-OAEP'` +* `'ECDH'` * `'ECDSA'` * `'Ed25519'` -* `'Ed448'` [^1] -* `'ECDH'` +* `'Ed448'`[^secure-curves] +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'RSA-OAEP'` +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` * `'X25519'` -* `'X448'` [^1] +* `'X448'`[^secure-curves] The {CryptoKey} (secret key) generating algorithms supported include: -* `'HMAC'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'HMAC'` ### `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `keyData` {ArrayBuffer|TypedArray|DataView|Buffer|Object} @@ -833,30 +1058,37 @@ If importing a `'PBKDF2'` key, `extractable` must be `false`. The algorithms currently supported include: -| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | -| ------------------------------------------------------- | -------- | --------- | ------- | ------- | -| `'AES-CBC'` | | | ✔ | ✔ | -| `'AES-CTR'` | | | ✔ | ✔ | -| `'AES-GCM'` | | | ✔ | ✔ | -| `'AES-KW'` | | | ✔ | ✔ | -| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | -| `'X25519'` | ✔ | ✔ | ✔ | ✔ | -| `'X448'` [^1] | ✔ | ✔ | ✔ | ✔ | -| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed448'` [^1] | ✔ | ✔ | ✔ | ✔ | -| `'HDKF'` | | | | ✔ | -| `'HMAC'` | | | ✔ | ✔ | -| `'PBKDF2'` | | | | ✔ | -| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | -| `'RSA-PSS'` | ✔ | ✔ | ✔ | | -| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | +| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` | +| ------------------------------------ | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ | +| `'AES-CBC'` | | | ✔ | ✔ | ✔ | | | +| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | | +| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | | +| `'AES-KW'` | | | ✔ | ✔ | ✔ | | | +| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | | +| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'HDKF'` | | | | ✔ | ✔ | | | +| `'HMAC'` | | | ✔ | ✔ | ✔ | | | +| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'PBKDF2'` | | | | ✔ | ✔ | | | +| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | | +| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | | +| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | | +| `'X25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'X448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | ### `subtle.sign(algorithm, key, data)` -* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params} +* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams} * `key` {CryptoKey} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -880,26 +1112,34 @@ an {ArrayBuffer} containing the generated signature. The algorithms currently supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` * `'ECDSA'` * `'Ed25519'` -* `'Ed448'` [^1] +* `'Ed448'`[^secure-curves] * `'HMAC'` +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` ### `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `wrappedKey` {ArrayBuffer|TypedArray|DataView|Buffer} * `unwrappingKey` {CryptoKey} -* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * `unwrappedKeyAlgo` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams} @@ -919,34 +1159,42 @@ promise is resolved with a {CryptoKey} object. The wrapping algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` The unwrapped key algorithms supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` -* `'RSA-OAEP'` -* `'ECDSA'` -* `'Ed25519'` -* `'Ed448'` [^1] -* `'ECDH'` -* `'X25519'` -* `'X448'` [^1] -* `'HMAC'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'ECDH'` +* `'ECDSA'` +* `'Ed25519'` +* `'Ed448'`[^secure-curves] +* `'HMAC'` +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'RSA-OAEP'` +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` +* `'X25519'` +* `'X448'`[^secure-curves] ### `subtle.verify(algorithm, key, signature, data)` -* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params} +* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams} * `key` {CryptoKey} * `signature` {ArrayBuffer|TypedArray|DataView|Buffer} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} @@ -971,25 +1219,33 @@ with either `true` or `false`. The algorithms currently supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` * `'ECDSA'` * `'Ed25519'` -* `'Ed448'` [^1] +* `'Ed448'`[^secure-curves] * `'HMAC'` +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` ### `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `key` {CryptoKey} * `wrappingKey` {CryptoKey} -* `wrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `wrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -1006,11 +1262,12 @@ containing the encrypted key data. The wrapping algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` ## Algorithm parameters @@ -1032,6 +1289,50 @@ added: v15.0.0 * Type: {string} +### Class: `AeadParams` + + + +#### `aeadParams.additionalData` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +Extra input that is not encrypted but is included in the authentication +of the data. The use of `additionalData` is optional. + +#### `aeadParams.iv` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer} + +The initialization vector must be unique for every encryption operation using a +given key. + +#### `aeadParams.name` + + + +* Type: {string} Must be `'AES-GCM'` or `'ChaCha20-Poly1305'`. + +#### `aeadParams.tagLength` + + + +* Type: {number} The size in bits of the generated authentication tag. + ### Class: `AesDerivedKeyParams` -#### `aesGcmParams.additionalData` +#### `aesKeyAlgorithm.length` -* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} +* Type: {number} -With the AES-GCM method, the `additionalData` is extra input that is not -encrypted but is included in the authentication of the data. The use of -`additionalData` is optional. +The length of the AES key in bits. -#### `aesGcmParams.iv` +#### `aesKeyAlgorithm.name` -* Type: {ArrayBuffer|TypedArray|DataView|Buffer} +* Type: {string} -The initialization vector must be unique for every encryption operation using a -given key. +### Class: `AesKeyGenParams` -Ideally, this is a deterministic 12-byte value that is computed in such a way -that it is guaranteed to be unique across all invocations that use the same key. -Alternatively, the initialization vector may consist of at least 12 -cryptographically random bytes. For more information on constructing -initialization vectors for AES-GCM, refer to Section 8 of [NIST SP 800-38D][]. + -#### `aesGcmParams.name` +#### `aesKeyGenParams.length` -* Type: {string} Must be `'AES-GCM'`. +* Type: {number} + +The length of the AES key to be generated. This must be either `128`, `192`, +or `256`. -#### `aesGcmParams.tagLength` +#### `aesKeyGenParams.name` -* Type: {number} The size in bits of the generated authentication tag. - This values must be one of `32`, `64`, `96`, `104`, `112`, `120`, or - `128`. **Default:** `128`. +* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or + `'AES-KW'` -### Class: `AesKeyAlgorithm` +### Class: `ContextParams` -#### `aesKeyAlgorithm.length` +#### `contextParams.name` -* Type: {number} - -The length of the AES key in bits. +* Type: {string} Must be `'ML-DSA-44'`[^modern-algos], `'ML-DSA-65'`[^modern-algos], or `'ML-DSA-87'`[^modern-algos]. -#### `aesKeyAlgorithm.name` +#### `contextParams.context` -* Type: {string} +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -### Class: `AesKeyGenParams` +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: `CShakeParams` -#### `aesKeyGenParams.length` +#### `cShakeParams.customization` -* Type: {number} +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -The length of the AES key to be generated. This must be either `128`, `192`, -or `256`. +The `customization` member represents the customization string. +The Node.js Web Crypto API implementation only supports zero-length customization +which is equivalent to not providing customization at all. -#### `aesKeyGenParams.name` +#### `cShakeParams.functionName` -* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or - `'AES-KW'` +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +The `functionName` member represents represents the function name, used by NIST to define +functions based on cSHAKE. +The Node.js Web Crypto API implementation only supports zero-length functionName +which is equivalent to not providing functionName at all. + +#### `cShakeParams.length` + + + +* Type: {number} represents the requested output length in bits. + +#### `cShakeParams.name` + + + +* Type: {string} Must be `'cSHAKE128'`[^modern-algos] or `'cSHAKE256'`[^modern-algos] ### Class: `EcdhKeyDeriveParams` @@ -1234,7 +1556,7 @@ added: v15.0.0 added: v15.0.0 --> -* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`. +* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`[^secure-curves]. #### `ecdhKeyDeriveParams.public` @@ -1259,6 +1581,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1269,6 +1595,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1361,7 +1690,7 @@ added: - v16.17.0 --> -* Type: {string} Must be `'Ed448'`. +* Type: {string} Must be `'Ed448'`[^secure-curves]. #### `ed448Params.context` @@ -1388,6 +1717,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1398,6 +1731,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1444,6 +1780,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1454,6 +1794,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1519,6 +1862,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1529,6 +1876,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1577,6 +1927,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1587,6 +1941,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1629,6 +1986,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1639,6 +2000,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1704,6 +2068,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1714,6 +2082,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1803,12 +2174,17 @@ added: v15.0.0 The length (in bytes) of the random salt to use. -[^1]: An experimental implementation of Ed448 and X448 algorithms from - [Secure Curves in the Web Cryptography API][] as of 21 October 2024 +[^secure-curves]: See [Secure Curves in the Web Cryptography API][] + +[^modern-algos]: See [Modern Algorithms in the Web Cryptography API][] + +[^openssl35]: Requires OpenSSL >= 3.5 [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 +[Modern Algorithms in the Web Cryptography API]: #modern-algorithms-in-the-web-cryptography-api [RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt -[Secure Curves in the Web Cryptography API]: https://wicg.github.io/webcrypto-secure-curves/ +[Secure Curves in the Web Cryptography API]: #secure-curves-in-the-web-cryptography-api [Web Crypto API]: https://www.w3.org/TR/WebCryptoAPI/ +[`SubtleCrypto.supports()`]: #static-method-subtlecryptosupportsoperation-algorithm-lengthoradditionalalgorithm +[`subtle.getPublicKey()`]: #subtlegetpublickeykey-keyusages diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js new file mode 100644 index 00000000000000..bcc778b24d7738 --- /dev/null +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -0,0 +1,191 @@ +'use strict'; + +const { + ArrayBufferIsView, + ArrayBufferPrototypeSlice, + ArrayFrom, + PromiseReject, + SafeSet, + TypedArrayPrototypeSlice, +} = primordials; + +const { + ChaCha20Poly1305CipherJob, + KeyObjectHandle, + kCryptoJobAsync, + kWebCryptoCipherDecrypt, + kWebCryptoCipherEncrypt, +} = internalBinding('crypto'); + +const { + hasAnyNotIn, + jobPromise, + validateKeyOps, + kHandle, + kKeyObject, +} = require('internal/crypto/util'); + +const { + lazyDOMException, + promisify, +} = require('internal/util'); + +const { + InternalCryptoKey, + SecretKeyObject, + createSecretKey, +} = require('internal/crypto/keys'); + +const { + randomBytes: _randomBytes, +} = require('internal/crypto/random'); + +const randomBytes = promisify(_randomBytes); + +function validateKeyLength(length) { + if (length !== 256) + throw lazyDOMException('Invalid key length', 'DataError'); +} + +function c20pCipher(mode, key, data, algorithm) { + let tag; + switch (mode) { + case kWebCryptoCipherDecrypt: { + const slice = ArrayBufferIsView(data) ? + TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; + + if (data.byteLength < 16) { + return PromiseReject(lazyDOMException( + 'The provided data is too small.', + 'OperationError')); + } + + tag = slice(data, -16); + data = slice(data, 0, -16); + break; + } + case kWebCryptoCipherEncrypt: + tag = 16; + break; + } + + return jobPromise(() => new ChaCha20Poly1305CipherJob( + kCryptoJobAsync, + mode, + key[kKeyObject][kHandle], + data, + algorithm.iv, + tag, + algorithm.additionalData)); +} + +async function c20pGenerateKey(algorithm, extractable, keyUsages) { + const { name } = algorithm; + + const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; + + const usagesSet = new SafeSet(keyUsages); + if (hasAnyNotIn(usagesSet, checkUsages)) { + throw lazyDOMException( + `Unsupported key usage for a ${algorithm.name} key`, + 'SyntaxError'); + } + + const keyData = await randomBytes(32).catch((err) => { + throw lazyDOMException( + 'The operation failed for an operation-specific reason' + + `[${err.message}]`, + { name: 'OperationError', cause: err }); + }); + + return new InternalCryptoKey( + createSecretKey(keyData), + { name }, + ArrayFrom(usagesSet), + extractable); +} + +function c20pImportKey( + algorithm, + format, + keyData, + extractable, + keyUsages) { + const { name } = algorithm; + const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; + + const usagesSet = new SafeSet(keyUsages); + if (hasAnyNotIn(usagesSet, checkUsages)) { + throw lazyDOMException( + `Unsupported key usage for a ${algorithm.name} key`, + 'SyntaxError'); + } + + let keyObject; + switch (format) { + case 'KeyObject': { + keyObject = keyData; + break; + } + case 'raw-secret': { + keyObject = createSecretKey(keyData); + break; + } + case 'jwk': { + if (!keyData.kty) + throw lazyDOMException('Invalid keyData', 'DataError'); + + if (keyData.kty !== 'oct') + throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + + if (usagesSet.size > 0 && + keyData.use !== undefined && + keyData.use !== 'enc') { + throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); + } + + validateKeyOps(keyData.key_ops, usagesSet); + + if (keyData.ext !== undefined && + keyData.ext === false && + extractable === true) { + throw lazyDOMException( + 'JWK "ext" Parameter and extractable mismatch', + 'DataError'); + } + + const handle = new KeyObjectHandle(); + try { + handle.initJwk(keyData); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + + if (keyData.alg !== undefined && keyData.alg !== 'C20P') { + throw lazyDOMException( + 'JWK "alg" does not match the requested algorithm', + 'DataError'); + } + + keyObject = new SecretKeyObject(handle); + break; + } + default: + return undefined; + } + + validateKeyLength(keyObject.symmetricKeySize * 8); + + return new InternalCryptoKey( + keyObject, + { name }, + keyUsages, + extractable); +} + +module.exports = { + c20pCipher, + c20pGenerateKey, + c20pImportKey, +}; diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index 067e0633b80007..1c4d1285ec6c34 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -209,10 +209,21 @@ async function asyncDigest(algorithm, data) { case 'SHA-384': // Fall through case 'SHA-512': + // Fall through + case 'SHA3-256': + // Fall through + case 'SHA3-384': + // Fall through + case 'SHA3-512': + // Fall through + case 'cSHAKE128': + // Fall through + case 'cSHAKE256': return jobPromise(() => new HashJob( kCryptoJobAsync, normalizeHashName(algorithm.name), - data)); + data, + algorithm.length)); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); diff --git a/lib/internal/crypto/hashnames.js b/lib/internal/crypto/hashnames.js index 7af2091d84de39..7a625c47e2f4b2 100644 --- a/lib/internal/crypto/hashnames.js +++ b/lib/internal/crypto/hashnames.js @@ -17,7 +17,7 @@ const kHashContextJwkHmac = 6; // make it easier in the code. const kHashNames = { - sha1: { + 'sha1': { [kHashContextNode]: 'sha1', [kHashContextWebCrypto]: 'SHA-1', [kHashContextJwkRsa]: 'RS1', @@ -25,7 +25,7 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP', [kHashContextJwkHmac]: 'HS1', }, - sha256: { + 'sha256': { [kHashContextNode]: 'sha256', [kHashContextWebCrypto]: 'SHA-256', [kHashContextJwkRsa]: 'RS256', @@ -33,7 +33,7 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP-256', [kHashContextJwkHmac]: 'HS256', }, - sha384: { + 'sha384': { [kHashContextNode]: 'sha384', [kHashContextWebCrypto]: 'SHA-384', [kHashContextJwkRsa]: 'RS384', @@ -41,7 +41,7 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP-384', [kHashContextJwkHmac]: 'HS384', }, - sha512: { + 'sha512': { [kHashContextNode]: 'sha512', [kHashContextWebCrypto]: 'SHA-512', [kHashContextJwkRsa]: 'RS512', @@ -49,6 +49,26 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP-512', [kHashContextJwkHmac]: 'HS512', }, + 'shake128': { + [kHashContextNode]: 'shake128', + [kHashContextWebCrypto]: 'cSHAKE128', + }, + 'shake256': { + [kHashContextNode]: 'shake256', + [kHashContextWebCrypto]: 'cSHAKE256', + }, + 'sha3-256': { + [kHashContextNode]: 'sha3-256', + [kHashContextWebCrypto]: 'SHA3-256', + }, + 'sha3-384': { + [kHashContextNode]: 'sha3-384', + [kHashContextWebCrypto]: 'SHA3-384', + }, + 'sha3-512': { + [kHashContextNode]: 'sha3-512', + [kHashContextWebCrypto]: 'SHA3-512', + }, }; { diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js index 9b4d1469cc9df5..1a5e9ccd06813e 100644 --- a/lib/internal/crypto/hkdf.js +++ b/lib/internal/crypto/hkdf.js @@ -170,4 +170,5 @@ module.exports = { hkdf, hkdfSync, hkdfDeriveBits, + validateHkdfDeriveBitsLength, }; diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 60b4d26d35e967..c34f36a277ea37 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -199,6 +199,10 @@ const { result = require('internal/crypto/aes') .aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages); break; + case 'ChaCha20-Poly1305': + result = require('internal/crypto/chacha20_poly1305') + .c20pImportKey(algorithm, 'KeyObject', this, extractable, keyUsages); + break; case 'HKDF': // Fall through case 'PBKDF2': @@ -293,6 +297,14 @@ const { result = require('internal/crypto/cfrg') .cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages); break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + result = require('internal/crypto/ml_dsa') + .mlDsaImportKey('KeyObject', this, algorithm, extractable, keyUsages); + break; default: throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 0f9b1f9618d260..4a78c45c70d84d 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -62,23 +62,11 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) { return new InternalCryptoKey( key, - { name, length, hash: { name: hash.name } }, + { name, length, hash }, ArrayFrom(usageSet), extractable); } -function getAlgorithmName(hash) { - switch (hash) { - case 'SHA-1': // Fall through - case 'SHA-256': // Fall through - case 'SHA-384': // Fall through - case 'SHA-512': // Fall through - return `HS${hash.slice(4)}`; - default: - throw lazyDOMException('Unsupported digest algorithm', 'DataError'); - } -} - function hmacImportKey( format, keyData, @@ -126,7 +114,9 @@ function hmacImportKey( } if (keyData.alg !== undefined) { - if (keyData.alg !== getAlgorithmName(algorithm.hash.name)) + const expected = + normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac); + if (expected && keyData.alg !== expected) throw lazyDOMException( 'JWK "alg" does not match the requested algorithm', 'DataError'); diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js new file mode 100644 index 00000000000000..21dc95786b10e1 --- /dev/null +++ b/lib/internal/crypto/ml_dsa.js @@ -0,0 +1,318 @@ +'use strict'; + +const { + SafeSet, + Uint8Array, +} = primordials; + +const { Buffer } = require('buffer'); + +const { + KeyObjectHandle, + SignJob, + kCryptoJobAsync, + kKeyTypePrivate, + kKeyTypePublic, + kSignJobModeSign, + kSignJobModeVerify, + kKeyFormatDER, + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatSPKI, +} = internalBinding('crypto'); + +const { + codes: { + ERR_CRYPTO_INVALID_JWK, + }, +} = require('internal/errors'); + +const { + getUsagesUnion, + hasAnyNotIn, + jobPromise, + validateKeyOps, + kHandle, + kKeyObject, +} = require('internal/crypto/util'); + +const { + lazyDOMException, + promisify, +} = require('internal/util'); + +const { + generateKeyPair: _generateKeyPair, +} = require('internal/crypto/keygen'); + +const { + InternalCryptoKey, + PrivateKeyObject, + PublicKeyObject, + createPrivateKey, + createPublicKey, +} = require('internal/crypto/keys'); + +const generateKeyPair = promisify(_generateKeyPair); + +function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) { + const checkSet = isPublic ? ['verify'] : ['sign']; + if (hasAnyNotIn(usages, checkSet)) { + throw lazyDOMException( + `Unsupported key usage for a ${name} key`, + 'SyntaxError'); + } +} + +function createMlDsaRawKey(name, keyData, isPublic) { + const handle = new KeyObjectHandle(); + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initMlDsaRaw(name, keyData, keyType)) { + throw lazyDOMException('Invalid keyData', 'DataError'); + } + + return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); +} + +async function mlDsaGenerateKey(algorithm, extractable, keyUsages) { + const { name } = algorithm; + + const usageSet = new SafeSet(keyUsages); + if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { + throw lazyDOMException( + `Unsupported key usage for an ${name} key`, + 'SyntaxError'); + } + + const keyPair = await generateKeyPair(name.toLowerCase()).catch((err) => { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + }); + + const publicUsages = getUsagesUnion(usageSet, 'verify'); + const privateUsages = getUsagesUnion(usageSet, 'sign'); + + const keyAlgorithm = { name }; + + const publicKey = + new InternalCryptoKey( + keyPair.publicKey, + keyAlgorithm, + publicUsages, + true); + + const privateKey = + new InternalCryptoKey( + keyPair.privateKey, + keyAlgorithm, + privateUsages, + extractable); + + return { __proto__: null, privateKey, publicKey }; +} + +function mlDsaExportKey(key, format) { + try { + switch (format) { + case kWebCryptoKeyFormatRaw: { + if (key.type === 'private') { + const { priv } = key[kKeyObject][kHandle].exportJwk({}, false); + return Buffer.alloc(32, priv, 'base64url').buffer; + } + + const { pub } = key[kKeyObject][kHandle].exportJwk({}, false); + return Buffer.alloc(Buffer.byteLength(pub, 'base64url'), pub, 'base64url').buffer; + } + case kWebCryptoKeyFormatSPKI: { + return key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI).buffer; + } + case kWebCryptoKeyFormatPKCS8: { + const { priv } = key[kKeyObject][kHandle].exportJwk({}, false); + const seed = Buffer.alloc(32, priv, 'base64url'); + const buffer = new Uint8Array(54); + buffer.set([ + 0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0B, 0x06, + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, + 0x03, 0x00, 0x04, 0x22, 0x80, 0x20, + ], 0); + switch (key.algorithm.name) { + case 'ML-DSA-44': + buffer.set([0x11], 17); + break; + case 'ML-DSA-65': + buffer.set([0x12], 17); + break; + case 'ML-DSA-87': + buffer.set([0x13], 17); + break; + } + buffer.set(seed, 22); + return buffer.buffer; + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } +} + +function mlDsaImportKey( + format, + keyData, + algorithm, + extractable, + keyUsages) { + + const { name } = algorithm; + let keyObject; + const usagesSet = new SafeSet(keyUsages); + switch (format) { + case 'KeyObject': { + verifyAcceptableMlDsaKeyUse(name, keyData.type === 'public', usagesSet); + keyObject = keyData; + break; + } + case 'spki': { + verifyAcceptableMlDsaKeyUse(name, true, usagesSet); + try { + keyObject = createPublicKey({ + key: keyData, + format: 'der', + type: 'spki', + }); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'pkcs8': { + verifyAcceptableMlDsaKeyUse(name, false, usagesSet); + try { + keyObject = createPrivateKey({ + key: keyData, + format: 'der', + type: 'pkcs8', + }); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'jwk': { + if (!keyData.kty) + throw lazyDOMException('Invalid keyData', 'DataError'); + if (keyData.kty !== 'AKP') + throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + if (keyData.alg !== name) + throw lazyDOMException( + 'JWK "alg" Parameter and algorithm name mismatch', 'DataError'); + const isPublic = keyData.priv === undefined; + + if (usagesSet.size > 0 && keyData.use !== undefined) { + if (keyData.use !== 'sig') + throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); + } + + validateKeyOps(keyData.key_ops, usagesSet); + + if (keyData.ext !== undefined && + keyData.ext === false && + extractable === true) { + throw lazyDOMException( + 'JWK "ext" Parameter and extractable mismatch', + 'DataError'); + } + + if (!isPublic && typeof keyData.pub !== 'string') { + throw lazyDOMException('Invalid JWK', 'DataError'); + } + + verifyAcceptableMlDsaKeyUse( + name, + isPublic, + usagesSet); + + try { + const publicKeyObject = createMlDsaRawKey( + name, + Buffer.from(keyData.pub, 'base64url'), + true); + + if (isPublic) { + keyObject = publicKeyObject; + } else { + keyObject = createMlDsaRawKey( + name, + Buffer.from(keyData.priv, 'base64url'), + false); + + if (!createPublicKey(keyObject).equals(publicKeyObject)) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + } + } catch (err) { + throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'raw-public': + case 'raw-seed': { + const isPublic = format === 'raw-public'; + verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet); + + try { + keyObject = createMlDsaRawKey(name, keyData, isPublic); + } catch (err) { + throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + default: + return undefined; + } + + if (keyObject.asymmetricKeyType !== name.toLowerCase()) { + throw lazyDOMException('Invalid key type', 'DataError'); + } + + return new InternalCryptoKey( + keyObject, + { name }, + keyUsages, + extractable); +} + +function mlDsaSignVerify(key, data, algorithm, signature) { + const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; + const type = mode === kSignJobModeSign ? 'private' : 'public'; + + if (key.type !== type) + throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); + + return jobPromise(() => new SignJob( + kCryptoJobAsync, + mode, + key[kKeyObject][kHandle], + undefined, + undefined, + undefined, + data, + undefined, + undefined, + undefined, + undefined, + signature)); +} + +module.exports = { + mlDsaExportKey, + mlDsaImportKey, + mlDsaGenerateKey, + mlDsaSignVerify, +}; diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js index 0dc0d25682d2cd..ebdba7334f6556 100644 --- a/lib/internal/crypto/pbkdf2.js +++ b/lib/internal/crypto/pbkdf2.js @@ -128,4 +128,5 @@ module.exports = { pbkdf2, pbkdf2Sync, pbkdf2DeriveBits, + validatePbkdf2DeriveBitsLength, }; diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index bf6c341c5a723d..dd0669df4ed032 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -161,7 +161,7 @@ async function rsaKeyGenerate( name, modulusLength, publicExponent, - hash: { name: hash.name }, + hash, }; let publicUsages; @@ -281,7 +281,7 @@ function rsaImportKey( algorithm.name === 'RSA-PSS' ? normalizeHashName.kContextJwkRsaPss : normalizeHashName.kContextJwkRsaOaep); - if (keyData.alg !== expected) + if (expected && keyData.alg !== expected) throw lazyDOMException( 'JWK "alg" does not match the requested algorithm', 'DataError'); diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index c7e0c31a4cc609..685637ab7435df 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -33,6 +33,9 @@ const { secureHeapUsed: _secureHeapUsed, getCachedAliases, getOpenSSLSecLevelCrypto: getOpenSSLSecLevel, + EVP_PKEY_ML_DSA_44, + EVP_PKEY_ML_DSA_65, + EVP_PKEY_ML_DSA_87, } = internalBinding('crypto'); const { getOptionValue } = require('internal/options'); @@ -168,140 +171,232 @@ const kNamedCurveAliases = { 'P-521': 'secp521r1', }; -const kSupportedAlgorithms = { - 'digest': { - 'SHA-1': null, - 'SHA-256': null, - 'SHA-384': null, - 'SHA-512': null, +// Algorithm definitions organized by algorithm name +const kAlgorithmDefinitions = { + 'AES-CBC': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AesCbcParams', + 'decrypt': 'AesCbcParams', + 'get key length': 'AesDerivedKeyParams', }, - 'generateKey': { - 'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams', - 'RSA-PSS': 'RsaHashedKeyGenParams', - 'RSA-OAEP': 'RsaHashedKeyGenParams', - 'ECDSA': 'EcKeyGenParams', - 'ECDH': 'EcKeyGenParams', - 'AES-CTR': 'AesKeyGenParams', - 'AES-CBC': 'AesKeyGenParams', - 'AES-GCM': 'AesKeyGenParams', - 'AES-KW': 'AesKeyGenParams', - 'HMAC': 'HmacKeyGenParams', - 'Ed25519': null, - 'X25519': null, + 'AES-CTR': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AesCtrParams', + 'decrypt': 'AesCtrParams', + 'get key length': 'AesDerivedKeyParams', }, - 'exportKey': { - 'RSASSA-PKCS1-v1_5': null, - 'RSA-PSS': null, - 'RSA-OAEP': null, - 'ECDSA': null, - 'ECDH': null, - 'HMAC': null, - 'AES-CTR': null, - 'AES-CBC': null, - 'AES-GCM': null, - 'AES-KW': null, - 'Ed25519': null, - 'X25519': null, + 'AES-GCM': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AeadParams', + 'decrypt': 'AeadParams', + 'get key length': 'AesDerivedKeyParams', }, - 'sign': { - 'RSASSA-PKCS1-v1_5': null, - 'RSA-PSS': 'RsaPssParams', - 'ECDSA': 'EcdsaParams', - 'HMAC': null, - 'Ed25519': null, + 'AES-KW': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'get key length': 'AesDerivedKeyParams', + 'wrapKey': null, + 'unwrapKey': null, }, - 'verify': { - 'RSASSA-PKCS1-v1_5': null, - 'RSA-PSS': 'RsaPssParams', - 'ECDSA': 'EcdsaParams', - 'HMAC': null, - 'Ed25519': null, + 'ChaCha20-Poly1305': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AeadParams', + 'decrypt': 'AeadParams', + 'get key length': null, }, - 'importKey': { - 'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams', - 'RSA-PSS': 'RsaHashedImportParams', - 'RSA-OAEP': 'RsaHashedImportParams', - 'ECDSA': 'EcKeyImportParams', - 'ECDH': 'EcKeyImportParams', - 'HMAC': 'HmacImportParams', - 'HKDF': null, - 'PBKDF2': null, - 'AES-CTR': null, - 'AES-CBC': null, - 'AES-GCM': null, - 'AES-KW': null, - 'Ed25519': null, - 'X25519': null, + 'cSHAKE128': { 'digest': 'CShakeParams' }, + 'cSHAKE256': { 'digest': 'CShakeParams' }, + 'ECDH': { + 'generateKey': 'EcKeyGenParams', + 'exportKey': null, + 'importKey': 'EcKeyImportParams', + 'deriveBits': 'EcdhKeyDeriveParams', }, - 'deriveBits': { - 'HKDF': 'HkdfParams', - 'PBKDF2': 'Pbkdf2Params', - 'ECDH': 'EcdhKeyDeriveParams', - 'X25519': 'EcdhKeyDeriveParams', + 'ECDSA': { + 'generateKey': 'EcKeyGenParams', + 'exportKey': null, + 'importKey': 'EcKeyImportParams', + 'sign': 'EcdsaParams', + 'verify': 'EcdsaParams', }, - 'encrypt': { - 'RSA-OAEP': 'RsaOaepParams', - 'AES-CBC': 'AesCbcParams', - 'AES-GCM': 'AesGcmParams', - 'AES-CTR': 'AesCtrParams', + 'Ed25519': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': null, + 'verify': null, }, - 'decrypt': { - 'RSA-OAEP': 'RsaOaepParams', - 'AES-CBC': 'AesCbcParams', - 'AES-GCM': 'AesGcmParams', - 'AES-CTR': 'AesCtrParams', + 'Ed448': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'Ed448Params', + 'verify': 'Ed448Params', }, - 'get key length': { - 'AES-CBC': 'AesDerivedKeyParams', - 'AES-CTR': 'AesDerivedKeyParams', - 'AES-GCM': 'AesDerivedKeyParams', - 'AES-KW': 'AesDerivedKeyParams', - 'HMAC': 'HmacImportParams', - 'HKDF': null, - 'PBKDF2': null, + 'HKDF': { + 'importKey': null, + 'deriveBits': 'HkdfParams', + 'get key length': null, }, - 'wrapKey': { - 'AES-KW': null, + 'HMAC': { + 'generateKey': 'HmacKeyGenParams', + 'exportKey': null, + 'importKey': 'HmacImportParams', + 'sign': null, + 'verify': null, + 'get key length': 'HmacImportParams', }, - 'unwrapKey': { - 'AES-KW': null, + 'ML-DSA-44': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'ContextParams', + 'verify': 'ContextParams', }, -}; - -const experimentalAlgorithms = ObjectEntries({ - 'X448': { - generateKey: null, - importKey: null, - deriveBits: 'EcdhKeyDeriveParams', - exportKey: null, + 'ML-DSA-65': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'ContextParams', + 'verify': 'ContextParams', }, - 'Ed448': { - generateKey: null, - sign: 'Ed448Params', - verify: 'Ed448Params', - importKey: null, - exportKey: null, + 'ML-DSA-87': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'ContextParams', + 'verify': 'ContextParams', }, -}); + 'PBKDF2': { + 'importKey': null, + 'deriveBits': 'Pbkdf2Params', + 'get key length': null, + }, + 'RSA-OAEP': { + 'generateKey': 'RsaHashedKeyGenParams', + 'exportKey': null, + 'importKey': 'RsaHashedImportParams', + 'encrypt': 'RsaOaepParams', + 'decrypt': 'RsaOaepParams', + }, + 'RSA-PSS': { + 'generateKey': 'RsaHashedKeyGenParams', + 'exportKey': null, + 'importKey': 'RsaHashedImportParams', + 'sign': 'RsaPssParams', + 'verify': 'RsaPssParams', + }, + 'RSASSA-PKCS1-v1_5': { + 'generateKey': 'RsaHashedKeyGenParams', + 'exportKey': null, + 'importKey': 'RsaHashedImportParams', + 'sign': null, + 'verify': null, + }, + 'SHA-1': { 'digest': null }, + 'SHA-256': { 'digest': null }, + 'SHA-384': { 'digest': null }, + 'SHA-512': { 'digest': null }, + 'SHA3-256': { 'digest': null }, + 'SHA3-384': { 'digest': null }, + 'SHA3-512': { 'digest': null }, + 'X25519': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'deriveBits': 'EcdhKeyDeriveParams', + }, + 'X448': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'deriveBits': 'EcdhKeyDeriveParams', + }, +}; + +// Conditionally supported algorithms +const conditionalAlgorithms = { + 'AES-KW': !process.features.openssl_is_boringssl, + 'ChaCha20-Poly1305': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'), + 'cSHAKE128': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'shake128'), + 'cSHAKE256': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'shake256'), + 'Ed448': !process.features.openssl_is_boringssl, + 'ML-DSA-44': !!EVP_PKEY_ML_DSA_44, + 'ML-DSA-65': !!EVP_PKEY_ML_DSA_65, + 'ML-DSA-87': !!EVP_PKEY_ML_DSA_87, + 'SHA3-256': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'sha3-256'), + 'SHA3-384': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'sha3-384'), + 'SHA3-512': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'sha3-512'), + 'X448': !process.features.openssl_is_boringssl, +}; -for (let i = 0; i < experimentalAlgorithms.length; i++) { - const name = experimentalAlgorithms[i][0]; - const ops = ObjectEntries(experimentalAlgorithms[i][1]); - for (let j = 0; j < ops.length; j++) { - const { 0: op, 1: dict } = ops[j]; - ObjectDefineProperty(kSupportedAlgorithms[op], name, { - get() { - emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); - return dict; - }, - __proto__: null, - enumerable: true, - }); +// Experimental algorithms +const experimentalAlgorithms = [ + 'ChaCha20-Poly1305', + 'cSHAKE128', + 'cSHAKE256', + 'Ed448', + 'ML-DSA-44', + 'ML-DSA-65', + 'ML-DSA-87', + 'SHA3-256', + 'SHA3-384', + 'SHA3-512', + 'X448', +]; + +// Transform the algorithm definitions into the operation-keyed structure +function createSupportedAlgorithms(algorithmDefs) { + const result = {}; + + for (const { 0: algorithmName, 1: operations } of ObjectEntries(algorithmDefs)) { + // Skip algorithms that are conditionally not supported + if (ObjectPrototypeHasOwnProperty(conditionalAlgorithms, algorithmName) && + !conditionalAlgorithms[algorithmName]) { + continue; + } + + for (const { 0: operation, 1: dict } of ObjectEntries(operations)) { + result[operation] ||= {}; + + // Add experimental warnings for experimental algorithms + if (ArrayPrototypeIncludes(experimentalAlgorithms, algorithmName)) { + ObjectDefineProperty(result[operation], algorithmName, { + get() { + emitExperimentalWarning(`The ${algorithmName} Web Crypto API algorithm`); + return dict; + }, + __proto__: null, + enumerable: true, + }); + } else { + result[operation][algorithmName] = dict; + } + } } + + return result; } +const kSupportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions); + const simpleAlgorithmDictionaries = { - AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, + AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, EcKeyGenParams: {}, HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, @@ -314,10 +409,15 @@ const simpleAlgorithmDictionaries = { info: 'BufferSource', }, Ed448Params: { context: 'BufferSource' }, + ContextParams: { context: 'BufferSource' }, Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' }, RsaOaepParams: { label: 'BufferSource' }, RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' }, EcKeyImportParams: {}, + CShakeParams: { + functionName: 'BufferSource', + customization: 'BufferSource', + }, }; function validateMaxBufferLength(data, name) { @@ -526,15 +626,28 @@ function getBlockSize(name) { // Fall through case 'SHA-512': return 1024; + case 'SHA3-256': + return 1088; + case 'SHA3-384': + return 832; + case 'SHA3-512': + return 576; } } function getDigestSizeInBytes(name) { switch (name) { - case 'SHA-1': return 20; - case 'SHA-256': return 32; - case 'SHA-384': return 48; - case 'SHA-512': return 64; + case 'SHA-1': + return 20; + case 'SHA-256': // Fall through + case 'SHA3-256': + return 32; + case 'SHA-384': // Fall through + case 'SHA3-384': + return 48; + case 'SHA-512': // Fall through + case 'SHA3-512': + return 64; } } diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 82bdc29f50a4d4..5961ee3e67dbf7 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -8,6 +8,7 @@ const { ReflectApply, ReflectConstruct, StringPrototypeRepeat, + StringPrototypeSlice, SymbolToStringTag, } = primordials; @@ -29,6 +30,7 @@ const { } = require('internal/errors'); const { + createPublicKey, CryptoKey, importGenericSecretKey, } = require('internal/crypto/keys'); @@ -47,6 +49,7 @@ const { } = require('internal/crypto/util'); const { + emitExperimentalWarning, kEnumerableProperty, lazyDOMException, } = require('internal/util'); @@ -152,6 +155,20 @@ async function generateKey( result = await require('internal/crypto/aes') .aesGenerateKey(algorithm, extractable, keyUsages); break; + case 'ChaCha20-Poly1305': + resultType = 'CryptoKey'; + result = await require('internal/crypto/chacha20_poly1305') + .c20pGenerateKey(algorithm, extractable, keyUsages); + break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + resultType = 'CryptoKeyPair'; + result = await require('internal/crypto/ml_dsa') + .mlDsaGenerateKey(algorithm, extractable, keyUsages); + break; default: throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } @@ -240,6 +257,8 @@ function getKeyLength({ name, length, hash }) { case 'HKDF': case 'PBKDF2': return null; + case 'ChaCha20-Poly1305': + return 256; } } @@ -311,7 +330,7 @@ async function deriveKey( return ReflectApply( importKey, this, - ['raw', bits, derivedKeyAlgorithm, extractable, keyUsages], + ['raw-secret', bits, derivedKeyAlgorithm, extractable, keyUsages], ); } @@ -338,6 +357,14 @@ async function exportKeySpki(key) { case 'X448': return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': { + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatSPKI); + } default: return undefined; } @@ -366,12 +393,20 @@ async function exportKeyPkcs8(key) { case 'X448': return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': { + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8); + } default: return undefined; } } -async function exportKeyRawPublic(key) { +async function exportKeyRawPublic(key, format) { switch (key.algorithm.name) { case 'ECDSA': // Fall through @@ -387,12 +422,39 @@ async function exportKeyRawPublic(key) { case 'X448': return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatRaw); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': { + // ML-DSA keys don't recognize "raw" + if (format !== 'raw-public') { + return undefined; + } + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); + } default: return undefined; } } -async function exportKeyRawSecret(key) { +async function exportKeyRawSeed(key) { + switch (key.algorithm.name) { + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': { + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); + } + default: + return undefined; + } +} + +async function exportKeyRawSecret(key, format) { switch (key.algorithm.name) { case 'AES-CTR': // Fall through @@ -404,6 +466,11 @@ async function exportKeyRawSecret(key) { // Fall through case 'HMAC': return key[kKeyObject][kHandle].export().buffer; + case 'ChaCha20-Poly1305': + if (format === 'raw-secret') { + return key[kKeyObject][kHandle].export().buffer; + } + return undefined; default: return undefined; } @@ -415,21 +482,27 @@ async function exportKeyJWK(key) { ext: key.extractable, }; switch (key.algorithm.name) { - case 'RSASSA-PKCS1-v1_5': - parameters.alg = normalizeHashName( + case 'RSASSA-PKCS1-v1_5': { + const alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkRsa); + if (alg) parameters.alg = alg; break; - case 'RSA-PSS': - parameters.alg = normalizeHashName( + } + case 'RSA-PSS': { + const alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkRsaPss); + if (alg) parameters.alg = alg; break; - case 'RSA-OAEP': - parameters.alg = normalizeHashName( + } + case 'RSA-OAEP': { + const alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkRsaOaep); + if (alg) parameters.alg = alg; break; + } case 'ECDSA': // Fall through case 'ECDH': @@ -437,6 +510,12 @@ async function exportKeyJWK(key) { case 'X25519': // Fall through case 'X448': + // Fall through + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': break; case 'Ed25519': // Fall through @@ -453,11 +532,16 @@ async function exportKeyJWK(key) { parameters.alg = require('internal/crypto/aes') .getAlgorithmName(key.algorithm.name, key.algorithm.length); break; - case 'HMAC': - parameters.alg = normalizeHashName( + case 'ChaCha20-Poly1305': + parameters.alg = 'C20P'; + break; + case 'HMAC': { + const alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkHmac); + if (alg) parameters.alg = alg; break; + } default: return undefined; } @@ -508,15 +592,29 @@ async function exportKey(format, key) { result = await exportKeyJWK(key); break; } - case 'raw': { + case 'raw-secret': { if (key.type === 'secret') { - result = await exportKeyRawSecret(key); - break; + result = await exportKeyRawSecret(key, format); } - + break; + } + case 'raw-public': { if (key.type === 'public') { - result = await exportKeyRawPublic(key); - break; + result = await exportKeyRawPublic(key, format); + } + break; + } + case 'raw-seed': { + if (key.type === 'private') { + result = await exportKeyRawSeed(key); + } + break; + } + case 'raw': { + if (key.type === 'secret') { + result = await exportKeyRawSecret(key, format); + } else if (key.type === 'public') { + result = await exportKeyRawPublic(key, format); } break; } @@ -531,6 +629,16 @@ async function exportKey(format, key) { return result; } +function aliasKeyFormat(format) { + switch (format) { + case 'raw-public': + case 'raw-secret': + return 'raw'; + default: + return format; + } +} + async function importKey( format, keyData, @@ -572,12 +680,14 @@ async function importKey( case 'RSA-PSS': // Fall through case 'RSA-OAEP': + format = aliasKeyFormat(format); result = require('internal/crypto/rsa') .rsaImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'ECDSA': // Fall through case 'ECDH': + format = aliasKeyFormat(format); result = require('internal/crypto/ec') .ecImportKey(format, keyData, algorithm, extractable, keyUsages); break; @@ -588,10 +698,12 @@ async function importKey( case 'X25519': // Fall through case 'X448': + format = aliasKeyFormat(format); result = require('internal/crypto/cfrg') .cfrgImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'HMAC': + format = aliasKeyFormat(format); result = require('internal/crypto/mac') .hmacImportKey(format, keyData, algorithm, extractable, keyUsages); break; @@ -602,12 +714,18 @@ async function importKey( case 'AES-GCM': // Fall through case 'AES-KW': + format = aliasKeyFormat(format); result = require('internal/crypto/aes') .aesImportKey(algorithm, format, keyData, extractable, keyUsages); break; + case 'ChaCha20-Poly1305': + result = require('internal/crypto/chacha20_poly1305') + .c20pImportKey(algorithm, format, keyData, extractable, keyUsages); + break; case 'HKDF': // Fall through case 'PBKDF2': + format = aliasKeyFormat(format); result = importGenericSecretKey( algorithm, format, @@ -615,6 +733,14 @@ async function importKey( extractable, keyUsages); break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + result = require('internal/crypto/ml_dsa') + .mlDsaImportKey(format, keyData, algorithm, extractable, keyUsages); + break; } if (!result) { @@ -796,6 +922,13 @@ function signVerify(algorithm, key, data, signature) { case 'HMAC': return require('internal/crypto/mac') .hmacSignVerify(key, data, algorithm, signature); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + return require('internal/crypto/ml_dsa') + .mlDsaSignVerify(key, data, algorithm, signature); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } @@ -877,6 +1010,9 @@ async function cipherOrWrap(mode, algorithm, key, data, op) { case 'AES-GCM': return require('internal/crypto/aes') .aesCipher(mode, key, data, algorithm); + case 'ChaCha20-Poly1305': + return require('internal/crypto/chacha20_poly1305') + .c20pCipher(mode, key, data, algorithm); case 'AES-KW': if (op === 'wrapKey' || op === 'unwrapKey') { return require('internal/crypto/aes') @@ -932,6 +1068,31 @@ async function decrypt(algorithm, key, data) { return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt'); } +// Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey +async function getPublicKey(key, keyUsages) { + emitExperimentalWarning('The getPublicKey Web Crypto API method'); + if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'getPublicKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: '1st argument', + }); + keyUsages = webidl.converters['sequence'](keyUsages, { + prefix, + context: '2nd argument', + }); + + if (key.type !== 'private') + throw lazyDOMException('key must be a private key', 'InvalidAccessError'); + + const keyObject = createPublicKey(key[kKeyObject]); + + return keyObject.toCryptoKey(key.algorithm, true, keyUsages); +} + // The SubtleCrypto and Crypto classes are defined as part of the // Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/ @@ -939,7 +1100,168 @@ class SubtleCrypto { constructor() { throw new ERR_ILLEGAL_CONSTRUCTOR(); } + + // Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports + static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) { + emitExperimentalWarning('The supports Web Crypto API method'); + if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor'); + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'supports' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + operation = webidl.converters.DOMString(operation, { + prefix, + context: '1st argument', + }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: '2nd argument', + }); + + switch (operation) { + case 'encrypt': + case 'decrypt': + case 'sign': + case 'verify': + case 'digest': + case 'generateKey': + case 'deriveKey': + case 'deriveBits': + case 'importKey': + case 'exportKey': + case 'wrapKey': + case 'unwrapKey': + case 'getPublicKey': + break; + default: + return false; + } + + let length; + let additionalAlgorithm; + if (operation === 'deriveKey') { + additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { + prefix, + context: '3rd argument', + }); + + if (!check('importKey', additionalAlgorithm)) { + return false; + } + + try { + length = getKeyLength(normalizeAlgorithm(additionalAlgorithm, 'get key length')); + } catch { + return false; + } + + operation = 'deriveBits'; + } else if (operation === 'wrapKey') { + additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { + prefix, + context: '3rd argument', + }); + + if (!check('exportKey', additionalAlgorithm)) { + return false; + } + } else if (operation === 'unwrapKey') { + additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { + prefix, + context: '3rd argument', + }); + + if (!check('importKey', additionalAlgorithm)) { + return false; + } + } else if (operation === 'deriveBits') { + length = lengthOrAdditionalAlgorithm; + if (length !== null) { + length = webidl.converters['unsigned long'](length, { + prefix, + context: '3rd argument', + }); + } + } else if (operation === 'getPublicKey') { + let normalizedAlgorithm; + try { + normalizedAlgorithm = normalizeAlgorithm(algorithm, 'exportKey'); + } catch { + return false; + } + + switch (StringPrototypeSlice(normalizedAlgorithm.name, 0, 2)) { + case 'ML': // ML-DSA-*, ML-KEM-* + case 'SL': // SLH-DSA-* + case 'RS': // RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5 + case 'EC': // ECDSA, ECDH + case 'Ed': // Ed* + case 'X2': // X25519 + case 'X4': // X448 + return true; + default: + return false; + } + } + + return check(operation, algorithm, length); + } +} + +function check(op, alg, length) { + let normalizedAlgorithm; + try { + normalizedAlgorithm = normalizeAlgorithm(alg, op); + } catch { + if (op === 'wrapKey') { + return check('encrypt', alg); + } + + if (op === 'unwrapKey') { + return check('decrypt', alg); + } + + return false; + } + + switch (op) { + case 'encrypt': + case 'decrypt': + case 'sign': + case 'verify': + case 'digest': + case 'generateKey': + case 'importKey': + case 'exportKey': + case 'wrapKey': + case 'unwrapKey': + return true; + case 'deriveBits': { + if (normalizedAlgorithm.name === 'HKDF') { + try { + require('internal/crypto/hkdf').validateHkdfDeriveBitsLength(length); + } catch { + return false; + } + } + + if (normalizedAlgorithm.name === 'PBKDF2') { + try { + require('internal/crypto/pbkdf2').validatePbkdf2DeriveBitsLength(length); + } catch { + return false; + } + } + + return true; + } + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + } } + const subtle = ReflectConstruct(function() {}, [], SubtleCrypto); class Crypto { @@ -1083,6 +1405,13 @@ ObjectDefineProperties( writable: true, value: unwrapKey, }, + getPublicKey: { + __proto__: null, + enumerable: true, + configurable: true, + writable: true, + value: getPublicKey, + }, }); module.exports = { diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index cba02279977e4b..cf930086e169ea 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -23,6 +23,8 @@ const { ObjectPrototypeIsPrototypeOf, SafeArrayIterator, String, + StringPrototypeStartsWith, + StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetSymbolToStringTag, } = primordials; @@ -192,6 +194,16 @@ converters.object = (V, opts) => { const isNonSharedArrayBuffer = isArrayBuffer; +function ensureSHA(V, label) { + if ( + typeof V === 'string' ? + !StringPrototypeStartsWith(StringPrototypeToLowerCase(V), 'sha') : + V.name?.toLowerCase?.().startsWith('sha') === false + ) + throw lazyDOMException( + `Only SHA hashes are supported in ${label}`, 'NotSupportedError'); +} + converters.Uint8Array = (V, opts = kEmptyObject) => { if (!ArrayBufferIsView(V) || TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') { @@ -332,6 +344,10 @@ converters.AlgorithmIdentifier = (V, opts) => { converters.KeyFormat = createEnumConverter('KeyFormat', [ 'raw', + 'raw-public', + 'raw-seed', + 'raw-secret', + 'raw-private', 'pkcs8', 'spki', 'jwk', @@ -389,6 +405,7 @@ converters.RsaHashedKeyGenParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'RsaHashedKeyGenParams'), required: true, }, ]); @@ -399,6 +416,7 @@ converters.RsaHashedImportParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'RsaHashedImportParams'), required: true, }, ]); @@ -445,6 +463,7 @@ converters.HmacKeyGenParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'HmacKeyGenParams'), required: true, }, { @@ -464,6 +483,15 @@ function validateHmacKeyAlgorithm(length) { throw lazyDOMException('Unsupported algorithm.length', 'NotSupportedError'); } +function validateZeroLength(parameterName) { + return (V, dict) => { + if (V.byteLength) { + throw lazyDOMException( + `Non zero-length ${parameterName} is not supported.`, 'NotSupportedError'); + } + }; +} + converters.RsaPssParams = createDictionaryConverter( 'RsaPssParams', [ ...new SafeArrayIterator(dictAlgorithm), @@ -490,6 +518,7 @@ converters.EcdsaParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'EcdsaParams'), required: true, }, ]); @@ -500,6 +529,7 @@ converters.HmacImportParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'HmacImportParams'), required: true, }, { @@ -545,6 +575,8 @@ converters.JsonWebKey = createDictionaryConverter( simpleDomStringKey('dp'), simpleDomStringKey('dq'), simpleDomStringKey('qi'), + simpleDomStringKey('pub'), + simpleDomStringKey('priv'), { key: 'oth', converter: converters['sequence'], @@ -558,6 +590,7 @@ converters.HkdfParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'HkdfParams'), required: true, }, { @@ -572,12 +605,40 @@ converters.HkdfParams = createDictionaryConverter( }, ]); +converters.CShakeParams = createDictionaryConverter( + 'CShakeParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'length', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + // The Web Crypto spec allows for SHAKE output length that are not multiples of + // 8. We don't. + if (V % 8) + throw lazyDOMException('Unsupported CShakeParams length', 'NotSupportedError'); + }, + required: true, + }, + { + key: 'functionName', + converter: converters.BufferSource, + validator: validateZeroLength('CShakeParams.functionName'), + }, + { + key: 'customization', + converter: converters.BufferSource, + validator: validateZeroLength('CShakeParams.customization'), + }, + ]); + converters.Pbkdf2Params = createDictionaryConverter( 'Pbkdf2Params', [ ...new SafeArrayIterator(dictAlgorithm), { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'Pbkdf2Params'), required: true, }, { @@ -620,13 +681,22 @@ converters.AesCbcParams = createDictionaryConverter( }, ]); -converters.AesGcmParams = createDictionaryConverter( - 'AesGcmParams', [ +converters.AeadParams = createDictionaryConverter( + 'AeadParams', [ ...new SafeArrayIterator(dictAlgorithm), { key: 'iv', converter: converters.BufferSource, - validator: (V, dict) => validateMaxBufferLength(V, 'algorithm.iv'), + validator: (V, dict) => { + switch (StringPrototypeToLowerCase(dict.name)) { + case 'chacha20-poly1305': + validateByteLength(V, 'algorithm.iv', 12); + break; + case 'aes-gcm': + validateMaxBufferLength(V, 'algorithm.iv'); + break; + } + }, required: true, }, { @@ -634,10 +704,21 @@ converters.AesGcmParams = createDictionaryConverter( converter: (V, opts) => converters.octet(V, { ...opts, enforceRange: true }), validator: (V, dict) => { - if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) { - throw lazyDOMException( - `${V} is not a valid AES-GCM tag length`, - 'OperationError'); + switch (StringPrototypeToLowerCase(dict.name)) { + case 'chacha20-poly1305': + if (V !== 128) { + throw lazyDOMException( + `${V} is not a valid ChaCha20-Poly1305 tag length`, + 'OperationError'); + } + break; + case 'aes-gcm': + if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) { + throw lazyDOMException( + `${V} is not a valid AES-GCM tag length`, + 'OperationError'); + } + break; } }, }, @@ -685,29 +766,26 @@ converters.EcdhKeyDeriveParams = createDictionaryConverter( throw lazyDOMException( 'algorithm.public must be a public key', 'InvalidAccessError'); - if (V.algorithm.name.toUpperCase() !== dict.name.toUpperCase()) + if (StringPrototypeToLowerCase(V.algorithm.name) !== StringPrototypeToLowerCase(dict.name)) throw lazyDOMException( - `algorithm.public must be an ${dict.name.toUpperCase()} key`, + 'key algorithm mismatch', 'InvalidAccessError'); }, required: true, }, ]); -converters.Ed448Params = createDictionaryConverter( - 'Ed448Params', [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: 'context', - converter: converters.BufferSource, - validator: (V, dict) => { - if (V.byteLength) - throw lazyDOMException( - 'Non zero-length context is not supported.', 'NotSupportedError'); +for (const name of ['Ed448Params', 'ContextParams']) { + converters[name] = createDictionaryConverter( + name, [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'context', + converter: converters.BufferSource, + validator: validateZeroLength(`${name}.context`), }, - required: false, - }, - ]); + ]); +} module.exports = { converters, diff --git a/node.gyp b/node.gyp index 428e0219ccd763..54d739d576973e 100644 --- a/node.gyp +++ b/node.gyp @@ -333,6 +333,7 @@ 'node_crypto_sources': [ 'src/crypto/crypto_aes.cc', 'src/crypto/crypto_bio.cc', + 'src/crypto/crypto_chacha20_poly1305.cc', 'src/crypto/crypto_common.cc', 'src/crypto/crypto_dsa.cc', 'src/crypto/crypto_hkdf.cc', diff --git a/src/crypto/crypto_chacha20_poly1305.cc b/src/crypto/crypto_chacha20_poly1305.cc new file mode 100644 index 00000000000000..bfe904c49ad771 --- /dev/null +++ b/src/crypto/crypto_chacha20_poly1305.cc @@ -0,0 +1,322 @@ +#include "crypto/crypto_chacha20_poly1305.h" +#include "async_wrap-inl.h" +#include "base_object-inl.h" +#include "crypto/crypto_cipher.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_util.h" +#include "env-inl.h" +#include "memory_tracker-inl.h" +#include "threadpoolwork-inl.h" +#include "v8.h" + +#include + +namespace node { + +using ncrypto::Cipher; +using ncrypto::CipherCtxPointer; +using ncrypto::DataPointer; +using v8::FunctionCallbackInfo; +using v8::JustVoid; +using v8::Local; +using v8::Maybe; +using v8::Nothing; +using v8::Object; +using v8::Value; + +namespace crypto { +namespace { +constexpr size_t kChaCha20Poly1305KeySize = 32; +constexpr size_t kChaCha20Poly1305IvSize = 12; +constexpr size_t kChaCha20Poly1305TagSize = 16; + +bool ValidateIV(Environment* env, + CryptoJobMode mode, + Local value, + ChaCha20Poly1305CipherConfig* params) { + ArrayBufferOrViewContents iv(value); + if (!iv.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "iv is too large"); + return false; + } + + if (iv.size() != kChaCha20Poly1305IvSize) { + THROW_ERR_CRYPTO_INVALID_IV(env); + return false; + } + + if (mode == kCryptoJobAsync) { + params->iv = iv.ToCopy(); + } else { + params->iv = iv.ToByteSource(); + } + + return true; +} + +bool ValidateAuthTag(Environment* env, + CryptoJobMode mode, + WebCryptoCipherMode cipher_mode, + Local value, + ChaCha20Poly1305CipherConfig* params) { + switch (cipher_mode) { + case kWebCryptoCipherDecrypt: { + if (!IsAnyBufferSource(value)) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( + env, "Authentication tag must be a buffer"); + return false; + } + + ArrayBufferOrViewContents tag(value); + if (!tag.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "tag is too large"); + return false; + } + + if (tag.size() != kChaCha20Poly1305TagSize) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( + env, "Invalid authentication tag length"); + return false; + } + + if (mode == kCryptoJobAsync) { + params->tag = tag.ToCopy(); + } else { + params->tag = tag.ToByteSource(); + } + break; + } + case kWebCryptoCipherEncrypt: { + // For encryption, the value should be the tag length (passed from + // JavaScript) We expect it to be the tag size constant for + // ChaCha20-Poly1305 + if (!value->IsUint32()) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env, "Tag length must be a number"); + return false; + } + + uint32_t tag_length = value.As()->Value(); + if (tag_length != kChaCha20Poly1305TagSize) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( + env, "Invalid tag length for ChaCha20-Poly1305"); + return false; + } + // Tag is generated during encryption, not provided + break; + } + default: + UNREACHABLE(); + } + + return true; +} + +bool ValidateAdditionalData(Environment* env, + CryptoJobMode mode, + Local value, + ChaCha20Poly1305CipherConfig* params) { + if (IsAnyBufferSource(value)) { + ArrayBufferOrViewContents additional_data(value); + if (!additional_data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "additional data is too large"); + return false; + } + + if (mode == kCryptoJobAsync) { + params->additional_data = additional_data.ToCopy(); + } else { + params->additional_data = additional_data.ToByteSource(); + } + } + + return true; +} +} // namespace + +ChaCha20Poly1305CipherConfig::ChaCha20Poly1305CipherConfig( + ChaCha20Poly1305CipherConfig&& other) noexcept + : mode(other.mode), + cipher(other.cipher), + iv(std::move(other.iv)), + additional_data(std::move(other.additional_data)), + tag(std::move(other.tag)) {} + +ChaCha20Poly1305CipherConfig& ChaCha20Poly1305CipherConfig::operator=( + ChaCha20Poly1305CipherConfig&& other) noexcept { + if (&other == this) return *this; + this->~ChaCha20Poly1305CipherConfig(); + return *new (this) ChaCha20Poly1305CipherConfig(std::move(other)); +} + +void ChaCha20Poly1305CipherConfig::MemoryInfo(MemoryTracker* tracker) const { + // If mode is sync, then the data in each of these properties + // is not owned by the ChaCha20Poly1305CipherConfig, so we ignore it. + if (mode == kCryptoJobAsync) { + tracker->TrackFieldWithSize("iv", iv.size()); + tracker->TrackFieldWithSize("additional_data", additional_data.size()); + tracker->TrackFieldWithSize("tag", tag.size()); + } +} + +Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + WebCryptoCipherMode cipher_mode, + ChaCha20Poly1305CipherConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->mode = mode; + params->cipher = ncrypto::Cipher::CHACHA20_POLY1305; + + if (!params->cipher) { + THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); + return Nothing(); + } + + // IV parameter (required) + if (!ValidateIV(env, mode, args[offset], params)) { + return Nothing(); + } + + // Authentication tag parameter (only for decryption) or tag length (for + // encryption) + if (static_cast(args.Length()) > offset + 1) { + if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 1], params)) { + return Nothing(); + } + } + + // Additional authenticated data parameter (optional) + if (static_cast(args.Length()) > offset + 2) { + if (!ValidateAdditionalData(env, mode, args[offset + 2], params)) { + return Nothing(); + } + } + + return JustVoid(); +} + +WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( + Environment* env, + const KeyObjectData& key_data, + WebCryptoCipherMode cipher_mode, + const ChaCha20Poly1305CipherConfig& params, + const ByteSource& in, + ByteSource* out) { + CHECK_EQ(key_data.GetKeyType(), kKeyTypeSecret); + + // Validate key size + if (key_data.GetSymmetricKeySize() != kChaCha20Poly1305KeySize) { + return WebCryptoCipherStatus::INVALID_KEY_TYPE; + } + + auto ctx = CipherCtxPointer::New(); + CHECK(ctx); + + const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt; + + if (!ctx.init(params.cipher, encrypt)) { + return WebCryptoCipherStatus::FAILED; + } + + if (!ctx.setKeyLength(key_data.GetSymmetricKeySize()) || + !ctx.init( + Cipher(), + encrypt, + reinterpret_cast(key_data.GetSymmetricKey()), + params.iv.data())) { + return WebCryptoCipherStatus::FAILED; + } + + size_t tag_len = 0; + + switch (cipher_mode) { + case kWebCryptoCipherDecrypt: { + if (params.tag.size() != kChaCha20Poly1305TagSize) { + return WebCryptoCipherStatus::FAILED; + } + if (!ctx.setAeadTag(ncrypto::Buffer{ + .data = params.tag.data(), + .len = params.tag.size(), + })) { + return WebCryptoCipherStatus::FAILED; + } + break; + } + case kWebCryptoCipherEncrypt: { + tag_len = kChaCha20Poly1305TagSize; + break; + } + default: + UNREACHABLE(); + } + + size_t total = 0; + int buf_len = in.size() + ctx.getBlockSize() + tag_len; + int out_len; + + // Process additional authenticated data if present + ncrypto::Buffer buffer = { + .data = params.additional_data.data(), + .len = params.additional_data.size(), + }; + if (params.additional_data.size() && !ctx.update(buffer, nullptr, &out_len)) { + return WebCryptoCipherStatus::FAILED; + } + + auto buf = DataPointer::Alloc(buf_len); + auto ptr = static_cast(buf.get()); + + // Process the input data + buffer = { + .data = in.data(), + .len = in.size(), + }; + if (in.empty()) { + if (!ctx.update({}, ptr, &out_len)) { + return WebCryptoCipherStatus::FAILED; + } + } else if (!ctx.update(buffer, ptr, &out_len)) { + return WebCryptoCipherStatus::FAILED; + } + + total += out_len; + CHECK_LE(out_len, buf_len); + out_len = ctx.getBlockSize(); + if (!ctx.update({}, ptr + total, &out_len, true)) { + return WebCryptoCipherStatus::FAILED; + } + total += out_len; + + // If encrypting, grab the generated auth tag and append it to the ciphertext + if (encrypt) { + if (!ctx.getAeadTag(kChaCha20Poly1305TagSize, ptr + total)) { + return WebCryptoCipherStatus::FAILED; + } + total += kChaCha20Poly1305TagSize; + } + + if (total == 0) { + *out = ByteSource(); + return WebCryptoCipherStatus::OK; + } + + // Size down to the actual used space + buf = buf.resize(total); + *out = ByteSource::Allocated(buf.release()); + + return WebCryptoCipherStatus::OK; +} + +void ChaCha20Poly1305::Initialize(Environment* env, Local target) { + ChaCha20Poly1305CryptoJob::Initialize(env, target); +} + +void ChaCha20Poly1305::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + ChaCha20Poly1305CryptoJob::RegisterExternalReferences(registry); +} + +} // namespace crypto +} // namespace node diff --git a/src/crypto/crypto_chacha20_poly1305.h b/src/crypto/crypto_chacha20_poly1305.h new file mode 100644 index 00000000000000..5b4d5cde2c3929 --- /dev/null +++ b/src/crypto/crypto_chacha20_poly1305.h @@ -0,0 +1,64 @@ +#ifndef SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_ +#define SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "crypto/crypto_cipher.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_util.h" +#include "env.h" +#include "v8.h" + +namespace node::crypto { +constexpr unsigned kChaCha20Poly1305AuthTagLength = 16; + +struct ChaCha20Poly1305CipherConfig final : public MemoryRetainer { + CryptoJobMode mode; + ncrypto::Cipher cipher; + ByteSource iv; + ByteSource additional_data; + ByteSource tag; + + ChaCha20Poly1305CipherConfig() = default; + + ChaCha20Poly1305CipherConfig(ChaCha20Poly1305CipherConfig&& other) noexcept; + + ChaCha20Poly1305CipherConfig& operator=( + ChaCha20Poly1305CipherConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(ChaCha20Poly1305CipherConfig) + SET_SELF_SIZE(ChaCha20Poly1305CipherConfig) +}; + +struct ChaCha20Poly1305CipherTraits final { + static constexpr const char* JobName = "ChaCha20Poly1305CipherJob"; + + using AdditionalParameters = ChaCha20Poly1305CipherConfig; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + WebCryptoCipherMode cipher_mode, + ChaCha20Poly1305CipherConfig* config); + + static WebCryptoCipherStatus DoCipher( + Environment* env, + const KeyObjectData& key_data, + WebCryptoCipherMode cipher_mode, + const ChaCha20Poly1305CipherConfig& params, + const ByteSource& in, + ByteSource* out); +}; + +using ChaCha20Poly1305CryptoJob = CipherJob; + +namespace ChaCha20Poly1305 { +void Initialize(Environment* env, v8::Local target); +void RegisterExternalReferences(ExternalReferenceRegistry* registry); +} // namespace ChaCha20Poly1305 +} // namespace node::crypto + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index a94ef62d2c6d78..168491796bd075 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -38,6 +38,7 @@ namespace crypto { #define CRYPTO_NAMESPACE_LIST_BASE(V) \ V(AES) \ + V(ChaCha20Poly1305) \ V(CipherBase) \ V(DiffieHellman) \ V(DSAAlg) \ diff --git a/src/node_crypto.h b/src/node_crypto.h index d34ff52daff8bc..78597168e6fc22 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -30,6 +30,7 @@ // code should include the relevant src/crypto headers directly. #include "crypto/crypto_aes.h" #include "crypto/crypto_bio.h" +#include "crypto/crypto_chacha20_poly1305.h" #include "crypto/crypto_cipher.h" #include "crypto/crypto_context.h" #include "crypto/crypto_dh.h" diff --git a/test/fixtures/crypto/chacha20_poly1305.js b/test/fixtures/crypto/chacha20_poly1305.js new file mode 100644 index 00000000000000..a43edbf6f96d99 --- /dev/null +++ b/test/fixtures/crypto/chacha20_poly1305.js @@ -0,0 +1,102 @@ +'use strict'; + +module.exports = function() { + const kPlaintext = + Buffer.from('546869732073706563696669636174696f6e206465736372696265' + + '732061204a6176615363726970742041504920666f722070657266' + + '6f726d696e672062617369632063727970746f6772617068696320' + + '6f7065726174696f6e7320696e20776562206170706c6963617469' + + '6f6e732c20737563682061732068617368696e672c207369676e61' + + '747572652067656e65726174696f6e20616e642076657269666963' + + '6174696f6e2c20616e6420656e6372797074696f6e20616e642064' + + '656372797074696f6e2e204164646974696f6e616c6c792c206974' + + '2064657363726962657320616e2041504920666f72206170706c69' + + '636174696f6e7320746f2067656e657261746520616e642f6f7220' + + '6d616e61676520746865206b6579696e67206d6174657269616c20' + + '6e656365737361727920746f20706572666f726d20746865736520' + + '6f7065726174696f6e732e205573657320666f7220746869732041' + + '50492072616e67652066726f6d2075736572206f72207365727669' + + '63652061757468656e7469636174696f6e2c20646f63756d656e74' + + '206f7220636f6465207369676e696e672c20616e64207468652063' + + '6f6e666964656e7469616c69747920616e6420696e746567726974' + + '79206f6620636f6d6d756e69636174696f6e732e', 'hex'); + + const kKeyBytes = Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' + + '3eb7b74d8000bbf30', 'hex') + + const iv = Buffer.from('3a92732aa6ea39bf3986e0c73', 'hex'); + + const additionalData = Buffer.from( + '5468657265206172652037206675727468657220656469746f72696' + + '16c206e6f74657320696e2074686520646f63756d656e742e', 'hex'); + + const tag = Buffer.from('87d611a2b8012f4eb792ccdee7998d22', 'hex') + + const tag_with_empty_ad = Buffer.from('2ba3e8380c1f49f10665fd15a4ac599e', 'hex') + + const kCiphertext = Buffer.from( + '01e15951ce23d7672df9d13f19c54ff5b3fe17114eb637ec25c1a8ac2' + + '24eebe154b3a1206187e18abd31d022b1a66551fbbf0ae2d9fa4e9ab4' + + 'a680b185528000a7654731f05f405ce164cfc904d1759afa758ac459f' + + 'e26420fccac9692af9259243f53e7e0f42d56c6a4b4827056ca76bc9d' + + 'e92577a9f405810fd1e4cb7289f7d528772bde654fef456f031b87802' + + '6616df7349bef4fdff4a52953afadddbd61a3bd1a43815daf1b1ab962' + + '8aaeaee52866466dcb45650b489b2226a01da24d85c20af24e2beb790' + + '233081c5651258cf77e5c47e87ac070aeaa470d13b28b4df82729c350' + + '3cd80a65ac50a8d7a10dabe29ac696410b70209064c3b698343f97f5a' + + '38d63265504ee0922cf5a7c03fe0f3ac1fce28f8eed0153d2f6c500ef' + + '68c71e56e3f1abbfc194be4dd75b73983c3e7c0c68555b71eb4695110' + + 'bb8cd8f495ce7c1e4512c72fca23a095897a9a0dfd584abc3e949cf3e' + + '0fa1d855284d74a915b6e7455e0307985a356c01878700b21c6e0afac' + + 'ee72021a81c3164193e0126d5b841018da2c7c9aa0afb8cd746b378e3' + + '04590eb8b0428b4409b7bcb0cb4a5e9072bb693f011edbe9ab6c5e0c5' + + 'ca51f344fb29034cdbe78b3b66d23467a75e5d28f7e7c92e4e7246ba0' + + 'db7aa408efa3b33e57a4d67fda86d346fc690f07981631', 'hex'); + + const kTagLengths = [128]; + + const passing = []; + kTagLengths.forEach((tagLength) => { + const byteCount = tagLength / 8; + const result = + new Uint8Array(kCiphertext.byteLength + byteCount); + result.set(kCiphertext, 0); + result.set(tag.slice(0, byteCount), + kCiphertext.byteLength); + passing.push({ + keyBuffer: kKeyBytes, + algorithm: { name: 'ChaCha20-Poly1305', iv, additionalData, tagLength }, + plaintext: kPlaintext, + result + }); + + const noadresult = + new Uint8Array(kCiphertext.byteLength + byteCount); + noadresult.set(kCiphertext, 0); + noadresult.set(tag_with_empty_ad.slice(0, byteCount), + kCiphertext.byteLength); + passing.push({ + keyBuffer: kKeyBytes, + algorithm: { name: 'ChaCha20-Poly1305', iv, tagLength }, + plaintext: kPlaintext, + result: noadresult + }); + }); + + const failing = []; + [24, 48, 72, 95, 129].forEach((badTagLength) => { + failing.push({ + keyBuffer: kKeyBytes, + algorithm: { + name: 'ChaCha20-Poly1305', + iv, + additionalData, + tagLength: badTagLength + }, + plaintext: kPlaintext, + result: kCiphertext + }); + }); + + return { passing, failing, decryptionFailing: [] }; +}; diff --git a/test/fixtures/crypto/ecdsa.js b/test/fixtures/crypto/ecdsa.js index 4b3539edb1fc89..b8827b24d41965 100644 --- a/test/fixtures/crypto/ecdsa.js +++ b/test/fixtures/crypto/ecdsa.js @@ -2,6 +2,12 @@ module.exports = function() { const pkcs8 = { + 'P-256': Buffer.from( + '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b020101' + + '04205119f596e17f3c55a170b674c96e6bea7269dc9240047c76c841ff49106c4989' + + 'a14403420004b1d1e7da708dfadf90bc013a4009184bdb3f9065078f5598f6ad2638' + + '65a387e249ebf1a514ad8c943635a66d8acd64ebf2c876e55448813f10026a5e1f0a' + + '9817', 'hex'), 'P-384': Buffer.from( '3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010104' + '3002a9a0d899efa87e7564110907e9d82c21bd6265a37abd9a6fdb0f80ec844dd3a1' + @@ -21,6 +27,10 @@ module.exports = function() { } const spki = { + 'P-256': Buffer.from( + '3059301306072a8648ce3d020106082a8648ce3d03010703420004b1d1e7da708dfa' + + 'df90bc013a4009184bdb3f9065078f5598f6ad263865a387e249ebf1a514ad8c9436' + + '35a66d8acd64ebf2c876e55448813f10026a5e1f0a9817', 'hex'), 'P-384': Buffer.from( '3076301006072a8648ce3d020106052b81040022036200041d319d692dca5f5754ba' + '7b32c11642c6d8d2b4fb8249c3f214d71e90b5252966d97f7beb1faab1e4f3e26055' + @@ -45,6 +55,36 @@ module.exports = function() { // For verification tests. const signatures = { + 'P-256': { + 'SHA-1': Buffer.from( + 'a6f9548fa945bca4ce6e41d4099623b409e21070c03179161867d3ca411ee4f39e' + + '51ba999723e609d0abb2cc48450e886544ca400bae09841651211b43907672', + 'hex'), + 'SHA-256': Buffer.from( + '15f4adec59122298cd1642ee9104748c705dc6a3f70ed8222a52ee0420a35ce4c8' + + '293db29689acf24f6009b98df0cb8ec1aab17f8ad448a8c0e86843dfa824a3', + 'hex'), + 'SHA-384': Buffer.from( + 'b860be71578d07ad137c2a75ac29528114b23f58021b2c2875ac1374ed3143a928' + + '4efd9402b950cfc738fc9df4e33d917594f2078b96a02d5fbf17efe94e72a6', + 'hex'), + 'SHA-512': Buffer.from( + 'b6a0a14d7e4bc6dd2eda82c9234f174b670b60c8f7d101f68fdf5889e02373b025' + + 'dcbc4c82f2929b8e06c68535da98e38fe399c53a814b097935581ef21535eb', + 'hex'), + 'SHA3-256': Buffer.from( + 'f6a48eb5557f484ed0c3e4b5c78a3cf497cbd346db06a4165d429248aa2cc51a69' + + '747d09f57af145469a8b607a9b8b9709629d74e8f5ca337c6ddc581b6f6103', + 'hex'), + 'SHA3-384': Buffer.from( + '777785978eb59da32888554dc7fd62d1ba1a3033cddaa8c36b8f3dcea8f85e1c8e' + + '6db26f509747bd144dfa9436784bf4abbcaa6abcf1ecc09cea3b921d46738c', + 'hex'), + 'SHA3-512': Buffer.from( + '0f01c2083b5dd7fccb2784563f88cd9a815d570a1690695e426643ab725780760d' + + 'e972e26e18d67f5557be89f17b4cd0065ce2937de299bdb2e972ebf7635084', + 'hex') + }, 'P-384': { 'SHA-1': Buffer.from( '65fe070ec3eac35250d00b9ee6db4d2dadd5f3bbb9c495c8671d2a0d2b99149fb2' + @@ -61,7 +101,19 @@ module.exports = function() { 'SHA-512': Buffer.from( '72fbdb369fd34c1c54264d07f4facd69b02e4206f8a8bb259b882a305c56fde2d3' + '5107e493c53cd6b4af0b31306f4d03fd43cfc762a1030e17a3d775453a1212b142' + - '9f7b3d93066a5f42a10b138cd177dc09616e827d598822d78d4627b754e6', 'hex') + '9f7b3d93066a5f42a10b138cd177dc09616e827d598822d78d4627b754e6', 'hex'), + 'SHA3-256': Buffer.from( + '0b07c078be30fa5925a307d6fc559c5f398e63fb5d007d6b24a834847f2d3d18d5' + + 'b5e840711c52a7bc6626c3ced93301e873c013a706f6b297c12cc6d47a71e0529e' + + '719f43957de9995621d3cb0217469adaa6fd3135470771d0aa9d05d7a9c6', 'hex'), + 'SHA3-384': Buffer.from( + '2f36e8b04af46f68ef900c2720e3518b06f5440865d44072bbad5d62288c575042' + + 'b183a372acd70328c738668dcecb9866801462d62df3c35450fdc6c95433103fcd' + + 'c77999b640e3f92bd4e9be6e27ab129d1bc4f0b2a4c829388666920892d3', 'hex'), + 'SHA3-512': Buffer.from( + '32a951e886c33ac57a008efe9643bc92aa3ece9521d115e0c7240caecf124d1f7c' + + 'dcba7fabb9ad5202e04f7aa591ab01ed3f060f04f493e4f24430fe8159200612f0' + + '2849108b8be6edc8494c328097ad9265928efe5cb9d91be2f013ee17ee4e', 'hex') }, 'P-521': { 'SHA-1': Buffer.from( @@ -87,12 +139,30 @@ module.exports = function() { '5ae23cfcca0aad78f6b6dee6b4718b95d0d1a715aa3378470e50b516c18e0f3305' + '01f0071e6a32867fa70f695cd39c4e87e142b9e4134d38740bd6fee354a575167e' + '13524e94832637910fe11e53a85fb21b91adb81bb1779c4e2b8bc87c717dc35084', + 'hex'), + 'SHA3-256': Buffer.from( + '00463679f47a4c705e03447360dcf34d1743e0d4b2591cc66832a6bc80d92e538c' + + '169a1fd330f98e7235ca7fec7e16ac44fb13095b8edf2c76b75c4845177d59e425' + + '0127c4359f6a4c9ccb63e7a9ff8122c0b4a8b7408e28c96817ecc3baf8c559c413' + + 'c3bb580447dec9f52139b2afde369cd51730f050bc94137556ae137f0509464219', + 'hex'), + 'SHA3-384': Buffer.from( + '01969a4db0888bc067a68a31fe5d0fc97e0b701f570565f7b25cb27707c6f020ff' + + '680f8553ec5c2d6885e9e91b39262ed1bde375525eb13fdf12089b7939c7689735' + + '0101c8b8d1129a217e8e956bef78cf7b9a0458523b04ac8e0b84ce73d54326f7a8' + + '704ee42fe183f3ef79d83e676f34dc5476e2342641a5b973d3d94e8503676fbbc5', + 'hex'), + 'SHA3-512': Buffer.from( + '000f362e914ee0136663cf57bf4085c25604af6dc198b4818751e1195ee7e41a16' + + '91be909dcbc2bae00b8917f6bb918eae3740ac1b76e0913137c2da1171d6400b55' + + '01ec6e1dc5987a27fe16fc2ce5c8e954088f898a9bbefb176eaa8bbd9ccc264c4c' + + 'cc38c83ac8b5a168f90228daf8405a2b9bf7829c263a646b4e1098e2ace38deec7', 'hex') } } - const curves = ['P-384', 'P-521']; - const hashes = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']; + const curves = ['P-256', 'P-384', 'P-521']; + const hashes = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512', 'SHA3-256', 'SHA3-384', 'SHA3-512']; const vectors = []; curves.forEach((namedCurve) => { diff --git a/test/fixtures/crypto/eddsa.js b/test/fixtures/crypto/eddsa.js index 8b1a5ce1c5a45d..f752d0d4427732 100644 --- a/test/fixtures/crypto/eddsa.js +++ b/test/fixtures/crypto/eddsa.js @@ -1,5 +1,7 @@ 'use strict'; +const common = require('../../common'); + module.exports = function() { const pkcs8 = { 'Ed25519': Buffer.from( @@ -37,7 +39,13 @@ module.exports = function() { '025a2a5a572b9d23b0642f00', 'hex') } - const algorithms = ['Ed25519', 'Ed448']; + const algorithms = ['Ed25519']; + + if (!process.features.openssl_is_boringssl) { + algorithms.push('Ed448') + } else { + common.printSkipMessage(`Skipping unsupported Ed448 test cases`); + } const vectors = algorithms.map((algorithm) => ({ publicKeyBuffer: spki[algorithm], diff --git a/test/fixtures/crypto/hmac.js b/test/fixtures/crypto/hmac.js index c4942976adf49b..6505c6e2ae55b5 100644 --- a/test/fixtures/crypto/hmac.js +++ b/test/fixtures/crypto/hmac.js @@ -19,6 +19,16 @@ module.exports = function () { '6b1da28eab1f582ad9718effe05e23d5fd2c9877a2d9443f90bec093bece2ea7' + 'd2354cd0bdc5e147d2e9009373494488', 'hex'), 'SHA-512': Buffer.from( + '5dcc359443aaf652fa1375d6b3e61fdcf29bb4a28bd5d3dcfa40f82f906bb280' + + '0455db03b5d31fb972a15a6d0103a24e56d156a119c0e5a1e92a44c3c5657cf9', + 'hex'), + 'SHA3-256': Buffer.from( + 'e588ec0811463d767241df1074b47ae4071b51f2ce36537ba69ccdc3fdc2b7a8', + 'hex'), + 'SHA3-384': Buffer.from( + '6b1da28eab1f582ad9718effe05e23d5fd2c9877a2d9443f90bec093bece2ea7' + + 'd2354cd0bdc5e147d2e9009373494488', 'hex'), + 'SHA3-512': Buffer.from( '5dcc359443aaf652fa1375d6b3e61fdcf29bb4a28bd5d3dcfa40f82f906bb280' + '0455db03b5d31fb972a15a6d0103a24e56d156a119c0e5a1e92a44c3c5657cf9', 'hex') @@ -35,6 +45,16 @@ module.exports = function () { 'SHA-512': Buffer.from( '61fb278c3ffb0cce2bf1cf723ddfd8ef1f931c0c618c25907324605939e3f9a2' + 'c6f4af690bda3407dc2f5770f6a0a44b954d64a332e3ee0821abf82b7f3e99c1', + 'hex'), + 'SHA3-256': Buffer.from( + 'c1ac5e11fcd50c48bf567f6e296632f5801c4eb07a8a47579b41dee971a3099b', + 'hex'), + 'SHA3-384': Buffer.from( + 'ac8c97f6dd8d9e16101063077c16b23fe291a5e6d149653e9ac7002365159317' + + 'adcfad511996578b0053a5c14b75f16c', 'hex'), + 'SHA3-512': Buffer.from( + '2162c2a8907e6b2f68599a69e81a464d8f076b5eeb555d98b4d20330034df3c7' + + 'cf35b1fa958a074ca12f0d242df39f0da3d4f1dbfb3629057798fe1f883974ee', 'hex') } diff --git a/test/fixtures/crypto/ml-dsa.js b/test/fixtures/crypto/ml-dsa.js new file mode 100644 index 00000000000000..26327ddf1ad1e8 --- /dev/null +++ b/test/fixtures/crypto/ml-dsa.js @@ -0,0 +1,47 @@ +'use strict'; + +const fixtures = require('../../common/fixtures'); + +function getKeyFileName(type, suffix) { + return `${type.replaceAll('-', '_')}_${suffix}.pem`; +} + +module.exports = function() { + const pkcs8 = { + 'ML-DSA-44': fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_seed_only'), 'ascii'), + 'ML-DSA-65': fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_seed_only'), 'ascii'), + 'ML-DSA-87': fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_seed_only'), 'ascii'), + } + + const spki = { + 'ML-DSA-44': fixtures.readKey(getKeyFileName('ml-dsa-44', 'public'), 'ascii'), + 'ML-DSA-65': fixtures.readKey(getKeyFileName('ml-dsa-65', 'public'), 'ascii'), + 'ML-DSA-87': fixtures.readKey(getKeyFileName('ml-dsa-87', 'public'), 'ascii'), + } + + const data = Buffer.from( + '2b7ed0bc7795694ab4acd35903fe8cd7d80f6a1c8688a6c3414409457514a1457855bb' + + 'b219e30a1beea8fe869082d99fc8282f9050d024e59eaf0730ba9db70a', 'hex'); + + // For verification tests. + const signatures = { + // eslint-disable-next-line @stylistic/js/max-len + 'ML-DSA-44': Buffer.from('f4d00cfbf585bc0178c62de0a37a3e3a2593a8fb4ea7147ae35c3020e0084b0a1ec21f6523695ca7e14a267083b95f56e9423b3487fb09be59c84f1ea80c8ec573e8e8d2eaa9a52dd7739295f6614fc705a2dc9b0122f9d32b731b3743d8bb44f47c5a4488cbaabbd44bc3aad0bbad9fa9ca6d29c4c3cf6a87f1c8654b2fd7c955f2721d0da7e39a01a7fa39b1b1a0c603da985a4b02e6951bb7b5ab0943fdb199defbdea65f64b619ab3334337a67b4547efa7e6e132f18b8abbe66875fc3f9fa95d9d4613f55356dfdb2357c7b7556560695aae35dbfa69d2c259ba7966dad1640fa6fefdfdcdee71aaf2a35784b19f8f6118ecb5a8f174bda4589f13dbfd21b5d90a68bca5c31a270ab3aecf9ac549568396396b40b2fba1e5e1500a9f127eb5d08685be93a4298f03351715a917e1cbedd4db605389b69448c06607d7f38a25746f9d1acae6c7827b620ea6e8526fce50e6ec4cdef1a7a154fb52db77cd3b0c4de8e54f43426fc9342aa4b9077dbc62b30a71af3eab48ccd83dd84122bdae87173bd9520c6caba46d8184340cb34da8a97803a21a670714096efc925f6920df3dd8b29aea13788040328192c44245275c562ffec15cbdca5bfd5a3514080353872b8d88bf4ecc58b6db8d16d00d16e13d536eb91df584c8aa8ac560f752c1d5b0c7247d628f4a569a6a3810562d1364055f3cef73f1b2f51a0f2595f50a3a09840c59a497f3cd837aa29f64f28b635fd0500b4b62766987c99cc5d3c79b01178a7fd2f84d588858905a74da8b725d094a7c255a12be113cd7907d44ebbabdae92ae21d9affb25dbd08524fa5fffa513d772df135bdd8bc99e48af30ea46567f8336cdb546cb2db23817b23c0915b5da6e56a83cf19ab742faf7eefc9f95e22ca95d0587b3dcc580cd2a932b884e0d7b6e72124cdcdd6f49caa63e4f85aac04825ffefcf223f4537ab04cb718dc18ba72993aa51d4d712e9a42fe3e1834e6c70b19762a5895ee4fe1e2329dadd053d296bc18084bac81431280433f8b73d3d7a63f9a9ce7a09b90698e467517382bd0d3e305e212381065bd2e01d6ac4b30a9ddb95f9af7b9724fe3e38d97b6324d90dae256fe389b3b835b63a47d80da3e9389b8970d58ff5b8bdc62c6984c86ae7629b9b6e728e87341515d2420275d00a5a65d9bd512af1b1d47fa84e422d6757a9a70244d0aa64ca818d5259f9efb80e5d251a6e1c4e1768ec9c77c6822246d2cbe18ed72b7877cf95002ebf4aefd93caacc5e130dedd3b35c782fb75a57ed29bf6dce66041e13ebeb54e98ea35eeaa6b32e943f6a7b668d5ecd563fa03be46151d863e9747cb1c355c2e3cb0b7627172e1ecaa50d295ed93ac0529d606b8c4c8285d9dce0c9b061ad93a12dc1832710bf1e4c5799fc0b7383c8055ea14a9a039d3f1ed8334741b7ec8bf2d8f7b1d6ad9db22b44a170aed2d358e5a20fc19e1ada04c390ec7647c49bde13924a9650dcc356d45acdbbcad1b2ec21a5eb835689dc958078e3c41898bad8989c5d816bbf403babe96661215e68e59f0ba747e433a3073afb957ba3ae3a435666dc287c2def5aba859674b181c522a6a54ed613faadcd26729725fffaed54692634b13908dd52db47d441306d8f31aafe6b1146cc736290ab4f8e01d54f10525a1454b5a49d389451d071076d8cac0b8d09ddbe44e2f5648a617cc075da438014eeb1c3bb3de26a2d1a332f82b1cdf11544e9f459334cd61e94d9ba2ba57b2db7e9d22b86950e2a2ef5bc5ecee6efb60d5123a7507b6fb73f2ffeab60c9a70de6098427aabaf8fd29f32335e2d14964b56d5bc86361fe01ab0ea6278579b4e7c3866c9e79ce006997a74d7964be9a632b376df797fb593ded91c802959792b3c38fc92186ae2eeb38dae94181f15c8a4abb5e1740c8eb3460fc88de5ca6a558a81381b29e2fafd2bca4dcabdaa4e45c9cd334614757c6a7be7788c93e27b6fe8a4135db82312274c57e98f06f8114e11ba5a4354cae97d44ae4f5b4682e34f955d5535b859a6cb59028feb0309470177163da3a7c1a76fb37f021f16678cdcb07bd2b49b0c77211560e02bc08446e0af3d1c7f736cf620a0a79d2a849117cf1af585e26b4984c157f5012557f96d41dabf98d1533c31df28bb8a43183e592d890bc111c0a94db3d081c34bd6c003cca0abef79bb8c95c62c6647de01e47962874a68b671042e315e6f192175ace030aa9b76ffa9f4e199c6a4f56453a2b312477eafa4c383ad2429fa453ac15bfde030532fa2e26cb200e3c7b7061f076fe28dda68cc07e7c58ccdc22fd164cf5643238d9a39945da27372f600b80f88d641734a1f8f5e4e47d0cdfc083139198f42908d766f308a3409f56b853dda9c4eeef87f9e772bc8a1a3d5f7c4ebf865a722d5181da636297161f8a7e23d54b8523e2b52c17ddcf8f388032e4b8780a5698dda7fcca8fb6fdeb5e4328d6a886da5015bd58087a0cc43a72f5b88a78a7a12b0daaf470ed049e170b8b03ef8620b654e744859bba5d5543f1e823eacb59511230aff547900091a37227c9021549e4eb954c2b7b587764fc88dcd224067d9639cbc8c3fcbef71651d4273f21f5813626c710c12f16cd0757b3d8b8ed19b474e53072207502b66faaf756fa8846304fbb20ece8ce87cb71dd8344d7b10c7e516fc3eec7854e6048a893bd456421402994612a52fef16a97ab642edd69c30b1cbbe0f8e73b8f12db6574f7c9c7de94cbd698688060de7a63c8247aa3b95bbabbfdbdab7fa637e5404123ebafba52383c459621c154a2a23eec97382eb531b2ca83cdf075ee5bd54ba3bc3e19dc3ccfff08e5e61e8b49b080809a57db3548845ae9f5e15fa10024e2b86b283259aa11f3620d5104561fc32afffe54526c5f26e1c941928fed2fc4ad247ae0f590484265589cf0ead253f0a9fd50ae2d8cf02deab6a0f715148f905274186f0c30743e3542bd6ee93563f596c43baa6e0b99be127d722fe17299af2716365a7e72ebc20eb811291ce489b739ad2d3f79a4e81a2090cf81b7a45ff782ba3655bc717e0f80e4a276c0c3db48036c67d1e95797e2c7c9e183a861e28c9604f3e2187e470cd0b37a7d2e78b3415f6391b7fa04c492edc03c5a728507f234fb6fa885ab1939f64291a2058538bf83cc7cc3ea785774332d92b816c76d8d99b94fb8c237113a24b90768b2314aeba2846a645e623834a3a947856e7c85eddd63a1865700d8ad5e36521fa1f90478601a557ce111bc7eed0b7542ff6e8e887e2253db793abed45581fbe9ff0a3100308102026616a7a858f9e9fa1b0e10e2254607f84b1b3b9c2cce4e7fe08162b31384354627b8296a5b4c6e3e6f1f6f7020a191c213b686f8491929c9faae3fb000000000000000000000000000000000f1d3040', 'hex'), + // eslint-disable-next-line @stylistic/js/max-len + 'ML-DSA-65': Buffer.from('0d7f54a113bc87e6be6abc34282a0abf0dd12e8b3ef64f338d62be9986eff721776a3ba2992b069c8c2d15e0b948f2615a7a71ba42650abcddf147f50e6ff7de01aea560ead3b540ca1754b4749ad345f750d75c379804b7893cdfd059345297c8776fca5378ebbe1ee1fceadae9bcd01baf39c8f01e8791730e7e1e5d0efdfae2acdb34e6be09a258b83eb29da9e48506c5d1029d1b7157b103fa4fd77c8b305a0e4f2dc82b68936254bf3a345143920f67bada15d3038f76c160281085a666aba741ee44b12ed4018e9a41d77c642bfefd658255ef560958575c338635d140bf73c2daf923f26898fdf45fd48ec24c4ecb8adbef15bcec7ef5302fb58ce6a16628bae20a55c497c0897f86f0ec22323b96e1bce5e0cd7c608aed5706a2c73d5d4d05cc637f86320a8eac06b8e26a13874bcaa9131db6c9e237841df21789a47fe48a13895575b77e141e3285197b5a16f19a00bccb1d384ae026a17d64a55152188b88a682562340f667a19dc3bd6b5592d39e8b7b408c1ecbf768628038fa4333a475a72c03f3cd8aef346b1910724a47ed915ff1b2bc71ae17f127b903c679b103026c2aeda967b0d6497a30790e2507cc79bb78e9326f66c8d015f20baab3311a7b436c357356f20cb03209bc82863004597439d0905d73eb6213d14ef4505879e5ac3f4988956d5483bd0665d0d9b3e9353de68e7108dd0a6af6d23422fe193294419c3887571b7470d206f23cd059895ed5a53cdaa6ec94969b43eb9c98ae53b909a4162d6230717b3105dd4d511a40b9817a71e037df02d95c508df1a392aa40d9516811bd520442c00b8377023cce3f1462e50636813ba1e4350942c435743939cb540d4688d0db69885ec2d2e3d37a408030737dc8fbd42cf19df941fa5f8b532cbf839ab13b940b5b37200b900d27681ad97a4f92c256c29d63888302b076665d5b9c2ce219fcb12f80dce2b94e6001dcf3f662e01f905694a78ca5e2afdf8ab7cc1fb72e04521d9998662d423936d5ad359ab44091b53467406b7c306c0a0ad2e480c968f2d9e4f392ecf983fd2a4ad198bd701d09a1e2efc5c313999ceb83695d99a5ef25daf159d26f4c0ffbe3c2ae03cde0962cbf1a4e6a98dfc660922c2a399190abcf933a73f68d491c4ad8806cc450bf23d4e1c1377f6168a3c4df5b6bf8e1cb4be11bda24b6d9c113ed4d40fc2dba9f541d97739b5e40e915be1e10eb01c30ef609b6eaaf5d570f886d031df36fb7614080dfc088e821d1ac4fc50a48c5dd7ca6f08c7ac280bfcca640cf738f08f7da29639341e396a46d7204cc514d9c8dbc213b967657f4a67a29458947687b55ecf26d833239a7c0915f10334befa61b74372a8e1f4adf44906ea6395e15dda5e4b2d2005d574a943abd9d224c7f904298e2b1e249ab33d93089f239de6ce6b0c2e1b856940b7b08ccf1f98a65dfb3fc615457e2ba436bc63cce72510757380a5c07d56b47c93430b6bf9a89e5f6e1721d7d50b7417432471dc2ee0b02430fe71f4122c0e3363db446e4f7ad21cee944f3e6072fc1fa57d175329707f52c51f97eb287d7af70ef91c0632ad5b89929507a2d01609993c2ff8bb991e232b554430cbc0ae4f6b3225013e1fd0f8e0a485f7b55c1f96dc0fb6b395ce104d9c3d780567cc9f7bcda296f665a5eca11a94fd1d0f35d10e0b017880e16f31de961f7b5fabafadd1c99c80cf7535e7df7b807fde38b530ee527234983e25eace30c4be8fe18edc58ae53ad79a145ee0700be63b636a18a4eedfab826383a9e5d9f2b7e490c6bbcc3ec0f37ee44b8e301091c6636cf212e3519b706919af41b872d6b4778cca8ca3373f62dbd8f68b59ca29467c63e10b886a18d2a32864ebca2ebb2f602b736ac8587d544d74d9aacbe6e436646ffccf2ca3da2121af8b3431b523e09a538bdd07ad3cd4c71a76cfcb67273f277e18094ca9730d06af60c354824abc84f623e1727a4f43c7021ca18d463c5aa1c53cd4adb77f13156103ac0907f6487689bf5eb2202a6ace8c9f55b5a285311d2e243090ce07e9cd2bcd253edc4ca57c1c42383184ef08d3f7b4ddc05fee25e8d72a6fa0dbe545dd3240edfc8081fa49542bc23d426ad286010920ad92b0fbae5833523a94ae59b87d90e1ae4cb504f06512b9c0c47d854750b45bb01a0a2788c51d808748f6cfab177fdd943965187fabc8419292f45f57718f709228a9d51dc43e21ed3af2bc5cf8ba75a4d22203a77662a71675b15bf91d3c7074657948b43e87837449bfc5eab1fb1fa9f8f91df5669b4957eceb02d013b0c04b493444ef673a103f630923c9ecc5c2cfb9fe40c1877fb9a67e54f7f0ea0c49d0a624220eea68bbe0e23d20092ae73a8a0f7749cf85499dbf4846308a190bf9ce68fb712a20db6910923eba88f42e93e76230bdbfa9890b6bce07a5ecffc396cbb314d3152bf1677a26a0245fd8373a1288d97e713d2326194e2e26990ef413a1076035e7e04be3d07cbf9403ce9f8b7f10c70badf62efd144324c1d37ead93ea94edff937b0d62820baeca5f56cdf30487097f5058ed3d74639543b1c160d6e876021c96feceff84bb2b1588c72f5f9a6a843b85d9b78da39a085f258296538467ae9568d23d9006e768445f9752ee754dc5ddf9b44998e90dae1a5c720d0abd3e81b750ff8a689547998a45b5a6155b462e8ed38c7b6c44bb81e64630449191a38ddff8eacb3f0cc675bd84aeb6acba135a65d2e7233e6b3d59f57d5a1bdf215a3a847a96306a203ff4a564524e03526fe3c35906fac3316cd19cbf39fa9b3a37bf2624c4f7eab0b98d43b967598349e7b0e69ae7a110aa5614e68afde1ac453bd8b02a39307c6423339d49df4a1d7c3042d0bd34ab9c36894eacaf1872ac70f0691dfec0ace938d17f1f61c7a0b6a12542f0d5e0438d2f0c69dc4f0a2eb5eebc56155954d97855a5e95d133663f29402279d39e41750c0667a721cee486847913d383940419dc9fbc118d1f608abf2f2ca244a7486d1ca2e883958a0d36e290edeea0e5dc8b44f22a6ae7efacec2ee642e8417d5b6338d58ef540b5438a2de2589e3c911d93570d1f7043da7f04f6a97c64af34f25a99c57662ddc27264d5fd880d6538a81661b8e83ce0f00e41a09dedc2dcf0935842b46c7b0560f0fef6c7e45d20de5566cd68c4bea75af56f68ec6f8061b59bad47d346c02d4ba89ed92ebe9276090a70f119faf4038cac4d655682285dd53707d00063f89f97dcda0e370a2b83bb6c8d9a4db2dc85efbf93d76b007de3566ae416a817d1fe9d7383451b8789d4892a4144befbf784a837dda4963cd20cca292273121b370cd1d0dbdc9e42aee99ea7c833779cd1fcd570f2c502c791ce06b082ca2f820eb75fa2b5ca072c915e3bdbc6eaa3cce642e1001fcca516c6635ee21c4a921af590d50d5127da0acb8ea48d11ea777192a540e588855acfe2ab0aa23e7743da7dddf11cb5fd7c3b0536b1dde5156b2968420b58d502d546781c2c85d39c80e9182474bf36970b19a721f01d103b99b7074e2fcd3bafe90609e6c3764e12bbdda9544cf0715aea922b34f33fbcbe0d3a878520f8fcb63a68302c704053ca1f4ec2ba20935561863d4a4d7c442b82f7e8d4c12a987ce5c1aca012fcbce39b96af6c34525eb197c82385771dda017e0d88007e944cce84336c08505f39138caa9f9b2106ad5aab0cb178d3361eeeae7b2124729afd40064c9dc3c211ddb5228791f888dfdebf9c17df3470f6c3ecd400ad150e2da5acebec9e0707916e481733b40eb728bf4530f634e0834e14d6e5024d5e00278f503cd0b308284016c829bab2239429eed5dd8b99b3295e27e4968a59f6e411abce882d1b4282f3cddad447ae8011740985ca9e8de7862f80ce1713a2e7261217864671567728fd0aca1812804a99264fba96e6b6ffb2aefcc1c8f95c990710f1e422175c8d26cb3968522c4216806d83982948d4a9a99af261c85cf713c70b4552079f9ec7e1563f4f3604cf3a8f258a63774a2c98071fde26a4b462ff64d3a829d53274ebc8306fbcdf90f244333a217196a744dc3bb67d998d6af560bd9e328aff3865d02621b4203caf986a0f6216c827eff118b28ed08ab42e70be5568d41a5e9ebfd4ece177670f5373797c0b9f99d07e25cf8a1615fbe0331f952685c6ff68626cd01551778bb6efc6e8912acfe7028254b20f3a8f6fa11d0ca336c73cfa4f754c16cbeebd89d11b91b5b59f422e14b5d419d19669af331427ec4949ebefd83ca4b973c53655a38d189c06764d3e7230fe2327d7d98e4e6f82b15d4ea0a4d3c07214d00b4bd00a6198010f13415d43076e0d6af24e94a242f70ae947bd0550f391516a9223bef09862bf5e13787561539fafe4c07e4db618239e6fd69c0ab130eea98e864f76ef0eb273af8eb7228016b495aa3b717ef057a7e28ebc1fc8e28d8549936f511382bdec65af3c7f1e149df5b51ad0480ce282fc605bccc8b0c4f7688351947b396643cb1f7f61dbcb123f2230175cf5fc6ecaf9ca6a58ec493cd6379095f6afc4e0f4cf0e6dc710d3e2dbbc307e4b80db6855bc26c47334d4e5a7d8691a3afea043bb9e5f818406f0e5da4c0f7fd57c9cad8e277b1b4e7000000000000000000000000000000000000000000000a0f12181d21', 'hex'), + // eslint-disable-next-line @stylistic/js/max-len + 'ML-DSA-87': Buffer.from('737897d83f7fb82c704f4660c16c544212f3d3d280b3390c43d356c281aca7a3c90637a5dea83bf3504e593b178b17ec38a8175507a6c28b80c21cee28146af2161af60fb123420578e0576fbb9d579631efda9a4304c0ed67572d393d813155024ceab089c1cf1d0a5e9716948312f70b4a7deff76853f5cdf29737160901c14ac2f24ea4c7f819d86fa9758b8ca6aa6ffcfcd6d60f90ce31d31cd9d88586b9a38a22402be7c89c9d57c2b194179011c4fd185a5bd737032c1e54da0108f355130ffecae3f39a47148dacafdf25f9590bf6c8fdd9d9b7cdfe30607a235993c59631f29fe01453e2fd61cd4e8e3be21ddffa054c5e91fec88ecd531090ce21d68b7b11888ae9372fe2dabdb00ea311bfa564f86de46c61f284a246e420dd8056f021448a2fa6dfa2391e5beb6ef0a5a28180dcdf20e00dff4f9628a99054ce72061b0359cc67d4329545faea8943d90bb0f85bcdc88ef2acfd72056d2b5fbfed40034f2d344a395434d604a336c1e418e2598d07e8611f9c28c23bd2771c4d4dd5f820f197b50e58bfea502f6a53893d7fe6a7460a62b8ee892d17dedf606cf61fd5f27e002230bd15f0e3d5d951df3c54228c20d58fe5e5d072a1869bdeba1d90cf019c6bc3fb0df24a11894bb3a2d279c67d3a6d4342e0e2913861bd3f684bce8ffccbd6358dc0778c02896b719d675b584621edceabec3a8e679686363825e6bcdf6579ec67ecafdf23d6e23b747e340fc0982422865b8002590f9e35c8332ac47be5c4be6eeac3ee4ba79574f7c0aee0fac0e5cc7a9b7288814c36b10e2dbc900acee6841c386fec11c69580a87244fc9f995f6adec68b308b28eaa17129c1edfcbb8592b4bfdf6604afbf1f206010b47e92d08d830052d109f0265213fffd6ecc47aef5bd6f4d5fc19d2b06924cb96309945168f382977e9f6a2f59237b4a91bdde2d00453df0e7c516b7a53b0b8582aaa7ad8af9f40723d09a247b653ee48999148b5c160a25f5e9cbbdb53642429909a5af11c6f36db3fe8f8916f36f565fdbcd863565a6dd63b974304b6aaeddf91decc981d21cc93c11d8f76dfeca8444b6b882ff59be214fb1ae9fd90108f65d4f533031c35bbc552950b1a96f4be7ea9cd167231d7a7fbbb203d34bd764dab3ed2eeaba3419ae3674f7f01347427b84ddb5b86969e57e6bbf86b61bbda8fe830f357d12d15eea0df7d5fa2d641b03077f058719bf11819f0eb1dd921b783b9c648b5b3cad3d22e9442e514e9eef96d53c1de328dbb79eb801974b88e179d98d28bcc75469a2be46ae9fcafbfdb22d302ad2b535d7c09d06cc5ce1a50a569dbb13debeee5cbdd3341d06d8be28681c7716b466284efb4bb1b316b72f50c85c2a260c68231e2c516265f156e5f5ac735b9a420878db52950cdfcf21dcd9da62e41a4e117386b624f638ed51b4d2e156573b6a327698bd41a3d036414b201b0a31d818fcdce9c1e4d55def388fff5fcf5110b680e5ba3808f244dccf7fb01e8197ba3c3a3a2be74f44551f5bc03f77ca5010d1c9d8020fcc7c354e3ce9fa25d4900943f564695da56a6e4388d1bc69a68d413fc80e5913d22d2414b081052c216afc67ecce4ed126a8e0762191558cbbdd8d4150af7bae41f4941ed42fdae4fed9a40d70a0159d4a8ae48006022dccb6b4ae05dd484c065dbcce9b253b40d1eb542073fac64dca11b6e1196c5b50fb33d6bb831b9977602bc9d43c80713a5d714116e62fda8e9aa2c790e8d5adbf8025dbfa5f80bb92b661d45fb972eafa25303f1db7839efab7271ae41ce9f7c28a7cb8349a3eb2734c6b13caab678d53eb742fbe84f53b8ec89e6c155ddb0a4908799372123023f4c8d6f97d30fb70ff2d192be0928fdfabb9aedd04f944b2c7e0ef143390a20a5d5ea93d3a8f425470666dbf8733a2e2564f27e1878b2805d71069e55b225e3d133a7cccbe0db3f2b14ee77d717bcef348df09259efe12bfef08cbb0e8d58f08bf10c6a7b0c2820b442c06209e9b8134aa20d05e8e2c763a8b88b5d4ec544aeb1ccd9468901f6ccfc20eb632a75d051a0295590e02f5ded6efb33e54479b7e1104062ccb73bb5bcea1da9d4d659e1ed40a42ec13a6f27606a002097565264954625103e921886df888740b7166640132b428a97062241d083cf8b4892c159902a9cf35921df2966b50f6f31209773d1bd10c61d14409cf1e90a7d06d3fa226a865fb241299a859b8dcc898b4bb1dcf65ea17353a130e71ad184e9e89f0676e666352ff4614d8a7046588d97038a449ec426b67c9ee3101b8091efdd10867366cdcdff3581c0e973ab6ed71c984b33a58c2e3a865ab1bd055e629a21fb07a8a23e998f2db958ccbc89fd1b3512330f2cc69c3d74e16f8848556011881701b213dd39e123aca241c7231052a7bfd4738fd69bbfcd0ab74276907c1be3f75311e395a485d15b592d16d22c33ebf91d64e81d500ea23d53f130804ea1cf51c4bf2b0474f4f62264ac7bd90e6e9dda4d2b627b1e8043dfae64c6920a4f9a14ef15826996035af30cfa59a6a2411c61d16938004e2ae2ec72c32825a44c3200853fce28e36620fd0c71121346aff28e19218899f8e894fd6e477571f2a5c6c850104495a89cdc0998496b979b038f65ebd15ffc477ef22e943358de58612bc734d06bf8b77fac755b64f4c71966c699c43c5c38b7fd647a8b82cf354b1da4d31c9e918e146a997f2b4fc612e2ec73ab8e017728308e589efe326d4a14eeb55de34849938446911b9f3c62ee51992da1ea284650e0fd6f6078301a46948abd7d6ec2ca9ee202f26b9aab99fc29271697d8565a5147631ddbc9295c44812262a9c39cf0df4e08c52749823bd51d117d6cc106d74fe329c658c1034dd7d9a2f073fdf8bec519aa4a2f9caf8d0d719cb37138a0383b263c1c28a8b15c1d18a63e366a9892299b507da6bc9926672289a4f9f679e645db6c85e1cc1f4e6f9581984b2f72283a8efa471614e3e3d4a58921bd0d5feb85dc9cccfe4a7eda2be3d8118b2f4ca35ef6824e1a2d09162fc81791c2e30fba9368022ce8e01e1601944f05a0771b2d3c970f88841389b11eff76c853570c66022c3d35c4e94714277cad948c1a2b3273c6b3d675f9606b8b1a5d1848a8fbaf0f6e108b0029b6fe2ca230cf0e5a24049616db1b5bbaa0116308fe5948c45a4569fb5e3aa0bc5ebfeeffec460935477ce1c06dab0a1e7012859435e9887052293ad68a92df2b498b07dc991f16d04dd96bff885bb49a381b27c7e3701aefedaac8472f9d547da1aa8e226db691fe48c0593c8824803e564f57bf6b1c927742c909666b07f92f77236a2268a6148b517c1d2dea60321ff1baa5f47c5dfc000c8abc1c78384e1d6ad67dd343ad5ca3577b254d0ff85f12953f2100e6ec07d3c87d8cb79538cc75aa60fb936823009a55a1e7642a18efb94ec2eb44724f5b492263070a3e5481d091820459691507ccca318ffdfa4a0f5bc079e4c6a0fd41e715d0a7aae8d1916b44edaeace00053ddd45aea137ec0ca8a9afd497fce05b6fee9d5eb8365f3bbde783e51e29bd399c4611b1bf67487b1a30c1e3097c599bc74a4531ef9f1cb3b9eee69ab195601bf3a75f4abd8cd2475211ae9de36b42c1cea349195a7758dcd6f0b0667012e271143ee765d48348b7ccc47a77ff7780fc071505b5a10c5734abcc07de9016e6efd1839eeb13d050bf1bbe28a119c81578516250eda43ee91effae2b3bde7128a6d0bd2426afbc1feb1f98d64de06d5d6899122013c467c7970e18edbe11a3c4900a182cb03cc149384ecff2833a3eed9e0071b7b3da23fb91302b76d8d386f60ac16e814be0114d16a85f757abd78e63b3b75e7b29b222aefbbb545e1286f10fcec514b9e8071de9861688b6c481e670a6d22a1d4f91f8d690df9e7d4e94b5846203d2fb865fe1a3fe7bfec8fc72c5e42a56cdfcbe6f7d3aedf685450d44f6143d1d64de4b093abd10d298accc5292109f67c4634c2dd973d38e2a4e48154e17f29a8aad0b8e6cf740437557e13c0b6d7fed4252d07e61db7f65e28e968a6dd47c1765973f13ef5d3c47801dd0cca810fe34ba111e3ffe18d3fea4814705c797cea0c53ad14f857e1e16a77eb649cbb3e3eadc613cf25f125f0c6a096b0d3b85b5a7bb53976a817235df8a384f62bf5afc08ae27e52863ac99ce1da62e592d41f5bc13550c6a77923c363cd3f5350a2781182b3469f34d6009a4417cb2bea309a40f5b6ad917c48adb6f9a2a3836d7491a96cea1328b1e0481a00a7b187d27fefceab3a09a2dc4741b699bee6ad6247a67b977dc329a54c05b2ba7dd3558125e73439570427f78e5f74576743e8649a9be6bdbd36c2b6cc070369b706e253ca2fcf2d82689b997873caddc13d1c0d62a98818d7525c11488dcca621cc7603de6be5ad7ea8b708e75361a357aeec7026d3ed9e92ec06057883a8d9afdf8701aa5074d2cd7414fce63bfd807d818fc11cbd31fef12bb398b286ddf68d8cabbc10d90f4ec191030c2fc0846ec8ed207b793be8e71c9d046cb6f39aff164d524386f89efdda65b48607f6b9ff82eb0b6ec7e606c732778c80edead9760b5aacc0eb0f8f9345b8d9b4e539818a7083a366ed5142f4dd8ff427833538545ee93e811e777db349ad85492f6c119491dba95ee5d3d681f8e2602808d366295aa90ae780e87a25408b6bba38fd74573d6e167ca96d29c8c21b648f92e0469b960c7320281dc9cd8aa03d5203e1ff47f9dc7beea4f49fc14b8047c0c40bba27637da755e0f14eea3a899e2c346483e26403ab7d119434c8c9ee35e3819cf578995cd50dcaf108af736e1bb769a64d64152b5dd760c51819b69379f1bf80bc291da1072e0d75b88ecf11e15b3a43d4bb9403359ed87530be3b949c1f11dfbe1f7d04936048039f1cf9b036e056bb451bd31b871b1632d4cc0c01db0caa48625a6a2fc33e94f9afd8afa4710c944b6b6dbcf1f0fdb687575d2f4d334985744a45f0b33ebd57c5557836996c58976850e8fbd6fec193d6d8d9d7b72af74d2db3d0183db111237612e376a2695b55643926c2a0043c40acd9584a36f79656ec1e3ffe48c10a311dff01572e34d74561e3be0cbbe6ed7dcdcb41cc406921130f8dcd49d71300fffcb896623a615120040f5eaf75a2f849d8bd7f1cd10036ffa400f4582f407b09f6cb466fa65d237afae541c378fe972a0829c5081236b2578bfc7e2daff68cbf87f73ba08de3040068984e5b3262160e5fa65450342af94ebd454d9c246de45d7b556207ce35b54661de74cd6bf12edfbabb9d4a67f34d6481712e2ffa08a2b5747cfefc5771148af9c8793b57ecb1d559ea6121ce7c8dcfa4fe5fc86417ade50a38f059719aadbf2546d4831443dbeb8d389c83bbb71be9936dc9f884ef5eeb69a04dcab8ef98f7ff3c57b9411f4b3535c99736fafcaa6f12255329839e34788671572087832ae5b1c65d553b7ca511eff57ebe30b857fbbb42ac2958f09aabe7e7372d406dfe920cbbbda4b3443940db9fa812aac43930b95eab060cfc89fc04c34a93306a5fa51e16bb7a1c29e144428e5a196733cffe4ebe2c72d0343cf40a1a1b103912ff47d699cc688baecb29b774c4611318038c47e3788a82b8f29541f423b31aaac2925d7f1251baef27e73f13cd6895598fe6411f7655ae2474c86c17ef48ae7151938328f2bdcee388efa792a01a2f59cd1bcf17787a093a4fef6f0d37a5693e934b9b869c272ce6e140780268358d6f7a6cec82e48915a46f4bf3368cb6c6ae3ce3d63a4ab497fd20babd3fd51c359daaef77ce8ebe578e9e108c8a251f8f3c7b5412a81aeb806532a02c6880783ac9ef01b2b9ded2bcd81de8da008d0c8303597e826bb64da7b1e59b31009680e09edb2b5e75129192d6e99b745fad01c554492b33bec31a2a10b52441f09ddf130a7b9d305081bc2b0db23cd70fdd91a472bab7804489aa612d8bf003a64b5383191ffa9cb97630dc81d80a616682463836005273f12b357002068d71a04c00739f5007978254db5a560e878697cddb4f6ff641ec98e64f67dcf83d7c1f063874fcbccbf30d9098eda3dc12fd75a9d7585360c9196fd1f6ad7d9607fa4761913be523fcb09d7af1c921065e8b8fa479b15477100219bee47c5e9f00b3e82657b5cd2f125792ac9f78e5a02f3c1c4c43823278c4358e8956569a3f53ff2e5c41f364a7f0d4a489ff59603cfac5680f1e0813ad4e0cd0e1fcdec1768823d8f021bfe6c82b0b8c238c5363820a79378f76f48828879b855ae6e9d6116cbe15c87594b210be86583b5dd0eea790fb4b8be58efb2d14ada79a1fb8a05838f367098ca73cc81dbc78d02de145849c31f22a3a2aa05f68b8435c28f13d3df5020b288c1e72985910bc7b5b6d1d5c14294e4ff20b197aa5afb1c2c9fa12295a6048585a5c68bdd43b3c476a7e80adb8cdde3742445c747994c6c9db4f5cabd6fc0f12385191c300000000000000000000000000000000000000050e1219232d3238', 'hex'), + } + + const algorithms = ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']; + + const vectors = algorithms.map((algorithm) => ({ + publicKeyPem: spki[algorithm], + privateKeyPem: pkcs8[algorithm], + name: algorithm, + data, + signature: signatures[algorithm], + })); + + return vectors; +} diff --git a/test/fixtures/crypto/rsa_pkcs.js b/test/fixtures/crypto/rsa_pkcs.js index 49e202c512d9e2..4630e4af913580 100644 --- a/test/fixtures/crypto/rsa_pkcs.js +++ b/test/fixtures/crypto/rsa_pkcs.js @@ -96,7 +96,34 @@ module.exports = function () { '688c993b58a0ed35e8f0a106d4e8b1b360e334415c742e94675823db0fd25e22cff' + '7a6335c70e193235dcda48add6858626bd96311e60f7e5ea4491b6c1e6248afe12b' + 'bbd54f8869b043a5b0444562813f0a98b300356f306e6b783a29f3bec97ca40ea20' + - '062cab8926ec5d96aa387cc84821a6d72b8ea126e7d', 'hex') + '062cab8926ec5d96aa387cc84821a6d72b8ea126e7d', 'hex'), + 'sha3-256': Buffer.from( + 'be1b476c1911a01d71710fd8a2f3158d6f7839e91443b01bed30dfdd04336d80c6b' + + 'f692c06fad254877901c10a73853e8fb202a29cddefdf16c3adcda1fc123625897d' + + '1b81b32a9dec38957e023be221d8f31e7470ad32e761edce9170eefa37ec19bd0c3' + + 'e0b0ad2a244e98f54a08f873efb63c6fad14d7322b50eb05b6bae767305da92a90a' + + '53cdae52b0d81e158a00003ec626e50423b7377a34a7b28cc7483b55bfde05bd431' + + 'cfa436c38c285531e0d476ee13f151c8ae832ffd51ba00f2ab06f1844e73c0fe0f6' + + 'ce17d966b1e07727af4161368aa0a74a594a6fdb782b46a9ae6098799c366fc0d71' + + '1b2d965cf5eeeed9175b39b1d0bcefdd7df376e8ac9', 'hex'), + 'sha3-384': Buffer.from( + '002eaf5837443f1a33dc03729a308c503888d7a8cc013be424a91bce18105f7334a' + + '499a5eddc5f4fab2fdf80f52988d53bf8bd5e78c3ce1a43abaf3b8146c260b6ce8b' + + 'ffc9857f4b35c190cea85921c46d3ab573113744472d1afb637a0e9ab5021bcb355' + + '7f5b52faf89fa864a7d3bf5799096c54ee53fa139e1bc13842a2a5bf0f1d85f041d' + + 'a4e0e87425b421f22f0240ad62ef77ba6f090e0d48e17c07fd1e477c7e16a3196f5' + + '0142d0f0c5e525a10325569e5a1f50cb4577e782a643972857cc918ae5409587d9e' + + '44e1c1e89540e87deed7dda5005ac63ba609f522fdd92c81d95c1ffa383558a10f3' + + '064f59ca0534bfad31acbf3e2807cb7d3147c59ee4d', 'hex'), + 'sha3-512': Buffer.from( + '561585b621c916453762285c8bb6ede3f303074ad6f2826ca15b3900e49c4d94c07' + + 'aab0b875eaa79049ba2ed97e9a87c44fff9bffe638a1bf8c4db69c627b6adbe8fca' + + '2b38cb8b4c2810a16286bef498327b9db4b53043ed5012c7c58f037edf669baf772' + + '9b58e413e133ebb90a5fcb6dc3936f4f87971c0e85f362189b4279bbb2d9293a427' + + '5653068c1bc8772cebc4733a5d1df0b454d4f628c645c22bb1c8cc601fbc92dc091' + + 'db38fad4a36289ae9ed424c46643a8161a102ae511877d25f2eab7342dff6b92bf3' + + '65951e76ee84c2bd84a595f63d7cc04d00e1589870956491e518b3ba245efc37a28' + + 'ec018d8788a92ab93a90bb314f9ab0788a0b5b50489', 'hex') } const vectors = [ @@ -131,7 +158,31 @@ module.exports = function () { hash: 'SHA-512', plaintext, signature: signatures['sha-512'] - } + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSASSA-PKCS1-v1_5' }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSASSA-PKCS1-v1_5' }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSASSA-PKCS1-v1_5' }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512'] + }, ]; return vectors; diff --git a/test/fixtures/crypto/rsa_pss.js b/test/fixtures/crypto/rsa_pss.js index effb3605a73f1a..101122b2ffe31c 100644 --- a/test/fixtures/crypto/rsa_pss.js +++ b/test/fixtures/crypto/rsa_pss.js @@ -61,21 +61,131 @@ module.exports = function() { const signatures = { 'sha-1, no salt': Buffer.from( - '1f1cd81ecb3bb31df2e5f0f64c5c0a310c7cf88d19eb512a5078e156d823727af88' + '68a12e5dfbfa976386784ba982c39928789b134952a4a28c6241177bcf2248f2adb' + '60077f545dd17e4f809b3b859fd430d1681e8047126d77369519eed5b618f3297a5' + '75085f0c931ed248cf60bbd7efffa0a8c2b874ba7f81ecd6bf391d01f1e881d827a' + '7b95df874d9adabb7b07f131ab33142a8b0b6d5ca9685671d49b982b67651909eaa' + '17b96b393e04fb36d972f9b258f1b79123df212d39924a4deaec506cf640f1dedd0' + '2d28845f3548d8488652788e2e2146f3ce8a86a556d84b4578f10da29abdb176a68' + '718cc1b2270b0735c2e5ca6c6bb0afac23a5bfa817a', 'hex'), + '1f1cd81ecb3bb31df2e5f0f64c5c0a310c7cf88d19eb512a5078e156d823727af88' + + '68a12e5dfbfa976386784ba982c39928789b134952a4a28c6241177bcf2248f2adb' + + '60077f545dd17e4f809b3b859fd430d1681e8047126d77369519eed5b618f3297a5' + + '75085f0c931ed248cf60bbd7efffa0a8c2b874ba7f81ecd6bf391d01f1e881d827a' + + '7b95df874d9adabb7b07f131ab33142a8b0b6d5ca9685671d49b982b67651909eaa' + + '17b96b393e04fb36d972f9b258f1b79123df212d39924a4deaec506cf640f1dedd0' + + '2d28845f3548d8488652788e2e2146f3ce8a86a556d84b4578f10da29abdb176a68' + + '718cc1b2270b0735c2e5ca6c6bb0afac23a5bfa817a', 'hex'), 'sha-256, no salt': Buffer.from( - '6157d668ed655d978b4c158c8419eb80718dfdfc7d4b34357f9917e9e116b6f3b65' + '040c9d16155c081d6887abcb3ba4ffa0191e4807ee206681aa1d4809ea20de5186b' + '77e3caced07fc9b3d71b9df0ac81b5c3273ff3f74f32a7ad34c65062a31540ced30' + '527efa4b7aa2d27ff7f80535f3e65ce352eb9e18b5054416de959354a4dcccb2542' + 'e33a8358eda620a8653dd6458f56ab94fee1dc01ef42fb8958aa134810e4d8fe1dd' + '4feee6af04742f80da5793875a78a2a4cc08d4e0a68ab03f1c022a0e8a7d3096089' + '92d24ecdd7e8f1895e3e5cd36e49906b531932d9ff958618b1a50f98455f515e0c6' + '3103d2e4e1651afc566eb9cad1e7efae1a9750c3880', 'hex'), + '6157d668ed655d978b4c158c8419eb80718dfdfc7d4b34357f9917e9e116b6f3b65' + + '040c9d16155c081d6887abcb3ba4ffa0191e4807ee206681aa1d4809ea20de5186b' + + '77e3caced07fc9b3d71b9df0ac81b5c3273ff3f74f32a7ad34c65062a31540ced30' + + '527efa4b7aa2d27ff7f80535f3e65ce352eb9e18b5054416de959354a4dcccb2542' + + 'e33a8358eda620a8653dd6458f56ab94fee1dc01ef42fb8958aa134810e4d8fe1dd' + + '4feee6af04742f80da5793875a78a2a4cc08d4e0a68ab03f1c022a0e8a7d3096089' + + '92d24ecdd7e8f1895e3e5cd36e49906b531932d9ff958618b1a50f98455f515e0c6' + + '3103d2e4e1651afc566eb9cad1e7efae1a9750c3880', 'hex'), 'sha-384, no salt': Buffer.from( - '7b95aab6b34c0962d228409e30df9b043c1b0baada08e73d887422552b8f1522e2e' + '42bf2b9ff2c6c9aa3eb0cd2370618e8f1a36873595e00bde75a9ce062ec32b5f639' + '4f2267a3f5c11840ff92e6e15bf31cc53e917ca8efc0895fb112c2ef8f681cbb6a4' + '10152f6e930caff1f260e31f983542e68cd15dea17ed3139cac735106fb05fc163b' + '2ed05a0ded939059a10c5cd7619e21b2d206907994274b34a4daefa1ce59b6b319f' + '73955a0918a5e237e1bbfdadb45c907a50083577e7192818845995b4a6d3ff1978e' + '0f9a42695853282e35c3b78133b3e0c624125aff14a1873d198f6304ffec7fc1cf2' + 'adecc6cd14b1f89b1a637f72ed1ff5de7c6b4d96599', 'hex'), + '7b95aab6b34c0962d228409e30df9b043c1b0baada08e73d887422552b8f1522e2e' + + '42bf2b9ff2c6c9aa3eb0cd2370618e8f1a36873595e00bde75a9ce062ec32b5f639' + + '4f2267a3f5c11840ff92e6e15bf31cc53e917ca8efc0895fb112c2ef8f681cbb6a4' + + '10152f6e930caff1f260e31f983542e68cd15dea17ed3139cac735106fb05fc163b' + + '2ed05a0ded939059a10c5cd7619e21b2d206907994274b34a4daefa1ce59b6b319f' + + '73955a0918a5e237e1bbfdadb45c907a50083577e7192818845995b4a6d3ff1978e' + + '0f9a42695853282e35c3b78133b3e0c624125aff14a1873d198f6304ffec7fc1cf2' + + 'adecc6cd14b1f89b1a637f72ed1ff5de7c6b4d96599', 'hex'), 'sha-512, no salt': Buffer.from( - 'af1bc07fa70add19f3ce1f1bef8dfc6e24af43671cfb97e6b869e86b7ef03550a65' + '81318fff6449afa8b67e73e2a6a14e20677d8b067145a84422574ae0cfd2a5dff70' + 'c6d7e97f6a0e166505079eb4264a43c493f2eb3fb06facc01be60774c277646a280' + '81247679622b220227e9249754867aa8fe1804015c4f98700982eda40e84d0ba033' + '6cf44f582fb8781374804e8fb43eb9d577acf4723587a39a2b4a9e168b767632b7a' + '554f77bc5272821c938c0994b162f7482636f7ffac564a19bd733f4877801dc324d' + 'c47196ef12ca9a8f4921a5496cd6737935ca555b73466ddd817eaff03feda0eb2d6' + '12e3cdb59b1989eeffdc18101d46e56b9ff5c91f95d', 'hex'), + 'af1bc07fa70add19f3ce1f1bef8dfc6e24af43671cfb97e6b869e86b7ef03550a65' + + '81318fff6449afa8b67e73e2a6a14e20677d8b067145a84422574ae0cfd2a5dff70' + + 'c6d7e97f6a0e166505079eb4264a43c493f2eb3fb06facc01be60774c277646a280' + + '81247679622b220227e9249754867aa8fe1804015c4f98700982eda40e84d0ba033' + + '6cf44f582fb8781374804e8fb43eb9d577acf4723587a39a2b4a9e168b767632b7a' + + '554f77bc5272821c938c0994b162f7482636f7ffac564a19bd733f4877801dc324d' + + 'c47196ef12ca9a8f4921a5496cd6737935ca555b73466ddd817eaff03feda0eb2d6' + + '12e3cdb59b1989eeffdc18101d46e56b9ff5c91f95d', 'hex'), 'sha-1, salted': Buffer.from( - '1f608a71d1884cfe2183b49037aa8555b0139a8a1267a5c5b9cce20701f2ad4bbd5' + 'b329740bff31accc34bf9afd1439a0536bb32b6d427d26968dbc9e9c80d2111d948' + 'c481cb1731778acd3110463241c4f23b3e13b855d162cb153851290fd95f781519e' + '2cef93745a413cfeec8e94fba7822b725d4744318458cf6b4a917b65b15ee6f54b9' + 'c391f6064a9e031f7009f592449c0b46d5457a2799cb0ebd78a102a055ee0470b26' + '0c2b3d8ffbdee0fd47644822090ec55ae6233be1062f441c432ed3c275e74d62013' + '2681ec2e801e9b5b6acc1ad71f8935388f7e2c03370d12e944e3418c2ab63bb42ab' + 'e1bb9e69530f02458ba28400b36806ff78da5791ace', 'hex'), + '1f608a71d1884cfe2183b49037aa8555b0139a8a1267a5c5b9cce20701f2ad4bbd5' + + 'b329740bff31accc34bf9afd1439a0536bb32b6d427d26968dbc9e9c80d2111d948' + + 'c481cb1731778acd3110463241c4f23b3e13b855d162cb153851290fd95f781519e' + + '2cef93745a413cfeec8e94fba7822b725d4744318458cf6b4a917b65b15ee6f54b9' + + 'c391f6064a9e031f7009f592449c0b46d5457a2799cb0ebd78a102a055ee0470b26' + + '0c2b3d8ffbdee0fd47644822090ec55ae6233be1062f441c432ed3c275e74d62013' + + '2681ec2e801e9b5b6acc1ad71f8935388f7e2c03370d12e944e3418c2ab63bb42ab' + + 'e1bb9e69530f02458ba28400b36806ff78da5791ace', 'hex'), 'sha-256, salted': Buffer.from( - '8c3d03bde8c42d9453631b0baac89e6296da20543713c004df35bc1a6fae205ab2b' + 'f585369689073cdee345ad6e2783b2dda187b4979ea0457463758156e103eedd0ef' + '1834d35bd6ad540d9b8b225fd1770e514ea0af35f707f2e7a0382be6f5ed9d6b591' + 'd536ce1215b17ef3eeb450bb48a0017497c67be0240470addd2891a81a8f1cf6e80' + 'e3f837fe42376292df555b8b05931b69530597fae36dcd01b1c81767d4ecd4caf06' + 'befc035224bdd2a5e6b89d51539235ac95570e757dbd70fdc15040001b07b937bf0' + '148ccc005f4c272acf5f8fc096a37d26208e96ac341c2d1d212c44d6d5156c934f6' + '6ef42fdbac77a208681550b048b466e32c76c7a7b07', 'hex'), + '8c3d03bde8c42d9453631b0baac89e6296da20543713c004df35bc1a6fae205ab2b' + + 'f585369689073cdee345ad6e2783b2dda187b4979ea0457463758156e103eedd0ef' + + '1834d35bd6ad540d9b8b225fd1770e514ea0af35f707f2e7a0382be6f5ed9d6b591' + + 'd536ce1215b17ef3eeb450bb48a0017497c67be0240470addd2891a81a8f1cf6e80' + + 'e3f837fe42376292df555b8b05931b69530597fae36dcd01b1c81767d4ecd4caf06' + + 'befc035224bdd2a5e6b89d51539235ac95570e757dbd70fdc15040001b07b937bf0' + + '148ccc005f4c272acf5f8fc096a37d26208e96ac341c2d1d212c44d6d5156c934f6' + + '6ef42fdbac77a208681550b048b466e32c76c7a7b07', 'hex'), 'sha-384, salted': Buffer.from( - '79f7284bb4216de68429854edb4218ef78ad1740848567377315db8867a15733c70' + '42e8bf90762e673c90c0e2c58c6c5cef497568bd92a6d219612c4756c55fac45507' + 'f81608bc2720da4eedd5b23e1f3c8740c6b4cd7e4cf0e04342b184c1110199e6508' + '0d73b985e611d66f8e97990816e4917badbb0425dd94383892e2aa96de4db0de093' + '6aee84d5482a3da31b27319f43830fc48703cc7d4eaedb20fd30323dbf3f22608db' + '51637d3b305b3197962658d80935c266d33ccfb297590621f4a967c7245e92b0158' + 'c0dcea943e2ace719ebdb196a9bae7df3ed9cc62765e27b63571743e28a0538db08' + '25cad2539eb5de5e6a320a88b573ec1972c26401530', 'hex'), + '79f7284bb4216de68429854edb4218ef78ad1740848567377315db8867a15733c70' + + '42e8bf90762e673c90c0e2c58c6c5cef497568bd92a6d219612c4756c55fac45507' + + 'f81608bc2720da4eedd5b23e1f3c8740c6b4cd7e4cf0e04342b184c1110199e6508' + + '0d73b985e611d66f8e97990816e4917badbb0425dd94383892e2aa96de4db0de093' + + '6aee84d5482a3da31b27319f43830fc48703cc7d4eaedb20fd30323dbf3f22608db' + + '51637d3b305b3197962658d80935c266d33ccfb297590621f4a967c7245e92b0158' + + 'c0dcea943e2ace719ebdb196a9bae7df3ed9cc62765e27b63571743e28a0538db08' + + '25cad2539eb5de5e6a320a88b573ec1972c26401530', 'hex'), 'sha-512, salted': Buffer.from( - 'b74f3099d80787118b1f9de79fc207893e0d2d75c4110f4b159b85ba07d63a0256f' + 'c3cd0f66ce8d9a2e3cf7a3d5a7b9c0befac6638894a3e36ce75e649ee069dd8dd98' + 'aa8b602474c98b14bb03492de551a9e8e77934ef9b684583934f218d9576be240b5' + 'c4f362eaf5e0140c8ea92639085a6269653505dcfa004226db9f63277653a64a182' + '6e4babb17ab54dd8543dcf1ce809706d6816e6a75ff846a3d4c18d11bdeb1f31b10' + 'd55a3795b6496319e6e751504d86a4e7bb6535b9f0415e815d8c789c5b1e387f2a8' + 'c00fef6e327462cb7e525b8f945be5b17248e0e0a4d855d397e22d067ce4539373d' + 'fba46d1799250afc70f535006cacd2766f5ddcf8f91', 'hex') + 'b74f3099d80787118b1f9de79fc207893e0d2d75c4110f4b159b85ba07d63a0256f' + + 'c3cd0f66ce8d9a2e3cf7a3d5a7b9c0befac6638894a3e36ce75e649ee069dd8dd98' + + 'aa8b602474c98b14bb03492de551a9e8e77934ef9b684583934f218d9576be240b5' + + 'c4f362eaf5e0140c8ea92639085a6269653505dcfa004226db9f63277653a64a182' + + '6e4babb17ab54dd8543dcf1ce809706d6816e6a75ff846a3d4c18d11bdeb1f31b10' + + 'd55a3795b6496319e6e751504d86a4e7bb6535b9f0415e815d8c789c5b1e387f2a8' + + 'c00fef6e327462cb7e525b8f945be5b17248e0e0a4d855d397e22d067ce4539373d' + + 'fba46d1799250afc70f535006cacd2766f5ddcf8f91', 'hex'), + 'sha3-256, no salt': Buffer.from( + '98787732f107a5390abc9ba3c93c2a0e30f6f31c3c76d73afee951a04525897df94' + + '67c7532ff1b5c12601369339edcac4654a173e61780a12a21b5f0500bf16e2445f9' + + 'f7e9adab1ea2bb7e901f615b514965047d53b12ff2ff19f94f320179946bbf1b19d' + + '88248e4fba7f49dc3c5af14de7a892a7718bd5962db33aa2b529c49e75d8fe936de' + + '45e1db225ed875486516cd7398b5ec19043cf6005e06ba2d60f807d34d4ced378ae' + + 'cef2b1f75f6ad52cdd674d944e48d484be2e5f799510f244d089eb3570a674b2585' + + '0616f12641e7e3e38e36fba1eefbed32d7a4809a4b5b1e557582303ab419bc128a1' + + '813857157985f075d5d89e6867b7864dac0369b2513', 'hex'), + 'sha3-384, no salt': Buffer.from( + '1d1399da211efc709b2cea90ff65e7c49162d943cadb59c78186889f7645e5d08f7' + + '490de3f7b65ee5664c140dd61334182ddf45bcdc844845e4d60c917bf00eb1321e7' + + '46cd7fce971af5ceea60b272219ccda2328b89a11b228cd42bdcc4c7eb40f0b6333' + + '5f7496931baf36c0d2497045687ad27bb156c20f7fdae3baa38d57e35918f328bdd' + + '2de7e6b23d6c676a18342a082f7ea019021903f103ccb4a6fddb2d88c30a1284764' + + 'b68c04bfe452c3adc6c10066a915231b7b404727eb6201b4921eb96d9407de2b963' + + '3879ceb71d759d9828d7b4d062f6ef100757d8328187caf57dfb859d1555345207c' + + '1cce7905c3564c08fec78867a53d5a2cf84810e1ffa', 'hex'), + 'sha3-512, no salt': Buffer.from( + 'd2430dc87abeaa7d13f7cec8510f1a296e1c608f44b1696829c59a99e8eefe9b2ee' + + '6ee8ad6fdc93c24fcba2f04d1da195924b6209717e1992c10ed9f4783478765fe34' + + '3e761203bff9d326bb6dc2061b0a7554c8ce0814b29249136c20c8e30054df0c6bc' + + '656509a82845149368896690e32ff5dd32ef01543686f01d6a69bb438b049e66a8b' + + 'df485a13edcd7dc482da4cc57d0b740aca3e56f0da247794e600afd27b22b6da13b' + + 'cc15dd2059b525f8cb6bcd07540aa843f0ae51d4b0eea27045485914b908bdd01d0' + + 'a9d42379f9f7180f4ad162ff73df5fed0200eb02ad01473975d54a77c15a9c61a3c' + + 'b5e27de5d1eecc363d45506f7123a5ddd115c5e4c9e', 'hex'), + 'sha3-256, salted': Buffer.from( + '59cb9cce6ae838eb20d38d6af4acb9b866b0753bb7df9e441037d788512c03279e8' + + '3d9a9cf5c0921fe1c0b6e8e895a8c0ad24a18b123f809b34ef2a3a1f05974030320' + + '435692ef5d378cef4368c3658c098a25371dfaf1c0b6910f653a4ec15f2c08956c1' + + '405136c2aba7f25a808fa7dbf57a4cb2978bd91af710b27ee239d955c8cac7a76ae' + + '9085cefeda2a585a99cc948f064b5da66a9c4aa4f3f767ac905a9f314b47038e05c' + + '3608fbb7e67a278e4f009a62c3cd3fdf43692e759d9361be1217999a76a69d4d119' + + 'f8791a90e207e46b3f6125721f56fd819292d06a3cdae2c62c9a1dc0d964a06036c' + + '8c18661cc6c873532a3536ab51e1ce210926db299e2', 'hex'), + 'sha3-384, salted': Buffer.from( + '8d1f9297c8169f27f0c58827dba991d862de58c1155f612ad2995d2bf862d051c4a' + + '91b48571849b0412384382e5b77990de6a3c84010046b35c4a504f175a3479483d9' + + '5c58f86bb96d53a27e59d6f67fddaae295ce90610f5086acc711557c2c85aac32d3' + + '24199cff2367ae44e1d91307a98c8cbfb085a8bce6b1c20714711bc15b0eddb7881' + + '823227d4be477ffdad8093663a6a1fc62eb39c49c2c3a821c2b202cf7904b49ca92' + + '3c83819602bb13931577354a80f99309030044935b1cd41f0513160e661db1959fb' + + '1ec15f087f3d288e875d54cbf070ec860b0aeecc951ea65e97cd5460750d4b7de52' + + '22cb9e7466b1f506ecf6a81fc399dfd8334160f9084', 'hex'), + 'sha3-512, salted': Buffer.from( + '7b6d7be418c5d37cc8070698b8b03d818ecd8b673d047d34921913f6d59c69cb496' + + '172d6118207d9ff92b8e1246acf0e03a845d935a70f8a82c3d5d6db6a1a0e337269' + + '4b904372413dcbaa7ac5486bc8ccaf70d7e9470be82b928a90017e272cf9761ed26' + + 'c160fe874a2675a4fb2acad72c50fbfffdd70b5a6f2919553d7ea1829934670f8de' + + 'f2a5c2816404b1aa153323c92c58400622f184b9b0463fa48d6b27091f68c287e3f' + + '6d9ab9eb451711a5d984c547f3d56f14a686a89ddf36c47ce25092b8c6530904de9' + + '5df7fc602fe9394315f1b1847aae304cb5ad71e2cb78acfbc997a87a9d62a6898bb' + + '6d84a81bb89b50186265f4be171a93d837a4bf777c8', 'hex') } const vectors = [ @@ -142,6 +252,54 @@ module.exports = function() { hash: 'SHA-512', plaintext, signature: signatures['sha-512, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 32 }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 48 }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 64 }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512, salted'] } ]; diff --git a/test/fixtures/webcrypto/supports-level-2.mjs b/test/fixtures/webcrypto/supports-level-2.mjs new file mode 100644 index 00000000000000..7a8f8cd9d5dd9e --- /dev/null +++ b/test/fixtures/webcrypto/supports-level-2.mjs @@ -0,0 +1,236 @@ +const { subtle } = globalThis.crypto; + +const RSA_KEY_GEN = { + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]) +}; + +const [ECDH, X448, X25519] = await Promise.all([ + subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']), + subtle.generateKey('X448', false, ['deriveBits', 'deriveKey']), + subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']), +]); + +const boringSSL = process.features.openssl_is_boringssl; + +export const vectors = { + 'encrypt': [ + [false, 'Invalid'], + [false, 'Ed25519'], + [true, { name: 'AES-CBC', iv: Buffer.alloc(16) }], + [false, 'AES-CBC'], + [true, { name: 'AES-GCM', iv: Buffer.alloc(12) }], + [false, 'AES-GCM'], + [true, { name: 'AES-CTR', counter: Buffer.alloc(16), length: 128 }], + [false, 'AES-CTR'], + [true, 'RSA-OAEP'], + [true, { name: 'RSA-OAEP', label: Buffer.alloc(0) }], + [true, 'RSA-OAEP'], + [false, { name: 'RSA-OAEP', label: null }], + ], + 'sign': [ + [false, 'Invalid'], + [false, 'SHA-1'], + + [true, 'Ed25519'], + + [true, 'Ed448'], + [true, { name: 'Ed448', context: Buffer.alloc(0) }], + [false, { name: 'Ed448', context: Buffer.alloc(1) }], + + [true, 'RSASSA-PKCS1-v1_5'], + + [true, { name: 'RSA-PSS', saltLength: 32 }], + [false, 'RSA-PSS'], + + [true, { name: 'ECDSA', hash: 'SHA-256' }], + [false, { name: 'ECDSA', hash: 'Invalid' }], + [false, { name: 'ECDSA', hash: 'Ed25519' }], + [false, 'ECDSA'], + + [true, 'HMAC'], + ], + 'digest': [ + [true, 'SHA-1'], + [true, 'SHA-256'], + [true, 'SHA-384'], + [true, 'SHA-512'], + [false, 'Invalid'], + [false, 'Ed25519'], + ], + 'generateKey': [ + [false, 'SHA-1'], + [false, 'Invalid'], + [false, 'HKDF'], + [false, 'PBKDF2'], + [true, 'X25519'], + [true, 'X448'], + [true, 'Ed25519'], + [true, 'Ed448'], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [true, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-PSS', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-OAEP', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'ECDSA', namedCurve: 'P-256' }], + [false, { name: 'ECDSA', namedCurve: 'X25519' }], + [true, { name: 'AES-CTR', length: 128 }], + [false, { name: 'AES-CTR', length: 25 }], + [true, { name: 'AES-CBC', length: 128 }], + [false, { name: 'AES-CBC', length: 25 }], + [true, { name: 'AES-GCM', length: 128 }], + [false, { name: 'AES-GCM', length: 25 }], + [!boringSSL, { name: 'AES-KW', length: 128 }], + [false, { name: 'AES-KW', length: 25 }], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 0 }], + ], + 'deriveKey': [ + [true, + { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'HMAC', hash: 'SHA-256' }], + [false, + { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + 'HKDF'], + [true, + { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'HMAC', hash: 'SHA-256' }], + [false, + { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, + 'HKDF'], + [true, + { name: 'X25519', public: X25519.publicKey }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'X25519', public: X25519.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [true, + { name: 'X25519', public: X25519.publicKey }, + 'HKDF'], + [true, + { name: 'X448', public: X448.publicKey }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'X448', public: X448.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [true, + { name: 'X448', public: X448.publicKey }, + 'HKDF'], + [true, + { name: 'ECDH', public: ECDH.publicKey }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'ECDH', public: ECDH.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [true, + { name: 'ECDH', public: ECDH.publicKey }, + 'HKDF'], + [false, + { name: 'X25519', public: X25519.publicKey }, + 'SHA-256'], + [false, + { name: 'X25519', public: X25519.publicKey }, + 'AES-CBC'], + ], + 'deriveBits': [ + [true, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + [true, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0], + [false, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, null], + [false, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 7], + [false, { name: 'HKDF', hash: 'Invalid', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + [false, { name: 'HKDF', hash: 'Ed25519', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + + [true, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 8], + [true, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 0], + [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 0 }, 8], + [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, null], + [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 7], + [false, { name: 'PBKDF2', hash: 'Invalid', salt: Buffer.alloc(0), iterations: 1 }, 8], + [false, { name: 'PBKDF2', hash: 'Ed25519', salt: Buffer.alloc(0), iterations: 1 }, 8], + + [true, + { name: 'ECDH', public: ECDH.publicKey }], + [false, { name: 'ECDH', public: X448.publicKey }], + [false, { name: 'ECDH', public: ECDH.privateKey }], + [false, 'ECDH'], + + [true, { name: 'X25519', public: X25519.publicKey }], + [false, { name: 'X25519', public: X448.publicKey }], + [false, { name: 'X25519', public: X25519.privateKey }], + [false, 'X25519'], + + [true, { name: 'X448', public: X448.publicKey }], + [false, { name: 'X448', public: X25519.publicKey }], + [false, { name: 'X448', public: X448.privateKey }], + [false, 'X448'], + ], + 'importKey': [ + [false, 'SHA-1'], + [false, 'Invalid'], + [true, 'X25519'], + [true, 'X448'], + [true, 'Ed25519'], + [true, 'Ed448'], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [true, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-PSS', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-OAEP', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'ECDSA', namedCurve: 'P-256' }], + [false, { name: 'ECDSA', namedCurve: 'X25519' }], + [true, 'AES-CTR'], + [true, 'AES-CBC'], + [true, 'AES-GCM'], + [!boringSSL, 'AES-KW'], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 0 }], + [true, 'HKDF'], + [true, 'PBKDF2'], + ], + 'exportKey': [ + [false, 'SHA-1'], + [false, 'Invalid'], + [false, 'HKDF'], + [false, 'PBKDF2'], + [true, 'RSASSA-PKCS1-v1_5'], + [true, 'RSA-PSS'], + [true, 'RSA-OAEP'], + [true, 'ECDSA'], + [true, 'ECDH'], + [true, 'HMAC'], + [true, 'AES-CTR'], + [true, 'AES-CBC'], + [true, 'AES-GCM'], + [!boringSSL, 'AES-KW'], + [true, 'Ed25519'], + [true, 'X25519'], + ], + 'wrapKey': [ + [false, 'AES-KW'], + [!boringSSL, 'AES-KW', 'AES-CTR'], + [!boringSSL, 'AES-KW', 'HMAC'], + ], + 'unwrapKey': [ + [false, 'AES-KW'], + [!boringSSL, 'AES-KW', 'AES-CTR'], + ], + 'unsupported operation': [ + [false, ''], + [false, 'Ed25519'], + ], + 'get key length': [ + [false, { name: 'HMAC', hash: 'SHA-256' }], + ], +}; diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs new file mode 100644 index 00000000000000..cb63856c715dff --- /dev/null +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -0,0 +1,72 @@ +import * as crypto from 'node:crypto' + +import { hasOpenSSL } from '../../common/crypto.js' + +const pqc = hasOpenSSL(3, 5); +const shake128 = crypto.getHashes().includes('shake128'); +const shake256 = crypto.getHashes().includes('shake256'); +const chacha = crypto.getCiphers().includes('chacha20-poly1305'); + +export const vectors = { + 'digest': [ + [false, 'cSHAKE128'], + [shake128, { name: 'cSHAKE128', length: 128 }], + [shake128, { name: 'cSHAKE128', length: 128, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], + [false, { name: 'cSHAKE128', length: 128, functionName: Buffer.alloc(1) }], + [false, { name: 'cSHAKE128', length: 128, customization: Buffer.alloc(1) }], + [false, { name: 'cSHAKE128', length: 127 }], + [false, 'cSHAKE256'], + [shake256, { name: 'cSHAKE256', length: 256 }], + [shake256, { name: 'cSHAKE256', length: 256, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], + [false, { name: 'cSHAKE256', length: 256, functionName: Buffer.alloc(1) }], + [false, { name: 'cSHAKE256', length: 256, customization: Buffer.alloc(1) }], + [false, { name: 'cSHAKE256', length: 255 }], + ], + 'sign': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + ], + 'generateKey': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + [chacha, 'ChaCha20-Poly1305'], + ], + 'importKey': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + [chacha, 'ChaCha20-Poly1305'], + ], + 'exportKey': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + [chacha, 'ChaCha20-Poly1305'], + ], + 'getPublicKey': [ + [true, 'RSA-OAEP'], + [true, 'RSA-PSS'], + [true, 'RSASSA-PKCS1-v1_5'], + [true, 'X25519'], + [true, 'X448'], + [true, 'Ed25519'], + [true, 'Ed448'], + [true, 'ECDH'], + [true, 'ECDSA'], + [pqc, 'ML-DSA-44'], + [false, 'AES-CTR'], + [false, 'AES-CBC'], + [false, 'AES-GCM'], + [false, 'AES-KW'], + [false, 'ChaCha20-Poly1305'], + ], + 'encrypt': [ + [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }], + [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(16) }], + [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }], + [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 64 }], + [false, 'ChaCha20-Poly1305'], + ] +}; diff --git a/test/fixtures/webcrypto/supports-secure-curves.mjs b/test/fixtures/webcrypto/supports-secure-curves.mjs new file mode 100644 index 00000000000000..eed95aa1f98b0d --- /dev/null +++ b/test/fixtures/webcrypto/supports-secure-curves.mjs @@ -0,0 +1,41 @@ +const { subtle } = globalThis.crypto; + +const boringSSL = process.features.openssl_is_boringssl; + +const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']); +let X448; +if (!boringSSL) { + X448 = await subtle.generateKey('X448', false, ['deriveBits', 'deriveKey']) +} + +export const vectors = { + 'generateKey': [ + [!boringSSL, 'X448'], + [!boringSSL, 'Ed448'], + ], + 'deriveKey': [ + [!boringSSL, + { name: 'X448', public: X448?.publicKey }, + { name: 'AES-CBC', length: 128 }], + [!boringSSL, + { name: 'X448', public: X448?.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [!boringSSL, + { name: 'X448', public: X448?.publicKey }, + 'HKDF'], + ], + 'deriveBits': [ + [!boringSSL, { name: 'X448', public: X448?.publicKey }], + [false, { name: 'X448', public: X25519.publicKey }], + [false, { name: 'X448', public: X448?.privateKey }], + [false, 'X448'], + ], + 'importKey': [ + [!boringSSL, 'X448'], + [!boringSSL, 'Ed448'], + ], + 'exportKey': [ + [!boringSSL, 'Ed448'], + [!boringSSL, 'X448'], + ], +}; diff --git a/test/fixtures/webcrypto/supports-sha3.mjs b/test/fixtures/webcrypto/supports-sha3.mjs new file mode 100644 index 00000000000000..0b7be66746ce64 --- /dev/null +++ b/test/fixtures/webcrypto/supports-sha3.mjs @@ -0,0 +1,86 @@ +const { subtle } = globalThis.crypto; + +const boringSSL = process.features.openssl_is_boringssl; + +const RSA_KEY_GEN = { + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]) +}; + +const [ECDH, X25519] = await Promise.all([ + subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']), + subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']), +]); + +export const vectors = { + 'digest': [ + [!boringSSL, 'SHA3-256'], + [!boringSSL, 'SHA3-384'], + [!boringSSL, 'SHA3-512'], + ], + 'generateKey': [ + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!boringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }], + ], + 'deriveKey': [ + [!boringSSL, + { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'AES-CBC', length: 128 }], + [!boringSSL, + { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'HMAC', hash: 'SHA3-256' }], + [false, + { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + 'HKDF'], + [!boringSSL, + { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'AES-CBC', length: 128 }], + [!boringSSL, + { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'HMAC', hash: 'SHA3-256' }], + [false, + { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, + 'HKDF'], + [!boringSSL, + { name: 'X25519', public: X25519.publicKey }, + { name: 'HMAC', hash: 'SHA3-256' }], + [!boringSSL, + { name: 'ECDH', public: ECDH.publicKey }, + { name: 'HMAC', hash: 'SHA3-256' }], + ], + 'deriveBits': [ + [!boringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + [!boringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0], + [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, null], + [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 7], + + [!boringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 8], + [!boringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 0], + [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 0 }, 8], + [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, null], + [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 7], + ], + 'importKey': [ + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!boringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }], + ], + 'get key length': [ + [false, { name: 'HMAC', hash: 'SHA3-256' }], + ], +}; diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js index 1656f37a3c58b5..0fe1b06996c19d 100644 --- a/test/parallel/test-crypto-key-objects-to-crypto-key.js +++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js @@ -4,6 +4,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { createSecretKey, @@ -23,13 +25,16 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { { for (const length of [128, 192, 256]) { - const aes = createSecretKey(randomBytes(length >> 3)); - for (const algorithm of ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']) { + const key = createSecretKey(randomBytes(length >> 3)); + const algorithms = ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']; + if (length === 256) + algorithms.push('ChaCha20-Poly1305'); + for (const algorithm of algorithms) { const usages = algorithm === 'AES-KW' ? ['wrapKey', 'unwrapKey'] : ['encrypt', 'decrypt']; for (const extractable of [true, false]) { - const cryptoKey = aes.toCryptoKey(algorithm, extractable, usages); - assertCryptoKey(cryptoKey, aes, algorithm, extractable, usages); - assert.strictEqual(cryptoKey.algorithm.length, length); + const cryptoKey = key.toCryptoKey(algorithm, extractable, usages); + assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.length, algorithm !== 'ChaCha20-Poly1305' ? length : undefined); } } } @@ -180,3 +185,23 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { } } } + +if (hasOpenSSL(3, 5)) { + for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { + const { publicKey, privateKey } = generateKeyPairSync(name.toLowerCase()); + assert.throws(() => { + privateKey.toCryptoKey(name, true, []); + }, { + name: 'SyntaxError', + message: 'Usages cannot be empty when importing a private key.' + }); + for (const key of [publicKey, privateKey]) { + const usages = key.type === 'public' ? ['verify'] : ['sign']; + for (const extractable of [true, false]) { + const cryptoKey = key.toCryptoKey({ name }, extractable, usages); + assertCryptoKey(cryptoKey, key, name, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.name, name); + } + } + } +} diff --git a/test/parallel/test-webcrypto-constructors.js b/test/parallel/test-webcrypto-constructors.js index 4a60c9325e24a1..782265edc29486 100644 --- a/test/parallel/test-webcrypto-constructors.js +++ b/test/parallel/test-webcrypto-constructors.js @@ -137,6 +137,20 @@ const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto); }).then(common.mustCall()); } +// Test SubtleCrypto.supports +{ + assert.throws(() => SubtleCrypto.supports.call(undefined), { + name: 'TypeError', code: 'ERR_INVALID_THIS', + }); +} + +// Test SubtleCrypto.prototype.getPublicKey +{ + assert.rejects(() => notSubtle.getPublicKey(), { + name: 'TypeError', code: 'ERR_INVALID_THIS', + }).then(common.mustCall()); +} + { subtle.importKey( 'raw', diff --git a/test/parallel/test-webcrypto-derivebits-cfrg.js b/test/parallel/test-webcrypto-derivebits-cfrg.js index d45e59fd5087b9..757c8127253662 100644 --- a/test/parallel/test-webcrypto-derivebits-cfrg.js +++ b/test/parallel/test-webcrypto-derivebits-cfrg.js @@ -18,19 +18,26 @@ const kTests = [ '64ea51fae5b3307cfe9706', result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008' }, - { - name: 'X448', - size: 56, - pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + +]; + +if (!process.features.openssl_is_boringssl) { + kTests.push( + { + name: 'X448', + size: 56, + pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + 'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' + 'd0cc53bf26929e', - spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + + spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + 'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' + 'ce6f', - result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + + result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + '726e0f6dcf810eb594dca97b4882bd44c43ea7dc67f49a4e', - }, -]; + } + ); +} else { + common.printSkipMessage('Skipping unsupported X448 test case'); +} async function prepareKeys() { const keys = {}; @@ -153,9 +160,9 @@ async function prepareKeys() { // Missing public property await assert.rejects( subtle.deriveBits( - { name: 'X448' }, - keys.X448.privateKey, - 8 * keys.X448.size), + { name: 'X25519' }, + keys.X25519.privateKey, + 8 * keys.X25519.size), { code: 'ERR_MISSING_OPTION' }); } @@ -164,15 +171,15 @@ async function prepareKeys() { await assert.rejects( subtle.deriveBits( { - name: 'X448', + name: 'X25519', public: { message: 'Not a CryptoKey' } }, - keys.X448.privateKey, - 8 * keys.X448.size), + keys.X25519.privateKey, + 8 * keys.X25519.size), { code: 'ERR_INVALID_ARG_TYPE' }); } - { + if (keys.X25519 && keys.X448) { // Mismatched types await assert.rejects( subtle.deriveBits( @@ -182,15 +189,15 @@ async function prepareKeys() { }, keys.X448.privateKey, 8 * keys.X448.size), - { message: 'algorithm.public must be an X448 key' }); + { message: 'key algorithm mismatch' }); } { // Base key is not a private key await assert.rejects(subtle.deriveBits({ - name: 'X448', - public: keys.X448.publicKey - }, keys.X448.publicKey, null), { + name: 'X25519', + public: keys.X25519.publicKey + }, keys.X25519.publicKey, null), { name: 'InvalidAccessError' }); } @@ -198,9 +205,9 @@ async function prepareKeys() { { // Base key is not a private key await assert.rejects(subtle.deriveBits({ - name: 'X448', - public: keys.X448.privateKey - }, keys.X448.publicKey, null), { + name: 'X25519', + public: keys.X25519.privateKey + }, keys.X25519.publicKey, null), { name: 'InvalidAccessError' }); } @@ -215,9 +222,9 @@ async function prepareKeys() { false, ['encrypt']); await assert.rejects(subtle.deriveBits({ - name: 'X448', + name: 'X25519', public: key - }, keys.X448.publicKey, null), { + }, keys.X25519.publicKey, null), { name: 'InvalidAccessError' }); } diff --git a/test/parallel/test-webcrypto-derivebits-ecdh.js b/test/parallel/test-webcrypto-derivebits-ecdh.js index 7a7fa09d2b3754..6bc7a9ab846941 100644 --- a/test/parallel/test-webcrypto-derivebits-ecdh.js +++ b/test/parallel/test-webcrypto-derivebits-ecdh.js @@ -218,7 +218,7 @@ async function prepareKeys() { name: 'ECDH', public: publicKey }, keys['P-521'].privateKey, null), { - message: 'algorithm.public must be an ECDH key' + message: 'key algorithm mismatch' }); } diff --git a/test/parallel/test-webcrypto-derivebits-hkdf.js b/test/parallel/test-webcrypto-derivebits-hkdf.js index 0e3b2992e1c137..590fef60dc9efe 100644 --- a/test/parallel/test-webcrypto-derivebits-hkdf.js +++ b/test/parallel/test-webcrypto-derivebits-hkdf.js @@ -19,14 +19,24 @@ const kDerivedKeyTypes = [ ['AES-CTR', 256, undefined, 'encrypt', 'decrypt'], ['AES-GCM', 128, undefined, 'encrypt', 'decrypt'], ['AES-GCM', 256, undefined, 'encrypt', 'decrypt'], - ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], - ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], ['HMAC', 256, 'SHA-1', 'sign', 'verify'], ['HMAC', 256, 'SHA-256', 'sign', 'verify'], ['HMAC', 256, 'SHA-384', 'sign', 'verify'], ['HMAC', 256, 'SHA-512', 'sign', 'verify'], ]; +if (!process.features.openssl_is_boringssl) { + kDerivedKeyTypes.push( + ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], + ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], + ['HMAC', 256, 'SHA3-256', 'sign', 'verify'], + ['HMAC', 256, 'SHA3-384', 'sign', 'verify'], + ['HMAC', 256, 'SHA3-512', 'sign', 'verify'], + ); +} else { + common.printSkipMessage('Skipping unsupported AES-KW test cases'); +} + const kDerivedKeys = { short: '5040737377307264', long: '55736572732073686f756c64207069636b206c6f6e6720706173737068726' + @@ -70,7 +80,19 @@ const kDerivations = { 'b127e92631c1c051482d6690941772b4', empty: '9e4b719033742101e90f1ad61e2ff3b4' + '256863667296d74389f1f02af2c4e6a6' - } + }, + 'SHA3-256': { + normal: '386b0693d7a58c4ddf01b49bfbbd2fa87c6f911991543995170ba20ed28df599', + empty: 'd029bc828b6c6c8bb16ce3d25f5058f19c7d2517745e11c5d65c6d242e82e47f', + }, + 'SHA3-384': { + normal: '8c3b72e659bad40bcd14bdc1f7c3836059d24253795ab046a272973fd0456508', + empty: '3211ff4c676f761494c1ca2683d2d4662fe1d770ae5c58ebf6af6acb181c7d71', + }, + 'SHA3-512': { + normal: '5588c5c70cb3dd2f95323da2e9d5f299ca99c301d920a499330c449d21c645cd', + empty: '2c944b916c2751a71a1b5e57fcb487939c624335683995770b9f7cc7cbbb21f0', + }, }, empty: { 'SHA-384': { @@ -96,7 +118,19 @@ const kDerivations = { '0acea6f165476eb83460b9353ed41dfe', empty: 'c8e12774135305c9147f2cc4766e5ead' + '25d8f457b9a1953d52677361ced558fb' - } + }, + 'SHA3-256': { + normal: '9befc557f5baf4075b5fb38c014b41b92ab7534150baf64201069e8807d0e83d', + empty: '54d1fa1aa7cad99dab0622b772170e775c103756183bac36a228fd817a98a3f6', + }, + 'SHA3-384': { + normal: '46b54c015e368677edf7ac16963bccd9d2ba8246eef0e8beb04d8d188774b91b', + empty: '46eb0b2649bb0f605d70e4818ffc8176ee1be9782396e69fb4d0fd7cfe902b55', + }, + 'SHA3-512': { + normal: 'aa4375c82b5d7a3cac88a0423250b3882f140c253e98e8e7a0f6055b0908e4c2', + empty: '6613003f98602ddb53ac35f5aa256c9f5279d50ee65bb08fdf2ecf65cc5df27f', + }, } }, long: { @@ -124,7 +158,19 @@ const kDerivations = { '50b3dd9a29f30606e2cad199bec14d13', empty: 'e579d1f9e7f08e6f990ffcfcce1ed201' + 'c5e37e62cdf606f0ba4aca80427fbc44' - } + }, + 'SHA3-256': { + normal: '24f38fd1905554b7cbf8395cc3976292d11ce24a0b3131da0fd4b109832d27e3', + empty: '33d0a5151c0f52e4bb7fb67cf7a17063127624dc3e685903f49ebb07872084d1', + }, + 'SHA3-384': { + normal: '15777581a1ea81ad0baac8a97d954df4142f7260d9e8351aa7f6ef6de2d04632', + empty: 'ada4da4e28dc971633a8760b265b3019db57baf17e7bf7e13cf78b1a676f6d44', + }, + 'SHA3-512': { + normal: '621e4602b07fcba55ed6b976a8bef513b0f7c4ad0c546e0f852993051d887408', + empty: 'f1292af65b05c86cf7146b739bc65785c707450316f3207ee54a3f596a7d0f7b', + }, }, empty: { 'SHA-384': { @@ -150,7 +196,19 @@ const kDerivations = { '851cc5baadb42cad024b6290fe213436', empty: 'b4f7e7557674d501cbfbc0148ad800c0' + '750189fe295a2aca5e1bf4122c85edf9' - } + }, + 'SHA3-256': { + normal: 'fe32459f7339dd2e8df6c6fc874ed9e81e3b7aad669edad9b71196f53ed95b12', + empty: '04519be1eb94079c91306cc5b21946b3de6a78ad35ec83d4f4a37bafbda678d7', + }, + 'SHA3-384': { + normal: 'a474e8289cb4a0511e90b87eaf9ec29cadd74d4c1f2ee1fb8cb5f7d08f91a379', + empty: '726c8c4b39083a7d5755604d3a67e9aa6139db00c08028ac9e69f7fb1525bf1d', + }, + 'SHA3-512': { + normal: 'c7a7f5004d1d595c6896498c169642ac24b946e13296ff53e12b534962a88675', + empty: '7b543480b5696932551abb3100d72e05c18f57fbb63aa44fe020bef1eec3555c', + }, } }, }; @@ -294,7 +352,7 @@ async function testDeriveBitsBadHash( subtle.deriveBits( { ...algorithm, - hash: hash.substring(0, 3) + hash.substring(4) + hash: hash.replace('-', '') }, baseKeys[size], 256), { message: /Unrecognized algorithm name/, name: 'NotSupportedError', @@ -306,7 +364,6 @@ async function testDeriveBitsBadHash( hash: 'PBKDF2' }, baseKeys[size], 256), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }), ]); @@ -431,16 +488,13 @@ async function testDeriveKeyBadHash( subtle.deriveKey( { ...algorithm, - hash: hash.substring(0, 3) + hash.substring(4) + hash: hash.replace('-', '') }, baseKeys[size], keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), assert.rejects( subtle.deriveKey( { @@ -451,10 +505,7 @@ async function testDeriveKeyBadHash( keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), ]); } diff --git a/test/parallel/test-webcrypto-derivebits.js b/test/parallel/test-webcrypto-derivebits.js index 0db467b852283a..50892be7400e4a 100644 --- a/test/parallel/test-webcrypto-derivebits.js +++ b/test/parallel/test-webcrypto-derivebits.js @@ -123,8 +123,10 @@ const { subtle } = globalThis.crypto; assert.deepStrictEqual(secret1, secret2); } + test('X25519').then(common.mustCall()); if (!process.features.openssl_is_boringssl) { - test('X25519').then(common.mustCall()); test('X448').then(common.mustCall()); + } else { + common.printSkipMessage('Skipping unsupported X448 test case'); } } diff --git a/test/parallel/test-webcrypto-derivekey-cfrg.js b/test/parallel/test-webcrypto-derivekey-cfrg.js index 7d8467b4a37cde..c5a5b1f3518fef 100644 --- a/test/parallel/test-webcrypto-derivekey-cfrg.js +++ b/test/parallel/test-webcrypto-derivekey-cfrg.js @@ -18,18 +18,25 @@ const kTests = [ '64ea51fae5b3307cfe9706', result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008' }, - { - name: 'X448', - size: 56, - pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + +]; + +if (!process.features.openssl_is_boringssl) { + kTests.push( + { + name: 'X448', + size: 56, + pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + 'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' + 'd0cc53bf26929e', - spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + + spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + 'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' + 'ce6f', - result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' - }, -]; + result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + }, + ); +} else { + common.printSkipMessage('Skipping unsupported X448 test case'); +} async function prepareKeys() { const keys = {}; @@ -107,8 +114,8 @@ async function prepareKeys() { // Missing public property await assert.rejects( subtle.deriveKey( - { name: 'X448' }, - keys.X448.privateKey, + { name: 'X25519' }, + keys.X25519.privateKey, ...otherArgs), { code: 'ERR_MISSING_OPTION' }); } @@ -118,15 +125,15 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', + name: 'X25519', public: { message: 'Not a CryptoKey' } }, - keys.X448.privateKey, + keys.X25519.privateKey, ...otherArgs), { code: 'ERR_INVALID_ARG_TYPE' }); } - { + if (keys.X25519 && keys.X448) { // Mismatched named curves await assert.rejects( subtle.deriveKey( @@ -136,7 +143,7 @@ async function prepareKeys() { }, keys.X448.privateKey, ...otherArgs), - { message: 'algorithm.public must be an X448 key' }); + { message: 'key algorithm mismatch' }); } { @@ -144,10 +151,10 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', - public: keys.X448.publicKey + name: 'X25519', + public: keys.X25519.publicKey }, - keys.X448.publicKey, + keys.X25519.publicKey, ...otherArgs), { name: 'InvalidAccessError' }); } @@ -157,10 +164,10 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', - public: keys.X448.privateKey + name: 'X25519', + public: keys.X25519.privateKey }, - keys.X448.privateKey, + keys.X25519.privateKey, ...otherArgs), { name: 'InvalidAccessError' }); } @@ -177,10 +184,10 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', + name: 'X25519', public: key }, - keys.X448.publicKey, + keys.X25519.publicKey, ...otherArgs), { name: 'InvalidAccessError' }); } diff --git a/test/parallel/test-webcrypto-derivekey-ecdh.js b/test/parallel/test-webcrypto-derivekey-ecdh.js index 3b4f86cc02d5ba..61a284cb8f8dea 100644 --- a/test/parallel/test-webcrypto-derivekey-ecdh.js +++ b/test/parallel/test-webcrypto-derivekey-ecdh.js @@ -174,7 +174,7 @@ async function prepareKeys() { }, keys['P-521'].privateKey, ...otherArgs), - { message: 'algorithm.public must be an ECDH key' }); + { message: 'key algorithm mismatch' }); } { diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js index 90f76839474e16..7a319643fa5f7b 100644 --- a/test/parallel/test-webcrypto-derivekey.js +++ b/test/parallel/test-webcrypto-derivekey.js @@ -73,12 +73,29 @@ const { KeyObject } = require('crypto'); } const kTests = [ + ['hello', 'there', 'my friend', 'SHA-1', + '365ca5d3f42d050c74302e420c83975327950f1913a151eecd00526bf52614a0'], ['hello', 'there', 'my friend', 'SHA-256', '14d93b0ccd99d4f2cbd9fbfe9c830b5b8a43e3e45e32941ef21bdeb0fa87b6b6'], ['hello', 'there', 'my friend', 'SHA-384', 'e36cf2cf943d8f3a88adb80f478745c336ac811b1a86d03a7d10eb0b6b52295c'], + ['hello', 'there', 'my friend', 'SHA-512', + '1e42d43fcacba361716f65853bd5f3c479f679612f0180eab3c51ed6c9d2b47d'], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + ['hello', 'there', 'my friend', 'SHA3-256', + '2a49a3b6fb219117af9e251c6c65f16600cbca13bd0be6e70d96b0b9fa4cf3fd'], + ['hello', 'there', 'my friend', 'SHA3-384', + '0437bb59b95f2db2c7684c0b439028cb0fdd6f0f5d03b9f489066a87ae147221'], + ['hello', 'there', 'my friend', 'SHA3-512', + '3bbc469d38214371921e52c6f147e96cb7eb370421a81f53dea8b4851dfb8bce'], + ); + } else { + common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + } + const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); @@ -109,10 +126,20 @@ const { KeyObject } = require('crypto'); } const kTests = [ - ['hello', 'there', 10, 'SHA-256', - 'f72d1cf4853fffbd16a42751765d11f8dc7939498ee7b7ce7678b4cb16fad880'], + ['hello', 'there', 5, 'SHA-1', + 'f8f65a5fd92c9b74916083a7e9b0001c46bc89e2a14c48014cf1e0e1dbabf635'], + ['hello', 'there', 5, 'SHA-256', + '2e575eae24267db32106c7dba01615e5417557e8c5cf33ba15a311cb0c2907ee'], ['hello', 'there', 5, 'SHA-384', '201509b012c9cd2fbe7ea938f0c509b36ecb140f38bf9130e96923f55f46756d'], + ['hello', 'there', 5, 'SHA-512', + '2e8d981741f98193e0af9c79870af0e985089341221edad9a130d297eae1984b'], + ['hello', 'there', 5, 'SHA3-256', + '0aed29b61b3ca3978aea34a9793276574ea997b69e8d03727438199f90571649'], + ['hello', 'there', 5, 'SHA3-384', + '7aa4a274aa19b4623c5d3091c4b06355de85ff6f25e53a83e3126cbb86ae68df'], + ['hello', 'there', 5, 'SHA3-512', + '4d909c47a81c625f866d1f9406248e6bc3c7ea89225fbccf1f08820254c9ef56'], ]; const tests = Promise.all(kTests.map((args) => test(...args))); @@ -128,26 +155,36 @@ const { KeyObject } = require('crypto'); [{ name: 'HMAC', hash: 'SHA-1' }, 'sign', 512], [{ name: 'HMAC', hash: 'SHA-256' }, 'sign', 512], // Not long enough secret generated by ECDH - // [{ name: 'HMAC', hash: 'SHA-384' }, 'sign', 1024], - // [{ name: 'HMAC', hash: 'SHA-512' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA-384' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA-512' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA3-256' }, 'sign', 1088], + [{ name: 'HMAC', hash: 'SHA3-384' }, 'sign', 832], + [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 576], ]; (async () => { const keyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-521' }, false, ['deriveKey']); for (const [derivedKeyAlgorithm, usage, expected] of vectors) { - const derived = await subtle.deriveKey( + const [result] = await Promise.allSettled([subtle.deriveKey( { name: 'ECDH', public: keyPair.publicKey }, keyPair.privateKey, derivedKeyAlgorithm, false, - [usage]); + [usage])]); - if (derived.algorithm.name === 'HMAC') { - assert.strictEqual(derived.algorithm.length, expected); + if (expected > 528) { + assert.strictEqual(result.status, 'rejected'); + assert.match(result.reason.message, /derived bit length is too small/); } else { - // KDFs cannot be exportable and do not indicate their length - const secretKey = KeyObject.from(derived); - assert.strictEqual(secretKey.symmetricKeySize, expected / 8); + assert.strictEqual(result.status, 'fulfilled'); + const derived = result.value; + if (derived.algorithm.name === 'HMAC') { + assert.strictEqual(derived.algorithm.length, expected); + } else { + // KDFs cannot be exportable and do not indicate their length + const secretKey = KeyObject.from(derived); + assert.strictEqual(secretKey.symmetricKeySize, expected / 8); + } } } })().then(common.mustCall()); @@ -159,6 +196,9 @@ const { KeyObject } = require('crypto'); [{ name: 'HMAC', hash: 'SHA-256' }, 'sign', 512], [{ name: 'HMAC', hash: 'SHA-384' }, 'sign', 1024], [{ name: 'HMAC', hash: 'SHA-512' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA3-256' }, 'sign', 1088], + [{ name: 'HMAC', hash: 'SHA3-384' }, 'sign', 832], + [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 576], ]; (async () => { @@ -206,8 +246,10 @@ const { KeyObject } = require('crypto'); assert.deepStrictEqual(raw1, raw2); } + test('X25519').then(common.mustCall()); if (!process.features.openssl_is_boringssl) { - test('X25519').then(common.mustCall()); test('X448').then(common.mustCall()); + } else { + common.printSkipMessage('Skipping unsupported X448 test case'); } } diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js index bfd01ecc12b63e..04507d77b59142 100644 --- a/test/parallel/test-webcrypto-digest.js +++ b/test/parallel/test-webcrypto-digest.js @@ -8,15 +8,27 @@ if (!common.hasCrypto) const assert = require('assert'); const { Buffer } = require('buffer'); const { subtle } = globalThis.crypto; -const { createHash } = require('crypto'); +const { createHash, getHashes } = require('crypto'); const kTests = [ - ['SHA-1', 'sha1', 160], - ['SHA-256', 'sha256', 256], - ['SHA-384', 'sha384', 384], - ['SHA-512', 'sha512', 512], + ['SHA-1', ['sha1'], 160], + ['SHA-256', ['sha256'], 256], + ['SHA-384', ['sha384'], 384], + ['SHA-512', ['sha512'], 512], ]; +if (!process.features.openssl_is_boringssl) { + kTests.push( + [{ name: 'cSHAKE128', length: 256 }, ['shake128', { outputLength: 256 >> 3 }], 256], + [{ name: 'cSHAKE256', length: 512 }, ['shake256', { outputLength: 512 >> 3 }], 512], + ['SHA3-256', ['sha3-256'], 256], + ['SHA3-384', ['sha3-384'], 384], + ['SHA3-512', ['sha3-512'], 512], + ); +} else { + common.printSkipMessage('Skipping unsupported test cases'); +} + // Empty hash just works, not checking result subtle.digest('SHA-512', Buffer.alloc(0)) .then(common.mustCall()); @@ -38,12 +50,10 @@ const kData = (new TextEncoder()).encode('hello'); await Promise.all(kTests.map(async (test) => { // Get the digest using the legacy crypto API const checkValue = - createHash(test[1]).update(kData).digest().toString('hex'); + createHash.apply(createHash, test[1]).update(kData).digest().toString('hex'); // Get the digest using the SubtleCrypto API const values = Promise.all([ - subtle.digest({ name: test[0] }, kData), - subtle.digest({ name: test[0], length: test[2] }, kData), subtle.digest(test[0], kData), subtle.digest(test[0], kData.buffer), subtle.digest(test[0], new DataView(kData.buffer)), @@ -137,17 +147,87 @@ const kDigestedData = { long: '4b02caf650276030ea5617e597c5d53fd9daa68b78bfe' + '60b22aab8d36a4c2a3affdb71234f49276737c575ddf7' + '4d14054cbd6fdb98fd0ddcbcb46f91ad76b6ee' + }, + 'cshake128': { + empty: '7f9c2ba4e88f827d616045507605853ed73b8093f6e' + + 'fbc88eb1a6eacfa66ef26', + short: 'dea62d73e6b59cf725d0320d660089a4475cbbd3b85' + + '39e36691f150d47556794', + medium: 'b1acd53a03e76a221e52ea578e042f686a68c3d1c9' + + '832ab18285cf4f304ca32d', + long: '3a5bf5676955e5dec87d430e526925558971ca14c370' + + 'ee5d7cf572b94c7c63d7' + }, + 'cshake256': { + empty: '46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b' + + '81b82b50c27646ed5762fd75dc4ddd8c0f200cb0501' + + '9d67b592f6fc821c49479ab48640292eacb3b7c4be', + short: '1738113f5abb3ee5320ee18aa266c3617a7475dbd8e' + + 'd9a985994fddd6112ad999ec8e2ebdfeafb96e76f6b' + + 'b3a3adba43da60f00cd12496df5af3e28ae6d3de42', + medium: '4146c13d86d9bc186b0b309ab6a124ee0c74ba26b8' + + 'c60dcc7b3ed505969aa8d19028c6317999a085b1e6' + + 'b6a785ce4ff632aeb27493227e44232fb7b3952141' + + '7b', + long: '0c42bfd1e282622fd8144aa29b072fd09fc2bae70885' + + 'd5290933492f9d17411926a613dd0611668c2ac999e8' + + 'c011aabaa9004323425fbad75b0f58ee6e777a94' + }, + 'sha3-256': { + empty: 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a', + short: '3059af7aa33b517084e8ad7bbc4fb208a44c28ef32b4698d103dd540e4f91aa1', + medium: '1fa7cd1da74cd8046417508c8314e74a9a4a9d38f9f18e6cb215b8c891a0a80e', + long: 'b2cfc61e0386cdaef5e10a2be189891f5ef52a7624bfcd8edc893acc64fec600' + }, + 'sha3-384': { + empty: '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa9' + + '4fc61995e71bbee983a2ac3713831264adb47fb6bd1' + + 'e058d5f004', + short: '54b8f0e4cf4974de740098f66b3024479b01631315a' + + '6773606c33eadc32556a6e778e08f0225ae79265aec' + + '666cb2390b', + medium: '437b7d8b68b250b5c1739ea4cc86db2033879dfb18' + + 'de292c9c50d9c193a4c79a08a6cae3f4e483c2795e' + + 'a5d1ef7e69d2', + long: '3b39c4c97ad87613305d0ccc987181713e2d5e84b1f9' + + '760011bcce0c297499005bdce8a3d2409b5ad0164f32' + + 'bb8778d0' + }, + 'sha3-512': { + empty: 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe' + + '25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9' + + '402c3ac558f500199d95b6d3e301758586281dcd26', + short: '2dd2e07a62e6ad0498ba84f313c4d4024cb46001f78' + + 'f75db336b0d4d8bd2a9ec152c4ad20878735d82ba08' + + '72ecf59608ef3ced2b2a8669427e7da31e362333d8', + medium: 'e640a21909536640369e9b0a48931c5cb2efcbc91f' + + 'ecf247306bc96a0e4ca33307cb8e1b9af367946dd01' + + 'c243f3907508d04f1692a3161df1f898de8ee25febe', + long: 'bd262cecf565c338032de5ba0138f0aacfe7dde83d27' + + '2d0d37d952829ed25de1a1342d98659ef7d2fa4aca7c' + + 'e2b1aa0784d8fc1dcbf81bcec7a7431a3da36bf7' } }; -async function testDigest(size, name) { +async function testDigest(size, alg) { const digest = await subtle.digest( - name, + alg, Buffer.from(kSourceData[size], 'hex')); assert.strictEqual( Buffer.from(digest).toString('hex'), - kDigestedData[name.toLowerCase()][size]); + kDigestedData[(alg.name || alg).toLowerCase()][size]); +} + +function applyXOF(name) { + if (name.match(/cshake128/i)) { + return { name, length: 256 }; + } + if (name.match(/cshake256/i)) { + return { name, length: 512 }; + } + return name; + } (async function() { @@ -158,9 +238,9 @@ async function testDigest(size, name) { const downCase = alg.toLowerCase(); const mixedCase = upCase.slice(0, 1) + downCase.slice(1); - variations.push(testDigest(size, upCase)); - variations.push(testDigest(size, downCase)); - variations.push(testDigest(size, mixedCase)); + variations.push(testDigest(size, applyXOF(upCase))); + variations.push(testDigest(size, applyXOF(downCase))); + variations.push(testDigest(size, applyXOF(mixedCase))); }); }); @@ -172,3 +252,18 @@ async function testDigest(size, name) { name: 'NotSupportedError', }); })().then(common.mustCall()); + +// CShake edge cases +if (getHashes().includes('shake128')) { + (async () => { + assert.deepStrictEqual( + new Uint8Array(await subtle.digest({ name: 'cSHAKE128', length: 0 }, Buffer.alloc(1))), + new Uint8Array(0), + ); + + await assert.rejects(subtle.digest({ name: 'cSHAKE128', length: 7 }, Buffer.alloc(1)), { + name: 'NotSupportedError', + message: 'Unsupported CShakeParams length', + }); + })().then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js new file mode 100644 index 00000000000000..aea9528f2463db --- /dev/null +++ b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js @@ -0,0 +1,255 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (process.features.openssl_is_boringssl) + common.skip('Skipping unsupported ChaCha20-Poly1305 test case'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) { + // Using a copy of plaintext to prevent tampering of the original + plaintext = Buffer.from(plaintext); + + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt', 'decrypt']); + + const output = await subtle.encrypt(algorithm, key, plaintext); + plaintext[0] = 255 - plaintext[0]; + + assert.strictEqual( + Buffer.from(output).toString('hex'), + Buffer.from(result).toString('hex')); + + // Converting the returned ArrayBuffer into a Buffer right away, + // so that the next line works + const check = Buffer.from(await subtle.decrypt(algorithm, key, output)); + check[0] = 255 - check[0]; + + assert.strictEqual( + Buffer.from(check).toString('hex'), + Buffer.from(plaintext).toString('hex')); +} + +async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) { + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['decrypt']); + + return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { + message: /The requested operation is not valid for the provided key/ + }); +} + +async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) { + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt']); + + const output = await subtle.encrypt(algorithm, key, plaintext); + + return assert.rejects(subtle.decrypt(algorithm, key, output), { + message: /The requested operation is not valid for the provided key/ + }); +} + +async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) { + assert.notStrictEqual(algorithm.name, alg); + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: alg }, + false, + ['encrypt']); + + return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { + message: /The requested operation is not valid for the provided key/ + }); +} + +async function testDecrypt({ keyBuffer, algorithm, result }) { + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt', 'decrypt']); + + await subtle.decrypt(algorithm, key, result); +} + +{ + const { + passing, + failing, + decryptionFailing + } = require('../fixtures/crypto/chacha20_poly1305')(); + + (async function() { + const variations = []; + + passing.forEach((vector) => { + variations.push(testEncrypt(vector)); + variations.push(testEncryptNoEncrypt(vector)); + variations.push(testEncryptNoDecrypt(vector)); + variations.push(testEncryptWrongAlg(vector, 'AES-GCM')); + }); + + failing.forEach((vector) => { + variations.push(assert.rejects(testEncrypt(vector), { + message: /is not a valid ChaCha20-Poly1305 tag length/ + })); + variations.push(assert.rejects(testDecrypt(vector), { + message: /is not a valid ChaCha20-Poly1305 tag length/ + })); + }); + + decryptionFailing.forEach((vector) => { + variations.push(assert.rejects(testDecrypt(vector), { + name: 'OperationError' + })); + }); + + await Promise.all(variations); + })().then(common.mustCall()); +} + +{ + (async function() { + const secretKey = await subtle.generateKey( + { + name: 'ChaCha20-Poly1305', + }, + false, + ['encrypt', 'decrypt'], + ); + + const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); + const aad = globalThis.crypto.getRandomValues(new Uint8Array(32)); + + const encrypted = await subtle.encrypt( + { + name: 'ChaCha20-Poly1305', + iv, + additionalData: aad, + }, + secretKey, + globalThis.crypto.getRandomValues(new Uint8Array(32)) + ); + + await subtle.decrypt( + { + name: 'ChaCha20-Poly1305', + iv, + additionalData: aad, + }, + secretKey, + new Uint8Array(encrypted), + ); + })().then(common.mustCall()); +} + +{ + + async function testRejectsImportKey(format, keyData, algorithm, extractable, usages, expectedError) { + await assert.rejects( + subtle.importKey(format, keyData, algorithm, extractable, usages), + expectedError + ); + } + + async function testRejectsGenerateKey(algorithm, extractable, usages, expectedError) { + await assert.rejects( + subtle.generateKey(algorithm, extractable, usages), + expectedError + ); + } + + (async function() { + const baseJwk = { kty: 'oct', k: 'AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA' }; + const alg = { name: 'ChaCha20-Poly1305' }; + const keyData32 = globalThis.crypto.getRandomValues(new Uint8Array(32)); + + // Test decrypt with data too small + const secretKey = await subtle.generateKey(alg, false, ['encrypt', 'decrypt']); + const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); + await assert.rejects( + subtle.decrypt({ name: 'ChaCha20-Poly1305', iv }, secretKey, new Uint8Array(8)), + { name: 'OperationError', message: /The provided data is too small/ } + ); + + // Test invalid tagLength values + await assert.rejects( + subtle.encrypt({ name: 'ChaCha20-Poly1305', iv, tagLength: 64 }, secretKey, keyData32), + { name: 'OperationError', message: /is not a valid ChaCha20-Poly1305 tag length/ } + ); + await assert.rejects( + subtle.encrypt({ name: 'ChaCha20-Poly1305', iv, tagLength: 96 }, secretKey, keyData32), + { name: 'OperationError', message: /is not a valid ChaCha20-Poly1305 tag length/ } + ); + + // JWK error conditions + const jwkTests = [ + [{ k: baseJwk.k }, /Invalid keyData/], + [{ ...baseJwk, kty: 'RSA' }, /Invalid JWK "kty" Parameter/], + [{ ...baseJwk, use: 'sig' }, /Invalid JWK "use" Parameter/], + [{ ...baseJwk, ext: false }, /JWK "ext" Parameter and extractable mismatch/, true], + [{ ...baseJwk, alg: 'A256GCM' }, /JWK "alg" does not match the requested algorithm/], + [{ ...baseJwk, key_ops: ['sign'] }, /Key operations and usage mismatch|Unsupported key usage/], + [{ ...baseJwk, key_ops: ['encrypt'] }, /Key operations and usage mismatch/, false, ['decrypt']], + ]; + + for (const [jwk, errorPattern, extractable = false, usages = ['encrypt']] of jwkTests) { + await testRejectsImportKey('jwk', jwk, alg, extractable, usages, + { name: 'DataError', message: errorPattern }); + } + + // Valid JWK imports + const validKeys = await Promise.all([ + subtle.importKey('jwk', { ...baseJwk, alg: 'C20P' }, alg, false, ['encrypt']), + subtle.importKey('jwk', { ...baseJwk, use: 'enc' }, alg, false, ['encrypt']), + ]); + validKeys.forEach((key) => assert.strictEqual(key.algorithm.name, 'ChaCha20-Poly1305')); + + // Invalid key usages + const usageTests = [ + [['sign'], 'generateKey'], + [['verify'], 'importKey'], + ]; + + for (const [usages, method] of usageTests) { + const fn = method === 'generateKey' ? + () => testRejectsGenerateKey(alg, false, usages, { name: 'SyntaxError', message: /Unsupported key usage/ }) : + () => testRejectsImportKey('raw-secret', keyData32, alg, false, usages, { name: 'SyntaxError', message: /Unsupported key usage/ }); + await fn(); + } + + // Valid wrapKey/unwrapKey usage + const wrapKey = await subtle.importKey('raw-secret', keyData32, alg, false, ['wrapKey', 'unwrapKey']); + assert.strictEqual(wrapKey.algorithm.name, 'ChaCha20-Poly1305'); + + // Invalid key lengths + for (const size of [16, 64]) { + await testRejectsImportKey('raw-secret', new Uint8Array(size), alg, false, ['encrypt'], + { name: 'DataError', message: /Invalid key length/ }); + } + + // Invalid JWK keyData + await testRejectsImportKey('jwk', { ...baseJwk, k: 'invalid-base64-!@#$%^&*()' }, alg, false, ['encrypt'], + { name: 'DataError' }); + })().then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-encrypt-decrypt.js b/test/parallel/test-webcrypto-encrypt-decrypt.js index 5d4ecc02c74a31..c21aa3c29d0afe 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt.js @@ -11,7 +11,7 @@ const { subtle } = globalThis.crypto; // This is only a partial test. The WebCrypto Web Platform Tests // will provide much greater coverage. -// Test Encrypt/Decrypt RSA-OAEP +// Test Encrypt/Decrypt RSA-OAEP w/ SHA-2 { const buf = globalThis.crypto.getRandomValues(new Uint8Array(50)); @@ -56,6 +56,51 @@ const { subtle } = globalThis.crypto; test().then(common.mustCall()); } +// Test Encrypt/Decrypt RSA-OAEP w/ SHA-3 +if (!process.features.openssl_is_boringssl) { + const buf = globalThis.crypto.getRandomValues(new Uint8Array(50)); + + async function test() { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'RSA-OAEP', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA3-384', + }, true, ['encrypt', 'decrypt']); + + const ciphertext = await subtle.encrypt({ + name: 'RSA-OAEP', + label: ec.encode('a label') + }, publicKey, buf); + + const plaintext = await subtle.decrypt({ + name: 'RSA-OAEP', + label: ec.encode('a label') + }, privateKey, ciphertext); + + assert.strictEqual( + Buffer.from(plaintext).toString('hex'), + Buffer.from(buf).toString('hex')); + + await assert.rejects(() => subtle.encrypt({ + name: 'RSA-OAEP', + }, privateKey, buf), { + name: 'InvalidAccessError', + message: 'The requested operation is not valid for the provided key' + }); + + await assert.rejects(() => subtle.decrypt({ + name: 'RSA-OAEP', + }, publicKey, ciphertext), { + name: 'InvalidAccessError', + message: 'The requested operation is not valid for the provided key' + }); + } + + test().then(common.mustCall()); +} + // Test Encrypt/Decrypt AES-CTR { const buf = globalThis.crypto.getRandomValues(new Uint8Array(50)); diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js index ea1dd8176ae413..ae203e1005de0a 100644 --- a/test/parallel/test-webcrypto-export-import-cfrg.js +++ b/test/parallel/test-webcrypto-export-import-cfrg.js @@ -87,23 +87,30 @@ const testVectors = [ privateUsages: ['sign'], publicUsages: ['verify'] }, - { - name: 'Ed448', - privateUsages: ['sign'], - publicUsages: ['verify'] - }, { name: 'X25519', privateUsages: ['deriveKey', 'deriveBits'], publicUsages: [] }, - { - name: 'X448', - privateUsages: ['deriveKey', 'deriveBits'], - publicUsages: [] - }, ]; +if (!process.features.openssl_is_boringssl) { + testVectors.push( + { + name: 'Ed448', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'X448', + privateUsages: ['deriveKey', 'deriveBits'], + publicUsages: [] + }, + ); +} else { + common.printSkipMessage('Skipping unsupported Curve448 test cases'); +} + async function testImportSpki({ name, publicUsages }, extractable) { const key = await subtle.importKey( 'spki', diff --git a/test/parallel/test-webcrypto-export-import-ec.js b/test/parallel/test-webcrypto-export-import-ec.js index 57f1b2831e33bc..46a7e9153f2668 100644 --- a/test/parallel/test-webcrypto-export-import-ec.js +++ b/test/parallel/test-webcrypto-export-import-ec.js @@ -423,14 +423,14 @@ async function testImportRaw({ name, publicUsages }, namedCurve) { subtle.importKey( 'spki', rsaPublic.export({ format: 'der', type: 'spki' }), - { name, hash: 'SHA-256', namedCurve: 'P-256' }, + { name, namedCurve: 'P-256' }, true, publicUsages), { message: /Invalid key type/ }, ).then(common.mustCall()); assert.rejects( subtle.importKey( 'pkcs8', rsaPrivate.export({ format: 'der', type: 'pkcs8' }), - { name, hash: 'SHA-256', namedCurve: 'P-256' }, + { name, namedCurve: 'P-256' }, true, privateUsages), { message: /Invalid key type/ }, ).then(common.mustCall()); } @@ -491,7 +491,7 @@ async function testImportRaw({ name, publicUsages }, namedCurve) { subtle.importKey( 'pkcs8', pkcs8, - { name, hash: 'SHA-256', namedCurve }, + { name, namedCurve }, true, privateUsages), { name: 'DataError', message: /Invalid keyData/ }, ).then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js new file mode 100644 index 00000000000000..390b6b07c30e61 --- /dev/null +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -0,0 +1,511 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 5)) + common.skip('requires OpenSSL >= 3.5'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +const fixtures = require('../common/fixtures'); + +function getKeyFileName(type, suffix) { + return `${type.replaceAll('-', '_')}_${suffix}.pem`; +} + +function toDer(pem) { + const der = pem.replace(/(?:-----(?:BEGIN|END) (?:PRIVATE|PUBLIC) KEY-----|\s)/g, ''); + return Buffer.alloc(Buffer.byteLength(der, 'base64'), der, 'base64'); +} + +/* eslint-disable @stylistic/js/max-len */ +const keyData = { + 'ML-DSA-44': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'public'), 'ascii')), + jwk: { + kty: 'AKP', + alg: 'ML-DSA-44', + pub: 'fYmD1Rx_jkoW9KG7Bs_5zyYEiWEZs15tYBxNdKq9NircZnvZBwwwaGbj0UsxJNc4Dyfp2IFAZZPO3rFCSUdpXHPrGRHwIVMzwiwfu2V7V02xoheW4mrkPThA3JRJSmNdsx6YGu37MaeJkIk6AlUexo46JfGrkRXZp_IyZxiL_L2dPrfwx-32j7WFI5sBadp7cDWfNkJjdQwW4puTe5Rw7h16GHb-DMOAKpfeMHujh7IYHuLCU6lVi90j1m8Ru0dxdmeQ1eY1vDnO7fNQKfzOLhpUNnj7BBZ24GTqFc-SN5HDCSCsSGKScTYYBwiSVTdSGG1GNqIiN2FgE4z1Jj6JFVB_OIUnl4sKbb3m8kB0BwtUPbkC0FVokGRUEGt6ba1Pc_IMpB5Gs3g9PFREI_C9o1yVW3NS2PzH_Vk4Tpf0N1K1kzIK_3IqekLfyqXmVDNsOovsS7Sw9TdmdWUNGRmhXFKRkex5VjpMIx7OwBGsYJCc4FhauWdrVtbkvHGggSpsla73ZcA4Vzh7aq47LMv0KS2YLp-DMn7SEohPHGg74118eLLn88yptxwtwt1dBFj8BKUfPrytuN1EIRQy34hwbkBLN9wDqhgn3Z3fvksRvmgN_4ZQ8YjeD-H3OFh5WJ_Rd66wHSl-YFat-_JF4UPcdlkNUbxPvDi5VL909Pe3VlwEZhT5otdtXQX4U3dUfqWKEh2kN0Q2lo8wbf3OMmBOFTfyX0eYa_5088ZnJvvliefn-TCDyc6WlcZrNqwBOF8N8-IN3b_8RPq-RuV8-mK-M83Hi4ElQB7Z44eZMmfUwFrozEG4Wq2K6MwQ_edG4dWeUVMCloTpGDFOtlLQlDoAN4m_sS2Lbwm_3ra29noUcK8_j10yy-hENE2Yluh1pIL-GoWZj3uYO-rEKVbszaagdE0DJ_uQcHUdNnBHKn64-cQ6xihXzxaeHx9OxkWWMKbzLtKpuYDK_X7EVvm8YTjl_oTsr2SWT2usjNJko32DhRV-OXLKKHo5FJpCy2bGFLXGG26CglUvgZQ2dyXiWeGVNKffOv1cQ5R_RlU2MpLiZ1bigy9hh4lu_XAHLfjQfhf71jeMuF4nEBWV-YOAjDTaDB2hcGqv_XcGXcmLWHqOWgc5Mb6lkb2zYs_oyOskmyFx6C0P7UrV8kCiN4zbuTqZNdNjlWL_QJUmU3vk6CpNa0XN1M3sLjZpOEsaqgRVPLcIDH-juVhyWiymuxe-8yNCOFSKxhscew08EQ9DEckP_iIA8qU2gcreHtvAS5VA5Emz1K2ypYe6oS3ogP-CX4nOAEfvjsb1HHJoclgiwjL1BtCLFgOE-0vn1M-nVOE6WbHGHoNKMJMHP2a3HQC7DmDfSOw5P6Cj5X7QVqhCY6tAGZWEPu3hUssp7K5UJePEdBn_LrErt4ucyXW6y1PAA2Fn8EuHaRyf2ggibDGnzq8E15m_R4LMvZAuGR0bN9jBTlm_x4ZQMqFwKkIdllkN1QTErazOyNsgU6fhA_20h5EIYT6-LqXr_Otj3Kp8MkJB9c3XNGoo5sbHTQCt0VNOHoxCFP_swiAJLtm743eOsI1M6naWLIqPagSCioosAvJYowypJQGvM-N3hBu8KUr0f911KRN7WqTAXTOHZ_vvTqcWKet0dFdh1EHuP3TrU8hSMciaphGvuK93T3gaWuJ6lcCkQndWvEo9S6FQB7eLU_ALKOQ3ROybUUkXgfyTkWDPxbHdeJCgMRv6Ig1PShPyxYb4ig', + priv: '273AhMPiZWLlSQCY41yi1fMj6xavGH0btB23zMhI1uY', + }, + }, + 'ML-DSA-65': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'public'), 'ascii')), + jwk: { + kty: 'AKP', + alg: 'ML-DSA-65', + pub: 'hxPP5LvG83t2fJyfA1TUssJK_ydrzryrCHGZuKFxmnl5Y3sxHRCPW_JpHEoiIgR6kgELnwibZnueax1zFerTOTA7o0NwXHFiaEB-8AmqJI93DkvtbUOSTCixa3admQBKW_PtgMCVtaEVuuvCuOEFhOyuZkyfvnpBwUKOkz3t-O1wpgrSmf-rdPXOEv8YcsSn-xfLYPSLzPCnt7gnIX_fwtkgnXjref-QqjFKlKZE2e7MkmHeViJ4iGy78r3UzVhBHsmFGC0ZNc8-iT3muH5Sn0SXmNq-F2EoerWLIAsPxL2KE6UrqPAwTbHn1B5sAGWvhsVVLlFPI1s1JLVLBNRJ5vhif525xNIpMAMuAZrteD827pve3zQo9_GHjWgykj9VzM9PEcVmVqxZ5u41kUXsM4PWZF29Oh2sYsmJ2LdiJ9RcA91vRLG2DqEYm-V5JwIz8uxL17DUsEC7zYthvtqGASq05CbfPTBev33rQUv4H0Etz99U89WooTk0FisHDz1uEUilU_VY1tN5byIDitXNf0jnz3SIHDUUZARn7ll0YwO0jtksT68sQW3Liy6Exhlp1td0so2qZUrbVZasjyCOVuibwbwvrdpP3QRsoG5UqkAqk8Rm2iCpdQSg87pswOscgA8AC8TczGHNfXc9PqzAmbsEPKvmZuE60HLGzqpRqFULf3nyYUQUqbdmKJsKQ29LXeDVbyy3-fkTUDuYqNC2tBY7PkzHJSA9Z4hDC_BHEFxcelibScSNyf7y4lDVWnuJMXpQ0WRh3UkUPa007IerhixwxvBvFXQR-ytYinixvjirlcEF1wQI1DzE8KjOXYYuFPS4Yl8HeZHQ-64Q0RuxlKIRP3YvjZWh4IDVvEVs5ZLzZPbE3Twe0N5a7iCu0BzZWTeHNbcMoViFyJTpec2w3vVHeI4PJB-5HeI8xuh-9y8ytTau8QtMe4thoROoajizDQLrkw3e6ryJJ3R84i0oni4vmZWyLDilwcLqPOkQJCIDMjq7exdmVX5t3DtAW4F6Coz0z3sf7tGlSMVxA7izCoVbG0y2_l1P2h7fWBuEPT7PWlMdPqu9Pj5jqXY6jJ0nkaR_pp7dDhO1HKae5edcBYunHZqVQQjRZ_DvKzbPrDk5t6Xq9fdSkiAeP3B4qn5uU-nx7OaX7DRoVEnbbiEDynIRPSEY-Ts3alPJtBv8zuzaGNyX05Z9MyZ0w-VlC-WxOBdVEsIAp_4uJ3kQ3UsfE9DLJH8WPuDI4t4i2VnNNyFlI0XSUocc_0rWgqp2I1UzSzkVbklwkuFywPI645u4G2XAlfdd_wpjFGC-IUPXgpeSfspPwW15sBP-ITS-gwtvfzQVLpRS0euzN97xo_GMhNPZ4bW-YyZt8z_R8bsQ8ktfoP-5RUV-yzYDt0tA01QJsZdBLf5J_H7qP8l4c8V4hPe_CFL032obbxmAnVPAP69u2SaMBlL8azjk4wGVFQpQp1JqMJCao2W8ZImCVegkPZxhGbx0nkgVfyFx4ihMeDNM288JbGC4CGON8C02Q84rQzhwzZE83Y9rSe1Bb0fUMHMu6ihD5jLdeltuBL4ZdJlKgL24KZK5o5pq4_l9SyzGAjB1KAQnClNOB1SxV89CtILu-65wb17s0z3qw2-NF0B6UVlGQFebjbSyLQv2ARaETh_8cBiPugVMgBIV3K1KBwNyWejyI1ZDCssvIZHJCF2SRW3HmJerTiB23eGHFYKSLdxW7LEzoHIc2xZEc3pwR43gavjeoL0pNc-HNFV_c19wiH7Tnw3IHld_FfTqAIPnqKMNIY7D_D0DmFNTOdnzcipqKxUB0Avc-wr8Fz0gjeRpLH2iDSCJWtvWjoeYvHTktGsblDAM5j9xznwEvZfQvj8fTUnFxl3clkD6e9V1jrDQDkXfOtl-bDIv9PtMwamfJFu-z2ubF-gKytUewPNo10uhwr2TDNdUayCZDR2T3HoRLN8goIw2bFoPJ98LoPcSukEvKABjH0DiHNeqFELNZPx_uCx5N-YFkUZxHWA1QUoGhqQ3REtcT3c-SZf_TDFOPvws6bmwt4lcWpLmubOAJLFt6J8m8HCkVUshRdFzvHQm_0JEvA3JtyXZzvsPUv5njdk0nxTZktvsnqX054RQk5x8U-lBY-bK3uMOoFnHju45LMoHCUgGJi22eUm7nLGZEh84ZAbNlPLXpfavXvPJh21OW5EOAeuQ-yWNHY2xbmAiHNnb-J2VpZc1Vy82sxn8umFtKduuQuIQMOsf4qHqj5MzDY_1NjrM4Wm7XAiLC4MpQ22w9PWNQXSZWvo2fj8WUnfEibpgyRkoD2P25GRQqsRJ3-Ykl5bm_2Vfe6i3oXHOwQwZwKGXfAqXyo4iU1UI7e-qC4sj5U64oB_A_NSBaJJrZoQ2fVeGTnFxA4QMMoWCT0VlwBXK0B3jht8Xal3WcI-i9ctQB1-GrmwwgG2ttePHt1IKy69bSZE3FLkFicaHg6VxypG6ef8rVsmMrfpTATOnF5_iEaLNY9428HHGW0iz4vXwaE-MkYy7NK2KMPFiCB0ec9OjIROwayK4LREv4qknWHnVQRSm25Rr9DcVFXKj16Au7X1hv7TuVH7h25U', + priv: '1X9VEr_iXMRwBvnSytEmHrtA-DpD6FWAUqMrDNlJVBg', + }, + }, + 'ML-DSA-87': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'public'), 'ascii')), + jwk: { + kty: 'AKP', + alg: 'ML-DSA-87', + pub: 'DZXqaBATRN0GRtigxzkLxp7C9fFYxI7Gl-tdfXqJHbzVCTTvRfRwZcu3YmpsUYXBdX2pVsQ51QlqxMslKBRmfNCanBLcfd57qoEIb0K6GIKZGHxlsr9aXNjEGcKMo0ICon0LYTvTWrl72Oz-2yEA_abPK3_dBUFGAYQ6kOQhAHcT1CMmTTck23PnEd5WUpYfZOA9giFX9dNVrrdFWczj_vDOty81ObNKsxfVWT1nG7c60UCJxb2c2tMrBx3rp7Hfc_aOg54W5KHocJi0Eai0ok4buySTe0UCSCUTkeoCcdiABgOFBiXRYzpm3Lz4uot6hSgFpuh67fE9Zpgtn64vfI-O1mgcPrPPpd3yA92Jrq-dvXuM55w1RmA_hha3U5Sh2vm0tD1U57q945UppFReIv_8NAKBkxQ_vHil7ySm-m7IAM-sTUY86_IqMZqisxoz7Ff7ZR2vIiUm3-L0ow4B8uPsCv2ZlUoVXvMF6XQiOHsgqgP1rfH8DmfmPFudwiXrAW6wEmi10skPmkN92aC3TPG6nmaNryQ7f8J82yVmGxW9U7zMbg21qNkRGBi_1YwEt6D8V2pUWv5U1a4p4-Ma0f4uQG4g0odM-WGomlh7pZWZf3sffiPXk9wBrGisxtCJuaB5vtkheWxpEfWqnhc3QdOWfrsRg6P1h7M95SNVW0U8A38wwrqPOpzEnckCVdCrZz2b2KVln6a4twfINg1-3lEZR4rkEmTaTYlLFlXzbRFWBBPGATxeRxhQ_9N5VhHi7STWPFD5HIJyVqz436bbVvM6Py_oldT_xt_tWlPc0w4Pesy2CgaCPlJCnx6cjEg_sRBUcRkBoHqa7aZj4JFFm9bzEaiJ2MKfkHVT4xdbEimMHsD0HkIQpg5-zoB2Jsqgc6Qi3L57hZi-Q1V0G2lmdZ5WZkQ2m5hxle4hHtAmghgynK2p0qzDWHScxHcdd2sInYqQgMYbnvs04YYKWpIfndCUBs9q_EONN5tn8gfSwHEKlQ-KpplEL5kc-99h2uaZsxRlJOF6_z8EZ-aKaKY8jvoAV0g4kZJH5UKy_MkBiva6r-zXUmo88qJjXQatOOSdfvJUTiZiSfcpBQqF9SSDD9WWsgInaOCKO_fAFf9fuacXDMEj0esUx1YrVEe_77S5uObg-UrK405U1JhKJJvd7o8xQKxenv5BJdsbbyYQDbSSe9BrCqeHEgmfRHTXdSvl_3QOP0Ej-dT8YJJHZ1lrujU7Zg5f5Kg99tU5GdLMbHX2kt4F2a0NX09HikEemvUg4NLPhjOihfVkChr-zdF69nfsnTiaQrMgpIcl9jttN99_8Gju-LU8OWbb92m9RLxAUFP115v22f77YPoILm92IjMZMkxEhGneoclWhnudkyR7YoTBjCnT5b7AC9_05uls637FmVf7Ck8-MF4gLil3dstXi4g24bitYhxxqWwiqF4vsDouSGUnuKCMwx3TLsII_xk77TjpQP4vpLdYM3tn94AVlTMMhnI-OZkVJk-_mIbywCwRHlQb5nzVCc0BWlM1kb9PJys2IfciS8LWEoxeq9moDX5w72yJKoLN3CWpD3VdJAiW79zUaySw-IeW0XaHnlze5fYnOozG8lIeyQ9sMZasMiFovGnR3b7jyMtA38U33v16fouWuBILOu0m_QOpRDI9i3rjRM6hdC48zCtNSzc1_1VPYkWDSFK1oVAjdd8-2rjyqdPeUwnqD26VA9_d3R7x8ThrazdbRC8U1hr9jpqNHuZ4LGYu3Ui8wB-lSt9QMaHz517MY_zBEoNGyvbQWtlM7mvLu12KoMM7nvGrPJnvD-HmxTqsVQolD8_lIV5ao72yiKDpArVr6RuV4PpI0j_Wy4-yDCuwBW0gjnB9GvCwOTeByYXJT6Ul7dgHck4BbF3IyFgvmY--ceWr5mBrbAC9LJP_4Wf5O6ul3hFrhiG6zSV4zzBYLnEwfW6LNLEZjZKgmBYiC5s1xlxYWDdcQ5FGmLQ9uEDkr4VItXQWvIIdeBQPyujxmd965Mig9-Sa7SCyV_3wH8fQnGlvU-jJMGL0zvzB2gcu7hMLMagUBj1AKXj-UxpbX1i95f2TOiZDwMeCCszgvCjQ21XKg07TBXrrOiFcgcADgdo-HJr7O1T3ozOIulq1PDM7QZH6i3wDD0j3b0NCdqKCWqhfLXz2-FszyUHmA_GCzOLVzrLT2DcGWIcQbkvF0yZPgTyqKArKa8qytOerdH6oCJ0bRl96855sMVjuUdyVLX7XW_rVTwsOwV0gVAx8SrzovtDFeHRNl7BQKMsyQ1BjWu25jqKJ598vAi3LCZv0kMdiC24qPdgZU4e2aUkco11EnD6nJgdqsVFxufCl4BD_D9g5Wy42fJt4ZgNPAcbUf341KERyReeBEQj-qlPB3IUTIXcJw68GScebhxb0W_tGKMBC6-ip4QfNW7UTxUxVxmCV7h0yRnBBlkuUR1eYQwWRmEPjKd3dLHvgHtr266NQmE1tnKtJlsdPKb0ztrI9vogsENsgGNFQ2tHoeX8vqxcagGznlPVPfc3DlqjBSeTFQaPWvmCQHVKgxkbvffKzFQyFvXEqt6bGGtkwBoRJ_IIwtSeWQ2nFPBe3rlyKrtSnQFIMibJbbYvPVE03Cld9R61r-GGDSQz4aXekLzePEVwnxpe4mWJGco7ctQyE73PekL1uo2g0bRK-KgaE878OiLRBo5T7c633xEf2hMy9532M2GVdTZuoE0LL-wpAh9GmNdvJZc7g2sINvwZi778v2WHcYEKqXvdmrX-Shyh3QkzgIGZrDzM4UlxxUWaXfZ0Z6PNguk7Jafqf3xuUe9Z8zfAJl5c_VA3k8dn7IRg99hRsh-TGBCzqzgjJq4p4XMWP2QuxFTGSgHRe2GSCFzd5-lPjrj76ZyT3MPUxQe_bV2VE-Oys3MT-VkCCM8jFCANXdrfltG6jSiUZ2uJUWNqdNnxPglmmyrgff_m-5CyIRWYXQsIdZGspqdjzb4F6RbBKfL2PQlM6zUfo9JNmE8YQq815Nxkex8vDOrImnew312fZA6rRjr9_uE4lEbw3U7PFlCKBUPvPnsdgedjKYhiS0xU6iS1NDKOvYhcrkCkiU67EmFD4U0-OCv9Kpbb5bIxTJuv405NxJBElAMVI-ya0ns7D4-xUPn05E7PhtGZT0eHwItjT6omThTsTHwB_bQqYfNrjrObO1l1go2hQ-cUadZYsG5l47CB5RlFhANtaC8tiq4KJi48TmEEApB_0VwOI4EmI7SR0oaqx3HRXZfeGevCx2yC9aCYM4HqcyqP2g_1HwsOYzwq4XDEbK5Yl1dtYABxPoo7t8FBq2sSmfrBJWFv_nvreb_DPwbfoSeCy9knqvOktSQRrPMmo-nNGpandBvjmrjSk3EdeziAP7XNre5I-bn_2voDxkzGFtUM-wzlL379ASRGej8FkNWaOyqGP6Anq5PSJ', + priv: 'LZSOlEPbU9S5_mSsMULffTyxZu6qKEOQ1nfEi2NCscg', + } + }, +}; +/* eslint-enable @stylistic/js/max-len */ + +const testVectors = [ + { + name: 'ML-DSA-44', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'ML-DSA-65', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'ML-DSA-87', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, +]; + +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); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + 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); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const pkcs8 = await subtle.exportKey('pkcs8', key); + assert.strictEqual( + Buffer.from(pkcs8).toString('hex'), + keyData[name].pkcs8_seed_only.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const pkcs8 = await subtle.exportKey('pkcs8', key); + assert.strictEqual( + Buffer.from(pkcs8).toString('hex'), + keyData[name].pkcs8_seed_only.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_priv_only, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { + assert.strictEqual(err.name, 'OperationError'); + assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); + assert.strictEqual(err.cause.message, 'key does not have an available seed'); + return true; + }); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportJwk({ name, publicUsages, privateUsages }, extractable) { + + const jwk = keyData[name].jwk; + + const tests = [ + subtle.importKey( + 'jwk', + { + kty: jwk.kty, + alg: jwk.alg, + pub: jwk.pub, + }, + { name }, + extractable, publicUsages), + subtle.importKey( + 'jwk', + jwk, + { name }, + extractable, + privateUsages), + ]; + + const [ + publicKey, + privateKey, + ] = await Promise.all(tests); + + 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); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + + 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, 'AKP'); + assert.strictEqual(pubJwk.pub, jwk.pub); + + assert.deepStrictEqual(pvtJwk.key_ops, privateUsages); + assert.strictEqual(pvtJwk.ext, true); + assert.strictEqual(pvtJwk.kty, 'AKP'); + assert.strictEqual(pvtJwk.pub, jwk.pub); + assert.strictEqual(pvtJwk.priv, jwk.priv); + + assert.strictEqual(pubJwk.alg, jwk.alg); + assert.strictEqual(pvtJwk.alg, jwk.alg); + } 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/ + }); + } + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, use: 'enc' }, + { name }, + extractable, + privateUsages), + { message: 'Invalid JWK "use" Parameter' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, pub: undefined }, + { name }, + extractable, + privateUsages), + { message: 'Invalid JWK' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, priv: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, // Public vs private mismatch + { name }, + extractable, + privateUsages), + { message: 'Invalid keyData' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, kty: 'OKP' }, + { name }, + extractable, + privateUsages), + { message: 'Invalid JWK "kty" Parameter' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk }, + { name }, + extractable, + publicUsages), // Invalid for a private key + { message: /Unsupported key usage/ }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, ext: false }, + { name }, + true, + privateUsages), + { message: 'JWK "ext" Parameter and extractable mismatch' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, priv: undefined }, + { name }, + extractable, + privateUsages), // Invalid for a public key + { message: /Unsupported key usage/ }); + + for (const alg of [undefined, name === 'ML-DSA-44' ? 'ML-DSA-87' : 'ML-DSA-44']) { + await assert.rejects( + subtle.importKey( + 'jwk', + { kty: jwk.kty, pub: jwk.pub, alg }, + { name }, + extractable, + publicUsages), + { message: 'JWK "alg" Parameter and algorithm name mismatch' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, alg }, + { name }, + extractable, + privateUsages), + { message: 'JWK "alg" Parameter and algorithm name mismatch' }); + } + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk }, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { kty: jwk.kty, /* missing pub */ alg: jwk.alg }, + { name }, + extractable, + publicUsages), + { name: 'DataError', message: 'Invalid keyData' }); +} + +async function testImportRawPublic({ name, publicUsages }, extractable) { + const jwk = keyData[name].jwk; + const pub = Buffer.from(jwk.pub, 'base64url'); + + const publicKey = await subtle.importKey( + 'raw-public', + pub, + { name }, + extractable, publicUsages); + + assert.strictEqual(publicKey.type, 'public'); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + assert.strictEqual(publicKey.extractable, extractable); + + if (extractable) { + const value = await subtle.exportKey('raw-public', publicKey); + assert.deepStrictEqual(Buffer.from(value), pub); + + await assert.rejects(subtle.exportKey('raw', publicKey), { + name: 'NotSupportedError', + message: `Unable to export ${publicKey.algorithm.name} public key using raw format`, + }); + } + + await assert.rejects( + subtle.importKey( + 'raw-public', + pub.subarray(0, pub.byteLength - 1), + { name }, + extractable, publicUsages), + { message: 'Invalid keyData' }); + + await assert.rejects( + subtle.importKey( + 'raw-public', + pub, + { name: name === 'ML-DSA-44' ? 'ML-DSA-65' : 'ML-DSA-44' }, + extractable, publicUsages), + { message: 'Invalid keyData' }); +} + +async function testImportRawSeed({ name, privateUsages }, extractable) { + const jwk = keyData[name].jwk; + const seed = Buffer.from(jwk.priv, 'base64url'); + + const privateKey = await subtle.importKey( + 'raw-seed', + seed, + { name }, + extractable, privateUsages); + + assert.strictEqual(privateKey.type, 'private'); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(privateKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(privateKey.extractable, extractable); + + if (extractable) { + const value = await subtle.exportKey('raw-seed', privateKey); + assert.deepStrictEqual(Buffer.from(value), seed); + } + + await assert.rejects( + subtle.importKey( + 'raw-seed', + seed.subarray(0, 30), + { name }, + extractable, + privateUsages), + { message: 'Invalid keyData' }); +} + +(async function() { + const tests = []; + for (const vector of testVectors) { + for (const extractable of [true, false]) { + tests.push(testImportSpki(vector, extractable)); + tests.push(testImportPkcs8(vector, extractable)); + tests.push(testImportPkcs8SeedOnly(vector, extractable)); + tests.push(testImportPkcs8PrivOnly(vector, extractable)); + tests.push(testImportJwk(vector, extractable)); + tests.push(testImportRawSeed(vector, extractable)); + tests.push(testImportRawPublic(vector, extractable)); + } + } + await Promise.all(tests); +})().then(common.mustCall()); + +(async function() { + const alg = 'ML-DSA-44'; + const pub = Buffer.from(keyData[alg].jwk.pub, 'base64url'); + await assert.rejects(subtle.importKey('raw', pub, alg, false, []), { + name: 'NotSupportedError', + message: 'Unable to import ML-DSA-44 using raw format', + }); +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-export-import-rsa.js b/test/parallel/test-webcrypto-export-import-rsa.js index f1bdaeed4dee8e..d3af8ec6c3adb9 100644 --- a/test/parallel/test-webcrypto-export-import-rsa.js +++ b/test/parallel/test-webcrypto-export-import-rsa.js @@ -19,6 +19,16 @@ const hashes = [ 'SHA-512', ]; +if (!process.features.openssl_is_boringssl) { + hashes.push( + 'SHA3-256', + 'SHA3-384', + 'SHA3-512', + ); +} else { + common.printSkipMessage('Skipping unsupported SHA-3 test cases'); +} + const keyData = { 1024: { spki: Buffer.from( @@ -387,13 +397,13 @@ async function testImportJwk( let alg; switch (name) { case 'RSA-PSS': - alg = `PS${hash === 'SHA-1' ? 1 : hash.substring(4)}`; + alg = hash.startsWith('SHA-') ? `PS${hash === 'SHA-1' ? 1 : hash.substring(4)}` : undefined; break; case 'RSA-OAEP': - alg = `RSA-OAEP${hash === 'SHA-1' ? '' : hash.substring(3)}`; + alg = hash.startsWith('SHA-') ? `RSA-OAEP${hash === 'SHA-1' ? '' : hash.substring(3)}` : undefined; break; case 'RSASSA-PKCS1-v1_5': - alg = `RS${hash === 'SHA-1' ? 1 : hash.substring(4)}`; + alg = hash.startsWith('SHA-') ? `RS${hash === 'SHA-1' ? 1 : hash.substring(4)}` : undefined; break; } @@ -497,7 +507,7 @@ async function testImportJwk( { message: 'Invalid JWK "use" Parameter' }); } - { + if (alg) { await assert.rejects( subtle.importKey( 'jwk', @@ -516,7 +526,7 @@ async function testImportJwk( { message: 'JWK "alg" does not match the requested algorithm' }); } - { + if (!hash.startsWith('SHA3-')) { let invalidAlgHash = name === 'RSA-OAEP' ? name : name === 'RSA-PSS' ? 'PS' : 'RS'; switch (name) { case 'RSA-OAEP': @@ -547,7 +557,7 @@ async function testImportJwk( { message: 'JWK "alg" does not match the requested algorithm' }); } - { + if (!hash.startsWith('SHA3-')) { const invalidAlgType = name === 'RSA-PSS' ? `RS${hash.substring(4)}` : `PS${hash.substring(4)}`; await assert.rejects( subtle.importKey( diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index bd0cd056d46741..a93afadcf914cc 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -1,12 +1,14 @@ 'use strict'; const common = require('../common'); +const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); const { subtle } = globalThis.crypto; +const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto'); { async function test() { @@ -291,3 +293,41 @@ const { subtle } = globalThis.crypto; test().then(common.mustCall()); } + +// SHA-3 hashes and JWK "alg" +if (!process.features.openssl_is_boringssl) { + const rsa = fixtures.readKey('rsa_private_2048.pem'); + const privateKey = createPrivateKey(rsa); + const publicKey = createPublicKey(privateKey); + + async function test(keyObject, algorithm, usages) { + const key = keyObject.toCryptoKey(algorithm, true, usages); + const jwk = await subtle.exportKey('jwk', key); + assert.strictEqual(jwk.alg, undefined); + } + + for (const hash of ['SHA3-256', 'SHA3-384', 'SHA3-512']) { + for (const name of ['RSA-OAEP', 'RSA-PSS', 'RSASSA-PKCS1-v1_5']) { + test(publicKey, { name, hash }, []).then(common.mustCall()); + test(privateKey, { name, hash }, [name === 'RSA-OAEP' ? 'unwrapKey' : 'sign']).then(common.mustCall()); + } + + test(createSecretKey(Buffer.alloc(32)), { name: 'HMAC', hash }, ['sign']); + } + + { + const jwk = createSecretKey(Buffer.alloc(16)).export({ format: 'jwk' }); + // This is rejected for SHA-2 but ignored for SHA-3 + // Otherwise, if the name attribute of hash is defined in another applicable specification: + // Perform any key import steps defined by other applicable specifications, passing format, + // jwk and hash and obtaining hash. + jwk.alg = 'HS3-256'; + + assert.rejects(subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']), { + name: 'DataError', + message: 'JWK "alg" does not match the requested algorithm', + }).then(common.mustCall()); + + subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA3-256' }, false, ['sign', 'verify']).then(common.mustCall()); + } +} diff --git a/test/parallel/test-webcrypto-get-public-key.mjs b/test/parallel/test-webcrypto-get-public-key.mjs new file mode 100644 index 00000000000000..dda8d7e8d58802 --- /dev/null +++ b/test/parallel/test-webcrypto-get-public-key.mjs @@ -0,0 +1,51 @@ +import * as common from '../common/index.mjs'; + +if (!common.hasCrypto) common.skip('missing crypto'); + +import * as assert from 'node:assert'; +const { subtle } = globalThis.crypto; + +const RSA_KEY_GEN = { + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', +}; + +const publicUsages = { + 'ECDH': [], + 'ECDSA': ['verify'], + 'Ed25519': ['verify'], + 'RSA-OAEP': ['encrypt', 'wrapKey'], + 'RSA-PSS': ['verify'], + 'RSASSA-PKCS1-v1_5': ['verify'], + 'X25519': [], +}; + +for await (const { privateKey } of [ + subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits']), + subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']), + subtle.generateKey('Ed25519', false, ['sign']), + subtle.generateKey({ name: 'RSA-OAEP', ...RSA_KEY_GEN }, false, ['decrypt', 'unwrapKey']), + subtle.generateKey({ name: 'RSA-PSS', ...RSA_KEY_GEN }, false, ['sign']), + subtle.generateKey({ name: 'RSASSA-PKCS1-v1_5', ...RSA_KEY_GEN }, false, ['sign']), + subtle.generateKey('X25519', false, ['deriveBits']), +]) { + const { name } = privateKey.algorithm; + const usages = publicUsages[name]; + const publicKey = await subtle.getPublicKey(privateKey, usages); + assert.deepStrictEqual(publicKey.algorithm, privateKey.algorithm); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.extractable, true); + + await assert.rejects(() => subtle.getPublicKey(privateKey, ['deriveBits']), { + name: 'SyntaxError', + message: /Unsupported key usage/ + }); +} + +const secretKey = await subtle.generateKey( + { name: 'AES-CBC', length: 128 }, true, ['encrypt', 'decrypt']); +await assert.rejects(() => subtle.getPublicKey(secretKey, ['encrypt', 'decrypt']), { + name: 'InvalidAccessError', + message: 'key must be a private key' +}); diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index a60463bdb5f139..95be384c16b046 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -6,6 +6,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { types: { isCryptoKey } } = require('util'); const { @@ -57,14 +59,6 @@ const vectors = { 'unwrapKey', ], }, - 'AES-KW': { - algorithm: { length: 256 }, - result: 'CryptoKey', - usages: [ - 'wrapKey', - 'unwrapKey', - ], - }, 'HMAC': { algorithm: { length: 256, hash: 'SHA-256' }, result: 'CryptoKey', @@ -134,13 +128,6 @@ const vectors = { 'verify', ], }, - 'Ed448': { - result: 'CryptoKeyPair', - usages: [ - 'sign', - 'verify', - ], - }, 'X25519': { result: 'CryptoKeyPair', usages: [ @@ -148,14 +135,55 @@ const vectors = { 'deriveBits', ], }, - 'X448': { +}; + +if (!process.features.openssl_is_boringssl) { + vectors.Ed448 = { + result: 'CryptoKeyPair', + usages: [ + 'sign', + 'verify', + ], + }; + vectors.X448 = { result: 'CryptoKeyPair', usages: [ 'deriveKey', 'deriveBits', ], - }, -}; + }; + vectors['AES-KW'] = { + algorithm: { length: 256 }, + result: 'CryptoKey', + usages: [ + 'wrapKey', + 'unwrapKey', + ], + }; + vectors['ChaCha20-Poly1305'] = { + result: 'CryptoKey', + usages: [ + 'encrypt', + 'decrypt', + 'wrapKey', + 'unwrapKey', + ], + }; +} else { + common.printSkipMessage('Skipping unsupported test cases'); +} + +if (hasOpenSSL(3, 5)) { + for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { + vectors[name] = { + result: 'CryptoKeyPair', + usages: [ + 'sign', + 'verify', + ], + }; + } +} // Test invalid algorithms { @@ -163,10 +191,7 @@ const vectors = { return assert.rejects( // The extractable and usages values are invalid here also, // but the unrecognized algorithm name should be caught first. - subtle.generateKey(algorithm, 7, []), { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }); + subtle.generateKey(algorithm, 7, []), { name: 'NotSupportedError' }); } const tests = [ @@ -359,10 +384,7 @@ const vectors = { modulusLength, publicExponent, hash - }, true, usages), { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }); + }, true, usages), { name: 'NotSupportedError' }); })); await Promise.all(['', {}, 1, false].map((usages) => { @@ -393,28 +415,36 @@ const vectors = { 'RSASSA-PKCS1-v1_5', 1024, Buffer.from([1, 0, 1]), - 'SHA-256', + 'SHA-1', ['sign'], ['verify'], ], [ 'RSA-PSS', - 2048, + 1024, Buffer.from([1, 0, 1]), - 'SHA-512', + 'SHA-256', ['sign'], ['verify'], ], - [ - 'RSA-OAEP', - 1024, - Buffer.from([3]), - 'SHA-384', - ['decrypt', 'unwrapKey'], - ['encrypt', 'wrapKey'], - ], ]; + + if (!process.features.openssl_is_boringssl) { + kTests.push( + [ + 'RSA-OAEP', + 1024, + Buffer.from([3]), + 'SHA3-256', + ['decrypt', 'unwrapKey'], + ['encrypt', 'wrapKey'], + ], + ); + } else { + common.printSkipMessage('Skipping unsupported SHA-3 test case'); + } + const tests = kTests.map((args) => test(...args)); Promise.all(tests).then(common.mustCall()); @@ -547,10 +577,17 @@ const vectors = { [ 'AES-CBC', 256, ['encrypt', 'decrypt']], [ 'AES-GCM', 128, ['encrypt', 'decrypt']], [ 'AES-GCM', 256, ['encrypt', 'decrypt']], - [ 'AES-KW', 128, ['wrapKey', 'unwrapKey']], - [ 'AES-KW', 256, ['wrapKey', 'unwrapKey']], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + [ 'AES-KW', 128, ['wrapKey', 'unwrapKey']], + [ 'AES-KW', 256, ['wrapKey', 'unwrapKey']], + ); + } else { + common.printSkipMessage('Skipping unsupported AES-KW test cases'); + } + const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); @@ -571,6 +608,9 @@ const vectors = { case 'SHA-256': length = 512; break; case 'SHA-384': length = 1024; break; case 'SHA-512': length = 1024; break; + case 'SHA3-256': length = 1088; break; + case 'SHA3-384': length = 832; break; + case 'SHA3-512': length = 576; break; } } @@ -590,7 +630,6 @@ const vectors = { [1, false, null].forEach(async (hash) => { await assert.rejects( subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }); }); @@ -605,6 +644,17 @@ const vectors = { [ 1024, 'SHA-512', ['sign', 'verify']], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + + [ undefined, 'SHA3-256', ['sign', 'verify']], + [ undefined, 'SHA3-384', ['sign', 'verify']], + [ undefined, 'SHA3-512', ['sign', 'verify']], + ); + } else { + common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + } + const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); @@ -662,24 +712,74 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); ['sign'], ['verify'], ], - [ - 'Ed448', - ['sign'], - ['verify'], - ], [ 'X25519', ['deriveKey', 'deriveBits'], [], ], - [ - 'X448', - ['deriveKey', 'deriveBits'], - [], - ], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + [ + 'Ed448', + ['sign'], + ['verify'], + ], + [ + 'X448', + ['deriveKey', 'deriveBits'], + [], + ], + ); + } else { + common.printSkipMessage('Skipping unsupported Curve448 test cases'); + } + const tests = kTests.map((args) => test(...args)); Promise.all(tests).then(common.mustCall()); } + +// Test ML-DSA Key Generation +if (hasOpenSSL(3, 5)) { + 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.toString(), '[object CryptoKey]'); + assert.strictEqual(privateKey.toString(), '[object CryptoKey]'); + 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); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + } + + const kTests = ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']; + + const tests = kTests.map((name) => test(name, ['sign'], ['verify'])); + + Promise.all(tests).then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-sign-verify-eddsa.js b/test/parallel/test-webcrypto-sign-verify-eddsa.js index 75d73a49ddea36..87f562043ad2ca 100644 --- a/test/parallel/test-webcrypto-sign-verify-eddsa.js +++ b/test/parallel/test-webcrypto-sign-verify-eddsa.js @@ -219,7 +219,7 @@ async function testSign({ name, })().then(common.mustCall()); // Ed448 context -{ +if (!process.features.openssl_is_boringssl) { const vector = vectors.find(({ name }) => name === 'Ed448'); Promise.all([ subtle.importKey( @@ -241,10 +241,12 @@ async function testSign({ name, 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 supported/ + message: /Non zero-length Ed448Params\.context is not supported/ }); await assert.rejects(subtle.verify({ name: 'Ed448', context: Buffer.alloc(1) }, publicKey, sig, vector.data), { - message: /Non zero-length context is not supported/ + message: /Non zero-length Ed448Params\.context is not supported/ }); }).then(common.mustCall()); +} else { + common.printSkipMessage('Skipping unsupported Ed448 test case'); } diff --git a/test/parallel/test-webcrypto-sign-verify-ml-dsa.js b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js new file mode 100644 index 00000000000000..5bd21f9c2735b4 --- /dev/null +++ b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js @@ -0,0 +1,228 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 5)) + common.skip('requires OpenSSL >= 3.5'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { subtle } = globalThis.crypto; + +const vectors = require('../fixtures/crypto/ml-dsa')(); + +async function testVerify({ name, + publicKeyPem, + privateKeyPem, + signature, + data }) { + const [ + publicKey, + noVerifyPublicKey, + privateKey, + hmacKey, + rsaKeys, + ecKeys, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['verify']), + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, [ /* No usages */ ]), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(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, + publicKeyPem, + privateKeyPem, + signature, + data }) { + const [ + publicKey, + privateKey, + hmacKey, + rsaKeys, + ecKeys, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['verify']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(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 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()); + +// ContextParams context not supported +{ + const vector = vectors[0]; + const name = vector.name; + const publicKey = crypto.createPublicKey(vector.publicKeyPem) + .toCryptoKey(vector.name, false, ['verify']); + const privateKey = crypto.createPrivateKey(vector.privateKeyPem) + .toCryptoKey(vector.name, false, ['sign']); + + (async () => { + const sig = await subtle.sign({ name, context: Buffer.alloc(0) }, privateKey, vector.data); + assert.strictEqual( + await subtle.verify({ name, context: Buffer.alloc(0) }, publicKey, sig, vector.data), true); + + await assert.rejects(subtle.sign({ name, context: Buffer.alloc(1) }, privateKey, vector.data), { + message: /Non zero-length ContextParams\.context is not supported/ + }); + await assert.rejects(subtle.verify({ name, context: Buffer.alloc(1) }, publicKey, sig, vector.data), { + message: /Non zero-length ContextParams\.context is not supported/ + }); + })().then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-sign-verify-rsa.js b/test/parallel/test-webcrypto-sign-verify-rsa.js index ef9f6e8bd45d72..7e90388cc4c270 100644 --- a/test/parallel/test-webcrypto-sign-verify-rsa.js +++ b/test/parallel/test-webcrypto-sign-verify-rsa.js @@ -236,8 +236,18 @@ async function testSaltLength(keyLength, hash, hLen) { }); for (const keyLength of [1024, 2048]) { - for (const [hash, hLen] of [['SHA-1', 20], ['SHA-256', 32], ['SHA-384', 48], ['SHA-512', 64]]) { - variations.push(testSaltLength(keyLength, hash, hLen)); + for (const [hash, hLen] of [ + ['SHA-1', 20], + ['SHA-256', 32], + ['SHA-384', 48], + ['SHA-512', 64], + ['SHA3-256', 32], + ['SHA3-384', 48], + ['SHA3-512', 64], + ]) { + if (hash.startsWith('SHA-3') && !process.features.openssl_is_boringssl) { + variations.push(testSaltLength(keyLength, hash, hLen)); + } } } diff --git a/test/parallel/test-webcrypto-sign-verify.js b/test/parallel/test-webcrypto-sign-verify.js index 1b2b40152f88d2..a43123ce294d27 100644 --- a/test/parallel/test-webcrypto-sign-verify.js +++ b/test/parallel/test-webcrypto-sign-verify.js @@ -5,6 +5,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { subtle } = globalThis.crypto; @@ -121,13 +123,11 @@ const { subtle } = globalThis.crypto; name: 'Ed25519', }, publicKey, signature, ec.encode(data))); } - if (!process.features.openssl_is_boringssl) { - test('hello world').then(common.mustCall()); - } + test('hello world').then(common.mustCall()); } // Test Sign/Verify Ed448 -{ +if (!process.features.openssl_is_boringssl) { async function test(data) { const ec = new TextEncoder(); const { publicKey, privateKey } = await subtle.generateKey({ @@ -143,7 +143,29 @@ const { subtle } = globalThis.crypto; }, publicKey, signature, ec.encode(data))); } - if (!process.features.openssl_is_boringssl) { - test('hello world').then(common.mustCall()); + test('hello world').then(common.mustCall()); +} else { + common.printSkipMessage('Skipping unsupported Ed448 test case'); +} + +// Test Sign/Verify ML-DSA +if (hasOpenSSL(3, 5)) { + async function test(name, data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name, + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name, + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name, + }, publicKey, signature, ec.encode(data))); } + + test('ML-DSA-44', 'hello world').then(common.mustCall()); + test('ML-DSA-65', 'hello world').then(common.mustCall()); + test('ML-DSA-87', 'hello world').then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-supports.mjs b/test/parallel/test-webcrypto-supports.mjs new file mode 100644 index 00000000000000..e2fd6f77b83749 --- /dev/null +++ b/test/parallel/test-webcrypto-supports.mjs @@ -0,0 +1,55 @@ +import * as common from '../common/index.mjs'; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +import * as assert from 'node:assert'; +const { SubtleCrypto } = globalThis; + +const sources = [ + import('../fixtures/webcrypto/supports-level-2.mjs'), + import('../fixtures/webcrypto/supports-secure-curves.mjs'), + import('../fixtures/webcrypto/supports-modern-algorithms.mjs'), + import('../fixtures/webcrypto/supports-sha3.mjs'), +]; + +const vectors = {}; + +for await (const mod of sources) { + for (const op of Object.keys(mod.vectors)) { + vectors[op] ||= []; + vectors[op] = vectors[op].concat(mod.vectors[op]); + } +} + +vectors.verify = vectors.sign; +vectors.decrypt = vectors.encrypt; + +for (const enc of vectors.encrypt) { + for (const exp of vectors.exportKey) { + vectors.wrapKey.push([enc[0] && exp[0], enc[1], exp[1]]); + } +} + +for (const dec of vectors.decrypt) { + for (const imp of vectors.importKey) { + vectors.unwrapKey.push([dec[0] && imp[0], dec[1], imp[1]]); + } +} + +for (const exportKey of vectors.exportKey) { + if (!exportKey[0]) vectors.getPublicKey.push(exportKey); +} + +for (const operation of Object.keys(vectors)) { + for (const [expectation, ...args] of vectors[operation]) { + assert.strictEqual( + SubtleCrypto.supports(operation, ...args), + expectation, + new Error( + `expected ${expectation}, got ${!expectation}`, + { cause: { operation, args } } + ) + ); + } +} diff --git a/test/parallel/test-webcrypto-webidl.js b/test/parallel/test-webcrypto-webidl.js index e1675fe5c4e558..107a28adb766be 100644 --- a/test/parallel/test-webcrypto-webidl.js +++ b/test/parallel/test-webcrypto-webidl.js @@ -462,19 +462,19 @@ const opts = { prefix, context }; }); } -// AesGcmParams +// AeadParams { for (const good of [ { name: 'AES-GCM', iv: Buffer.alloc(0) }, { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 64 }, { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 64, additionalData: Buffer.alloc(0) }, ]) { - assert.deepStrictEqual(converters.AesGcmParams({ ...good, filtered: 'out' }, opts), good); + assert.deepStrictEqual(converters.AeadParams({ ...good, filtered: 'out' }, opts), good); - assert.throws(() => converters.AesGcmParams({ ...good, iv: undefined }, opts), { + assert.throws(() => converters.AeadParams({ ...good, iv: undefined }, opts), { name: 'TypeError', code: 'ERR_MISSING_OPTION', - message: `${prefix}: ${context} can not be converted to 'AesGcmParams' because 'iv' is required in 'AesGcmParams'.`, + message: `${prefix}: ${context} can not be converted to 'AeadParams' because 'iv' is required in 'AeadParams'.`, }); } } diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js index d1ca571af4be71..6a504692ec53ed 100644 --- a/test/parallel/test-webcrypto-wrap-unwrap.js +++ b/test/parallel/test-webcrypto-wrap-unwrap.js @@ -5,6 +5,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { subtle } = globalThis.crypto; @@ -37,12 +39,25 @@ const kWrappingData = { }, pair: false }, - 'AES-KW': { +}; + +if (!process.features.openssl_is_boringssl) { + kWrappingData['AES-KW'] = { generate: { length: 128 }, wrap: { }, pair: false - } -}; + }; + kWrappingData['ChaCha20-Poly1305'] = { + wrap: { + iv: new Uint8Array(12), + additionalData: new Uint8Array(16), + tagLength: 128 + }, + pair: false + }; +} else { + common.printSkipMessage('Skipping unsupported AES-KW test case'); +} function generateWrappingKeys() { return Promise.all(Object.keys(kWrappingData).map(async (name) => { @@ -121,14 +136,6 @@ async function generateKeysToWrap() { publicUsages: ['verify'], pair: true, }, - { - algorithm: { - name: 'Ed448', - }, - privateUsages: ['sign'], - publicUsages: ['verify'], - pair: true, - }, { algorithm: { name: 'X25519', @@ -137,14 +144,6 @@ async function generateKeysToWrap() { publicUsages: [], pair: true, }, - { - algorithm: { - name: 'X448', - }, - privateUsages: ['deriveBits'], - publicUsages: [], - pair: true, - }, { algorithm: { name: 'AES-CTR', @@ -170,10 +169,9 @@ async function generateKeysToWrap() { }, { algorithm: { - name: 'AES-KW', - length: 128 + name: 'ChaCha20-Poly1305' }, - usages: ['wrapKey', 'unwrapKey'], + usages: ['encrypt', 'decrypt'], pair: false, }, { @@ -187,6 +185,53 @@ async function generateKeysToWrap() { }, ]; + if (!process.features.openssl_is_boringssl) { + parameters.push({ + algorithm: { + name: 'AES-KW', + length: 128 + }, + usages: ['wrapKey', 'unwrapKey'], + pair: false, + }); + } else { + common.printSkipMessage('Skipping unsupported AES-KW test case'); + } + + if (hasOpenSSL(3, 5)) { + for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { + parameters.push({ + algorithm: { name }, + privateUsages: ['sign'], + publicUsages: ['verify'], + pair: true, + }); + } + } + + if (!process.features.openssl_is_boringssl) { + parameters.push( + { + algorithm: { + name: 'Ed448', + }, + privateUsages: ['sign'], + publicUsages: ['verify'], + pair: true, + }, + { + algorithm: { + name: 'X448', + }, + privateUsages: ['deriveBits'], + publicUsages: [], + pair: true, + }, + ); + } else { + common.printSkipMessage('Skipping unsupported Curve test cases'); + } + const allkeys = await Promise.all(parameters.map(async (params) => { const usages = 'usages' in params ? params.usages : @@ -220,10 +265,29 @@ async function generateKeysToWrap() { } function getFormats(key) { - switch (key.key.type) { - case 'secret': return ['raw', 'jwk']; - case 'public': return ['spki', 'jwk']; - case 'private': return ['pkcs8', 'jwk']; + switch (key.type) { + case 'secret': { + if (key.algorithm.name === 'ChaCha20-Poly1305') return ['raw-secret', 'jwk']; + return ['raw-secret', 'raw', 'jwk']; + }; + case 'public': { + switch (key.algorithm.name.slice(0, 2)) { + case 'EC': // ECDSA, ECDH + return ['spki', 'jwk', 'raw', 'raw-public']; + case 'ML': // ML-DSA + return ['jwk', 'raw-public']; + default: + return ['spki', 'jwk']; + } + } + case 'private': { + switch (key.algorithm.name.slice(0, 2)) { + case 'ML': // ML-DSA + return ['jwk', 'raw-seed']; + default: + return ['pkcs8', 'jwk']; + } + } } } @@ -285,7 +349,7 @@ function testWrapping(name, keys) { } = kWrappingData[name]; keys.forEach((key) => { - getFormats(key).forEach((format) => { + getFormats(key.key).forEach((format) => { variations.push(testWrap(wrappingKey, unwrappingKey, key, wrap, format)); }); }); diff --git a/test/pummel/test-webcrypto-derivebits-pbkdf2.js b/test/pummel/test-webcrypto-derivebits-pbkdf2.js index 28c6fe871c578b..5db210b899efea 100644 --- a/test/pummel/test-webcrypto-derivebits-pbkdf2.js +++ b/test/pummel/test-webcrypto-derivebits-pbkdf2.js @@ -483,7 +483,6 @@ async function testDeriveBitsBadHash( ...algorithm, hash: hash.substring(0, 3) + hash.substring(4), }, baseKeys[size], 256), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }), assert.rejects( @@ -493,7 +492,6 @@ async function testDeriveBitsBadHash( hash: 'HKDF', }, baseKeys[size], 256), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }), ]); @@ -571,10 +569,7 @@ async function testDeriveKeyBadHash( keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), assert.rejects( subtle.deriveKey( { @@ -585,10 +580,7 @@ async function testDeriveKeyBadHash( keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), ]); } diff --git a/tools/doc/type-parser.mjs b/tools/doc/type-parser.mjs index 206875917c1958..60e7c3df2d3415 100644 --- a/tools/doc/type-parser.mjs +++ b/tools/doc/type-parser.mjs @@ -98,7 +98,7 @@ const customTypesMap = { 'AesCtrParams': 'webcrypto.html#class-aesctrparams', 'AesCbcParams': 'webcrypto.html#class-aescbcparams', 'AesDerivedKeyParams': 'webcrypto.html#class-aesderivedkeyparams', - 'AesGcmParams': 'webcrypto.html#class-aesgcmparams', + 'AeadParams': 'webcrypto.html#class-aeadparams', 'EcdhKeyDeriveParams': 'webcrypto.html#class-ecdhkeyderiveparams', 'HkdfParams': 'webcrypto.html#class-hkdfparams', 'KeyAlgorithm': 'webcrypto.html#class-keyalgorithm', @@ -120,6 +120,8 @@ const customTypesMap = { 'EcdsaParams': 'webcrypto.html#class-ecdsaparams', 'RsaPssParams': 'webcrypto.html#class-rsapssparams', 'Ed448Params': 'webcrypto.html#class-ed448params', + 'ContextParams': 'webcrypto.html#class-contextparams', + 'CShakeParams': 'webcrypto.html#class-cshakeparams', 'dgram.Socket': 'dgram.html#class-dgramsocket',