mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
WebCrypto: the crypto global, node:crypto's webcrypto, and secret-key SubtleCrypto
This commit is contained in:
@@ -16,4 +16,7 @@
|
||||
A call on an ECDH value whose arguments no @types/node overload accepts (`getPublicKey("HEX")`) is refused at compile time, as is `computeSecret(view, encoding)`: Node reads that encoding as the input encoding and answers a Buffer where @types/node declares a string.
|
||||
ECDH property reads and writes throw a coded SC2020 error.
|
||||
Other public-key operations remain unsupported.
|
||||
The WebCrypto `crypto` global, which node:crypto also exports as `webcrypto`, supports `randomUUID()` and `getRandomValues()`, also read as values from untyped JavaScript.
|
||||
Its `subtle` object, also node:crypto's `subtle` export, supports `generateKey()` and `exportKey()` for HMAC and AES keys, `importKey()` for HMAC, AES, PBKDF2, and HKDF keys, `encrypt()` and `decrypt()` with AES-GCM, AES-CBC, and AES-CTR on native targets, `deriveBits()` and `deriveKey()` with PBKDF2 and HKDF, HMAC `sign()` and `verify()`, and SHA-1 and SHA-2 `digest()`.
|
||||
Secret keys import and export in the `raw`, `raw-secret`, and `jwk` formats; other SubtleCrypto methods, algorithms, and key formats are refused.
|
||||
Native derivations finish before their deferred callbacks, so computation blocks the caller.
|
||||
|
||||
@@ -12442,9 +12442,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented by a dedicated static compiler/runtime path.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -12465,9 +12465,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "refused",
|
||||
"detail": "Explicitly rejected by a named compiler diagnostic.",
|
||||
"verification": "explicit-refusal"
|
||||
"status": "partial",
|
||||
"detail": "Implemented by a dedicated static compiler/runtime path.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -79656,9 +79656,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -79680,7 +79680,7 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "supported",
|
||||
"detail": "Implemented by a dedicated static compiler/runtime path.",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
@@ -79703,7 +79703,7 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "supported",
|
||||
"detail": "Implemented by a dedicated static compiler/runtime path.",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
@@ -80001,9 +80001,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80024,9 +80024,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80047,9 +80047,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80139,9 +80139,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80162,9 +80162,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80208,9 +80208,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -19313,6 +19313,8 @@
|
||||
"tests/corpus/6703-ecdh-private-keys.ts",
|
||||
"tests/corpus/6704-ecdh-set-public-key.cjs",
|
||||
"tests/corpus/6705-ecdh-handles.ts",
|
||||
"tests/corpus/7122-webcrypto-node-crypto.ts",
|
||||
"tests/corpus/7123-webcrypto-node-crypto.cjs",
|
||||
"tests/corpus/node24-crypto-kdf-errors.ts",
|
||||
"tests/corpus/node24-crypto-kdf-options.ts",
|
||||
"tests/corpus/node24-crypto-kdf.ts",
|
||||
@@ -21215,8 +21217,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:crypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:subtle",
|
||||
"tests": [
|
||||
"tests/corpus/7122-webcrypto-node-crypto.ts",
|
||||
"tests/corpus/7123-webcrypto-node-crypto.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -21238,8 +21244,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "refused",
|
||||
"evidence": "surface-manifest:unsupported:node-builtin.crypto.webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:crypto.webcrypto",
|
||||
"tests": [
|
||||
"tests/corpus/7122-webcrypto-node-crypto.ts",
|
||||
"tests/corpus/7123-webcrypto-node-crypto.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -106607,8 +106617,6 @@
|
||||
"tests/corpus/1534-crypto-hash-chain.ts",
|
||||
"tests/corpus/2556-crypto-introspection.ts",
|
||||
"tests/corpus/2890-crypto-static-utility.ts",
|
||||
"tests/corpus/5442-crypto-random-values.ts",
|
||||
"tests/corpus/5443-crypto-random-arguments.js",
|
||||
"tests/corpus/6204-crypto-get-random-values.ts",
|
||||
"tests/corpus/6205-crypto-get-random-values.js",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
@@ -106616,11 +106624,20 @@
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/corpus/7120-webcrypto-random-uuid.ts",
|
||||
"tests/corpus/7121-webcrypto-crypto-object.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/node24-crypto-kdf-errors.ts",
|
||||
"tests/corpus/node24-crypto-kdf-options.ts",
|
||||
"tests/corpus/node24-crypto-kdf.ts",
|
||||
"tests/corpus/node24-crypto-sha2.ts",
|
||||
"tests/fixtures/node-types/webcrypto-hmac.ts",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/harness/npm-static.test.ts"
|
||||
]
|
||||
},
|
||||
@@ -110078,8 +110095,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.Crypto",
|
||||
"tests": [
|
||||
"tests/corpus/7120-webcrypto-random-uuid.ts",
|
||||
"tests/corpus/7121-webcrypto-crypto-object.mjs",
|
||||
"tests/corpus/6293-webcrypto-digest.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110102,8 +110124,10 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "supported",
|
||||
"evidence": "compiler-dedicated:crypto.getRandomValues",
|
||||
"evidence": "compiler-feature:webcrypto.crypto.getRandomValues",
|
||||
"tests": [
|
||||
"tests/corpus/7120-webcrypto-random-uuid.ts",
|
||||
"tests/corpus/7121-webcrypto-crypto-object.mjs",
|
||||
"tests/corpus/6204-crypto-get-random-values.ts",
|
||||
"tests/corpus/6205-crypto-get-random-values.js"
|
||||
]
|
||||
@@ -110129,11 +110153,12 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "supported",
|
||||
"evidence": "compiler-dedicated:crypto.randomUUID",
|
||||
"evidence": "compiler-feature:webcrypto.crypto.randomUUID",
|
||||
"tests": [
|
||||
"tests/corpus/5442-crypto-random-values.ts",
|
||||
"tests/corpus/5443-crypto-random-arguments.js",
|
||||
"tests/corpus/1358-crypto-random.ts"
|
||||
"tests/corpus/7120-webcrypto-random-uuid.ts",
|
||||
"tests/corpus/7121-webcrypto-crypto-object.mjs",
|
||||
"tests/corpus/6204-crypto-get-random-values.ts",
|
||||
"tests/corpus/6205-crypto-get-random-values.js"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -110189,6 +110214,7 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.CryptoKey",
|
||||
"tests": [
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
@@ -110217,6 +110243,7 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.cryptoKey.algorithm",
|
||||
"tests": [
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
@@ -110245,6 +110272,7 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.extractable",
|
||||
"tests": [
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
@@ -110273,6 +110301,7 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.type",
|
||||
"tests": [
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
@@ -110301,6 +110330,7 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.usages",
|
||||
"tests": [
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
@@ -110471,8 +110501,23 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.decrypt",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-hmac.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110494,8 +110539,23 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.deriveBits",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-hmac.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110517,8 +110577,23 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.deriveKey",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-hmac.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110543,6 +110618,13 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.digest",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
@@ -110617,8 +110699,23 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.encrypt",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-hmac.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110640,8 +110737,23 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.exportKey",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-hmac.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110686,8 +110798,23 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.generateKey",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-hmac.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110712,6 +110839,13 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.importKey",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
@@ -110743,6 +110877,13 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.sign",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
@@ -110797,6 +110938,13 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.verify",
|
||||
"tests": [
|
||||
"tests/corpus/7128-webcrypto-derive.ts",
|
||||
"tests/corpus/7129-webcrypto-derive-errors.mjs",
|
||||
"tests/corpus/7126-webcrypto-aes.ts",
|
||||
"tests/corpus/7127-webcrypto-aes-errors.mjs",
|
||||
"tests/corpus/7124-webcrypto-secret-keys.ts",
|
||||
"tests/corpus/7125-webcrypto-key-errors.mjs",
|
||||
"tests/fixtures/node-types/webcrypto-keys.ts",
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6292-webcrypto-hmac-errors.ts",
|
||||
"tests/corpus/6293-webcrypto-digest.ts",
|
||||
|
||||
@@ -187,16 +187,18 @@
|
||||
"stdlib.set": ["tests/corpus/typed-collection-iterators.ts", "tests/corpus/typed-collection-iterator-references.ts", "tests/corpus/checked-collection-iterators.ts"],
|
||||
"stdlib.headers": ["tests/harness/fetch-conformance.test.ts"],
|
||||
"stdlib.request": ["tests/harness/fetch-conformance.test.ts"],
|
||||
"stdlib.webcrypto.subtle": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
|
||||
"stdlib.webcrypto.CryptoKey": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
|
||||
"stdlib.webcrypto.subtle": ["tests/corpus/7128-webcrypto-derive.ts", "tests/corpus/7129-webcrypto-derive-errors.mjs", "tests/corpus/7126-webcrypto-aes.ts", "tests/corpus/7127-webcrypto-aes-errors.mjs", "tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/7125-webcrypto-key-errors.mjs", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
|
||||
"stdlib.webcrypto.CryptoKey": ["tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
|
||||
"stdlib.response": ["tests/harness/fetch-conformance.test.ts"],
|
||||
"stdlib.readable-stream": ["tests/harness/fetch-conformance.test.ts"],
|
||||
"stdlib.abort": ["tests/harness/fetch-conformance.test.ts"]
|
||||
},
|
||||
"features": [
|
||||
{ "chapter": "webcrypto", "symbols": ["subtle.importKey", "subtle.sign", "subtle.verify", "subtle.digest"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs", "tests/fixtures/node-types/webcrypto-hmac.ts"] },
|
||||
{ "chapter": "webcrypto", "symbols": ["subtle.importKey", "subtle.generateKey", "subtle.exportKey", "subtle.encrypt", "subtle.decrypt", "subtle.deriveBits", "subtle.deriveKey", "subtle.sign", "subtle.verify", "subtle.digest"], "status": "partial", "evidence": ["tests/corpus/7128-webcrypto-derive.ts", "tests/corpus/7129-webcrypto-derive-errors.mjs", "tests/corpus/7126-webcrypto-aes.ts", "tests/corpus/7127-webcrypto-aes-errors.mjs", "tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/7125-webcrypto-key-errors.mjs", "tests/fixtures/node-types/webcrypto-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs", "tests/fixtures/node-types/webcrypto-hmac.ts"] },
|
||||
{ "chapter": "webcrypto", "signatures": ["Class: `SubtleCrypto`", "`subtle` Type: {SubtleCrypto}"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
|
||||
{ "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
|
||||
{ "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
|
||||
{ "chapter": "webcrypto", "signatures": ["Class: `Crypto`"], "status": "partial", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6293-webcrypto-digest.ts"] },
|
||||
{ "chapter": "webcrypto", "symbols": ["crypto.randomUUID", "crypto.getRandomValues"], "status": "supported", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6204-crypto-get-random-values.ts", "tests/corpus/6205-crypto-get-random-values.js"] },
|
||||
{ "chapter": "globals", "signatures": ["`navigator`"], "status": "partial", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts"] },
|
||||
{ "chapter": "globals", "signatures": ["`userAgent` Type: {string}"], "status": "supported", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts"] },
|
||||
{ "chapter": "globals", "signatures": ["Class: `CryptoKey`", "Class: `SubtleCrypto`"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
|
||||
@@ -413,6 +415,7 @@
|
||||
{ "symbols": ["assert.deepEqual", "assert.notDeepEqual"], "status": "partial", "evidence": ["tests/corpus/5513-assert-deep-equal-loose.js", "tests/corpus/5514-assert-deep-equal-typed.ts", "tests/corpus/5516-assert-deep-equal-edges.js"] },
|
||||
{ "symbols": ["assert", "assert.ok", "assert.equal", "assert.notEqual", "assert.strictEqual", "assert.notStrictEqual", "assert.deepStrictEqual", "assert.notDeepStrictEqual", "assert.fail", "assert.match", "assert.doesNotMatch", "assert.throws", "assert.rejects", "assert.ifError"], "status": "partial", "evidence": ["tests/corpus/1600-assert-passing.ts", "tests/corpus/1604-assert-deep-structures.ts", "tests/corpus/1609-assert-async.ts", "tests/corpus/1721-assert-throws-regex-class.ts", "tests/corpus/2936-static-loose-equality.ts", "tests/corpus/5515-assert-deep-strict-typed-messages.ts"] },
|
||||
{ "chapter": "crypto", "symbols": ["Cipheriv", "cipher.update", "cipher.final", "cipher.setAAD", "cipher.getAuthTag", "cipher.setAutoPadding", "Decipheriv", "decipher.update", "decipher.final", "decipher.setAAD", "decipher.setAuthTag", "decipher.setAutoPadding"], "status": "partial", "evidence": ["tests/corpus/5250-cipher-aes-gcm.ts", "tests/corpus/5251-cipher-aes-cbc.ts", "tests/corpus/5252-cipher-aes-ctr.ts", "tests/corpus/5253-cipher-chacha20-poly1305.ts", "tests/corpus/5254-cipher-errors.js", "tests/corpus/5255-cipher-encodings.cjs", "tests/corpus/5256-cipher-deprecation.cjs", "tests/corpus/5257-cipher-vectors.ts", "tests/corpus/5258-cipher-handles.js", "tests/corpus/5259-cipher-chunked.ts", "tests/fixtures/node-types/cipher.ts"] },
|
||||
{ "chapter": "crypto", "symbols": ["crypto.webcrypto", "subtle"], "status": "partial", "evidence": ["tests/corpus/7122-webcrypto-node-crypto.ts", "tests/corpus/7123-webcrypto-node-crypto.cjs"] },
|
||||
{ "chapter": "crypto", "symbols": ["ECDH", "ecdh.computeSecret", "ecdh.generateKeys", "ecdh.getPrivateKey", "ecdh.getPublicKey", "ecdh.setPrivateKey", "ecdh.setPublicKey"], "status": "partial", "evidence": ["tests/corpus/6700-ecdh-shared-secret.ts", "tests/corpus/6701-ecdh-formats.ts", "tests/corpus/6702-ecdh-errors.js", "tests/corpus/6703-ecdh-private-keys.ts", "tests/corpus/6704-ecdh-set-public-key.cjs", "tests/corpus/6705-ecdh-handles.ts", "tests/fixtures/node-types/ecdh.ts"] },
|
||||
{ "chapter": "errors", "symbols": ["ERR_CRYPTO_ECDH_INVALID_FORMAT", "ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY"], "status": "partial", "evidence": ["tests/corpus/6702-ecdh-errors.js"] },
|
||||
{ "symbols": ["crypto.createHash", "Hash", "hash.update", "hash.digest", "hash.copy", "crypto.createHmac", "Hmac", "hmac.update", "hmac.digest", "crypto.hash", "crypto.pbkdf2", "crypto.pbkdf2Sync"], "status": "partial", "evidence": ["tests/corpus/5760-crypto-hash-stream.ts", "tests/corpus/5768-crypto-hash-unknown.ts", "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", "tests/corpus/node24-crypto-sha2.ts", "tests/corpus/5435-crypto-hash-algorithms.ts", "tests/corpus/5436-crypto-hash-names.js", "tests/corpus/5437-crypto-hmac-algorithms.ts", "tests/corpus/5438-crypto-kdf-digests.ts", "tests/corpus/5440-crypto-hash-encodings.js", "tests/corpus/5441-crypto-hash-output-length.ts", "tests/harness/npm-static.test.ts"] },
|
||||
|
||||
+91
-12
@@ -544,12 +544,15 @@ declare var DOMException: {
|
||||
readonly prototype: DOMException;
|
||||
};
|
||||
|
||||
/* WebCrypto's HMAC slice (scr_webcrypto.c): crypto.subtle's importKey for
|
||||
* raw HMAC keys, HMAC sign/verify and the SHA-1/SHA-2 digests, typed as
|
||||
/* WebCrypto's secret-key slice (scr_webcrypto.c): crypto.subtle's
|
||||
* importKey, generateKey and exportKey for HMAC and AES keys (importKey
|
||||
* also for PBKDF2 and HKDF keys), AES-GCM/CBC/CTR encrypt and decrypt,
|
||||
* PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign/verify and the SHA-1/SHA-2 digests, typed as
|
||||
* exactly that surface (the declarations mirror @types/node's webcrypto
|
||||
* shapes). Every other SubtleCrypto method and crypto member is absent,
|
||||
* so a use is an honest type error. CryptoKey values are native handles
|
||||
* whose four members read through the handle. */
|
||||
* shapes), plus the global's randomUUID and getRandomValues. Every other
|
||||
* SubtleCrypto method and crypto member is absent, so a use is an honest
|
||||
* type error. CryptoKey values are native handles whose four members read
|
||||
* through the handle. */
|
||||
declare var crypto: Crypto;
|
||||
/* The Crypto and SubtleCrypto constructors (feature probes only) stay
|
||||
* `unknown`; the values keep this declaration order. */
|
||||
@@ -582,9 +585,44 @@ interface HmacImportParams extends Algorithm {
|
||||
hash: HashAlgorithmIdentifier;
|
||||
length?: number;
|
||||
}
|
||||
interface HmacKeyGenParams extends Algorithm {
|
||||
hash: HashAlgorithmIdentifier;
|
||||
length?: number;
|
||||
}
|
||||
interface AesKeyGenParams extends Algorithm {
|
||||
length: number;
|
||||
}
|
||||
interface AesGcmParams extends Algorithm {
|
||||
additionalData?: BufferSource;
|
||||
iv: BufferSource;
|
||||
tagLength?: number;
|
||||
}
|
||||
interface AesCbcParams extends Algorithm {
|
||||
iv: BufferSource;
|
||||
}
|
||||
interface AesCtrParams extends Algorithm {
|
||||
counter: BufferSource;
|
||||
length: number;
|
||||
}
|
||||
interface Pbkdf2Params extends Algorithm {
|
||||
hash: HashAlgorithmIdentifier;
|
||||
iterations: number;
|
||||
salt: BufferSource;
|
||||
}
|
||||
interface HkdfParams extends Algorithm {
|
||||
hash: HashAlgorithmIdentifier;
|
||||
info: BufferSource;
|
||||
salt: BufferSource;
|
||||
}
|
||||
interface AesDerivedKeyParams extends Algorithm {
|
||||
length: number;
|
||||
}
|
||||
interface KeyAlgorithm {
|
||||
name: string;
|
||||
}
|
||||
interface AesKeyAlgorithm extends KeyAlgorithm {
|
||||
length: number;
|
||||
}
|
||||
interface HmacKeyAlgorithm extends KeyAlgorithm {
|
||||
hash: KeyAlgorithm;
|
||||
length: number;
|
||||
@@ -623,10 +661,37 @@ interface JsonWebKey {
|
||||
x?: string;
|
||||
y?: string;
|
||||
}
|
||||
/* The 'jwk' form type-checks like @types/node's; the compiler refuses it
|
||||
* (the JsonWebKey import is not implemented). */
|
||||
interface SubtleCrypto {
|
||||
decrypt(
|
||||
algorithm: AesGcmParams | AesCbcParams | AesCtrParams,
|
||||
key: CryptoKey,
|
||||
data: BufferSource,
|
||||
): Promise<ArrayBuffer>;
|
||||
deriveBits(
|
||||
algorithm: Pbkdf2Params | HkdfParams,
|
||||
baseKey: CryptoKey,
|
||||
length?: number | null,
|
||||
): Promise<ArrayBuffer>;
|
||||
deriveKey(
|
||||
algorithm: Pbkdf2Params | HkdfParams,
|
||||
baseKey: CryptoKey,
|
||||
derivedKeyType: AlgorithmIdentifier | AesDerivedKeyParams | HmacImportParams,
|
||||
extractable: boolean,
|
||||
keyUsages: readonly KeyUsage[],
|
||||
): Promise<CryptoKey>;
|
||||
digest(algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer>;
|
||||
encrypt(
|
||||
algorithm: AesGcmParams | AesCbcParams | AesCtrParams,
|
||||
key: CryptoKey,
|
||||
data: BufferSource,
|
||||
): Promise<ArrayBuffer>;
|
||||
exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>;
|
||||
exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>;
|
||||
generateKey(
|
||||
algorithm: AesKeyGenParams | HmacKeyGenParams,
|
||||
extractable: boolean,
|
||||
keyUsages: readonly KeyUsage[],
|
||||
): Promise<CryptoKey>;
|
||||
importKey(
|
||||
format: "jwk",
|
||||
keyData: JsonWebKey,
|
||||
@@ -651,6 +716,21 @@ interface SubtleCrypto {
|
||||
}
|
||||
interface Crypto {
|
||||
readonly subtle: SubtleCrypto;
|
||||
getRandomValues<
|
||||
T extends
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| BigInt64Array
|
||||
| BigUint64Array,
|
||||
>(
|
||||
typedArray: T,
|
||||
): T;
|
||||
randomUUID(): `${string}-${string}-${string}-${string}-${string}`;
|
||||
}
|
||||
|
||||
/* Capability-gated globals harness code probes behind feature guards
|
||||
@@ -2784,8 +2864,7 @@ declare module "crypto" {
|
||||
b: ArrayBufferView | ArrayBufferLike,
|
||||
): boolean;
|
||||
/* WebCrypto's getRandomValues (also the webcrypto object's): integer
|
||||
* typed arrays, filled in place and returned. The crypto GLOBAL stays
|
||||
* unknown in fallback builds. */
|
||||
* typed arrays, filled in place and returned. */
|
||||
type RandomValuesArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
@@ -2797,9 +2876,9 @@ declare module "crypto" {
|
||||
| BigInt64Array
|
||||
| BigUint64Array;
|
||||
export function getRandomValues<T extends RandomValuesArray>(typedArray: T): T;
|
||||
export const webcrypto: {
|
||||
getRandomValues<T extends RandomValuesArray>(typedArray: T): T;
|
||||
};
|
||||
/* The WebCrypto global's object and its SubtleCrypto. */
|
||||
export const webcrypto: Crypto;
|
||||
export const subtle: SubtleCrypto;
|
||||
export function randomFillSync<T extends ArrayBufferView | ArrayBufferLike>(
|
||||
buffer: T,
|
||||
offset?: number,
|
||||
|
||||
@@ -615,10 +615,17 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"crypto.keyObjectInput": "scr_crypto_key_object_input",
|
||||
"crypto.isKeyObject": "scr_crypto_is_key_object",
|
||||
"webcrypto.importKey": "scr_webcrypto_import_key",
|
||||
"webcrypto.generateKey": "scr_webcrypto_generate_key",
|
||||
"webcrypto.exportKey": "scr_webcrypto_export_key",
|
||||
"webcrypto.encrypt": "scr_webcrypto_encrypt",
|
||||
"webcrypto.decrypt": "scr_webcrypto_decrypt",
|
||||
"webcrypto.deriveBits": "scr_webcrypto_derive_bits",
|
||||
"webcrypto.deriveKey": "scr_webcrypto_derive_key",
|
||||
"webcrypto.sign": "scr_webcrypto_sign",
|
||||
"webcrypto.verify": "scr_webcrypto_verify",
|
||||
"webcrypto.digest": "scr_webcrypto_digest",
|
||||
"webcrypto.isCryptoKey": "scr_webcrypto_is_crypto_key",
|
||||
"webcrypto.object": "scr_webcrypto_object",
|
||||
"crypto.randomUUIDv7": "scr_crypto_random_uuid_v7",
|
||||
"crypto.pbkdf2": "scr_crypto_pbkdf2",
|
||||
"crypto.hkdf": "scr_crypto_hkdf",
|
||||
@@ -1573,6 +1580,11 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
// ecdh.setPublicKey reports DEP0031 on the next tick.
|
||||
"crypto.ecdhNew",
|
||||
// WebCrypto results settle through the deferred crypto queue.
|
||||
"webcrypto.generateKey",
|
||||
"webcrypto.encrypt",
|
||||
"webcrypto.decrypt",
|
||||
"webcrypto.deriveBits",
|
||||
"webcrypto.deriveKey",
|
||||
"webcrypto.sign",
|
||||
"webcrypto.verify",
|
||||
"webcrypto.digest",
|
||||
|
||||
@@ -84,8 +84,9 @@ export interface NativeLinkFeatures {
|
||||
foreignFfi: boolean;
|
||||
nodeTest: boolean;
|
||||
tls: boolean;
|
||||
/** crypto.createCipheriv/createDecipheriv: scr_cipher.c plus the mbedTLS
|
||||
* archive. */
|
||||
/** crypto.createCipheriv/createDecipheriv and WebCrypto's encrypt and
|
||||
* decrypt: scr_cipher.c plus the mbedTLS archive (and, with webcrypto,
|
||||
* scr_webcrypto_aes.c). */
|
||||
cipher: boolean;
|
||||
/** crypto.createECDH and ECDH.convertKey: scr_ecdh.c plus the mbedTLS
|
||||
* archive. */
|
||||
|
||||
@@ -243,8 +243,9 @@ export interface CcOptions {
|
||||
* The unit also compiles whenever `tls` does — scr_tls.c consults its
|
||||
* default-set override and shared Windows-certificate enumerator. */
|
||||
tlsCa?: boolean;
|
||||
/** crypto.createCipheriv/createDecipheriv: compiles scr_cipher.c and links
|
||||
* the mbedTLS archive (shared with tls). */
|
||||
/** crypto.createCipheriv/createDecipheriv and WebCrypto's encrypt and
|
||||
* decrypt: compiles scr_cipher.c (and, with webcrypto,
|
||||
* scr_webcrypto_aes.c) and links the mbedTLS archive (shared with tls). */
|
||||
cipher?: boolean;
|
||||
/** crypto.createECDH and ECDH.convertKey: compiles scr_ecdh.c and links
|
||||
* the mbedTLS archive (shared with tls). */
|
||||
|
||||
@@ -297,6 +297,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
unit((u) => u.opts.foreignFfi, "scr_ffi_queue.c"),
|
||||
unit((u) => u.opts.nodeTest, "scr_test.c"),
|
||||
unit((u) => u.opts.webcrypto, "scr_webcrypto.c"),
|
||||
// The crypto global's object extends scr_async_dyn.c's.
|
||||
unit((u) => u.opts.webcrypto && u.opts.dynAsync, "scr_webcrypto_object.c"),
|
||||
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
|
||||
// references its default-set override unconditionally.
|
||||
unit((u) => u.tlsCa, "scr_tls_ca.c"),
|
||||
@@ -308,6 +310,7 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
...(u.tls ? [u.rt(join(u.rtDir, "scr_tls.c"))] : []),
|
||||
...(u.cipher ? [u.rt(join(u.rtDir, "scr_cipher.c"))] : []),
|
||||
...(u.ecdh ? [u.rt(join(u.rtDir, "scr_ecdh.c"))] : []),
|
||||
...(u.cipher && u.opts.webcrypto ? [u.rt(join(u.rtDir, "scr_webcrypto_aes.c"))] : []),
|
||||
u.tlsArchive,
|
||||
// mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h).
|
||||
// The unconditional win32 libs above do not carry it.
|
||||
|
||||
@@ -208,6 +208,14 @@ export function moduleWasiUnavailableSurface(
|
||||
};
|
||||
return false;
|
||||
}
|
||||
if (node.fn === "webcrypto.encrypt" || node.fn === "webcrypto.decrypt") {
|
||||
found = {
|
||||
surface:
|
||||
"WebCrypto encrypt and decrypt (the wasm32-wasi runtime does not include the mbedTLS library)",
|
||||
loc,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
if (node.fn === "crypto.ecdhNew" || node.fn === "crypto.ecdhConvertKey") {
|
||||
found = {
|
||||
surface: "ECDH (the wasm32-wasi runtime does not include the mbedTLS library)",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { type Lowerer, own } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { CIPHER_TYPE_NAMES } from "../../type-mapper.js";
|
||||
import { CRYPTO_CIPHERS, CRYPTO_CURVES, CRYPTO_HASHES } from "../crypto-tables.js";
|
||||
import { stdlibGlobalNameOf } from "../surfaces.js";
|
||||
import {
|
||||
BOOL,
|
||||
BYTES_U8,
|
||||
@@ -1316,8 +1317,10 @@ export function lowerCryptoGetRandomValues(
|
||||
}
|
||||
|
||||
/** Whether `receiver` names a WebCrypto Crypto object: the `crypto`
|
||||
* global (also through globalThis) or node:crypto's `webcrypto`. */
|
||||
* global (also through globalThis or an alias of it) or node:crypto's
|
||||
* `webcrypto`. */
|
||||
export function isWebCryptoReceiver(lowerer: Lowerer, receiver: ts.Expression): boolean {
|
||||
if (stdlibGlobalNameOf(lowerer, receiver) === "crypto") return true;
|
||||
if (ts.isIdentifier(receiver)) {
|
||||
if (receiver.text === "crypto" && lowerer.isStdlibGlobal(receiver, "crypto")) return true;
|
||||
const imported = lowerer.builtinImportOf(receiver);
|
||||
|
||||
@@ -5,34 +5,57 @@ import { BOOL, DYN, type IrExpr, type IrLibFn } from "../../../ir/ir.js";
|
||||
import { lowerDynObjectLiteral } from "../expressions/object-literals.js";
|
||||
import { stdlibGlobalNameOf } from "../surfaces.js";
|
||||
import { WEBCRYPTO_DYN_TYPES } from "../../type-mapper.js";
|
||||
import { isWebCryptoReceiver } from "./crypto.js";
|
||||
|
||||
/* WebCrypto's HMAC slice (scr_webcrypto.c): `crypto.subtle.importKey`,
|
||||
* `sign`, `verify` and `digest` called on the crypto global (also through
|
||||
* globalThis) lower to runtime entry points that take the argument vector
|
||||
* as one dyn array and answer a promise, so Web IDL conversion, algorithm
|
||||
* normalization and every error stay Node-shaped at run time. CryptoKey
|
||||
/* WebCrypto's secret-key slice (scr_webcrypto.c, with encrypt and
|
||||
* decrypt in scr_webcrypto_aes.c): `crypto.subtle`'s `importKey`,
|
||||
* `generateKey`, `exportKey`, `encrypt`, `decrypt`, `deriveBits`,
|
||||
* `deriveKey`, `sign`, `verify` and `digest`
|
||||
* called on the crypto global (also through
|
||||
* globalThis, node:crypto's webcrypto and node:crypto's subtle) lower to
|
||||
* runtime entry points that take the argument vector as one dyn array and
|
||||
* answer a promise, so Web IDL conversion, algorithm normalization and
|
||||
* every error stay Node-shaped at run time. CryptoKey
|
||||
* values are native handles; their type maps to dyn and their members
|
||||
* are handle reads. Other SubtleCrypto methods are refused by name. */
|
||||
|
||||
const SUBTLE_LOWERED: ReadonlyMap<string, IrLibFn> = new Map([
|
||||
["importKey", "webcrypto.importKey"],
|
||||
["generateKey", "webcrypto.generateKey"],
|
||||
["exportKey", "webcrypto.exportKey"],
|
||||
["encrypt", "webcrypto.encrypt"],
|
||||
["decrypt", "webcrypto.decrypt"],
|
||||
["deriveBits", "webcrypto.deriveBits"],
|
||||
["deriveKey", "webcrypto.deriveKey"],
|
||||
["sign", "webcrypto.sign"],
|
||||
["verify", "webcrypto.verify"],
|
||||
["digest", "webcrypto.digest"],
|
||||
]);
|
||||
|
||||
const SUBTLE_HINT =
|
||||
"the native WebCrypto surface is crypto.subtle.importKey('raw', ...) for HMAC keys, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle";
|
||||
"the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle";
|
||||
|
||||
/** Whether `node` is `crypto.subtle` on the WebCrypto global. */
|
||||
/** Whether `node` is a SubtleCrypto object: `subtle` on a WebCrypto
|
||||
* Crypto object (the global, globalThis.crypto or node:crypto's
|
||||
* `webcrypto`) or node:crypto's own `subtle` export, named or through
|
||||
* the namespace. */
|
||||
function isSubtleReceiver(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
||||
return (
|
||||
ts.isPropertyAccessExpression(node) &&
|
||||
!node.questionDotToken &&
|
||||
node.name.text === "subtle" &&
|
||||
stdlibGlobalNameOf(lowerer, node.expression) === "crypto"
|
||||
);
|
||||
if (ts.isIdentifier(node)) {
|
||||
const imported = lowerer.builtinImportOf(node);
|
||||
return imported?.module === "crypto" && imported.member === "subtle";
|
||||
}
|
||||
if (
|
||||
!ts.isPropertyAccessExpression(node) ||
|
||||
node.questionDotToken ||
|
||||
node.name.text !== "subtle"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (stdlibGlobalNameOf(lowerer, node.expression) === "crypto") return true;
|
||||
if (isWebCryptoReceiver(lowerer, node.expression)) return true;
|
||||
const member = lowerer.builtinMemberOf(node);
|
||||
return member?.module === "crypto" && member.member === "subtle";
|
||||
}
|
||||
|
||||
/** `crypto.subtle.<method>(...args)`: the lowered methods become their
|
||||
@@ -51,17 +74,6 @@ export function lowerWebCryptoCall(lowerer: Lowerer, call: ts.CallExpression): I
|
||||
if (call.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`crypto.subtle.${member} with spread arguments`, call, SUBTLE_HINT);
|
||||
}
|
||||
// JsonWebKey imports are not implemented: a literal 'jwk' format is
|
||||
// refused here (the runtime refuses a computed one).
|
||||
const format = call.arguments.nodes[0];
|
||||
if (
|
||||
member === "importKey" &&
|
||||
format !== undefined &&
|
||||
ts.isStringLiteralLike(format) &&
|
||||
format.text === "jwk"
|
||||
) {
|
||||
lowerer.noLowering("crypto.subtle.importKey with the 'jwk' key format", format, SUBTLE_HINT);
|
||||
}
|
||||
const loc = locOf(call);
|
||||
const args = call.arguments.nodes.map((arg) =>
|
||||
ts.isObjectLiteralExpression(arg)
|
||||
|
||||
@@ -549,6 +549,9 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc:
|
||||
const callable = lowerer.lowerBuiltinCallableValue(bi, loc);
|
||||
if (callable) return callable;
|
||||
}
|
||||
// node:crypto's webcrypto is the WebCrypto global's object.
|
||||
if (bi.module === "crypto" && bi.member === "webcrypto")
|
||||
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
|
||||
// JavaScript sources: an otherwise unsupported builtin VALUE is
|
||||
// the same identity-token story as stdlib globals above (the
|
||||
// harness adds worker_threads.Worker to its identity Set).
|
||||
@@ -721,7 +724,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc:
|
||||
return { kind: "closure", fnName: name, captures: [], type, loc };
|
||||
}
|
||||
if (lowerer.isStdlibGlobal(expr, "crypto"))
|
||||
return { kind: "libCall", fn: "crypto.native", args: [], type: DYN, loc };
|
||||
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
|
||||
const nativeAlias = stdlibGlobalNameOf(lowerer, expr);
|
||||
if (nativeAlias === "Buffer" && expr.text !== "Buffer") {
|
||||
return { kind: "strLit", value: "[builtin Buffer]", type: STRING, loc };
|
||||
|
||||
@@ -162,6 +162,7 @@ import { lowerUnionFieldRead } from "./union-fields.js";
|
||||
import { errorPropertyRead } from "../error-methods.js";
|
||||
import { isSafeToDiscard, isSafeToRepeat } from "./evaluation-safety.js";
|
||||
import { isZlibStreamMember, lowerZlibStreamBytesWritten } from "../builtins/compression.js";
|
||||
import { isWebCryptoReceiver } from "../builtins/crypto.js";
|
||||
import { isFsStreamMember, lowerFsStreamProperty } from "../builtins/fs-streams.js";
|
||||
import { keyedFieldRead } from "./keyed-field-targets.js";
|
||||
import { promisePrototypeValue } from "../thenables.js";
|
||||
@@ -842,8 +843,16 @@ export function lowerPropertyAccessExpr(
|
||||
if (runtimeOptionalClassField(lowerer, handled, expr)) return handled;
|
||||
return iteratorResultValueRead(lowerer, handled, expr) ?? lowerer.maybeNarrow(handled, expr);
|
||||
}
|
||||
// `globalThis.crypto` is the WebCrypto object the bare `crypto` answers.
|
||||
if (
|
||||
!expr.questionDotToken &&
|
||||
stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis" &&
|
||||
stdlibGlobalNameOf(lowerer, expr) === "crypto"
|
||||
) {
|
||||
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
|
||||
}
|
||||
// `globalThis.<name>` that no lowering above claimed
|
||||
// (globalThis.crypto, globalThis.SubtleCrypto, globalThis.localStorage
|
||||
// (globalThis.SubtleCrypto, globalThis.localStorage
|
||||
// — the harness's capability-conditional knownGlobals adds): the
|
||||
// PROPERTY spelling of the identifier chokepoint's JS rule — a stdlib
|
||||
// global taken as a VALUE in a JavaScript source is the same opaque
|
||||
@@ -2959,6 +2968,9 @@ export function lowerFieldRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressi
|
||||
if (
|
||||
receiverIr?.kind !== "object" &&
|
||||
propSym &&
|
||||
// The WebCrypto object's generic getRandomValues reads off its
|
||||
// dynamic object.
|
||||
!isWebCryptoReceiver(lowerer, expr.expression) &&
|
||||
isGenericCallableMemberType(lowerer.checker.getTypeOfSymbol(propSym), lowerer.checker) &&
|
||||
// CLASS members keep the class-path fences (a poisoned class's own
|
||||
// diagnostics, the bound-method fence above).
|
||||
|
||||
@@ -1542,9 +1542,10 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
lowerer.fenceStaticReadableStreamMember(expr.expression, "call") ??
|
||||
lowerDisposableStackUseCall(lowerer, expr, expr.expression) ??
|
||||
lowerer.lowerStdlibMethodCall(expr, expr.expression) ??
|
||||
// crypto.getRandomValues on the WebCrypto global (untyped in
|
||||
// fallback builds, so before the dyn receiver path) or webcrypto.
|
||||
lowerWebCryptoGetRandomValues(lowerer, expr, expr.expression) ??
|
||||
// crypto.getRandomValues / randomUUID on the WebCrypto global
|
||||
// (untyped in fallback builds, so before the dyn receiver path) or
|
||||
// webcrypto.
|
||||
lowerWebCryptoMethodCall(lowerer, expr, expr.expression) ??
|
||||
// Dyn receivers (JSON.parse-derived `unknown`/`any` values) —
|
||||
|
||||
lowerDynReceiverMethodCall(lowerer, expr, expr.expression) ??
|
||||
@@ -10492,20 +10493,40 @@ export function lowerStaticNumberGlobal(
|
||||
return null;
|
||||
}
|
||||
|
||||
/** `crypto.getRandomValues(view)` on the WebCrypto global or webcrypto. */
|
||||
function lowerWebCryptoGetRandomValues(
|
||||
/** `crypto.getRandomValues(view)` and `crypto.randomUUID()` on the
|
||||
* WebCrypto global or webcrypto. Other argument counts (JavaScript only)
|
||||
* invoke the dynamic object's method, which ignores extra arguments and
|
||||
* throws Node's missing-argument TypeError. */
|
||||
function lowerWebCryptoMethodCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
const member = access.name.text;
|
||||
if (
|
||||
access.name.text !== "getRandomValues" ||
|
||||
(member !== "getRandomValues" && member !== "randomUUID") ||
|
||||
call.questionDotToken ||
|
||||
access.questionDotToken ||
|
||||
!isWebCryptoReceiver(lowerer, access.expression)
|
||||
)
|
||||
return null;
|
||||
return lowerCryptoGetRandomValues(lowerer, call);
|
||||
const args = call.arguments.nodes;
|
||||
if (member === "getRandomValues" && args.length === 1) {
|
||||
return lowerCryptoGetRandomValues(lowerer, call);
|
||||
}
|
||||
if (member === "randomUUID" && args.length === 0) {
|
||||
return { kind: "libCall", fn: "crypto.randomUUID", args: [], type: STRING, loc: locOf(call) };
|
||||
}
|
||||
if (args.some(ts.isSpreadElement)) return null;
|
||||
return {
|
||||
kind: "dynInvoke",
|
||||
recv: lowerer.lowerExprExpecting(access.expression, DYN),
|
||||
method: member,
|
||||
calleeName: v8CalleeText(access),
|
||||
args: args.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
|
||||
type: DYN,
|
||||
loc: locOf(call),
|
||||
};
|
||||
}
|
||||
|
||||
/** A then handler's promise-armed result (`.then(() => cached ?? load())`
|
||||
|
||||
@@ -3964,9 +3964,13 @@ function stdlibGlobalTokenDestructure(
|
||||
const canonical = ts.symbolName(member) === "global" ? "globalThis" : ts.symbolName(member);
|
||||
token = `[builtin ${canonical}]`;
|
||||
alias = canonical;
|
||||
// Builtin constructors read as their interned function values, the
|
||||
// same values the bare and `globalThis.X` spellings read.
|
||||
value = builtinGlobalValue(canonical, locOf(el), true);
|
||||
// Builtin constructors read as their interned function values, and
|
||||
// crypto as the WebCrypto object: the same values the bare and
|
||||
// `globalThis.X` spellings read.
|
||||
value =
|
||||
canonical === "crypto"
|
||||
? { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc: locOf(el) }
|
||||
: builtinGlobalValue(canonical, locOf(el), true);
|
||||
} else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) {
|
||||
token = `[builtin console.${propName}]`;
|
||||
} else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) {
|
||||
|
||||
@@ -13410,6 +13410,10 @@ export class Lowerer {
|
||||
const callable = this.lowerBuiltinCallableValue(bi, loc);
|
||||
if (callable) return callable;
|
||||
}
|
||||
// node:crypto's webcrypto is the WebCrypto global's object.
|
||||
if (bi.module === "crypto" && bi.member === "webcrypto") {
|
||||
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
|
||||
}
|
||||
// node:stream/web's exports are the web stream globals themselves: in
|
||||
// JavaScript sources the same identity token as the global spelling
|
||||
// (`require("stream/web").ReadableStream === globalThis.ReadableStream`).
|
||||
|
||||
@@ -2210,15 +2210,26 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
|
||||
id: "stdlib.webcrypto.subtle",
|
||||
kind: "stdlib",
|
||||
name: "globalThis.crypto.subtle",
|
||||
fns: ["webcrypto.importKey", "webcrypto.sign", "webcrypto.verify", "webcrypto.digest"],
|
||||
note: "importKey('raw') for HMAC keys, HMAC sign and verify, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused",
|
||||
fns: [
|
||||
"webcrypto.importKey",
|
||||
"webcrypto.generateKey",
|
||||
"webcrypto.exportKey",
|
||||
"webcrypto.encrypt",
|
||||
"webcrypto.decrypt",
|
||||
"webcrypto.deriveBits",
|
||||
"webcrypto.deriveKey",
|
||||
"webcrypto.sign",
|
||||
"webcrypto.verify",
|
||||
"webcrypto.digest",
|
||||
],
|
||||
note: "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused",
|
||||
},
|
||||
{
|
||||
id: "stdlib.webcrypto.CryptoKey",
|
||||
kind: "stdlib",
|
||||
name: "CryptoKey",
|
||||
fns: ["webcrypto.isCryptoKey"],
|
||||
note: "secret HMAC keys with type, extractable, algorithm and usages, and instanceof CryptoKey",
|
||||
note: "secret HMAC, AES, PBKDF2 and HKDF keys with type, extractable, algorithm and usages, and instanceof CryptoKey",
|
||||
},
|
||||
{
|
||||
id: "stdlib.atomics",
|
||||
@@ -3088,9 +3099,6 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record<
|
||||
setFips:
|
||||
"a compiled binary has no FIPS provider to enable, and Node itself throws on setFips(true) " +
|
||||
"in a non-FIPS build — getFips() answers 0 here",
|
||||
webcrypto:
|
||||
"the WebCrypto object has no lowering — the lowered crypto surface is randomUUID, " +
|
||||
"randomBytes, and the createHash chain",
|
||||
},
|
||||
zlib: {
|
||||
brotliCompressSync: BROTLI_HINT,
|
||||
|
||||
@@ -146,6 +146,16 @@ export const WEBCRYPTO_DYN_TYPES: ReadonlySet<string> = new Set([
|
||||
"KeyAlgorithm",
|
||||
"HmacImportParams",
|
||||
"HmacKeyAlgorithm",
|
||||
"HmacKeyGenParams",
|
||||
"AesKeyGenParams",
|
||||
"AesKeyAlgorithm",
|
||||
"JsonWebKey",
|
||||
"AesGcmParams",
|
||||
"AesCbcParams",
|
||||
"AesCtrParams",
|
||||
"Pbkdf2Params",
|
||||
"HkdfParams",
|
||||
"AesDerivedKeyParams",
|
||||
]);
|
||||
|
||||
export const FETCH_AMBIENT_TYPES = [
|
||||
|
||||
@@ -2846,10 +2846,17 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* one dyn array and answers a promise that argument and validation
|
||||
* failures reject. CryptoKey values are native handles. */
|
||||
"webcrypto.importKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.generateKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.exportKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.encrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.decrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.deriveBits": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.deriveKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.sign": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.verify": { argTypes: [DYN], result: { kind: "promise", inner: BOOL } },
|
||||
"webcrypto.digest": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"webcrypto.isCryptoKey": { argTypes: [DYN], result: BOOL },
|
||||
"webcrypto.object": { argTypes: [], result: DYN },
|
||||
"crypto.randomUUIDv7": { argTypes: [DYN], result: STRING },
|
||||
"crypto.pbkdf2": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: BYTES_U8 },
|
||||
"crypto.randomBytesCb": { argTypes: [F64, null], result: VOID },
|
||||
|
||||
@@ -75,6 +75,8 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"child.propsInstall",
|
||||
"child.propsResolve",
|
||||
"crypto.native",
|
||||
// The crypto global's object extends crypto.native's.
|
||||
"webcrypto.object",
|
||||
"dyn.promiseAll",
|
||||
"dyn.promiseAllSettled",
|
||||
"dyn.promiseAny",
|
||||
@@ -647,8 +649,9 @@ const RUNTIME_FEATURE_RULES = {
|
||||
}),
|
||||
/** scr_cipher.c over the mbedTLS archive; DEP0182 reports through the
|
||||
* process-warning unit, and unknown output encodings render through the
|
||||
* inspect unit. */
|
||||
cipher: rule({ fns: ["crypto.cipherNew"] }),
|
||||
* inspect unit. WebCrypto's encrypt and decrypt (scr_webcrypto_aes.c)
|
||||
* link the same archive. */
|
||||
cipher: rule({ fns: ["crypto.cipherNew", "webcrypto.encrypt", "webcrypto.decrypt"] }),
|
||||
copying: rule({
|
||||
expr: (node, _tables) =>
|
||||
(node.kind === "arrIntrinsic" && COPYING_ARR_METHODS.has(node.method)) ||
|
||||
@@ -675,12 +678,15 @@ const RUNTIME_FEATURE_RULES = {
|
||||
),
|
||||
// The legacy url functions, crypto.createCipheriv and
|
||||
// ecdh.setPublicKey emit their deprecation warnings through the
|
||||
// dyn-async unit; scr_wasm.c boxes its handles and promises through it.
|
||||
// dyn-async unit (WebCrypto's encrypt and decrypt link scr_cipher.c
|
||||
// too); scr_wasm.c boxes its handles and promises through it.
|
||||
fns: [
|
||||
...DYN_ASYNC_LIB_FNS,
|
||||
...READABLE_DYN_ASYNC_LIB_FNS,
|
||||
...LEGACY_URL_LIB_FNS,
|
||||
"crypto.cipherNew",
|
||||
"webcrypto.encrypt",
|
||||
"webcrypto.decrypt",
|
||||
"crypto.ecdhNew",
|
||||
],
|
||||
prefixes: [...READABLE_DYN_ASYNC_PREFIXES, "wasm."],
|
||||
@@ -787,6 +793,8 @@ const RUNTIME_FEATURE_RULES = {
|
||||
"global.native",
|
||||
"util.styleText",
|
||||
"crypto.cipherNew",
|
||||
"webcrypto.encrypt",
|
||||
"webcrypto.decrypt",
|
||||
"crypto.ecdhNew",
|
||||
"crypto.ecdhConvertKey",
|
||||
...BUILTIN_CLASS_LIB_FNS,
|
||||
|
||||
@@ -762,14 +762,6 @@
|
||||
"code": "SC2020",
|
||||
"note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.crypto.webcrypto",
|
||||
"kind": "node-builtin",
|
||||
"name": "crypto.webcrypto",
|
||||
"status": "unsupported",
|
||||
"code": "SC2020",
|
||||
"note": "the WebCrypto object has no lowering — the lowered crypto surface is randomUUID, randomBytes, and the createHash chain"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.dgram",
|
||||
"kind": "node-builtin",
|
||||
@@ -5719,14 +5711,14 @@
|
||||
"kind": "stdlib",
|
||||
"name": "CryptoKey",
|
||||
"status": "static",
|
||||
"note": "secret HMAC keys with type, extractable, algorithm and usages, and instanceof CryptoKey"
|
||||
"note": "secret HMAC, AES, PBKDF2 and HKDF keys with type, extractable, algorithm and usages, and instanceof CryptoKey"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.webcrypto.subtle",
|
||||
"kind": "stdlib",
|
||||
"name": "globalThis.crypto.subtle",
|
||||
"status": "static",
|
||||
"note": "importKey('raw') for HMAC keys, HMAC sign and verify, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused"
|
||||
"note": "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused"
|
||||
},
|
||||
{
|
||||
"id": "syntax.async-generators",
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7120-webcrypto-random-uuid.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7121-webcrypto-crypto-object.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7122-webcrypto-node-crypto.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7123-webcrypto-node-crypto.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7124-webcrypto-secret-keys.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7125-webcrypto-key-errors.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7126-webcrypto-aes.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7127-webcrypto-aes-errors.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7128-webcrypto-derive.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7129-webcrypto-derive-errors.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/fixtures/node-types/webcrypto-keys.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -172,7 +172,8 @@ async function build() {
|
||||
...(unit.source === "scr_regex.c" ? ["-I", quickjs] : []),
|
||||
...(unit.source === "scr_tls.c" ||
|
||||
unit.source === "scr_cipher.c" ||
|
||||
unit.source === "scr_ecdh.c"
|
||||
unit.source === "scr_ecdh.c" ||
|
||||
unit.source === "scr_webcrypto_aes.c"
|
||||
? ["-I", join(mbedtls, "include")]
|
||||
: []),
|
||||
...(unit.source === "scr_zlib.c" || unit.source === "scr_fetch.c"
|
||||
|
||||
@@ -72,6 +72,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
["scr_js_object.c", "dynTier"],
|
||||
["scr_event_target.c", any("eventTarget", "nativeFetch")],
|
||||
["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "fileHandle", "nativeFetch")],
|
||||
// The crypto global's object extends scr_async_dyn.c's crypto object.
|
||||
["scr_webcrypto_object.c", all("webcrypto", "dynAsync")],
|
||||
// The performance timeline emits its buffer warning through scr_async_dyn.c.
|
||||
["scr_perf.c", "dynAsync"],
|
||||
// perf_hooks event-loop metrics, only for programs that read them.
|
||||
@@ -151,6 +153,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
["scr_ecdh.c", "ecdh"],
|
||||
["scr_wasm.c", "wasm"],
|
||||
["scr_webcrypto.c", "webcrypto"],
|
||||
// WebCrypto's AES ciphers run over the cipher feature's mbedTLS archive.
|
||||
["scr_webcrypto_aes.c", all("cipher", "webcrypto")],
|
||||
["scr_fetch.c", "nativeFetch"],
|
||||
];
|
||||
|
||||
|
||||
@@ -223,7 +223,8 @@ async function build() {
|
||||
...(unit.source === "scr_regex.c" ? ["-I", quickjs] : []),
|
||||
...(unit.source === "scr_tls.c" ||
|
||||
unit.source === "scr_cipher.c" ||
|
||||
unit.source === "scr_ecdh.c"
|
||||
unit.source === "scr_ecdh.c" ||
|
||||
unit.source === "scr_webcrypto_aes.c"
|
||||
? ["-I", join(mbedtls, "include")]
|
||||
: []),
|
||||
...(unit.source === "scr_zlib.c" || unit.source === "scr_fetch.c"
|
||||
|
||||
@@ -22,6 +22,7 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({
|
||||
"scr_tls.c",
|
||||
"scr_cipher.c",
|
||||
"scr_ecdh.c",
|
||||
"scr_webcrypto_aes.c",
|
||||
"scr_wasm.c",
|
||||
"scr_fetch.c",
|
||||
],
|
||||
|
||||
@@ -20,6 +20,9 @@ ScrStr *scr_crypto_hash_digest_bytes(ScrStr *alg, ScrBytes *data, ScrStr *enc);
|
||||
typedef struct ScrCryptoHash ScrCryptoHash;
|
||||
ScrCryptoHash *scr_crypto_hash_new(ScrStr *alg);
|
||||
ScrDyn *scr_crypto_native(void);
|
||||
/* The crypto global as a value: scr_crypto_native's object with the Crypto
|
||||
* methods (scr_webcrypto_object.c, linked with the webcrypto feature). */
|
||||
ScrDyn *scr_webcrypto_object(void);
|
||||
ScrCryptoHash *scr_crypto_hmac_new_str(ScrStr *alg, ScrStr *key);
|
||||
ScrCryptoHash *scr_crypto_hmac_new_bytes(ScrStr *alg, ScrBytes *key);
|
||||
ScrCryptoHash *scr_crypto_hash_retain(ScrCryptoHash *h);
|
||||
@@ -202,19 +205,48 @@ size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t
|
||||
size_t scr_crypto_hmac_raw(const char *alg, const unsigned char *key, size_t keylen,
|
||||
const unsigned char *data, size_t len, unsigned char out[64]);
|
||||
/* ── WebCrypto (scr_webcrypto.c) ─────────────────────────────────────
|
||||
* crypto.subtle.importKey (raw HMAC keys), sign and verify (HMAC) and
|
||||
* digest (SHA-1/SHA-2) over node:crypto's digest core. Each takes the
|
||||
* crypto.subtle.importKey (HMAC, AES, PBKDF2 and HKDF keys, raw and jwk),
|
||||
* generateKey and exportKey (HMAC and AES keys), sign and verify (HMAC)
|
||||
* and digest (SHA-1/SHA-2) over node:crypto's digest core. Each takes the
|
||||
* call's argument vector as one dyn array (borrowed) and answers a +1
|
||||
* promise that every argument or validation failure rejects with Node's
|
||||
* TypeError or DOMException; algorithms and formats outside the slice
|
||||
* reject with a coded SC2020 Error. importKey settles at once with a
|
||||
* CryptoKey handle; sign and digest fulfill with an ArrayBuffer and
|
||||
* verify with a boolean on a later loop turn. scr_webcrypto_is_crypto_key
|
||||
* answers `value instanceof CryptoKey`. */
|
||||
* reject with a coded SC2020 Error. importKey and exportKey settle at
|
||||
* once with a CryptoKey handle, an ArrayBuffer or a JWK object;
|
||||
* generateKey fulfills with a CryptoKey, sign and digest with an
|
||||
* ArrayBuffer and verify with a boolean on a later loop turn.
|
||||
* scr_webcrypto_is_crypto_key answers `value instanceof CryptoKey`. */
|
||||
ScrPromise *scr_webcrypto_import_key(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_generate_key(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_export_key(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_sign(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_verify(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_digest(ScrDyn *args);
|
||||
/* encrypt and decrypt (scr_webcrypto_aes.c, linked with the cipher
|
||||
* feature's mbedTLS archive) run scr_webcrypto_cipher with the AES cipher
|
||||
* as `aes`, which answers the +1 output bytes or NULL when the operation
|
||||
* fails. encrypt and decrypt fulfill with an ArrayBuffer, or reject with
|
||||
* a job failure, on a later loop turn. */
|
||||
typedef struct {
|
||||
const unsigned char *iv; /* the IV, or the CTR counter block */
|
||||
size_t iv_len;
|
||||
const unsigned char *aad;
|
||||
size_t aad_len;
|
||||
size_t tag_len; /* bytes */
|
||||
unsigned counter_bits;
|
||||
} ScrWebCryptoAesParams;
|
||||
typedef ScrBytes *(*ScrWebCryptoAesFn)(const char *name, bool decrypt, const unsigned char *key,
|
||||
size_t key_len, const ScrWebCryptoAesParams *params,
|
||||
const unsigned char *in, size_t len);
|
||||
ScrPromise *scr_webcrypto_cipher(ScrDyn *args, bool decrypt, ScrWebCryptoAesFn aes);
|
||||
ScrPromise *scr_webcrypto_encrypt(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_decrypt(ScrDyn *args);
|
||||
/* deriveBits and deriveKey (PBKDF2 and HKDF) over node:crypto's KDF core:
|
||||
* deriveBits fulfills with an ArrayBuffer (at once for a zero length) and
|
||||
* deriveKey with a CryptoKey of the derived key type, on a later loop
|
||||
* turn. */
|
||||
ScrPromise *scr_webcrypto_derive_bits(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_derive_key(ScrDyn *args);
|
||||
bool scr_webcrypto_is_crypto_key(const ScrDyn *value);
|
||||
|
||||
/* The composed `new crypto.X509Certificate(data).fingerprint` read (the
|
||||
|
||||
+1232
-140
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,137 @@
|
||||
/* WebCrypto's AES ciphers: crypto.subtle.encrypt and decrypt with AES-CBC,
|
||||
* AES-CTR and AES-GCM keys, over the vendored mbedTLS block cipher.
|
||||
*
|
||||
* scr_webcrypto.c converts and validates the arguments, normalizes the
|
||||
* algorithm and checks the key; this unit runs the cipher itself, the way
|
||||
* Node 24's src/crypto/crypto_aes.cc does: CBC with PKCS#7 padding, CTR
|
||||
* with a counter that wraps within its low `length` bits (and fails when
|
||||
* the input needs more blocks than those bits can count), and GCM with a
|
||||
* 12 to 128 byte IV and a 4 to 16 byte tag appended to the ciphertext.
|
||||
* The unit links only into programs that call these methods, alongside
|
||||
* the cipher feature's mbedTLS archive. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mbedtls/aes.h"
|
||||
#include "mbedtls/gcm.h"
|
||||
#include "mbedtls/platform_util.h"
|
||||
|
||||
static ScrBytes *scr_wc_aes_gcm(bool decrypt, const unsigned char *key, size_t key_len,
|
||||
const ScrWebCryptoAesParams *p, const unsigned char *in,
|
||||
size_t len) {
|
||||
if (p->iv_len < 12 || p->iv_len > 128) return NULL;
|
||||
if (decrypt && len < p->tag_len) return NULL;
|
||||
size_t out_len = decrypt ? len - p->tag_len : len + p->tag_len;
|
||||
ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)out_len);
|
||||
mbedtls_gcm_context gcm;
|
||||
mbedtls_gcm_init(&gcm);
|
||||
int rc = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, (unsigned)(key_len * 8));
|
||||
if (rc == 0 && decrypt) {
|
||||
rc = mbedtls_gcm_auth_decrypt(&gcm, out_len, p->iv, p->iv_len, p->aad, p->aad_len,
|
||||
in + out_len, p->tag_len, in, out->data);
|
||||
} else if (rc == 0) {
|
||||
rc = mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, len, p->iv, p->iv_len, p->aad,
|
||||
p->aad_len, in, out->data, p->tag_len, out->data + len);
|
||||
}
|
||||
mbedtls_gcm_free(&gcm);
|
||||
if (rc != 0) {
|
||||
mbedtls_platform_zeroize(out->data, out_len);
|
||||
scr_bytes_release(out);
|
||||
return NULL;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrBytes *scr_wc_aes_cbc(bool decrypt, const unsigned char *key, size_t key_len,
|
||||
const ScrWebCryptoAesParams *p, const unsigned char *in,
|
||||
size_t len) {
|
||||
if (decrypt && (len == 0 || len % 16 != 0)) return NULL;
|
||||
size_t padded = decrypt ? len : (len / 16 + 1) * 16;
|
||||
unsigned char *buf = malloc(padded);
|
||||
if (!buf) scr_trap("scriptc: out of memory\n");
|
||||
memcpy(buf, in, len);
|
||||
if (!decrypt) memset(buf + len, (int)(padded - len), padded - len);
|
||||
unsigned char iv[16];
|
||||
memcpy(iv, p->iv, 16);
|
||||
mbedtls_aes_context aes;
|
||||
mbedtls_aes_init(&aes);
|
||||
int rc = decrypt ? mbedtls_aes_setkey_dec(&aes, key, (unsigned)(key_len * 8))
|
||||
: mbedtls_aes_setkey_enc(&aes, key, (unsigned)(key_len * 8));
|
||||
if (rc == 0)
|
||||
rc = mbedtls_aes_crypt_cbc(&aes, decrypt ? MBEDTLS_AES_DECRYPT : MBEDTLS_AES_ENCRYPT, padded,
|
||||
iv, buf, buf);
|
||||
mbedtls_aes_free(&aes);
|
||||
size_t out_len = padded;
|
||||
if (rc == 0 && decrypt) {
|
||||
/* PKCS#7: the final byte names 1 to 16 bytes of padding, each of
|
||||
* which holds that value. Checked without data-dependent branches. */
|
||||
unsigned char pad = buf[padded - 1];
|
||||
unsigned char diff = (unsigned char)-(pad == 0 || pad > 16);
|
||||
for (unsigned i = 1; i <= 16; i++)
|
||||
diff |= (unsigned char)((buf[padded - i] ^ pad) & (unsigned char)-(i <= pad));
|
||||
if (diff) rc = -1;
|
||||
else out_len = padded - pad;
|
||||
}
|
||||
ScrBytes *out = NULL;
|
||||
if (rc == 0) {
|
||||
out = scr_bytes_new(SCR_BYTES_U8, (double)out_len);
|
||||
memcpy(out->data, buf, out_len);
|
||||
}
|
||||
mbedtls_platform_zeroize(buf, padded);
|
||||
free(buf);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Adds one to the low `bits` bits of a big-endian counter block, leaving
|
||||
* the bits above them unchanged. */
|
||||
static void scr_wc_ctr_increment(unsigned char block[16], unsigned bits) {
|
||||
for (int i = 15; i >= 0 && bits > 0; i--) {
|
||||
unsigned width = bits >= 8 ? 8 : bits;
|
||||
unsigned mask = (1u << width) - 1;
|
||||
unsigned low = ((unsigned)block[i] + 1) & mask;
|
||||
block[i] = (unsigned char)((block[i] & ~mask) | low);
|
||||
bits -= width;
|
||||
if (low != 0) return;
|
||||
}
|
||||
}
|
||||
|
||||
static ScrBytes *scr_wc_aes_ctr(const unsigned char *key, size_t key_len,
|
||||
const ScrWebCryptoAesParams *p, const unsigned char *in,
|
||||
size_t len) {
|
||||
size_t blocks = (len + 15) / 16;
|
||||
if (p->counter_bits < 64 && (double)blocks > (double)(1ull << p->counter_bits)) return NULL;
|
||||
mbedtls_aes_context aes;
|
||||
mbedtls_aes_init(&aes);
|
||||
if (mbedtls_aes_setkey_enc(&aes, key, (unsigned)(key_len * 8)) != 0) {
|
||||
mbedtls_aes_free(&aes);
|
||||
return NULL;
|
||||
}
|
||||
ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)len);
|
||||
unsigned char counter[16], stream[16];
|
||||
memcpy(counter, p->iv, 16);
|
||||
for (size_t off = 0; off < len; off += 16) {
|
||||
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, counter, stream);
|
||||
size_t n = len - off < 16 ? len - off : 16;
|
||||
for (size_t i = 0; i < n; i++) out->data[off + i] = (unsigned char)(in[off + i] ^ stream[i]);
|
||||
scr_wc_ctr_increment(counter, p->counter_bits);
|
||||
}
|
||||
mbedtls_platform_zeroize(stream, sizeof stream);
|
||||
mbedtls_aes_free(&aes);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* The cipher scr_webcrypto_cipher runs: +1 output bytes, or NULL when the
|
||||
* operation fails (Node's OperationError). */
|
||||
static ScrBytes *scr_wc_aes(const char *name, bool decrypt, const unsigned char *key,
|
||||
size_t key_len, const ScrWebCryptoAesParams *p,
|
||||
const unsigned char *in, size_t len) {
|
||||
if (strcmp(name, "AES-GCM") == 0) return scr_wc_aes_gcm(decrypt, key, key_len, p, in, len);
|
||||
if (strcmp(name, "AES-CBC") == 0) return scr_wc_aes_cbc(decrypt, key, key_len, p, in, len);
|
||||
return scr_wc_aes_ctr(key, key_len, p, in, len);
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_encrypt(ScrDyn *args) { return scr_webcrypto_cipher(args, false, scr_wc_aes); }
|
||||
|
||||
ScrPromise *scr_webcrypto_decrypt(ScrDyn *args) { return scr_webcrypto_cipher(args, true, scr_wc_aes); }
|
||||
@@ -0,0 +1,53 @@
|
||||
/* The crypto global as a value: node:crypto's dynamic Crypto object
|
||||
* (scr_crypto_native in scr_async_dyn.c, which carries subtle.digest) with
|
||||
* the Crypto object's own randomUUID and getRandomValues methods, for
|
||||
* untyped reads such as uuid's `crypto.randomUUID && crypto.randomUUID()`.
|
||||
* Node's receiver check (ERR_INVALID_THIS on a detached call) is not
|
||||
* modeled. There is one object per thread, so every spelling of the
|
||||
* global (crypto, globalThis.crypto, node:crypto's webcrypto) is the same
|
||||
* value; it is released at exit. Linked only into programs that read the
|
||||
* global as a value. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
static ScrDyn *scr_webcrypto_random_uuid_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
(void)args;
|
||||
(void)argc;
|
||||
ScrStr *uuid = scr_crypto_random_uuid();
|
||||
ScrDyn *result = scr_dyn_new_str(uuid);
|
||||
scr_str_release(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_webcrypto_get_random_values_call(ScrClosure *closure, ScrDyn *const *args,
|
||||
size_t argc) {
|
||||
(void)closure;
|
||||
if (argc == 0) {
|
||||
static const char message[] =
|
||||
"Failed to execute 'getRandomValues' on 'Crypto': 1 argument required, but only 0 present.";
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, "ERR_MISSING_ARGS");
|
||||
return NULL;
|
||||
}
|
||||
return scr_crypto_get_random_values_dyn(args[0]);
|
||||
}
|
||||
|
||||
static SCR_TL ScrDyn *scr_webcrypto_object_value;
|
||||
|
||||
static void scr_webcrypto_object_cleanup(void) {
|
||||
if (scr_webcrypto_object_value) scr_dyn_release(scr_webcrypto_object_value);
|
||||
scr_webcrypto_object_value = NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_webcrypto_object(void) {
|
||||
if (scr_webcrypto_object_value) return scr_dyn_retain(scr_webcrypto_object_value);
|
||||
ScrDyn *result = scr_crypto_native();
|
||||
scr_dyn_obj_set(result, "randomUUID", 10,
|
||||
scr_dyn_new_func(scr_closure_new(NULL, 0), scr_webcrypto_random_uuid_call, 0,
|
||||
"crypto.randomUUID", "randomUUID"));
|
||||
scr_dyn_obj_set(result, "getRandomValues", 15,
|
||||
scr_dyn_new_func(scr_closure_new(NULL, 0), scr_webcrypto_get_random_values_call, 1,
|
||||
"crypto.getRandomValues", "getRandomValues"));
|
||||
scr_webcrypto_object_value = result;
|
||||
scr_atexit(scr_webcrypto_object_cleanup);
|
||||
return scr_dyn_retain(result);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// crypto.randomUUID() on the WebCrypto global, directly and through
|
||||
// globalThis: version-4 UUIDs, distinct on every call, and inside
|
||||
// closures and default parameters the way request-id middleware uses it.
|
||||
const shape = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||
|
||||
const direct: string = crypto.randomUUID();
|
||||
console.log(direct.length, shape.test(direct));
|
||||
const viaGlobalThis = globalThis.crypto.randomUUID();
|
||||
console.log(viaGlobalThis.length, shape.test(viaGlobalThis), viaGlobalThis !== direct);
|
||||
|
||||
const seen = new Set<string>();
|
||||
for (let i = 0; i < 100; i++) seen.add(crypto.randomUUID());
|
||||
console.log("distinct", seen.size);
|
||||
|
||||
function requestId(generator: () => string = () => crypto.randomUUID()): string {
|
||||
return generator();
|
||||
}
|
||||
console.log(shape.test(requestId()), requestId(() => "fixed"));
|
||||
|
||||
const ids = ["a", "b", "c"].map((name) => ({ name, id: crypto.randomUUID() }));
|
||||
console.log(ids.every((entry) => shape.test(entry.id)), ids[0]!.id !== ids[1]!.id);
|
||||
console.log(typeof crypto.randomUUID());
|
||||
@@ -0,0 +1,35 @@
|
||||
// The crypto global from JavaScript: uuid's feature test
|
||||
// (`crypto.randomUUID && crypto.randomUUID()`), the methods read as values,
|
||||
// and getRandomValues through the dynamic object, including its
|
||||
// missing-argument and non-integer-view errors.
|
||||
const shape = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||
|
||||
function v4(options) {
|
||||
if (!options && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return "fallback";
|
||||
}
|
||||
console.log(shape.test(v4()), v4({}));
|
||||
console.log(shape.test(globalThis.crypto.randomUUID()));
|
||||
console.log(typeof crypto.randomUUID, typeof crypto.getRandomValues, typeof crypto.subtle);
|
||||
console.log(crypto.randomUUID.name, crypto.getRandomValues.name, crypto.getRandomValues.length);
|
||||
|
||||
const native = typeof crypto !== "undefined" && crypto.randomUUID ? { randomUUID: crypto.randomUUID.bind(crypto) } : {};
|
||||
console.log(shape.test(native.randomUUID()));
|
||||
|
||||
const rng = (n) => crypto.getRandomValues(new Uint8Array(n));
|
||||
console.log(rng(16).length, rng(16) instanceof Uint8Array);
|
||||
const words = new Uint32Array(4);
|
||||
console.log(crypto.getRandomValues(words) === words);
|
||||
|
||||
function attempt(label, run) {
|
||||
try {
|
||||
console.log(label, run());
|
||||
} catch (e) {
|
||||
console.log(label, e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
attempt("no argument", () => crypto.getRandomValues());
|
||||
attempt("float view", () => crypto.getRandomValues(new Float64Array(2)));
|
||||
attempt("too long", () => crypto.getRandomValues(new Uint8Array(65537)));
|
||||
@@ -0,0 +1,38 @@
|
||||
// WebCrypto reached through node:crypto: the webcrypto export (named and
|
||||
// through the namespace), the module's own subtle export, and their
|
||||
// identity with the crypto global. Each form runs the same HMAC and
|
||||
// digest calls as the global.
|
||||
import * as nodeCrypto from "node:crypto";
|
||||
import { subtle, webcrypto } from "node:crypto";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const message = encoder.encode("node:crypto webcrypto");
|
||||
|
||||
function hex(buffer: ArrayBuffer): string {
|
||||
return Buffer.from(buffer).toString("hex");
|
||||
}
|
||||
|
||||
console.log(webcrypto === crypto, webcrypto === globalThis.crypto, nodeCrypto.webcrypto === crypto);
|
||||
console.log(hex(await webcrypto.subtle.digest("SHA-256", message)));
|
||||
console.log(hex(await subtle.digest("SHA-1", message)));
|
||||
console.log(hex(await nodeCrypto.subtle.digest({ name: "SHA-384" }, message)));
|
||||
console.log(hex(await nodeCrypto.webcrypto.subtle.digest("SHA-512", message)));
|
||||
|
||||
const secret = encoder.encode("shared secret");
|
||||
const params = { name: "HMAC", hash: "SHA-256" };
|
||||
const signer = await webcrypto.subtle.importKey("raw", secret, params, false, ["sign"]);
|
||||
const verifier = await subtle.importKey("raw", secret, params, false, ["verify"]);
|
||||
const signature = await nodeCrypto.subtle.sign("HMAC", signer, message);
|
||||
console.log(hex(signature));
|
||||
console.log(await nodeCrypto.webcrypto.subtle.verify("HMAC", verifier, signature, message));
|
||||
console.log(await subtle.verify("HMAC", verifier, signature, encoder.encode("other")));
|
||||
console.log(signer instanceof CryptoKey, signer.algorithm.name, verifier.usages.join(","));
|
||||
|
||||
const shape = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||
console.log(shape.test(webcrypto.randomUUID()), shape.test(nodeCrypto.webcrypto.randomUUID()));
|
||||
console.log(webcrypto.getRandomValues(new Uint32Array(4)).length);
|
||||
try {
|
||||
await subtle.digest("MD5", message);
|
||||
} catch (e) {
|
||||
console.log((e as Error).name, (e as Error).message);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// node:crypto's WebCrypto from CommonJS: destructured webcrypto and
|
||||
// subtle, the module object's members, the
|
||||
// `globalThis.crypto ?? require("crypto").webcrypto` fallback libraries
|
||||
// use to find a Crypto object, and crypto destructured from globalThis,
|
||||
// all the same object.
|
||||
const nodeCrypto = require("node:crypto");
|
||||
const { webcrypto, subtle } = require("crypto");
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const hex = (buffer) => Buffer.from(new Uint8Array(buffer)).toString("hex");
|
||||
|
||||
function cryptoObject() {
|
||||
return globalThis.crypto ?? nodeCrypto.webcrypto;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(webcrypto === globalThis.crypto, cryptoObject() === crypto, typeof subtle);
|
||||
console.log(hex(await webcrypto.subtle.digest("SHA-256", encoder.encode("abc"))));
|
||||
console.log(hex(await subtle.digest("SHA-1", encoder.encode("abc"))));
|
||||
console.log(hex(await nodeCrypto.subtle.digest("SHA-256", encoder.encode("abc"))));
|
||||
const key = await nodeCrypto.webcrypto.subtle.importKey("raw", encoder.encode("k"), { name: "HMAC", hash: "SHA-1" }, true, ["sign"]);
|
||||
console.log(hex(await subtle.sign("HMAC", key, encoder.encode("abc"))));
|
||||
const wc = cryptoObject();
|
||||
console.log(wc.randomUUID().length, wc.getRandomValues(new Uint8Array(8)).length);
|
||||
console.log(typeof webcrypto.randomUUID, typeof nodeCrypto.webcrypto.getRandomValues);
|
||||
const { crypto: destructured } = globalThis;
|
||||
console.log(destructured === webcrypto, destructured.randomUUID().length);
|
||||
console.log(hex(await destructured.subtle.digest("SHA-1", encoder.encode("abc"))));
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,91 @@
|
||||
// crypto.crypto.subtle.generateKey, exportKey and importKey for secret keys:
|
||||
// HMAC and AES keys generated at every length, exported as raw bytes and
|
||||
// as JSON Web Keys, imported back from both forms, and PBKDF2 and HKDF
|
||||
// base keys imported from raw bytes. Random key bytes are checked by
|
||||
// length and round trip, not by value.
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
type Key = Awaited<ReturnType<typeof crypto.subtle.importKey>>;
|
||||
|
||||
function describe(key: Key): string {
|
||||
return JSON.stringify({
|
||||
type: key.type,
|
||||
extractable: key.extractable,
|
||||
algorithm: key.algorithm,
|
||||
usages: key.usages,
|
||||
});
|
||||
}
|
||||
|
||||
function hex(buffer: ArrayBuffer): string {
|
||||
return Buffer.from(buffer).toString("hex");
|
||||
}
|
||||
|
||||
async function show(label: string, run: () => Promise<string>): Promise<void> {
|
||||
try {
|
||||
console.log(label, await run());
|
||||
} catch (e) {
|
||||
const error = e as Error & { code?: string };
|
||||
console.log(label, "rejects", error.name, error.code, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) {
|
||||
await show(`hmac ${hash}`, async () => {
|
||||
const key = await crypto.subtle.generateKey({ name: "HMAC", hash }, true, ["sign", "verify"]);
|
||||
const raw = await crypto.subtle.exportKey("raw", key);
|
||||
return `${describe(key)} ${raw.byteLength}`;
|
||||
});
|
||||
}
|
||||
await show("hmac length 100", async () => {
|
||||
const key = await crypto.subtle.generateKey({ name: "HMAC", hash: { name: "SHA-512" }, length: 100 }, true, ["sign"]);
|
||||
const raw = new Uint8Array(await crypto.subtle.exportKey("raw", key));
|
||||
return `${describe(key)} ${raw.length} ${raw[12]! & 0x0f}`;
|
||||
});
|
||||
for (const name of ["AES-GCM", "AES-CBC", "AES-CTR", "AES-KW"]) {
|
||||
for (const length of [128, 192, 256]) {
|
||||
await show(`${name} ${length}`, async () => {
|
||||
const usages: ("wrapKey" | "unwrapKey" | "encrypt" | "decrypt")[] =
|
||||
name === "AES-KW" ? ["wrapKey", "unwrapKey"] : ["encrypt", "decrypt"];
|
||||
const key = await crypto.subtle.generateKey({ name, length }, true, usages);
|
||||
const jwk = await crypto.subtle.exportKey("jwk", key);
|
||||
return `${describe(key)} ${jwk.alg} ${jwk.kty} ${jwk.k!.length} ${JSON.stringify(jwk.key_ops)} ${jwk.ext}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed key bytes: the exported forms are deterministic.
|
||||
const bytes = new Uint8Array(32);
|
||||
for (let i = 0; i < bytes.length; i++) bytes[i] = i * 7 + 1;
|
||||
const hmac = await crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA-384" }, true, ["verify", "sign"]);
|
||||
const hmacJwk = await crypto.subtle.exportKey("jwk", hmac);
|
||||
console.log(JSON.stringify(hmacJwk), Object.keys(hmacJwk).join(","));
|
||||
console.log(hex(await crypto.subtle.exportKey("raw", hmac)) === Buffer.from(bytes).toString("hex"));
|
||||
const reimported = await crypto.subtle.importKey("jwk", hmacJwk, { name: "HMAC", hash: "SHA-384" }, true, ["sign"]);
|
||||
console.log(describe(reimported));
|
||||
const signature = await crypto.subtle.sign("HMAC", reimported, encoder.encode("payload"));
|
||||
console.log(await crypto.subtle.verify("HMAC", hmac, signature, encoder.encode("payload")));
|
||||
|
||||
const short = await crypto.subtle.importKey("raw", bytes.subarray(0, 20), { name: "HMAC", hash: "SHA-1", length: 156 }, true, ["sign"]);
|
||||
console.log(describe(short), JSON.stringify(await crypto.subtle.exportKey("jwk", short)));
|
||||
console.log(hex(await crypto.subtle.exportKey("raw-secret", short)));
|
||||
|
||||
for (const length of [16, 24, 32]) {
|
||||
const key = await crypto.subtle.importKey("raw", bytes.subarray(0, length), "AES-GCM", true, ["encrypt"]);
|
||||
const jwk = await crypto.subtle.exportKey("jwk", key);
|
||||
console.log(describe(key), JSON.stringify(jwk));
|
||||
const back = await crypto.subtle.importKey("jwk", jwk, { name: "AES-GCM" }, false, ["encrypt"]);
|
||||
console.log(describe(back));
|
||||
}
|
||||
const cbc = await crypto.subtle.importKey("raw-secret", bytes.subarray(0, 16), "aes-cbc", true, ["decrypt", "unwrapKey"]);
|
||||
console.log(describe(cbc), hex(await crypto.subtle.exportKey("raw", cbc)));
|
||||
const wrap = await crypto.subtle.importKey("raw", bytes.subarray(0, 24), "AES-KW", true, ["unwrapKey", "wrapKey"]);
|
||||
console.log(describe(wrap), JSON.stringify(await crypto.subtle.exportKey("jwk", wrap)));
|
||||
|
||||
const password = encoder.encode("correct horse battery staple");
|
||||
const pbkdf2 = await crypto.subtle.importKey("raw", password, "PBKDF2", false, ["deriveBits", "deriveKey"]);
|
||||
console.log(describe(pbkdf2));
|
||||
const hkdf = await crypto.subtle.importKey("raw-secret", bytes, { name: "hkdf" }, false, ["deriveKey"]);
|
||||
console.log(describe(hkdf));
|
||||
const empty = await crypto.subtle.importKey("raw", new Uint8Array(0), "PBKDF2", false, ["deriveBits"]);
|
||||
console.log(describe(empty));
|
||||
console.log(hmac instanceof CryptoKey, pbkdf2 instanceof CryptoKey);
|
||||
@@ -0,0 +1,95 @@
|
||||
// Rejections of crypto.crypto.subtle.generateKey, exportKey and importKey for
|
||||
// secret keys, in Node's order: argument conversion, algorithm
|
||||
// normalization, usages, then the per-algorithm checks, and the JSON Web
|
||||
// Key validation of oct keys.
|
||||
|
||||
async function attempt(label, run) {
|
||||
try {
|
||||
const value = await run();
|
||||
const shown = value instanceof ArrayBuffer ? `ArrayBuffer(${value.byteLength})` : value instanceof CryptoKey ? JSON.stringify(value.algorithm) : JSON.stringify(value);
|
||||
console.log(label, "resolves", shown);
|
||||
} catch (e) {
|
||||
console.log(label, e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
await attempt("generate too few", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1" }, true));
|
||||
await attempt("generate unknown", () => crypto.subtle.generateKey({ name: "FOO" }, true, ["sign"]));
|
||||
await attempt("generate PBKDF2", () => crypto.subtle.generateKey({ name: "PBKDF2" }, false, ["deriveBits"]));
|
||||
await attempt("generate no name", () => crypto.subtle.generateKey({ hash: "SHA-1" }, true, ["sign"]));
|
||||
await attempt("generate hmac string", () => crypto.subtle.generateKey("HMAC", true, ["sign"]));
|
||||
await attempt("generate hmac no hash", () => crypto.subtle.generateKey({ name: "HMAC" }, true, ["sign"]));
|
||||
await attempt("generate hmac md5", () => crypto.subtle.generateKey({ name: "HMAC", hash: "MD5" }, true, ["sign"]));
|
||||
await attempt("generate hmac bogus hash", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-999" }, true, ["sign"]));
|
||||
await attempt("generate hmac length 0", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1", length: 0 }, true, ["sign"]));
|
||||
await attempt("generate hmac length NaN", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1", length: NaN }, true, ["sign"]));
|
||||
await attempt("generate hmac length -1", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1", length: -1 }, true, ["sign"]));
|
||||
await attempt("generate hmac encrypt", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1" }, true, ["encrypt"]));
|
||||
await attempt("generate hmac no usages", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1" }, true, []));
|
||||
await attempt("generate bad usage", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1" }, true, ["fly"]));
|
||||
await attempt("generate usages string", () => crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-1" }, true, "sign"));
|
||||
await attempt("generate aes no length", () => crypto.subtle.generateKey({ name: "AES-GCM" }, true, ["encrypt"]));
|
||||
await attempt("generate aes length 100", () => crypto.subtle.generateKey({ name: "AES-GCM", length: 100 }, true, ["encrypt"]));
|
||||
await attempt("generate aes length 70000", () => crypto.subtle.generateKey({ name: "AES-CBC", length: 70000 }, true, ["encrypt"]));
|
||||
await attempt("generate aes sign", () => crypto.subtle.generateKey({ name: "AES-CTR", length: 128 }, true, ["sign"]));
|
||||
await attempt("generate aes-kw encrypt", () => crypto.subtle.generateKey({ name: "AES-KW", length: 128 }, true, ["encrypt"]));
|
||||
await attempt("generate aes no usages", () => crypto.subtle.generateKey({ name: "AES-GCM", length: 128 }, true, []));
|
||||
await attempt("generate aes string length", async () => (await crypto.subtle.generateKey({ name: "aes-gcm", length: "256" }, true, ["encrypt"])).algorithm);
|
||||
|
||||
const hmac = await crypto.subtle.importKey("raw", new Uint8Array(20), { name: "HMAC", hash: "SHA-256" }, true, ["sign"]);
|
||||
const locked = await crypto.subtle.importKey("raw", new Uint8Array(16), "AES-GCM", false, ["encrypt"]);
|
||||
const pbkdf2 = await crypto.subtle.importKey("raw", new Uint8Array(4), "PBKDF2", false, ["deriveBits"]);
|
||||
await attempt("export too few", () => crypto.subtle.exportKey("raw"));
|
||||
await attempt("export bad format", () => crypto.subtle.exportKey("pem", hmac));
|
||||
await attempt("export not a key", () => crypto.subtle.exportKey("raw", { type: "secret" }));
|
||||
await attempt("export spki", () => crypto.subtle.exportKey("spki", hmac));
|
||||
await attempt("export pkcs8", () => crypto.subtle.exportKey("pkcs8", hmac));
|
||||
await attempt("export raw-public", () => crypto.subtle.exportKey("raw-public", hmac));
|
||||
await attempt("export non-extractable", () => crypto.subtle.exportKey("raw", locked));
|
||||
await attempt("export pbkdf2", () => crypto.subtle.exportKey("raw", pbkdf2));
|
||||
await attempt("export pbkdf2 jwk", () => crypto.subtle.exportKey("jwk", pbkdf2));
|
||||
|
||||
const sixteen = new Uint8Array(16);
|
||||
await attempt("import aes 15 bytes", () => crypto.subtle.importKey("raw", new Uint8Array(15), "AES-GCM", true, ["encrypt"]));
|
||||
await attempt("import aes sign", () => crypto.subtle.importKey("raw", sixteen, "AES-GCM", true, ["sign"]));
|
||||
await attempt("import aes-kw encrypt", () => crypto.subtle.importKey("raw", sixteen, "AES-KW", true, ["encrypt"]));
|
||||
await attempt("import aes no usages", () => crypto.subtle.importKey("raw", sixteen, "AES-CBC", true, []));
|
||||
await attempt("import aes spki", () => crypto.subtle.importKey("spki", sixteen, "AES-CBC", true, ["encrypt"]));
|
||||
await attempt("import aes length ignored", () => crypto.subtle.importKey("raw", sixteen, { name: "AES-CTR", length: 256 }, true, ["encrypt"]));
|
||||
await attempt("import pbkdf2 extractable", () => crypto.subtle.importKey("raw", sixteen, "PBKDF2", true, ["deriveBits"]));
|
||||
await attempt("import pbkdf2 sign", () => crypto.subtle.importKey("raw", sixteen, "PBKDF2", false, ["sign"]));
|
||||
await attempt("import pbkdf2 no usages", () => crypto.subtle.importKey("raw", sixteen, "PBKDF2", false, []));
|
||||
await attempt("import hkdf jwk", () => crypto.subtle.importKey("jwk", { kty: "oct", k: "AAAA" }, "HKDF", false, ["deriveKey"]));
|
||||
await attempt("import hkdf pkcs8", () => crypto.subtle.importKey("pkcs8", sixteen, "HKDF", false, ["deriveKey"]));
|
||||
await attempt("sign with aes key", () => crypto.subtle.sign("HMAC", locked, sixteen));
|
||||
|
||||
const hs256 = { name: "HMAC", hash: "SHA-256" };
|
||||
const jwk = (data, alg = hs256, extractable = true, usages = ["sign"]) => crypto.subtle.importKey("jwk", data, alg, extractable, usages);
|
||||
await attempt("jwk ok", () => jwk({ kty: "oct", k: "AAECAwQFBgc" }));
|
||||
await attempt("jwk alg match", () => jwk({ kty: "oct", k: "AAECAwQFBgc", alg: "HS256" }));
|
||||
await attempt("jwk alg mismatch", () => jwk({ kty: "oct", k: "AAECAwQFBgc", alg: "HS512" }));
|
||||
await attempt("jwk no kty", () => jwk({ k: "AAECAwQFBgc" }));
|
||||
await attempt("jwk kty RSA", () => jwk({ kty: "RSA", k: "AAECAwQFBgc" }));
|
||||
await attempt("jwk no k", () => jwk({ kty: "oct" }));
|
||||
await attempt("jwk k number", () => jwk({ kty: "oct", k: 5 }));
|
||||
await attempt("jwk k empty", () => jwk({ kty: "oct", k: "" }));
|
||||
await attempt("jwk k standard alphabet", () => jwk({ kty: "oct", k: "AA+/AA==" }));
|
||||
await attempt("jwk k junk", () => jwk({ kty: "oct", k: "!!!!" }));
|
||||
await attempt("jwk use enc", () => jwk({ kty: "oct", k: "AAECAwQFBgc", use: "enc" }));
|
||||
await attempt("jwk use sig", () => jwk({ kty: "oct", k: "AAECAwQFBgc", use: "sig" }));
|
||||
await attempt("jwk key_ops superset", () => jwk({ kty: "oct", k: "AAECAwQFBgc", key_ops: ["sign", "verify", "fly"] }));
|
||||
await attempt("jwk key_ops missing", () => jwk({ kty: "oct", k: "AAECAwQFBgc", key_ops: ["verify"] }));
|
||||
await attempt("jwk key_ops duplicate", () => jwk({ kty: "oct", k: "AAECAwQFBgc", key_ops: ["sign", "sign"] }));
|
||||
await attempt("jwk key_ops string", () => jwk({ kty: "oct", k: "AAECAwQFBgc", key_ops: "sign" }));
|
||||
await attempt("jwk ext false", () => jwk({ kty: "oct", k: "AAECAwQFBgc", ext: false }));
|
||||
await attempt("jwk ext false locked", () => jwk({ kty: "oct", k: "AAECAwQFBgc", ext: false }, hs256, false));
|
||||
await attempt("jwk not an object", () => jwk("key"));
|
||||
await attempt("jwk null", () => jwk(null));
|
||||
await attempt("jwk kty symbol", () => jwk({ kty: Symbol("oct"), k: "AA" }));
|
||||
await attempt("jwk hmac length", () => jwk({ kty: "oct", k: "AAECAwQFBgc" }, { name: "HMAC", hash: "SHA-1", length: 60 }));
|
||||
await attempt("jwk hmac length mismatch", () => jwk({ kty: "oct", k: "AAECAwQFBgc" }, { name: "HMAC", hash: "SHA-1", length: 128 }));
|
||||
await attempt("jwk no usages", () => jwk({ kty: "oct", k: "AAECAwQFBgc" }, hs256, true, []));
|
||||
await attempt("jwk aes", () => jwk({ kty: "oct", k: "AAECAwQFBgcICQoLDA0ODw", alg: "A128GCM" }, "AES-GCM", true, ["encrypt"]));
|
||||
await attempt("jwk aes alg mismatch", () => jwk({ kty: "oct", k: "AAECAwQFBgcICQoLDA0ODw", alg: "A256GCM" }, "AES-GCM", true, ["encrypt"]));
|
||||
await attempt("jwk aes use sig", () => jwk({ kty: "oct", k: "AAECAwQFBgcICQoLDA0ODw", use: "sig" }, "AES-GCM", true, ["encrypt"]));
|
||||
await attempt("jwk aes length", () => jwk({ kty: "oct", k: "AAECAwQFBgc" }, "AES-CBC", true, ["encrypt"]));
|
||||
@@ -0,0 +1,77 @@
|
||||
// crypto.crypto.subtle.encrypt and decrypt with AES-GCM, AES-CBC and AES-CTR:
|
||||
// fixed keys, IVs and inputs give Node's exact ciphertexts, every key
|
||||
// length and GCM tag length round-trips, and CTR counters wrap within
|
||||
// their low `length` bits.
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
function hex(buffer: ArrayBuffer): string {
|
||||
return Buffer.from(buffer).toString("hex");
|
||||
}
|
||||
|
||||
function bytes(length: number, seed: number): Uint8Array {
|
||||
const out = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) out[i] = (i * 31 + seed) & 0xff;
|
||||
return out;
|
||||
}
|
||||
|
||||
const plaintext = encoder.encode("The quick brown fox jumps over the lazy dog");
|
||||
for (const length of [16, 24, 32]) {
|
||||
const raw = bytes(length, length);
|
||||
const gcm = await crypto.subtle.importKey("raw", raw, "AES-GCM", false, ["encrypt", "decrypt"]);
|
||||
const cbc = await crypto.subtle.importKey("raw", raw, "AES-CBC", false, ["encrypt", "decrypt"]);
|
||||
const ctr = await crypto.subtle.importKey("raw", raw, "AES-CTR", false, ["encrypt", "decrypt"]);
|
||||
const iv = bytes(12, 7);
|
||||
const sealed = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, gcm, plaintext);
|
||||
console.log("gcm", length, sealed.byteLength, hex(sealed));
|
||||
console.log(decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, gcm, sealed)));
|
||||
const aad = encoder.encode("header");
|
||||
const tagged = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData: aad, tagLength: 96 }, gcm, plaintext);
|
||||
console.log("gcm aad", length, hex(tagged));
|
||||
console.log(decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv, additionalData: aad, tagLength: 96 }, gcm, tagged)));
|
||||
const cbcIv = bytes(16, 3);
|
||||
const padded = await crypto.subtle.encrypt({ name: "AES-CBC", iv: cbcIv }, cbc, plaintext);
|
||||
console.log("cbc", length, padded.byteLength, hex(padded));
|
||||
console.log(decoder.decode(await crypto.subtle.decrypt({ name: "AES-CBC", iv: cbcIv }, cbc, padded)));
|
||||
const counter = bytes(16, 9);
|
||||
const streamed = await crypto.subtle.encrypt({ name: "AES-CTR", counter, length: 64 }, ctr, plaintext);
|
||||
console.log("ctr", length, hex(streamed));
|
||||
console.log(decoder.decode(await crypto.subtle.decrypt({ name: "AES-CTR", counter, length: 64 }, ctr, streamed)));
|
||||
}
|
||||
|
||||
const key = await crypto.subtle.importKey("raw", bytes(16, 1), "AES-GCM", true, ["encrypt", "decrypt"]);
|
||||
for (const tagLength of [32, 64, 96, 104, 112, 120, 128]) {
|
||||
const iv = bytes(16, tagLength);
|
||||
const sealed = await crypto.subtle.encrypt({ name: "AES-GCM", iv, tagLength }, key, plaintext.subarray(0, 5));
|
||||
const opened = await crypto.subtle.decrypt({ name: "AES-GCM", iv, tagLength }, key, sealed);
|
||||
console.log("tag", tagLength, sealed.byteLength, hex(sealed), decoder.decode(opened));
|
||||
}
|
||||
console.log("gcm empty", hex(await crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(12) }, key, new Uint8Array(0))));
|
||||
console.log("gcm long iv", hex(await crypto.subtle.encrypt({ name: "AES-GCM", iv: bytes(128, 5) }, key, plaintext.subarray(0, 3))));
|
||||
|
||||
const cbcKey = await crypto.subtle.importKey("raw", bytes(16, 2), "AES-CBC", false, ["encrypt", "decrypt"]);
|
||||
for (const length of [0, 1, 15, 16, 17, 32]) {
|
||||
const sealed = await crypto.subtle.encrypt({ name: "AES-CBC", iv: new Uint8Array(16) }, cbcKey, bytes(length, 4));
|
||||
const opened = await crypto.subtle.decrypt({ name: "AES-CBC", iv: new Uint8Array(16) }, cbcKey, sealed);
|
||||
console.log("cbc length", length, sealed.byteLength, opened.byteLength, hex(opened) === hex(bytes(length, 4).buffer as ArrayBuffer));
|
||||
}
|
||||
|
||||
const ctrKey = await crypto.subtle.importKey("raw", new Uint8Array(16), "AES-CTR", false, ["encrypt"]);
|
||||
const allOnes = new Uint8Array(16).fill(0xff);
|
||||
const zeros = new Uint8Array(64);
|
||||
console.log("ctr wrap 4", hex(await crypto.subtle.encrypt({ name: "AES-CTR", counter: allOnes, length: 4 }, ctrKey, zeros)));
|
||||
console.log("ctr wrap 128", hex(await crypto.subtle.encrypt({ name: "AES-CTR", counter: allOnes, length: 128 }, ctrKey, zeros)));
|
||||
console.log("ctr wrap 2", hex(await crypto.subtle.encrypt({ name: "AES-CTR", counter: allOnes, length: 2 }, ctrKey, zeros)));
|
||||
console.log("ctr 1 bit, 2 blocks", hex(await crypto.subtle.encrypt({ name: "AES-CTR", counter: new Uint8Array(16), length: 1 }, ctrKey, zeros.subarray(0, 32))));
|
||||
console.log("ctr partial", hex(await crypto.subtle.encrypt({ name: "AES-CTR", counter: bytes(16, 6), length: 128 }, ctrKey, zeros.subarray(0, 21))));
|
||||
|
||||
const generated = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||
const message = encoder.encode("generated key round trip");
|
||||
const sealed = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, generated, message);
|
||||
console.log(sealed.byteLength, decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, generated, sealed)));
|
||||
const exported = await crypto.subtle.exportKey("jwk", generated);
|
||||
const reimported = await crypto.subtle.importKey("jwk", exported, "AES-GCM", false, ["decrypt"]);
|
||||
console.log(decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, reimported, sealed)));
|
||||
console.log(decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, generated, new DataView(sealed))));
|
||||
console.log(decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv: iv.buffer as ArrayBuffer }, generated, Buffer.from(sealed))));
|
||||
@@ -0,0 +1,71 @@
|
||||
// Rejections of crypto.crypto.subtle.encrypt and decrypt in Node's order:
|
||||
// argument conversion, the AesCbcParams, AesCtrParams and AeadParams
|
||||
// dictionaries, the key checks, then the cipher's own OperationErrors
|
||||
// (bad tags, bad padding, IV and counter limits).
|
||||
|
||||
async function attempt(label, run) {
|
||||
try {
|
||||
const value = await run();
|
||||
console.log(label, "resolves", value instanceof ArrayBuffer ? `ArrayBuffer(${value.byteLength})` : value);
|
||||
} catch (e) {
|
||||
console.log(label, e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
const raw = new Uint8Array(16).map((_, i) => i);
|
||||
const gcm = await crypto.subtle.importKey("raw", raw, "AES-GCM", true, ["encrypt", "decrypt"]);
|
||||
const cbc = await crypto.subtle.importKey("raw", raw, "AES-CBC", true, ["encrypt", "decrypt"]);
|
||||
const ctr = await crypto.subtle.importKey("raw", raw, "AES-CTR", true, ["encrypt", "decrypt"]);
|
||||
const encryptOnly = await crypto.subtle.importKey("raw", raw, "AES-GCM", true, ["encrypt"]);
|
||||
const hmac = await crypto.subtle.importKey("raw", raw, { name: "HMAC", hash: "SHA-1" }, true, ["sign"]);
|
||||
const iv12 = new Uint8Array(12);
|
||||
const iv16 = new Uint8Array(16);
|
||||
const data = new TextEncoder().encode("hello world");
|
||||
|
||||
await attempt("too few", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12 }, gcm));
|
||||
await attempt("decrypt too few", () => crypto.subtle.decrypt({ name: "AES-GCM", iv: iv12 }));
|
||||
await attempt("not a key", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12 }, JSON.parse("{}"), data));
|
||||
await attempt("data string", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12 }, gcm, "hello"));
|
||||
await attempt("unknown algorithm", () => crypto.subtle.encrypt({ name: "AES-XTS" }, gcm, data));
|
||||
await attempt("AES-KW", () => crypto.subtle.encrypt({ name: "AES-KW" }, gcm, data));
|
||||
await attempt("no name", () => crypto.subtle.encrypt({ iv: iv12 }, gcm, data));
|
||||
await attempt("string algorithm", () => crypto.subtle.encrypt("AES-GCM", gcm, data));
|
||||
await attempt("gcm no iv", () => crypto.subtle.encrypt({ name: "AES-GCM" }, gcm, data));
|
||||
await attempt("gcm iv string", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: "abc" }, gcm, data));
|
||||
await attempt("gcm aad string", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12, additionalData: "x" }, gcm, data));
|
||||
await attempt("gcm aad null", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12, additionalData: null }, gcm, data));
|
||||
await attempt("gcm tag 100", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12, tagLength: 100 }, gcm, data));
|
||||
await attempt("gcm tag 300", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12, tagLength: 300 }, gcm, data));
|
||||
await attempt("gcm tag NaN", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12, tagLength: NaN }, gcm, data));
|
||||
await attempt("gcm tag string", () => crypto.subtle.encrypt({ name: "aes-gcm", iv: iv12, tagLength: "96" }, gcm, data));
|
||||
await attempt("gcm iv 11 bytes", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(11) }, gcm, data));
|
||||
await attempt("gcm iv 129 bytes", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(129) }, gcm, data));
|
||||
await attempt("gcm iv empty", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(0) }, gcm, data));
|
||||
const sealed = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12 }, gcm, data));
|
||||
const tampered = sealed.slice();
|
||||
tampered[0] ^= 1;
|
||||
await attempt("gcm tampered", () => crypto.subtle.decrypt({ name: "AES-GCM", iv: iv12 }, gcm, tampered));
|
||||
await attempt("gcm wrong aad", () => crypto.subtle.decrypt({ name: "AES-GCM", iv: iv12, additionalData: data }, gcm, sealed));
|
||||
await attempt("gcm wrong tag length", () => crypto.subtle.decrypt({ name: "AES-GCM", iv: iv12, tagLength: 96 }, gcm, sealed));
|
||||
await attempt("gcm shorter than tag", () => crypto.subtle.decrypt({ name: "AES-GCM", iv: iv12 }, gcm, new Uint8Array(15)));
|
||||
await attempt("gcm tag only", () => crypto.subtle.decrypt({ name: "AES-GCM", iv: iv12 }, gcm, new Uint8Array(16)));
|
||||
await attempt("cbc no iv", () => crypto.subtle.encrypt({ name: "AES-CBC" }, cbc, data));
|
||||
await attempt("cbc iv 12 bytes", () => crypto.subtle.encrypt({ name: "AES-CBC", iv: iv12 }, cbc, data));
|
||||
await attempt("cbc decrypt 15 bytes", () => crypto.subtle.decrypt({ name: "AES-CBC", iv: iv16 }, cbc, new Uint8Array(15)));
|
||||
await attempt("cbc decrypt empty", () => crypto.subtle.decrypt({ name: "AES-CBC", iv: iv16 }, cbc, new Uint8Array(0)));
|
||||
const block = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-CBC", iv: iv16 }, cbc, data));
|
||||
const badPad = block.slice();
|
||||
badPad[15] ^= 0x55;
|
||||
await attempt("cbc bad padding", () => crypto.subtle.decrypt({ name: "AES-CBC", iv: iv16 }, cbc, badPad));
|
||||
await attempt("cbc other iv", () => crypto.subtle.decrypt({ name: "AES-CBC", iv: new Uint8Array(16).fill(1) }, cbc, block));
|
||||
await attempt("ctr no counter", () => crypto.subtle.encrypt({ name: "AES-CTR", length: 64 }, ctr, data));
|
||||
await attempt("ctr no length", () => crypto.subtle.encrypt({ name: "AES-CTR", counter: iv16 }, ctr, data));
|
||||
await attempt("ctr length 0", () => crypto.subtle.encrypt({ name: "AES-CTR", counter: iv16, length: 0 }, ctr, data));
|
||||
await attempt("ctr length 129", () => crypto.subtle.encrypt({ name: "AES-CTR", counter: iv16, length: 129 }, ctr, data));
|
||||
await attempt("ctr length 300", () => crypto.subtle.encrypt({ name: "AES-CTR", counter: iv16, length: 300 }, ctr, data));
|
||||
await attempt("ctr counter 8 bytes", () => crypto.subtle.encrypt({ name: "AES-CTR", counter: new Uint8Array(8), length: 64 }, ctr, data));
|
||||
await attempt("ctr 1 bit, 3 blocks", () => crypto.subtle.encrypt({ name: "AES-CTR", counter: iv16, length: 1 }, ctr, new Uint8Array(48)));
|
||||
await attempt("ctr empty", () => crypto.subtle.encrypt({ name: "AES-CTR", counter: iv16, length: 1 }, ctr, new Uint8Array(0)));
|
||||
await attempt("algorithm mismatch", () => crypto.subtle.encrypt({ name: "AES-CBC", iv: iv16 }, gcm, data));
|
||||
await attempt("hmac key", () => crypto.subtle.encrypt({ name: "AES-GCM", iv: iv12 }, hmac, data));
|
||||
await attempt("missing usage", () => crypto.subtle.decrypt({ name: "AES-GCM", iv: iv12 }, encryptOnly, sealed));
|
||||
@@ -0,0 +1,45 @@
|
||||
// crypto.crypto.subtle.deriveBits and deriveKey with PBKDF2 and HKDF: Node's
|
||||
// exact derived bytes for every SHA digest, zero-length derivations, the
|
||||
// SCRAM-style PBKDF2-then-HMAC chain, and derived AES and HMAC keys that
|
||||
// encrypt and sign.
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
function hex(buffer: ArrayBuffer): string {
|
||||
return Buffer.from(buffer).toString("hex");
|
||||
}
|
||||
|
||||
const password = await crypto.subtle.importKey("raw", encoder.encode("pencil"), "PBKDF2", false, ["deriveBits", "deriveKey"]);
|
||||
const ikm = await crypto.subtle.importKey("raw", encoder.encode("input keying material"), { name: "HKDF" }, false, ["deriveBits", "deriveKey"]);
|
||||
const salt = encoder.encode("QSXCR+Q6sek8bf92");
|
||||
const info = encoder.encode("context");
|
||||
|
||||
for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) {
|
||||
console.log("pbkdf2", hash, hex(await crypto.subtle.deriveBits({ name: "PBKDF2", hash, salt, iterations: 4096 }, password, 256)));
|
||||
console.log("hkdf", hash, hex(await crypto.subtle.deriveBits({ name: "HKDF", hash: { name: hash }, salt, info }, ikm, 256)));
|
||||
}
|
||||
console.log("pbkdf2 0", (await crypto.subtle.deriveBits({ name: "PBKDF2", hash: "SHA-256", salt, iterations: 1 }, password, 0)).byteLength);
|
||||
console.log("hkdf 0", (await crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt, info }, ikm, 0)).byteLength);
|
||||
console.log("hkdf empty salt and info", hex(await crypto.subtle.deriveBits({ name: "hkdf", hash: "SHA-256", salt: new Uint8Array(0), info: new Uint8Array(0) }, ikm, 128)));
|
||||
console.log("hkdf max", (await crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-1", salt, info }, ikm, 255 * 20 * 8)).byteLength);
|
||||
|
||||
// SCRAM-SHA-256's client proof inputs, the way database drivers compute them.
|
||||
const saltedPassword = await crypto.subtle.deriveBits({ name: "PBKDF2", hash: "SHA-256", salt, iterations: 4096 }, password, 256);
|
||||
const salted = await crypto.subtle.importKey("raw", saltedPassword, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
||||
const clientKey = await crypto.subtle.sign("HMAC", salted, encoder.encode("Client Key"));
|
||||
console.log("client key", hex(clientKey));
|
||||
console.log("stored key", hex(await crypto.subtle.digest("SHA-256", clientKey)));
|
||||
|
||||
const aes = await crypto.subtle.deriveKey({ name: "PBKDF2", hash: "SHA-256", salt, iterations: 1000 }, password, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
|
||||
console.log(JSON.stringify(aes.algorithm), aes.usages.join(","), aes.extractable);
|
||||
console.log("aes key", hex(await crypto.subtle.exportKey("raw", aes)));
|
||||
const iv = new Uint8Array(12);
|
||||
const sealed = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aes, encoder.encode("derived"));
|
||||
console.log(hex(sealed), new TextDecoder().decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aes, sealed)));
|
||||
|
||||
const mac = await crypto.subtle.deriveKey({ name: "HKDF", hash: "SHA-256", salt, info }, ikm, { name: "HMAC", hash: "SHA-384" }, false, ["sign", "verify"]);
|
||||
console.log(JSON.stringify(mac.algorithm), mac.extractable);
|
||||
console.log("mac", hex(await crypto.subtle.sign("HMAC", mac, encoder.encode("message"))));
|
||||
const shortMac = await crypto.subtle.deriveKey({ name: "HKDF", hash: "SHA-1", salt, info }, ikm, { name: "HMAC", hash: "SHA-1", length: 128 }, true, ["sign"]);
|
||||
console.log(JSON.stringify(shortMac.algorithm), hex(await crypto.subtle.exportKey("raw", shortMac)));
|
||||
const wrap = await crypto.subtle.deriveKey({ name: "HKDF", hash: "SHA-512", salt, info }, ikm, { name: "AES-KW", length: 128 }, true, ["wrapKey"]);
|
||||
console.log(JSON.stringify(wrap.algorithm), JSON.stringify(await crypto.subtle.exportKey("jwk", wrap)));
|
||||
@@ -0,0 +1,72 @@
|
||||
// Rejections of crypto.crypto.subtle.deriveBits and deriveKey in Node's order:
|
||||
// argument conversion, the Pbkdf2Params and HkdfParams dictionaries, the
|
||||
// base key's usages and algorithm, the length rules, and for deriveKey
|
||||
// the derived key type's normalization and import.
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
async function attempt(label, run) {
|
||||
try {
|
||||
const value = await run();
|
||||
const shown = value instanceof ArrayBuffer ? `ArrayBuffer(${value.byteLength})` : value instanceof CryptoKey ? JSON.stringify(value.algorithm) : value;
|
||||
console.log(label, "resolves", shown);
|
||||
} catch (e) {
|
||||
console.log(label, e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
const password = await crypto.subtle.importKey("raw", encoder.encode("password"), "PBKDF2", false, ["deriveBits", "deriveKey"]);
|
||||
const ikm = await crypto.subtle.importKey("raw", encoder.encode("input key"), "HKDF", false, ["deriveBits", "deriveKey"]);
|
||||
const bitsOnly = await crypto.subtle.importKey("raw", encoder.encode("x"), "PBKDF2", false, ["deriveBits"]);
|
||||
const keyOnly = await crypto.subtle.importKey("raw", encoder.encode("x"), "PBKDF2", false, ["deriveKey"]);
|
||||
const hmac = await crypto.subtle.importKey("raw", encoder.encode("x"), { name: "HMAC", hash: "SHA-1" }, false, ["sign"]);
|
||||
const salt = encoder.encode("salt");
|
||||
const pbkdf2 = (extra = {}) => ({ name: "PBKDF2", hash: "SHA-256", salt, iterations: 1000, ...extra });
|
||||
const hkdf = (extra = {}) => ({ name: "HKDF", hash: "SHA-256", salt, info: encoder.encode("info"), ...extra });
|
||||
|
||||
await attempt("bits too few", () => crypto.subtle.deriveBits(pbkdf2()));
|
||||
await attempt("bits not a key", () => crypto.subtle.deriveBits(pbkdf2(), JSON.parse("{}"), 64));
|
||||
await attempt("bits length null", () => crypto.subtle.deriveBits(pbkdf2(), password, null));
|
||||
await attempt("bits length undefined", () => crypto.subtle.deriveBits(pbkdf2(), password, undefined));
|
||||
await attempt("bits length absent", () => crypto.subtle.deriveBits(pbkdf2(), password));
|
||||
await attempt("bits length 12", () => crypto.subtle.deriveBits(pbkdf2(), password, 12));
|
||||
await attempt("bits length -1", () => crypto.subtle.deriveBits(pbkdf2(), password, -1));
|
||||
await attempt("bits length NaN", () => crypto.subtle.deriveBits(pbkdf2(), password, NaN));
|
||||
await attempt("bits length string", () => crypto.subtle.deriveBits(pbkdf2(), password, "64"));
|
||||
await attempt("bits unknown", () => crypto.subtle.deriveBits({ name: "SCRYPT" }, password, 64));
|
||||
await attempt("bits no name", () => crypto.subtle.deriveBits({ hash: "SHA-1" }, password, 64));
|
||||
await attempt("pbkdf2 iterations 0", () => crypto.subtle.deriveBits(pbkdf2({ iterations: 0 }), password, 64));
|
||||
await attempt("pbkdf2 iterations 2^31", () => crypto.subtle.deriveBits(pbkdf2({ iterations: 2 ** 31 }), password, 64));
|
||||
await attempt("pbkdf2 iterations -1", () => crypto.subtle.deriveBits(pbkdf2({ iterations: -1 }), password, 64));
|
||||
await attempt("pbkdf2 no iterations", () => crypto.subtle.deriveBits({ name: "PBKDF2", hash: "SHA-256", salt }, password, 64));
|
||||
await attempt("pbkdf2 no salt", () => crypto.subtle.deriveBits({ name: "PBKDF2", hash: "SHA-256", iterations: 1 }, password, 64));
|
||||
await attempt("pbkdf2 no hash", () => crypto.subtle.deriveBits({ name: "PBKDF2", salt, iterations: 1 }, password, 64));
|
||||
await attempt("pbkdf2 string", () => crypto.subtle.deriveBits("PBKDF2", password, 64));
|
||||
await attempt("pbkdf2 md5", () => crypto.subtle.deriveBits(pbkdf2({ hash: "MD5" }), password, 64));
|
||||
await attempt("pbkdf2 bogus sha", () => crypto.subtle.deriveBits(pbkdf2({ hash: "SHA-999" }), password, 64));
|
||||
await attempt("pbkdf2 salt string", () => crypto.subtle.deriveBits(pbkdf2({ salt: "salt" }), password, 64));
|
||||
await attempt("pbkdf2 hkdf key", () => crypto.subtle.deriveBits(pbkdf2(), ikm, 64));
|
||||
await attempt("pbkdf2 hmac key", () => crypto.subtle.deriveBits(pbkdf2(), hmac, 64));
|
||||
await attempt("pbkdf2 no deriveBits usage", () => crypto.subtle.deriveBits(pbkdf2(), keyOnly, 64));
|
||||
await attempt("hkdf no info", () => crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt }, ikm, 64));
|
||||
await attempt("hkdf no salt", () => crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", info: salt }, ikm, 64));
|
||||
await attempt("hkdf info 1025 bytes", () => crypto.subtle.deriveBits(hkdf({ info: new Uint8Array(1025) }), ikm, 64));
|
||||
await attempt("hkdf over maximum", () => crypto.subtle.deriveBits(hkdf({ hash: "SHA-1" }), ikm, 255 * 20 * 8 + 8));
|
||||
await attempt("hkdf length 4", () => crypto.subtle.deriveBits(hkdf(), ikm, 4));
|
||||
await attempt("hkdf null", () => crypto.subtle.deriveBits(hkdf(), ikm, null));
|
||||
|
||||
const aes = { name: "AES-GCM", length: 128 };
|
||||
await attempt("key too few", () => crypto.subtle.deriveKey(pbkdf2(), password, aes, true));
|
||||
await attempt("key not a key", () => crypto.subtle.deriveKey(pbkdf2(), "key", aes, true, ["encrypt"]));
|
||||
await attempt("key no deriveKey usage", () => crypto.subtle.deriveKey(pbkdf2(), bitsOnly, aes, true, ["encrypt"]));
|
||||
await attempt("key mismatch", () => crypto.subtle.deriveKey(hkdf(), password, aes, true, ["encrypt"]));
|
||||
await attempt("key aes no length", () => crypto.subtle.deriveKey(pbkdf2(), password, { name: "AES-GCM" }, true, ["encrypt"]));
|
||||
await attempt("key aes length 100", () => crypto.subtle.deriveKey(pbkdf2(), password, { name: "AES-CBC", length: 100 }, true, ["encrypt"]));
|
||||
await attempt("key aes sign usage", () => crypto.subtle.deriveKey(pbkdf2(), password, aes, true, ["sign"]));
|
||||
await attempt("key aes no usages", () => crypto.subtle.deriveKey(pbkdf2(), password, aes, true, []));
|
||||
await attempt("key hmac no hash", () => crypto.subtle.deriveKey(pbkdf2(), password, { name: "HMAC" }, true, ["sign"]));
|
||||
await attempt("key hmac length 0", () => crypto.subtle.deriveKey(pbkdf2(), password, { name: "HMAC", hash: "SHA-1", length: 0 }, true, ["sign"]));
|
||||
await attempt("key hmac length 100", () => crypto.subtle.deriveKey(pbkdf2(), password, { name: "HMAC", hash: "SHA-1", length: 100 }, true, ["sign"]));
|
||||
await attempt("key hkdf type", () => crypto.subtle.deriveKey(pbkdf2(), password, "HKDF", false, ["deriveBits"]));
|
||||
await attempt("key unknown type", () => crypto.subtle.deriveKey(pbkdf2(), password, { name: "TWOFISH" }, true, ["encrypt"]));
|
||||
await attempt("key pbkdf2 type", () => crypto.subtle.deriveKey(hkdf(), ikm, { name: "PBKDF2" }, false, ["deriveKey"]));
|
||||
await attempt("key ok", () => crypto.subtle.deriveKey(pbkdf2(), password, aes, false, ["encrypt"]));
|
||||
@@ -1,17 +1,8 @@
|
||||
// The native WebCrypto surface is HMAC importKey('raw'), sign and verify,
|
||||
// and the SHA-1/SHA-2 digests, called directly on crypto.subtle. The 'jwk'
|
||||
// key format and crypto.subtle or its methods taken as values are refused
|
||||
// by name; the bundled declarations omit the other SubtleCrypto methods
|
||||
// (node-types/fenced.ts pins their fence under @types/node).
|
||||
const fromJwk = await crypto.subtle.importKey(
|
||||
"jwk",
|
||||
{ kty: "oct", k: "c2VjcmV0" },
|
||||
{ name: "HMAC", hash: "SHA-256" },
|
||||
false,
|
||||
["sign"],
|
||||
);
|
||||
console.log(fromJwk.type);
|
||||
|
||||
// The native WebCrypto surface is crypto.subtle's secret-key slice, called
|
||||
// directly on crypto.subtle. crypto.subtle or its methods taken as values
|
||||
// are refused by name; the bundled declarations omit the other
|
||||
// SubtleCrypto methods (node-types/fenced.ts pins their fence under
|
||||
// @types/node).
|
||||
const subtle = crypto.subtle;
|
||||
console.log(typeof subtle);
|
||||
|
||||
|
||||
Vendored
+6
-6
@@ -103,13 +103,13 @@ cipherStream.on("data", () => {});
|
||||
import { createECDH } from "node:crypto";
|
||||
createECDH("sect163k1");
|
||||
createECDH("prime256v1").computeSecret(Buffer.alloc(65), "hex");
|
||||
/* WebCrypto beyond the HMAC slice: @types/node declares the whole
|
||||
* SubtleCrypto surface, and the methods outside importKey, sign, verify
|
||||
* and digest fence by name. */
|
||||
async function encryptWithWebCrypto(key: import("node:crypto").webcrypto.CryptoKey): Promise<void> {
|
||||
await crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(12) }, key, new Uint8Array(1));
|
||||
/* WebCrypto beyond the secret-key slice: @types/node declares the whole
|
||||
* SubtleCrypto surface, and the methods outside the slice, such as
|
||||
* wrapKey, fence by name. */
|
||||
async function wrapWithWebCrypto(key: import("node:crypto").webcrypto.CryptoKey): Promise<void> {
|
||||
await crypto.subtle.wrapKey("raw", key, key, "AES-KW");
|
||||
}
|
||||
void encryptWithWebCrypto;
|
||||
void wrapWithWebCrypto;
|
||||
/* Node's navigator global: typeof navigator and navigator.userAgent
|
||||
* compile; the other members fence by name. */
|
||||
const cores = navigator.hardwareConcurrency;
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/* WebCrypto secret keys under @types/node: generateKey, exportKey and
|
||||
* importKey for HMAC and AES keys, and PBKDF2 key imports, lower to the
|
||||
* same native values as under the bundled declarations. @types/node
|
||||
* names the dictionary, key and JSON Web Key types in node:crypto's
|
||||
* webcrypto namespace. */
|
||||
import type { webcrypto } from "node:crypto";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const hmacParams: webcrypto.HmacKeyGenParams = { name: "HMAC", hash: "SHA-256", length: 256 };
|
||||
const hmac: webcrypto.CryptoKey = await crypto.subtle.generateKey(hmacParams, true, ["sign", "verify"]);
|
||||
const hmacAlgorithm = hmac.algorithm as webcrypto.HmacKeyAlgorithm;
|
||||
console.log(hmac.type, hmacAlgorithm.name, hmacAlgorithm.length, hmacAlgorithm.hash.name, hmac.usages.join(","));
|
||||
const raw: ArrayBuffer = await crypto.subtle.exportKey("raw", hmac);
|
||||
console.log(raw.byteLength);
|
||||
|
||||
const aesParams: webcrypto.AesKeyGenParams = { name: "AES-GCM", length: 192 };
|
||||
const aes = await crypto.subtle.generateKey(aesParams, true, ["encrypt", "decrypt"]);
|
||||
const aesAlgorithm = aes.algorithm as webcrypto.AesKeyAlgorithm;
|
||||
console.log(aesAlgorithm.name, aesAlgorithm.length, aes.extractable);
|
||||
const jwk: webcrypto.JsonWebKey = await crypto.subtle.exportKey("jwk", aes);
|
||||
console.log(jwk.kty, jwk.alg, jwk.ext, jwk.key_ops?.join(","), jwk.k?.length);
|
||||
const back = await crypto.subtle.importKey("jwk", jwk, "AES-GCM", false, ["decrypt"]);
|
||||
console.log(back.algorithm.name, back.extractable, back.usages.join(","));
|
||||
|
||||
const fixed = new Uint8Array(16).fill(9);
|
||||
const imported = await crypto.subtle.importKey("raw", fixed, { name: "AES-CBC" }, true, ["encrypt"]);
|
||||
console.log(Buffer.from(await crypto.subtle.exportKey("raw", imported)).toString("hex"));
|
||||
const password = await crypto.subtle.importKey("raw", new TextEncoder().encode("pw"), "PBKDF2", false, ["deriveBits"]);
|
||||
console.log(password.algorithm.name, password.extractable, password.usages.join(","));
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -228,18 +228,18 @@ fenced.ts:105:1 - error SC2020: 'ECDH.computeSecret(key, encoding) with a Buffer
|
||||
104 | createECDH("sect163k1");
|
||||
105 | createECDH("prime256v1").computeSecret(Buffer.alloc(65), "hex");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
106 | /* WebCrypto beyond the HMAC slice: @types/node declares the whole
|
||||
106 | /* WebCrypto beyond the secret-key slice: @types/node declares the whole
|
||||
|
||||
hint: Node reads the second argument as the key's input encoding and answers a Buffer; pass the output encoding third: computeSecret(key, undefined, encoding)
|
||||
|
||||
fenced.ts:110:9 - error SC2020: 'crypto.subtle.encrypt' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:110:9 - error SC2020: 'crypto.subtle.wrapKey' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
109 | async function encryptWithWebCrypto(key: import("node:crypto").webcrypto.CryptoKey): Promise<void> {
|
||||
110 | await crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(12) }, key, new Uint8Array(1));
|
||||
109 | async function wrapWithWebCrypto(key: import("node:crypto").webcrypto.CryptoKey): Promise<void> {
|
||||
110 | await crypto.subtle.wrapKey("raw", key, key, "AES-KW");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~
|
||||
111 | }
|
||||
|
||||
hint: the native WebCrypto surface is crypto.subtle.importKey('raw', ...) for HMAC keys, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle
|
||||
hint: the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle
|
||||
|
||||
fenced.ts:115:15 - error SC2020: 'navigator.hardwareConcurrency' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
webcrypto-refusals.ts:7:3 - error SC2020: 'crypto.subtle.importKey with the 'jwk' key format' is part of the standard library types but has no scriptc lowering yet
|
||||
webcrypto-refusals.ts:6:16 - error SC2020: 'crypto.subtle as a value' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
6 | const fromJwk = await crypto.subtle.importKey(
|
||||
7 | "jwk",
|
||||
| ^~~~~
|
||||
8 | { kty: "oct", k: "c2VjcmV0" },
|
||||
5 | // @types/node).
|
||||
6 | const subtle = crypto.subtle;
|
||||
| ^~~~~~~~~~~~~
|
||||
7 | console.log(typeof subtle);
|
||||
|
||||
hint: the native WebCrypto surface is crypto.subtle.importKey('raw', ...) for HMAC keys, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle
|
||||
hint: the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle
|
||||
|
||||
webcrypto-refusals.ts:15:16 - error SC2020: 'crypto.subtle as a value' is part of the standard library types but has no scriptc lowering yet
|
||||
webcrypto-refusals.ts:9:20 - error SC2020: 'crypto.subtle.sign as a value' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
14 |
|
||||
15 | const subtle = crypto.subtle;
|
||||
| ^~~~~~~~~~~~~
|
||||
16 | console.log(typeof subtle);
|
||||
|
||||
hint: the native WebCrypto surface is crypto.subtle.importKey('raw', ...) for HMAC keys, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle
|
||||
|
||||
webcrypto-refusals.ts:18:20 - error SC2020: 'crypto.subtle.sign as a value' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
17 |
|
||||
18 | console.log(typeof crypto.subtle.sign);
|
||||
8 |
|
||||
9 | console.log(typeof crypto.subtle.sign);
|
||||
| ^~~~~~~~~~~~~~~~~~
|
||||
19 |
|
||||
10 |
|
||||
|
||||
hint: the native WebCrypto surface is crypto.subtle.importKey('raw', ...) for HMAC keys, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle
|
||||
hint: the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4431, "san": 4365 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5043, "san": 4705 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4805, "san": 6923 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4728, "san": 6725 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5860, "san": 9241 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 6582, "san": 9054 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4563, "san": 6500 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4385, "san": 8939 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5042, "san": 9143 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5549, "san": 7441 }
|
||||
@@ -1634,14 +1634,14 @@ console.log(typeof promisePrototype, typeof promisePrototype.then, promiseProtot
|
||||
});
|
||||
});
|
||||
|
||||
// WebCrypto's native slice is HMAC importKey('raw'), sign and verify, and
|
||||
// the SHA-1/SHA-2 digests. Algorithms Node recognizes outside that slice
|
||||
// reject with a coded SC2020 Error instead of a Node-shaped success, and a
|
||||
// JavaScript call with the 'jwk' format meets its named fence when it runs.
|
||||
// WebCrypto's native slice is crypto.subtle's secret-key methods.
|
||||
// Algorithms Node recognizes outside that slice reject with a coded SC2020
|
||||
// Error instead of a Node-shaped success, and a JavaScript call of a
|
||||
// method outside it meets its named fence when it runs.
|
||||
// Node's own rejections (unknown names, bad usages) are corpus-pinned
|
||||
// (6292, 6294); these refusals have no Node twin, so they live here.
|
||||
describe("WebCrypto refusals (JS lane)", () => {
|
||||
test("recognized algorithms outside the HMAC slice reject with SC2020", async () => {
|
||||
test("recognized algorithms outside the secret-key slice reject with SC2020", async () => {
|
||||
const r = await compileAndRun(
|
||||
"webcrypto-unsupported-algorithms",
|
||||
`const bytes = new TextEncoder().encode("secret");
|
||||
@@ -1662,10 +1662,10 @@ await attempt("sha3", () => crypto.subtle.digest("SHA3-256", bytes));
|
||||
await attempt("sha3 hmac", () =>
|
||||
crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA3-256" }, false, ["sign"]),
|
||||
);
|
||||
const format = JSON.parse('"jwk"');
|
||||
await attempt("computed jwk", () =>
|
||||
crypto.subtle.importKey(format, { kty: "oct", k: "c2VjcmV0" }, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]),
|
||||
);
|
||||
await attempt("ed25519 key", () => crypto.subtle.generateKey({ name: "Ed25519" }, false, ["sign"]));
|
||||
const aes = await crypto.subtle.generateKey({ name: "AES-GCM", length: 128 }, false, ["encrypt"]);
|
||||
await attempt("aes-ocb", () => crypto.subtle.encrypt({ name: "AES-OCB", iv: new Uint8Array(12) }, aes, bytes));
|
||||
await attempt("ecdh bits", () => crypto.subtle.deriveBits({ name: "ECDH" }, key, 256));
|
||||
`,
|
||||
"mjs",
|
||||
);
|
||||
@@ -1676,22 +1676,25 @@ await attempt("computed jwk", () =>
|
||||
"ecdsa Error SC2020 crypto.subtle.sign with the ECDSA algorithm is not supported yet\n" +
|
||||
"sha3 Error SC2020 crypto.subtle.digest with the SHA3-256 algorithm is not supported yet\n" +
|
||||
"sha3 hmac Error SC2020 crypto.subtle.importKey with the SHA3-256 algorithm is not supported yet\n" +
|
||||
"computed jwk Error SC2020 crypto.subtle.importKey with the 'jwk' key format is not supported yet\n",
|
||||
"ed25519 key Error SC2020 crypto.subtle.generateKey with the Ed25519 algorithm is not supported yet\n" +
|
||||
"aes-ocb Error SC2020 crypto.subtle.encrypt with the AES-OCB algorithm is not supported yet\n" +
|
||||
"ecdh bits Error SC2020 crypto.subtle.deriveBits with the ECDH algorithm is not supported yet\n",
|
||||
);
|
||||
});
|
||||
|
||||
test("a literal 'jwk' import is a named fence where it runs", async () => {
|
||||
test("a method outside the slice is a named fence where it runs", async () => {
|
||||
const r = await compileAndRun(
|
||||
"webcrypto-jwk-literal",
|
||||
"webcrypto-wrap-key",
|
||||
`console.log("before");
|
||||
await crypto.subtle.importKey("jwk", { kty: "oct", k: "c2VjcmV0" }, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
||||
const key = await crypto.subtle.generateKey({ name: "AES-KW", length: 128 }, true, ["wrapKey"]);
|
||||
await crypto.subtle.wrapKey("raw", key, key, "AES-KW");
|
||||
`,
|
||||
"mjs",
|
||||
);
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.stdout).toBe("before\n");
|
||||
expect(r.stderr).toMatch(
|
||||
/^Uncaught Error: 'crypto\.subtle\.importKey with the 'jwk' key format' is part of the standard library types but has no scriptc lowering yet.*\n$/,
|
||||
/^Uncaught Error: 'crypto\.subtle\.wrapKey' is part of the standard library types but has no scriptc lowering yet.*\n$/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1132,6 +1132,24 @@ test("node-types: WebCrypto HMAC matches Node under @types/node", async () => {
|
||||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||||
});
|
||||
|
||||
test("node-types: WebCrypto secret keys match Node under @types/node", async () => {
|
||||
const outDir = outDirFor("node-webcrypto-keys");
|
||||
const entry = join(nodeTypesDir, "webcrypto-keys.ts");
|
||||
const result = await compile(entry, {
|
||||
outPath: join(outDir, "webcrypto-keys"),
|
||||
outDir,
|
||||
sanitize,
|
||||
});
|
||||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||||
if (!result.ok) return;
|
||||
const [native, node] = await Promise.all([
|
||||
execFileAsync(result.binaryPath),
|
||||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||||
]);
|
||||
expect(native.stdout).toBe(node.stdout);
|
||||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||||
});
|
||||
|
||||
test("node-types: the navigator global's runtime probe matches Node under @types/node", async () => {
|
||||
const outDir = outDirFor("node-navigator");
|
||||
const entry = join(nodeTypesDir, "navigator.ts");
|
||||
|
||||
Reference in New Issue
Block a user