mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
crypto: add SubtleCrypto.supports feature detection in Web Cryptography
PR-URL: https://github.com/nodejs/node/pull/59365 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
9 files changed
+598
No files matched your search
@@ -102,6 +102,10 @@ Key Formats:
|
||||
* `'raw-secret'`
|
||||
* `'raw-seed'`
|
||||
|
||||
Methods:
|
||||
|
||||
* [`SubtleCrypto.supports()`][]
|
||||
|
||||
## Secure Curves in the Web Cryptography API
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
@@ -388,6 +392,76 @@ async function digest(data, algorithm = 'SHA-512') {
|
||||
}
|
||||
```
|
||||
|
||||
### Checking for runtime algorithm support
|
||||
|
||||
[`SubtleCrypto.supports()`][] 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.
|
||||
|
||||
This example derives a key from a password using Argon2, if available,
|
||||
or PBKDF2, otherwise; and then encrypts and decrypts some text with it
|
||||
using AES-OCB, if available, and AES-GCM, otherwise.
|
||||
|
||||
```mjs
|
||||
const { SubtleCrypto, crypto } = globalThis;
|
||||
|
||||
const password = 'correct horse battery staple';
|
||||
const derivationAlg =
|
||||
SubtleCrypto.supports?.('importKey', 'Argon2id') ?
|
||||
'Argon2id' :
|
||||
'PBKDF2';
|
||||
const encryptionAlg =
|
||||
SubtleCrypto.supports?.('importKey', 'AES-OCB') ?
|
||||
'AES-OCB' :
|
||||
'AES-GCM';
|
||||
const passwordKey = await crypto.subtle.importKey(
|
||||
derivationAlg === 'Argon2id' ? 'raw-secret' : 'raw',
|
||||
new TextEncoder().encode(password),
|
||||
derivationAlg,
|
||||
false,
|
||||
['deriveKey'],
|
||||
);
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(16));
|
||||
const derivationParams =
|
||||
derivationAlg === 'Argon2id' ?
|
||||
{
|
||||
nonce,
|
||||
parallelism: 4,
|
||||
memory: 2 ** 21,
|
||||
passes: 1,
|
||||
} :
|
||||
{
|
||||
salt: nonce,
|
||||
iterations: 100_000,
|
||||
hash: 'SHA-256',
|
||||
};
|
||||
const key = await crypto.subtle.deriveKey(
|
||||
{
|
||||
name: derivationAlg,
|
||||
...derivationParams,
|
||||
},
|
||||
passwordKey,
|
||||
{
|
||||
name: encryptionAlg,
|
||||
length: 256,
|
||||
},
|
||||
false,
|
||||
['encrypt', 'decrypt'],
|
||||
);
|
||||
const plaintext = 'Hello, world!';
|
||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||
const encrypted = await crypto.subtle.encrypt(
|
||||
{ name: encryptionAlg, iv },
|
||||
key,
|
||||
new TextEncoder().encode(plaintext),
|
||||
);
|
||||
const decrypted = new TextDecoder().decode(await crypto.subtle.decrypt(
|
||||
{ name: encryptionAlg, iv },
|
||||
key,
|
||||
encrypted,
|
||||
));
|
||||
```
|
||||
|
||||
## Algorithm matrix
|
||||
|
||||
The table details the algorithms supported by the Node.js Web Crypto API
|
||||
@@ -592,6 +666,27 @@ added: v15.0.0
|
||||
added: v15.0.0
|
||||
-->
|
||||
|
||||
### Static method: `SubtleCrypto.supports(operation, algorithm[, lengthOrAdditionalAlgorithm])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
<!--lint disable maximum-line-length remark-lint-->
|
||||
|
||||
* `operation` {string} "encrypt", "decrypt", "sign", "verify", "digest", "generateKey", "deriveKey", "deriveBits", "importKey", "exportKey", "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
|
||||
|
||||
<!--lint enable maximum-line-length remark-lint-->
|
||||
|
||||
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)`
|
||||
|
||||
<!-- YAML
|
||||
@@ -1923,3 +2018,4 @@ The length (in bytes) of the random salt to use.
|
||||
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
|
||||
[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
|
||||
@@ -170,4 +170,5 @@ module.exports = {
|
||||
hkdf,
|
||||
hkdfSync,
|
||||
hkdfDeriveBits,
|
||||
validateHkdfDeriveBitsLength,
|
||||
};
|
||||
@@ -128,4 +128,5 @@ module.exports = {
|
||||
pbkdf2,
|
||||
pbkdf2Sync,
|
||||
pbkdf2DeriveBits,
|
||||
validatePbkdf2DeriveBitsLength,
|
||||
};
|
||||
@@ -47,6 +47,7 @@ const {
|
||||
} = require('internal/crypto/util');
|
||||
|
||||
const {
|
||||
emitExperimentalWarning,
|
||||
kEnumerableProperty,
|
||||
lazyDOMException,
|
||||
} = require('internal/util');
|
||||
@@ -1026,7 +1027,147 @@ 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':
|
||||
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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
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']),
|
||||
]);
|
||||
|
||||
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 }],
|
||||
[true, { 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'],
|
||||
[true, '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'],
|
||||
[true, 'AES-KW'],
|
||||
[true, 'Ed25519'],
|
||||
[true, 'X25519'],
|
||||
],
|
||||
'wrapKey': [
|
||||
[false, 'AES-KW'],
|
||||
[true, 'AES-KW', 'AES-CTR'],
|
||||
[true, 'AES-KW', 'HMAC'],
|
||||
],
|
||||
'unwrapKey': [
|
||||
[false, 'AES-KW'],
|
||||
[true, 'AES-KW', 'AES-CTR'],
|
||||
],
|
||||
'unsupported operation': [
|
||||
[false, ''],
|
||||
[false, 'Ed25519'],
|
||||
],
|
||||
'get key length': [
|
||||
[false, { name: 'HMAC', hash: 'SHA-256' }],
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { hasOpenSSL } from '../../common/crypto.js'
|
||||
|
||||
const pqc = hasOpenSSL(3, 5);
|
||||
|
||||
export const vectors = {
|
||||
'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'],
|
||||
],
|
||||
'importKey': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
[pqc, 'ML-DSA-65'],
|
||||
[pqc, 'ML-DSA-87'],
|
||||
],
|
||||
'exportKey': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
[pqc, 'ML-DSA-65'],
|
||||
[pqc, 'ML-DSA-87'],
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
const { subtle } = globalThis.crypto;
|
||||
|
||||
const [X448, X25519] = await Promise.all([
|
||||
subtle.generateKey('X448', false, ['deriveBits', 'deriveKey']),
|
||||
subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']),
|
||||
]);
|
||||
|
||||
export const vectors = {
|
||||
'generateKey': [
|
||||
[true, 'X448'],
|
||||
[true, 'Ed448'],
|
||||
],
|
||||
'deriveKey': [
|
||||
[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'],
|
||||
],
|
||||
'deriveBits': [
|
||||
[true, { name: 'X448', public: X448.publicKey }],
|
||||
[false, { name: 'X448', public: X25519.publicKey }],
|
||||
[false, { name: 'X448', public: X448.privateKey }],
|
||||
[false, 'X448'],
|
||||
],
|
||||
'importKey': [
|
||||
[true, 'X448'],
|
||||
[true, 'Ed448'],
|
||||
],
|
||||
'exportKey': [
|
||||
[true, 'Ed448'],
|
||||
[true, 'X448'],
|
||||
],
|
||||
};
|
||||
@@ -137,6 +137,13 @@ const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto);
|
||||
}).then(common.mustCall());
|
||||
}
|
||||
|
||||
// Test SubtleCrypto.supports
|
||||
{
|
||||
assert.throws(() => SubtleCrypto.supports.call(undefined), {
|
||||
name: 'TypeError', code: 'ERR_INVALID_THIS',
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
subtle.importKey(
|
||||
'raw',
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
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'),
|
||||
];
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
if (vectors.sign) vectors.verify = vectors.sign;
|
||||
if (vectors.encrypt) vectors.decrypt = vectors.encrypt;
|
||||
|
||||
if (vectors.encrypt && vectors.exportKey) {
|
||||
for (const enc of vectors.encrypt) {
|
||||
for (const exp of vectors.exportKey) {
|
||||
vectors.wrapKey.push([enc[0] && exp[0], enc[1], exp[1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vectors.decrypt && vectors.importKey) {
|
||||
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 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 } }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user