mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Compile the real jose root entry: string durations, chained builders and navigator (#1407)
This commit is contained in:
@@ -35,6 +35,7 @@ output/
|
||||
!tests/fixtures/npm/cases/workspace-copied/node_modules/*/dist/
|
||||
!tests/fixtures/provenance/node_modules/*/dist/
|
||||
!tests/fixtures/npm-static/node_modules/*/dist/
|
||||
!tests/corpus/*/node_modules/*/dist/
|
||||
.scriptc/
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
@@ -328,10 +328,10 @@
|
||||
"entries": 92,
|
||||
"apiEntries": 92,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 12,
|
||||
"supported": 1,
|
||||
"partial": 15,
|
||||
"refused": 1,
|
||||
"not-implemented": 79,
|
||||
"not-implemented": 75,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -854,9 +854,9 @@
|
||||
"apiEntries": 156,
|
||||
"static": {
|
||||
"supported": 2,
|
||||
"partial": 10,
|
||||
"partial": 21,
|
||||
"refused": 0,
|
||||
"not-implemented": 144,
|
||||
"not-implemented": 133,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -35693,9 +35693,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -36360,9 +36360,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -37165,9 +37165,9 @@
|
||||
"inherited": false
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -37303,9 +37303,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "supported",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80229,9 +80229,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80252,9 +80252,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80275,9 +80275,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80298,9 +80298,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80321,9 +80321,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80344,9 +80344,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80436,9 +80436,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80574,9 +80574,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80735,9 +80735,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80758,9 +80758,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -80804,9 +80804,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
"nodeVersion": "24.22.0",
|
||||
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
|
||||
"summary": {
|
||||
"taskCount": 3454,
|
||||
"tierItemCount": 3454,
|
||||
"taskCount": 3453,
|
||||
"tierItemCount": 3453,
|
||||
"tiers": {
|
||||
"static": {
|
||||
"audit-partial": 1165,
|
||||
"verify-gap": 2161,
|
||||
"audit-partial": 1179,
|
||||
"verify-gap": 2146,
|
||||
"replace-refusal": 41,
|
||||
"classify": 2,
|
||||
"implement": 85
|
||||
@@ -17,7 +17,7 @@
|
||||
"priorities": {
|
||||
"high": 2287,
|
||||
"normal": 694,
|
||||
"low": 473
|
||||
"low": 472
|
||||
}
|
||||
},
|
||||
"tasks": [
|
||||
@@ -33201,12 +33201,18 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:globals",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:globals.CryptoKey",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -33932,12 +33938,18 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:globals",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:globals.SubtleCrypto",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -34853,12 +34865,16 @@
|
||||
"priority": "low",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:globals",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:globals.navigator",
|
||||
"tests": [
|
||||
"tests/fixtures/node-types/navigator.ts",
|
||||
"tests/harness/project-config.test.ts",
|
||||
"tests/corpus/6331-jose-root-entry/main.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -34987,31 +35003,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "globals:fc0a32e8c26f",
|
||||
"chapter": "globals",
|
||||
"label": "userAgent",
|
||||
"signature": "`userAgent` Type: {string}",
|
||||
"apiSymbol": "userAgent",
|
||||
"docsUrl": "https://nodejs.org/docs/v24.22.0/api/globals.html#navigator",
|
||||
"nodeStability": {
|
||||
"index": "1.1",
|
||||
"level": 1,
|
||||
"text": "Active development. Disable this API with the [`--no-experimental-global-navigator`][] CLI flag.",
|
||||
"inherited": true
|
||||
},
|
||||
"priority": "low",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:globals",
|
||||
"tests": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "globals:b412bf9a456d",
|
||||
"chapter": "globals",
|
||||
@@ -81024,12 +81015,18 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.subtle",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81049,12 +81046,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.CryptoKey",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81074,12 +81075,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.cryptoKey.algorithm",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81099,12 +81104,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.extractable",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81124,12 +81133,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.type",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81149,12 +81162,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.usages",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81249,12 +81266,18 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.SubtleCrypto",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81399,12 +81422,19 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.subtle.digest",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81574,12 +81604,19 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.subtle.importKey",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81599,12 +81636,19 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.subtle.sign",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81649,12 +81693,19 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webcrypto",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webcrypto.subtle.verify",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -340,10 +340,10 @@
|
||||
"entries": 94,
|
||||
"apiEntries": 92,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 12,
|
||||
"supported": 1,
|
||||
"partial": 15,
|
||||
"refused": 1,
|
||||
"not-implemented": 79,
|
||||
"not-implemented": 75,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -911,9 +911,9 @@
|
||||
"apiEntries": 156,
|
||||
"static": {
|
||||
"supported": 2,
|
||||
"partial": 10,
|
||||
"partial": 21,
|
||||
"refused": 0,
|
||||
"not-implemented": 144,
|
||||
"not-implemented": 133,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -52242,8 +52242,16 @@
|
||||
"tests/corpus/5288-blob-validation.js",
|
||||
"tests/corpus/5290-blob-dynamic.js",
|
||||
"tests/corpus/5830-timers-promises-signal.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/corpus/6331-jose-root-entry/main.ts",
|
||||
"tests/corpus/text-codec-values/main.ts",
|
||||
"tests/harness/fetch-conformance.test.ts"
|
||||
"tests/fixtures/node-types/navigator.ts",
|
||||
"tests/harness/fetch-conformance.test.ts",
|
||||
"tests/harness/project-config.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "chapter"
|
||||
@@ -52664,8 +52672,15 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:globals"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:globals.CryptoKey",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -53338,8 +53353,15 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:globals"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:globals.SubtleCrypto",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -54244,8 +54266,13 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:globals"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:globals.navigator",
|
||||
"tests": [
|
||||
"tests/fixtures/node-types/navigator.ts",
|
||||
"tests/harness/project-config.test.ts",
|
||||
"tests/corpus/6331-jose-root-entry/main.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -54382,8 +54409,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:globals"
|
||||
"status": "supported",
|
||||
"evidence": "compiler-feature:globals.userAgent",
|
||||
"tests": [
|
||||
"tests/fixtures/node-types/navigator.ts",
|
||||
"tests/harness/project-config.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -106872,10 +106903,16 @@
|
||||
"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",
|
||||
"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/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/harness/npm-static.test.ts"
|
||||
]
|
||||
},
|
||||
@@ -110411,8 +110448,15 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -110434,8 +110478,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.CryptoKey",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110457,8 +110506,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.cryptoKey.algorithm",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110480,8 +110534,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.extractable",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -110503,8 +110562,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.type",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -110526,8 +110590,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.usages",
|
||||
"tests": [
|
||||
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
|
||||
"tests/corpus/6294-webcrypto-js-values.mjs",
|
||||
"tests/corpus/6295-jose-hs256/main.mjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -110618,8 +110687,15 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.SubtleCrypto",
|
||||
"tests": [
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -110756,8 +110832,16 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.digest",
|
||||
"tests": [
|
||||
"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"
|
||||
},
|
||||
@@ -110917,8 +111001,16 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.importKey",
|
||||
"tests": [
|
||||
"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"
|
||||
},
|
||||
@@ -110940,8 +111032,16 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.sign",
|
||||
"tests": [
|
||||
"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"
|
||||
},
|
||||
@@ -110986,8 +111086,16 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webcrypto"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webcrypto.subtle.verify",
|
||||
"tests": [
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -179,11 +179,19 @@
|
||||
"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.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", "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": "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"] },
|
||||
{ "chapter": "errors", "parent": "Class: `SystemError`", "symbols": ["code", "errno", "syscall", "path", "dest"], "status": "partial", "evidence": ["tests/corpus/caught-system-error-fields.ts", "tests/fixtures/node-types/terminal-error-fields.ts", "tests/harness/project-config.test.ts"] },
|
||||
{"chapter": "events", "symbols": ["EventTarget", "eventTarget.addEventListener", "eventTarget.removeEventListener", "eventTarget.dispatchEvent"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5131-event-target-listener-errors.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5133-abort-signal-event-target.ts", "tests/corpus/5135-event-target-warnings.js", "tests/corpus/5240-event-target-subclass.ts", "tests/corpus/5242-event-target-subclass-hierarchy.ts", "tests/corpus/5243-event-target-subclass-dynamic.ts", "tests/corpus/5244-event-target-subclass.js", "tests/corpus/5245-event-target-subclass-signals.ts", "tests/corpus/5248-event-target-subclass-warnings.ts"]},
|
||||
{"chapter": "events", "symbols": ["Event"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5241-event-subclass.ts", "tests/corpus/5242-event-target-subclass-hierarchy.ts", "tests/corpus/5244-event-target-subclass.js", "tests/corpus/5247-event-target-subclass-lifetimes.ts"]},
|
||||
|
||||
+111
-6
@@ -511,14 +511,119 @@ declare var DOMException: {
|
||||
readonly prototype: DOMException;
|
||||
};
|
||||
|
||||
/* Capability-gated globals harness code probes behind feature guards
|
||||
* (globalThis.crypto under a hasCrypto check, the SQLite-backed storage
|
||||
* pair): typed `unknown` — no lowering exists, the guard is expected to
|
||||
* be false in a compiled binary, and any reached use fences per site. */
|
||||
declare var crypto: unknown;
|
||||
/* 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
|
||||
* 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. */
|
||||
declare var crypto: Crypto;
|
||||
/* The Crypto and SubtleCrypto constructors (feature probes only) stay
|
||||
* `unknown`; the values keep this declaration order. */
|
||||
declare var Crypto: unknown;
|
||||
declare var CryptoKey: unknown;
|
||||
declare var CryptoKey: {
|
||||
prototype: CryptoKey;
|
||||
new (): CryptoKey;
|
||||
};
|
||||
declare var SubtleCrypto: unknown;
|
||||
type KeyUsage =
|
||||
| "encrypt"
|
||||
| "decrypt"
|
||||
| "sign"
|
||||
| "verify"
|
||||
| "deriveKey"
|
||||
| "deriveBits"
|
||||
| "encapsulateBits"
|
||||
| "decapsulateBits"
|
||||
| "encapsulateKey"
|
||||
| "decapsulateKey"
|
||||
| "wrapKey"
|
||||
| "unwrapKey";
|
||||
type KeyType = "private" | "public" | "secret";
|
||||
interface Algorithm {
|
||||
name: string;
|
||||
}
|
||||
type AlgorithmIdentifier = Algorithm | string;
|
||||
type HashAlgorithmIdentifier = AlgorithmIdentifier;
|
||||
interface HmacImportParams extends Algorithm {
|
||||
hash: HashAlgorithmIdentifier;
|
||||
length?: number;
|
||||
}
|
||||
interface KeyAlgorithm {
|
||||
name: string;
|
||||
}
|
||||
interface HmacKeyAlgorithm extends KeyAlgorithm {
|
||||
hash: KeyAlgorithm;
|
||||
length: number;
|
||||
}
|
||||
interface CryptoKey {
|
||||
readonly algorithm: KeyAlgorithm;
|
||||
readonly extractable: boolean;
|
||||
readonly type: KeyType;
|
||||
readonly usages: KeyUsage[];
|
||||
}
|
||||
type KeyFormat =
|
||||
| "jwk"
|
||||
| "pkcs8"
|
||||
| "raw"
|
||||
| "raw-private"
|
||||
| "raw-public"
|
||||
| "raw-secret"
|
||||
| "raw-seed"
|
||||
| "spki";
|
||||
interface JsonWebKey {
|
||||
alg?: string;
|
||||
crv?: string;
|
||||
d?: string;
|
||||
dp?: string;
|
||||
dq?: string;
|
||||
e?: string;
|
||||
ext?: boolean;
|
||||
k?: string;
|
||||
key_ops?: string[];
|
||||
kty?: string;
|
||||
n?: string;
|
||||
p?: string;
|
||||
q?: string;
|
||||
qi?: string;
|
||||
use?: string;
|
||||
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 {
|
||||
digest(algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer>;
|
||||
importKey(
|
||||
format: "jwk",
|
||||
keyData: JsonWebKey,
|
||||
algorithm: AlgorithmIdentifier | HmacImportParams,
|
||||
extractable: boolean,
|
||||
keyUsages: readonly KeyUsage[],
|
||||
): Promise<CryptoKey>;
|
||||
importKey(
|
||||
format: Exclude<KeyFormat, "jwk">,
|
||||
keyData: BufferSource,
|
||||
algorithm: AlgorithmIdentifier | HmacImportParams,
|
||||
extractable: boolean,
|
||||
keyUsages: readonly KeyUsage[],
|
||||
): Promise<CryptoKey>;
|
||||
sign(algorithm: AlgorithmIdentifier, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>;
|
||||
verify(
|
||||
algorithm: AlgorithmIdentifier,
|
||||
key: CryptoKey,
|
||||
signature: BufferSource,
|
||||
data: BufferSource,
|
||||
): Promise<boolean>;
|
||||
}
|
||||
interface Crypto {
|
||||
readonly subtle: SubtleCrypto;
|
||||
}
|
||||
|
||||
/* Capability-gated globals harness code probes behind feature guards
|
||||
* (the SQLite-backed storage pair): typed `unknown` — no lowering exists,
|
||||
* the guard is expected to be false in a compiled binary, and any reached
|
||||
* use fences per site. */
|
||||
declare var localStorage: unknown;
|
||||
declare var sessionStorage: unknown;
|
||||
|
||||
|
||||
@@ -40,5 +40,6 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
tls: features.tls,
|
||||
tlsCa: features.tlsCa,
|
||||
cipher: features.cipher,
|
||||
webcrypto: features.webcrypto,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export async function compilePackedLibrary(
|
||||
nodeTest: false,
|
||||
tls: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
tlsCa: false,
|
||||
};
|
||||
const selection = await loadRuntimePack({
|
||||
|
||||
@@ -832,6 +832,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
case "util":
|
||||
case "bigint":
|
||||
case "crypto":
|
||||
case "webcrypto":
|
||||
case "buffer":
|
||||
case "bytes":
|
||||
case "tlsca":
|
||||
|
||||
@@ -588,6 +588,11 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"crypto.generateKeySync": "scr_crypto_generate_key_sync",
|
||||
"crypto.keyObjectInput": "scr_crypto_key_object_input",
|
||||
"crypto.isKeyObject": "scr_crypto_is_key_object",
|
||||
"webcrypto.importKey": "scr_webcrypto_import_key",
|
||||
"webcrypto.sign": "scr_webcrypto_sign",
|
||||
"webcrypto.verify": "scr_webcrypto_verify",
|
||||
"webcrypto.digest": "scr_webcrypto_digest",
|
||||
"webcrypto.isCryptoKey": "scr_webcrypto_is_crypto_key",
|
||||
"crypto.randomUUIDv7": "scr_crypto_random_uuid_v7",
|
||||
"crypto.pbkdf2": "scr_crypto_pbkdf2",
|
||||
"crypto.hkdf": "scr_crypto_hkdf",
|
||||
@@ -1471,6 +1476,10 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"process.emitWarning",
|
||||
// Decipheriv.setAuthTag reports DEP0182 on the next tick.
|
||||
"crypto.cipherNew",
|
||||
// WebCrypto results settle through the deferred crypto queue.
|
||||
"webcrypto.sign",
|
||||
"webcrypto.verify",
|
||||
"webcrypto.digest",
|
||||
"process.emitWarningText",
|
||||
// The legacy url entries queue DEP0169/DEP0170 on the next tick.
|
||||
"url.legacyParse",
|
||||
|
||||
@@ -51,6 +51,9 @@ export interface NativeLinkFeatures {
|
||||
* archive. */
|
||||
cipher: boolean;
|
||||
tlsCa: boolean;
|
||||
/** crypto.subtle importKey/sign/verify/digest and CryptoKey values:
|
||||
* scr_webcrypto.c. */
|
||||
webcrypto: boolean;
|
||||
}
|
||||
|
||||
export interface NativeLinkInfo {
|
||||
|
||||
@@ -199,6 +199,8 @@ export interface CcOptions {
|
||||
/** crypto.createCipheriv/createDecipheriv: compiles scr_cipher.c and links
|
||||
* the mbedTLS archive (shared with tls). */
|
||||
cipher?: boolean;
|
||||
/** crypto.subtle importKey/sign/verify/digest: compiles scr_webcrypto.c. */
|
||||
webcrypto?: boolean;
|
||||
/** Internal compiler hook: called only after a strict native artifact hit
|
||||
* or a successful stable build has installed `outPath`. The executable
|
||||
* frontend cache uses it to publish its stamp after native dependencies
|
||||
|
||||
@@ -578,6 +578,7 @@ export async function compileCInternal(
|
||||
...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []),
|
||||
...(opts.foreignFfi ? [rt(join(rtDir, "scr_ffi_queue.c"))] : []),
|
||||
...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []),
|
||||
...(opts.webcrypto ? [rt(join(rtDir, "scr_webcrypto.c"))] : []),
|
||||
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
|
||||
// references its default-set override unconditionally.
|
||||
...(tlsCa ? [rt(join(rtDir, "scr_tls_ca.c"))] : []),
|
||||
|
||||
@@ -119,6 +119,7 @@ const features = {
|
||||
tls: false,
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
};
|
||||
|
||||
test.each([
|
||||
|
||||
@@ -56,6 +56,7 @@ const BASE: NativeLinkFeatures = {
|
||||
tls: false,
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
};
|
||||
|
||||
async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
|
||||
@@ -61,6 +61,7 @@ export async function warmNativeCaches(
|
||||
nodeTest: false,
|
||||
tls: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
tlsCa: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,7 +11,8 @@ afterAll(() => rmSync(dir, { recursive: true, force: true }));
|
||||
|
||||
test("unchanged and comment-only reruns replay the verdict; code edits relower", () => {
|
||||
const entry = join(dir, "main.ts");
|
||||
const program = `const pairs: [number, number][] = [[1, 2]];\nconst [[a, b]] = pairs;\nconsole.log(a + b);\nconst { length } = [1, 2] as number[];\nconsole.log(length);\n`;
|
||||
// The blocker: an object pattern over a union source (arrays alone compile).
|
||||
const program = `const pairs: [number, number][] = [[1, 2]];\nconst [[a, b]] = pairs;\nconsole.log(a + b);\nconst { length } = (a > 9 ? [1, 2] : "ab") as number[] | string;\nconsole.log(length);\n`;
|
||||
writeFileSync(entry, program);
|
||||
const entries = new Map<string, string>();
|
||||
const store: VerdictStore = {
|
||||
|
||||
@@ -57,6 +57,7 @@ const native: EarlyExecutableNativeFeatures = {
|
||||
tls: false,
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
};
|
||||
|
||||
async function fixture(): Promise<{
|
||||
|
||||
@@ -80,6 +80,7 @@ export interface EarlyExecutableNativeFeatures {
|
||||
tls: boolean;
|
||||
cipher: boolean;
|
||||
tlsCa: boolean;
|
||||
webcrypto: boolean;
|
||||
}
|
||||
|
||||
interface EarlyExecutableCacheStamp {
|
||||
@@ -193,6 +194,7 @@ const BOOLEAN_NATIVE_KEYS = [
|
||||
"tls",
|
||||
"tlsCa",
|
||||
"cipher",
|
||||
"webcrypto",
|
||||
] as const satisfies readonly (keyof EarlyExecutableNativeFeatures)[];
|
||||
|
||||
function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeatures {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Node 24's `navigator` global (lib/internal/navigator.js), the part
|
||||
* libraries read to tell runtimes apart: `typeof navigator` is "object"
|
||||
* and `navigator.userAgent` is `Node.js/<major>`. jose reads exactly
|
||||
* that at module load (jwks/remote.js). Both fold to string literals, so
|
||||
* nothing links. The other members (hardwareConcurrency, language,
|
||||
* languages, platform, locks) and the object as a value keep a named
|
||||
* refusal. */
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { strLit } from "../../../ir/build.js";
|
||||
import type { IrExpr } from "../../../ir/ir.js";
|
||||
import { isJsSourceFile, locOf } from "../../program.js";
|
||||
import type { Lowerer } from "../lowerer.js";
|
||||
|
||||
/** True when `node` names Node's global `navigator`: the @types/node
|
||||
* declaration, or, in a JavaScript file, the free name no declaration
|
||||
* binds (Node defines it on the global object). */
|
||||
export function isNavigatorGlobal(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
let expr = node;
|
||||
while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
|
||||
if (!ts.isIdentifier(expr) || expr.text !== "navigator") return false;
|
||||
if (lowerer.isStdlibGlobal(expr, "navigator")) return true;
|
||||
return (
|
||||
isJsSourceFile(expr.getSourceFile()) && lowerer.checker.getSymbolAtLocation(expr) === undefined
|
||||
);
|
||||
}
|
||||
|
||||
/** `navigator.userAgent` (`Node.js/<major>` of `nodeVersion`, the pinned
|
||||
* release builtins/process.ts names), or the named refusal for another
|
||||
* member. Null when the receiver is not the global. */
|
||||
export function lowerNavigatorMember(
|
||||
lowerer: Lowerer,
|
||||
access: ts.PropertyAccessExpression,
|
||||
nodeVersion: string,
|
||||
): IrExpr | null {
|
||||
if (!isNavigatorGlobal(lowerer, access.expression)) return null;
|
||||
if (access.name.text === "userAgent") {
|
||||
return strLit(`Node.js/${nodeVersion.split(".")[0]}`, locOf(access));
|
||||
}
|
||||
return refuseNavigator(
|
||||
lowerer,
|
||||
access,
|
||||
`navigator.${access.name.text}`,
|
||||
lowerer.checker.getSymbolAtLocation(access.name),
|
||||
);
|
||||
}
|
||||
|
||||
/** The refusal for the `navigator` object itself or an unlowered member. */
|
||||
export function refuseNavigator(
|
||||
lowerer: Lowerer,
|
||||
node: ts.Node,
|
||||
what: string,
|
||||
symbol?: ts.Symbol,
|
||||
): never {
|
||||
return lowerer.noLowering(
|
||||
what,
|
||||
node,
|
||||
"of Node's navigator global only typeof navigator and navigator.userAgent compile",
|
||||
symbol ?? null,
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,19 @@ import { type Lowerer } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { lowerOptionalArgument, lowerStringSearchArgument } from "../optional-arguments.js";
|
||||
import { lowerToNumberArgument } from "../lower-exprs.js";
|
||||
import { F64, type IrExpr, STRING, arrayOf, isUnitType, typeEquals } from "../../../ir/ir.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
F64,
|
||||
type IrExpr,
|
||||
type IrStmt,
|
||||
NULL_T,
|
||||
STRING,
|
||||
arrayOf,
|
||||
bytesOf,
|
||||
isUnitType,
|
||||
typeEquals,
|
||||
} from "../../../ir/ir.js";
|
||||
|
||||
/** `String.fromCharCode/fromCodePoint(...codes)` on THE String global: every argument
|
||||
* lowers as a number and packs into ONE f64[] array-literal argument
|
||||
@@ -17,6 +29,8 @@ export function lowerStringStaticCall(
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
if (call.questionDotToken) return null;
|
||||
const applied = lowerFromCharCodeApply(lowerer, call, access);
|
||||
if (applied) return applied;
|
||||
const member = lowerer.stdlibGlobalMember(access, "String");
|
||||
if (member !== "fromCharCode" && member !== "fromCodePoint" && member !== "raw") return null;
|
||||
const loc = locOf(call);
|
||||
@@ -108,6 +122,99 @@ export function lowerStringStaticCall(
|
||||
return { kind: "libCall", fn: `string.${member}`, args: [packed], type: STRING, loc };
|
||||
}
|
||||
|
||||
/** `String.fromCharCode.apply(thisArg, codes)`, the btoa chunking idiom
|
||||
* (`String.fromCharCode.apply(null, bytes.subarray(i, j))`): the receiver
|
||||
* is ignored, and a typed array or number array forwards to the packed
|
||||
* fromCharCode like a spread. An untyped list reads a Uint8Array the same
|
||||
* way at run time; any other value keeps Function.prototype.apply's
|
||||
* generic array-like call. Null for every other call. */
|
||||
function lowerFromCharCodeApply(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
if (access.name.text !== "apply" || access.questionDotToken) return null;
|
||||
const target = access.expression;
|
||||
if (
|
||||
!ts.isPropertyAccessExpression(target) ||
|
||||
lowerer.stdlibGlobalMember(target, "String") !== "fromCharCode"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (call.arguments.nodes.length !== 2 || call.arguments.nodes.some(ts.isSpreadElement))
|
||||
return null;
|
||||
const listNode = call.arguments.nodes[1]!;
|
||||
const checkerT = lowerer.typeOf(listNode);
|
||||
const untyped = (checkerT.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
|
||||
const listT = untyped ? DYN : lowerer.mapTypeOf(checkerT);
|
||||
if (
|
||||
listT === null ||
|
||||
(listT.kind !== "bytes" && listT.kind !== "dyn" && !typeEquals(listT, arrayOf(F64)))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const loc = locOf(call);
|
||||
const thisArg = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN);
|
||||
const stmts: IrStmt[] = [{ kind: "exprStmt", expr: thisArg, loc: thisArg.loc }];
|
||||
if (listT.kind !== "dyn") {
|
||||
const list = lowerer.lowerExprExpecting(listNode, listT);
|
||||
const result: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "string.fromCharCode",
|
||||
args: [list],
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
return { kind: "seqExpr", stmts, result, type: STRING, loc };
|
||||
}
|
||||
const listLocal = lowerer.declareHiddenLocal("%charCodes", DYN);
|
||||
stmts.push({
|
||||
kind: "varDecl",
|
||||
localId: listLocal.id,
|
||||
init: lowerer.lowerExprExpecting(listNode, DYN),
|
||||
loc,
|
||||
});
|
||||
const list = (): IrExpr => ({ kind: "varRef", localId: listLocal.id, type: DYN, loc });
|
||||
const bytes = bytesOf("u8");
|
||||
const generic: IrExpr = {
|
||||
kind: "dynCheck",
|
||||
value: {
|
||||
kind: "dynInvoke",
|
||||
recv: lowerer.lowerExprExpecting(target, DYN),
|
||||
method: "apply",
|
||||
calleeName: "String.fromCharCode.apply",
|
||||
args: [
|
||||
{
|
||||
kind: "dynFrom",
|
||||
value: { kind: "unitLit", unit: "null", type: NULL_T, loc },
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
list(),
|
||||
],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
const result: IrExpr = {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "bytes", bytesElem: "u8", value: list(), type: BOOL, loc },
|
||||
then: {
|
||||
kind: "libCall",
|
||||
fn: "string.fromCharCode",
|
||||
args: [{ kind: "dynCheck", value: list(), type: bytes, loc }],
|
||||
type: STRING,
|
||||
loc,
|
||||
},
|
||||
else_: generic,
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
return { kind: "seqExpr", stmts, result, type: STRING, loc };
|
||||
}
|
||||
|
||||
/** `s.lastIndexOf(searchValue?, position?)` on string receivers, using UTF-16
|
||||
* indices. An omitted search value searches for "undefined". Omitted or undefined positions clamp to the string's end;
|
||||
* MAX_SAFE_INTEGER has the same effect for every representable string.
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { isJsSourceFile, locOf } from "../../program.js";
|
||||
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";
|
||||
|
||||
/* 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
|
||||
* 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"],
|
||||
["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";
|
||||
|
||||
/** Whether `node` is `crypto.subtle` on the WebCrypto global. */
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
/** `crypto.subtle.<method>(...args)`: the lowered methods become their
|
||||
* libCall over the argument vector; every other method is refused. */
|
||||
export function lowerWebCryptoCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null {
|
||||
const callee = call.expression;
|
||||
if (!ts.isPropertyAccessExpression(callee) || !isSubtleReceiver(lowerer, callee.expression)) {
|
||||
return null;
|
||||
}
|
||||
const member = callee.name.text;
|
||||
const fn = SUBTLE_LOWERED.get(member);
|
||||
if (fn === undefined) lowerer.noLowering(`crypto.subtle.${member}`, callee, SUBTLE_HINT);
|
||||
if (callee.questionDotToken || call.questionDotToken) {
|
||||
lowerer.noLowering(`optional calls of crypto.subtle.${member}`, call, SUBTLE_HINT);
|
||||
}
|
||||
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)
|
||||
? lowerDynObjectLiteral(lowerer, arg)
|
||||
: lowerer.lowerExprExpecting(arg, DYN),
|
||||
);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn,
|
||||
args: [{ kind: "dynArrLit", elems: args, type: DYN, loc }],
|
||||
type: { kind: "promise", inner: member === "verify" ? BOOL : DYN },
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `v instanceof CryptoKey` against the global. */
|
||||
export function lowerCryptoKeyInstanceof(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.BinaryExpression,
|
||||
): IrExpr | null {
|
||||
if (stdlibGlobalNameOf(lowerer, expr.right) !== "CryptoKey") return null;
|
||||
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "webcrypto.isCryptoKey",
|
||||
args: [value],
|
||||
type: BOOL,
|
||||
loc: locOf(expr),
|
||||
};
|
||||
}
|
||||
|
||||
/** TypeScript reads of `crypto.subtle` and its members outside a direct
|
||||
* call: the global's dynamic object does not carry the lowered methods,
|
||||
* so a detached read is refused rather than answering a value without
|
||||
* them. JavaScript keeps the dynamic object's reads. */
|
||||
export function fenceWebCryptoMember(
|
||||
lowerer: Lowerer,
|
||||
access: ts.PropertyAccessExpression | ts.ElementAccessExpression,
|
||||
use: "read" | "call",
|
||||
): IrExpr | null {
|
||||
if (use !== "read" || !ts.isPropertyAccessExpression(access)) return null;
|
||||
if (isJsSourceFile(access.getSourceFile())) return null;
|
||||
if (isSubtleReceiver(lowerer, access)) {
|
||||
lowerer.noLowering("crypto.subtle as a value", access, SUBTLE_HINT);
|
||||
}
|
||||
if (isSubtleReceiver(lowerer, access.expression)) {
|
||||
lowerer.noLowering(`crypto.subtle.${access.name.text} as a value`, access, SUBTLE_HINT);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Whether a checker type is one of the WebCrypto interfaces reached
|
||||
* through the crypto global's members (SubtleCrypto, CryptoKey and the
|
||||
* algorithm dictionaries). While `crypto` was typed `unknown` those member
|
||||
* reads were untyped, so JavaScript destructurings of them keep the
|
||||
* module-global path untyped bindings take. `Crypto` itself is excluded:
|
||||
* the global was `unknown`, which already mapped to dyn. */
|
||||
export function isWebCryptoMemberType(lowerer: Lowerer, type: ts.Type): boolean {
|
||||
const symbol = type.getAliasSymbol() ?? type.getSymbol();
|
||||
return (
|
||||
symbol !== undefined &&
|
||||
symbol.name !== "Crypto" &&
|
||||
WEBCRYPTO_DYN_TYPES.has(symbol.name) &&
|
||||
lowerer.isStdlibSymbol(symbol)
|
||||
);
|
||||
}
|
||||
@@ -686,12 +686,23 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
};
|
||||
// The declared result is the value's own type (the generic's T):
|
||||
// validate the dyn copy back into it when the type can be checked;
|
||||
// dyn-typed and unmappable results stay dyn values (JS files).
|
||||
// dyn-typed and unmappable results stay dyn values (JS files). An
|
||||
// empty record (`{}` inferred from a JavaScript `payload = {}`
|
||||
// default) is the checker's supertype of whatever arrives, not a
|
||||
// layout: checking into it would drop every member of the copy.
|
||||
const resultT = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
const resultShape =
|
||||
resultT?.kind === "record" ? lowerer.shapes.get(resultT.shapeId) : undefined;
|
||||
const emptyRecord =
|
||||
resultShape !== undefined &&
|
||||
!resultShape.tuple &&
|
||||
resultShape.indexValue === undefined &&
|
||||
resultShape.fields.length === 0;
|
||||
if (
|
||||
resultT !== null &&
|
||||
resultT.kind !== "dyn" &&
|
||||
resultT.kind !== "void" &&
|
||||
!emptyRecord &&
|
||||
canDynCheckTo(
|
||||
resultT,
|
||||
(id) => lowerer.shapes.get(id),
|
||||
|
||||
@@ -178,6 +178,39 @@ export function returnsOnlyThis(member: ts.MethodDeclaration): boolean {
|
||||
return valid;
|
||||
}
|
||||
|
||||
/** returnsOnlyThis, also accepting a comma expression whose last operand
|
||||
* is `this` (the minifier's `return a(), this.x = v, this`, jose's fluent
|
||||
* claim setters). Used only to narrow a call's result back to the
|
||||
* receiver's class. */
|
||||
export function returnsOnlyThisValue(member: ts.MethodDeclaration): boolean {
|
||||
const body = member.body;
|
||||
const last = body?.statements?.nodes.at(-1);
|
||||
if (!body || !last || !ts.isReturnStatement(last)) return false;
|
||||
let valid = true;
|
||||
const visit = (node: ts.Node): void => {
|
||||
if (ts.isFunctionLike(node)) return;
|
||||
if (ts.isReturnStatement(node)) {
|
||||
let value = node.expression;
|
||||
for (;;) {
|
||||
while (value && ts.isParenthesizedExpression(value)) value = value.expression;
|
||||
if (
|
||||
value &&
|
||||
ts.isBinaryExpression(value) &&
|
||||
value.operatorToken.kind === ts.SyntaxKind.CommaToken
|
||||
) {
|
||||
value = value.right;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!value || value.kind !== ts.SyntaxKind.ThisKeyword) valid = false;
|
||||
}
|
||||
node.forEachChild(visit);
|
||||
};
|
||||
body.forEachChild(visit);
|
||||
return valid;
|
||||
}
|
||||
|
||||
function functionLocalClass(decl: ts.ClassLikeDeclaration): boolean {
|
||||
for (
|
||||
let parent: ts.Node | undefined = decl.parent;
|
||||
@@ -6970,19 +7003,43 @@ export function lowerClassGenericMethodCall(
|
||||
}
|
||||
: upcastTo(lowerer, receiver, declarer.def.name);
|
||||
const args = lowerer.completeArgs(call.arguments.nodes, instance.params, loc, call);
|
||||
return {
|
||||
const result: IrExpr = {
|
||||
kind: "call",
|
||||
callee: instance.name,
|
||||
args: [thisArg, ...args],
|
||||
type: instance.returnType,
|
||||
loc,
|
||||
};
|
||||
// A fluent JavaScript method (every return ends in `this`, jose's claim
|
||||
// setters) answers the receiver: the instance returns the declarer's
|
||||
// class, a used result's checker type is the receiver's subclass.
|
||||
if (
|
||||
result.type.kind === "object" &&
|
||||
!ts.isExpressionStatement(call.parent) &&
|
||||
ts.isMethodDeclaration(info.decl) &&
|
||||
isJsSourceFile(info.decl.getSourceFile()) &&
|
||||
returnsOnlyThisValue(info.decl)
|
||||
) {
|
||||
return lowerer.maybeNarrow(result, call);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Wraps a derived-class expression in an upcast when the target base
|
||||
* class differs (a no-op reinterpret at runtime; keeps IR types exact). */
|
||||
export function upcastTo(lowerer: Lowerer, expr: IrExpr, className: string): IrExpr {
|
||||
if (expr.type.kind === "object" && expr.type.className !== className) {
|
||||
// A value lowered at an ANCESTOR of the class the site needs (a
|
||||
// fluent `this` result whose lowering kept the method's declaring
|
||||
// class, the receiver of the next call in a chain): the checker
|
||||
// vouches for the subclass, so the bridge is the verified downcast,
|
||||
// never an upcast the validator rejects (an internal error before).
|
||||
if (
|
||||
!lowerer.isSubclassOf(expr.type.className, className) &&
|
||||
lowerer.isSubclassOf(className, expr.type.className)
|
||||
) {
|
||||
return { kind: "downcast", value: expr, type: { kind: "object", className }, loc: expr.loc };
|
||||
}
|
||||
return { kind: "upcast", value: expr, type: { kind: "object", className }, loc: expr.loc };
|
||||
}
|
||||
return expr;
|
||||
|
||||
@@ -6,13 +6,14 @@ import { lowerEventObjectProperty, lowerEventObjectPropertyWrite } from "./lower
|
||||
import { lowerDisposableStackInstanceOf } from "./builtins/disposable-stack.js";
|
||||
import { lowerEventStaticProperty } from "./builtins/numbers.js";
|
||||
import { lowerUnionEquality, tagEqualityMayMissAlias } from "./strict-equality.js";
|
||||
import { isOptionalProcessStreamProperty } from "./builtins/process.js";
|
||||
import { isOptionalProcessStreamProperty, NODE_COMPAT_VERSION } from "./builtins/process.js";
|
||||
import { lowerWorkerMetadata } from "./builtins/workers.js";
|
||||
import { esmImportBindingKind } from "./builtins/module-bindings.js";
|
||||
import { lowerReadlineProperty } from "./builtins/readline.js";
|
||||
import { lowerBlobInstanceof } from "./builtins/blob.js";
|
||||
import { lowerWebAssemblyTypeof, refuseWebAssemblyUse } from "./builtins/webassembly.js";
|
||||
import { lowerKeyObjectInstanceof } from "./builtins/key-object.js";
|
||||
import { lowerCryptoKeyInstanceof } from "./builtins/webcrypto.js";
|
||||
import { lowerNetAddressInstanceof, lowerNetAddressStaticRead } from "./builtins/net-address.js";
|
||||
import { isExecExceptionType, refuseExecExceptionCode } from "./builtins/errors.js";
|
||||
import { STATS_TYPE_TESTS } from "./builtins/filesystem.js";
|
||||
@@ -221,6 +222,7 @@ import { errorPropertyRead, errorPropertyWrite, errorToStringCall } from "./erro
|
||||
import { lowerStreamProperty, lowerStreamStateProperty, streamSidesOf } from "./lower-stream.js";
|
||||
import { unionWideningTags } from "../../ir/analysis.js";
|
||||
import { isSafeToDiscard, isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
||||
import { isNavigatorGlobal, lowerNavigatorMember, refuseNavigator } from "./builtins/navigator.js";
|
||||
import { globalSymbolKey } from "./expressions/global-symbols.js";
|
||||
import { lowerShortCircuitAssignment } from "./expressions/nullish-assignment.js";
|
||||
import {
|
||||
@@ -362,6 +364,26 @@ export function abstractPropertyDeclOf(
|
||||
* Narrowing a ParseArgsToken exposes one anonymous union arm, so its alias
|
||||
* identity no longer reaches mapType; declaration ancestry is the stable
|
||||
* provenance check. */
|
||||
/** The Uint8Array base64/hex prototype methods (ES2026) Node 24 lacks. */
|
||||
const UINT8ARRAY_BASE64_METHODS: ReadonlySet<string> = new Set([
|
||||
"toBase64",
|
||||
"toHex",
|
||||
"setFromBase64",
|
||||
"setFromHex",
|
||||
]);
|
||||
|
||||
/** `Uint8Array.prototype.<base64/hex method>` read off the global. */
|
||||
function isUint8ArrayBase64Probe(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean {
|
||||
if (!UINT8ARRAY_BASE64_METHODS.has(expr.name.text)) return false;
|
||||
const proto = expr.expression;
|
||||
return (
|
||||
ts.isPropertyAccessExpression(proto) &&
|
||||
!proto.questionDotToken &&
|
||||
proto.name.text === "prototype" &&
|
||||
stdlibGlobalNameOf(lowerer, proto.expression) === "Uint8Array"
|
||||
);
|
||||
}
|
||||
|
||||
function isParseArgsDynProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean {
|
||||
const sym = lowerer.checker.getSymbolAtLocation(expr.name);
|
||||
if (!sym) return false;
|
||||
@@ -650,6 +672,8 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
return { kind: "strLit", value: "object", type: STRING, loc };
|
||||
if (lowerer.isStdlibGlobal(expr.expression, "fetch"))
|
||||
return { kind: "strLit", value: "function", type: STRING, loc };
|
||||
if (isNavigatorGlobal(lowerer, expr.expression))
|
||||
return { kind: "strLit", value: "object", type: STRING, loc };
|
||||
const performance = lowerPerfHooksTypeof(lowerer, expr.expression);
|
||||
if (performance) return performance;
|
||||
if (isNodeModuleValue(lowerer, expr.expression)) {
|
||||
@@ -1697,6 +1721,14 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
const ns = lowerNsIdentifierValue(lowerer, expr);
|
||||
if (ns) return ns;
|
||||
}
|
||||
if (isNavigatorGlobal(lowerer, expr)) {
|
||||
refuseNavigator(
|
||||
lowerer,
|
||||
expr,
|
||||
"the navigator object as a value",
|
||||
lowerer.checker.getSymbolAtLocation(expr),
|
||||
);
|
||||
}
|
||||
// Preflight guarantees no unresolved identifiers; anything else here
|
||||
// is a blocked declaration's binding (the SC2004 cascade) or a
|
||||
// binding form we don't model yet.
|
||||
@@ -1960,6 +1992,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
if (ts.isConditionalExpression(expr)) return lowerTernary(lowerer, expr);
|
||||
|
||||
if (ts.isPropertyAccessExpression(expr)) {
|
||||
if (!expr.questionDotToken) {
|
||||
const navigator = lowerNavigatorMember(lowerer, expr, NODE_COMPAT_VERSION);
|
||||
if (navigator) return navigator;
|
||||
}
|
||||
// `require("./x").member` over a program module's export table: the
|
||||
// module's %init at this position, then the member read exactly like
|
||||
// a `const m = require("./x")` binding's (cjs-inline-require.ts).
|
||||
@@ -2158,6 +2194,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
) {
|
||||
return dynUndefinedExpr(loc);
|
||||
}
|
||||
// `Uint8Array.prototype.toBase64` and its siblings, the base64/hex
|
||||
// feature probes libraries such as jose make: the methods are newer
|
||||
// than Node 24, whose Uint8Array prototype answers undefined for them.
|
||||
if (isUint8ArrayBase64Probe(lowerer, expr)) return dynUndefinedExpr(loc);
|
||||
if (expr.questionDotToken && expr.name.text === "electron") {
|
||||
const processProperty = lowerer.lowerProcessProperty(expr);
|
||||
if (processProperty) return processProperty;
|
||||
@@ -7086,6 +7126,10 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key.type.kind === "f64") {
|
||||
const safe = lowerOptionalArrayIndexRead(lowerer, expr, value, key);
|
||||
if (safe) return safe;
|
||||
}
|
||||
if (key.type.kind === "string" || key.type.kind === "f64") {
|
||||
const lit = ts.isStringLiteral(expr.argumentExpression)
|
||||
? expr.argumentExpression.text
|
||||
@@ -8047,6 +8091,22 @@ export function lowerAbsenceProbe(lowerer: Lowerer, node: ts.Expression): IrExpr
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// `m[2]` on a JavaScript `RegExpExecArray | null` that a previous
|
||||
// statement ruled non-null without narrowing it (jose's secs()).
|
||||
if (
|
||||
receiverT?.kind === "union" &&
|
||||
isJsSourceFile(expr.getSourceFile()) &&
|
||||
!expr.questionDotToken &&
|
||||
!hasOptionalChainGuard(expr.expression)
|
||||
) {
|
||||
const arms = lowerer.unions.get(receiverT.unionId)?.arms ?? [];
|
||||
const present = arms.filter((arm) => !isUnitType(arm));
|
||||
if (present.length !== 1 || present[0]!.kind !== "array") return null;
|
||||
const value = lowerer.lowerExpr(expr.expression);
|
||||
const index = lowerer.lowerExpr(expr.argumentExpression);
|
||||
if (index.type.kind !== "f64") return null;
|
||||
return lowerOptionalArrayIndexRead(lowerer, expr, value, index);
|
||||
}
|
||||
if (receiverT?.kind !== "array") return null;
|
||||
const arr = lowerer.lowerExpr(expr.expression);
|
||||
if (arr.type.kind !== "array") return null;
|
||||
@@ -12914,6 +12974,8 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
|
||||
if (blob) return blob;
|
||||
const keyObject = lowerKeyObjectInstanceof(lowerer, expr);
|
||||
if (keyObject) return keyObject;
|
||||
const cryptoKey = lowerCryptoKeyInstanceof(lowerer, expr);
|
||||
if (cryptoKey) return cryptoKey;
|
||||
const netAddress = lowerNetAddressInstanceof(lowerer, expr);
|
||||
if (netAddress) return netAddress;
|
||||
}
|
||||
@@ -15396,6 +15458,62 @@ export function lowerUnionProperty(
|
||||
);
|
||||
}
|
||||
|
||||
/** A number-keyed read in JavaScript on an array that may be undefined or
|
||||
* null outside an optional chain (`m[1]` on a `RegExpExecArray | null`
|
||||
* that an earlier statement ruled non-null without narrowing it): the
|
||||
* array arm is an ordinary JavaScript element read, so a hole, a present
|
||||
* undefined (a regex group that did not participate) or an invalid index
|
||||
* answers undefined, as lowerSafeIndexRead does for a plain array. The
|
||||
* unit arms keep the keyed read's answer. The receiver and key evaluate
|
||||
* once, in source order. Null when the union is not exactly one array arm
|
||||
* beside unit arms; optional-chain tails and TypeScript keep the keyed
|
||||
* read. */
|
||||
function lowerOptionalArrayIndexRead(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.ElementAccessExpression,
|
||||
value: IrExpr,
|
||||
key: IrExpr,
|
||||
): IrExpr | null {
|
||||
if (
|
||||
!isJsSourceFile(expr.getSourceFile()) ||
|
||||
expr.questionDotToken ||
|
||||
hasOptionalChainGuard(expr.expression)
|
||||
)
|
||||
return null;
|
||||
if (value.type.kind !== "union") return null;
|
||||
const unionId = value.type.unionId;
|
||||
const arms = lowerer.unions.get(unionId)?.arms;
|
||||
if (!arms) return null;
|
||||
const present = arms.filter((arm) => !isUnitType(arm));
|
||||
const arm = present[0];
|
||||
if (present.length !== 1 || arm?.kind !== "array" || arm.elem.kind === "void") return null;
|
||||
const tag = arms.indexOf(arm);
|
||||
const loc = locOf(expr);
|
||||
const stmts: IrStmt[] = [];
|
||||
const stable = (e: IrExpr, name: string): IrExpr => {
|
||||
if (e.kind === "varRef" || e.kind === "numLit") return e;
|
||||
const local = lowerer.declareHiddenLocal(name, e.type);
|
||||
stmts.push({ kind: "varDecl", localId: local.id, init: e, loc });
|
||||
return varRef(local.id, e.type, loc);
|
||||
};
|
||||
if (lowerUnionKeyedRead(lowerer, expr, unionId, value, key, null) === null) return null;
|
||||
const recv = stable(value, "%indexReceiver");
|
||||
const index = stable(key, "%indexKey");
|
||||
const keyed = lowerUnionKeyedRead(lowerer, expr, unionId, recv, index, null)!;
|
||||
const narrowed: IrExpr = { kind: "unionNarrow", unionId, tag, value: recv, type: arm, loc };
|
||||
const read = lowerSafeIndexRead(lowerer, narrowed, index, loc);
|
||||
if (read === null) return null;
|
||||
const result: IrExpr = {
|
||||
kind: "ternary",
|
||||
cond: { kind: "unionIsTag", unionId, tag, value: recv, negated: false, type: BOOL, loc },
|
||||
then: lowerer.coerceToExpected(read, keyed.type),
|
||||
else_: keyed,
|
||||
type: keyed.type,
|
||||
loc,
|
||||
};
|
||||
return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: keyed.type, loc };
|
||||
}
|
||||
|
||||
/** The unionDisc generalization: a keyed read on a union receiver whose
|
||||
* arms answer DIFFERENT (but joinable) types. Each arm contributes its
|
||||
* declared answer — an array element's type (number keys), a declared field's type (literal keys), an
|
||||
|
||||
@@ -93,6 +93,7 @@ import {
|
||||
import { isPromisifyCall, registerBuiltinCallableAlias } from "./builtins/callable-aliases.js";
|
||||
import { registerBuiltinClassAlias } from "./builtins/class-values.js";
|
||||
import { stripTypeCasts } from "./builtins/arguments.js";
|
||||
import { isWebCryptoMemberType } from "./builtins/webcrypto.js";
|
||||
import {
|
||||
bindingContextualGenericFnNodeOf,
|
||||
bindingGenericFnAliasInfoOf,
|
||||
@@ -1917,7 +1918,8 @@ export function collectGlobals(
|
||||
decl.initializer &&
|
||||
!decl.type &&
|
||||
!hasJsTypeAnnotation(decl) &&
|
||||
!lowerer.mapTypeOf(lowerer.typeOf(nameNode))
|
||||
(!lowerer.mapTypeOf(lowerer.typeOf(nameNode)) ||
|
||||
isWebCryptoMemberType(lowerer, lowerer.typeOf(nameNode)))
|
||||
) {
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
|
||||
if (symbol && !lowerer.globalsBySymbol.has(symbol)) {
|
||||
|
||||
@@ -2063,6 +2063,43 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool
|
||||
.some((p) => p.declarations.some(declarationBelongs));
|
||||
}
|
||||
|
||||
/** True when an `any` value comes from a JavaScript function's result:
|
||||
* a call (possibly awaited) whose callee is declared in a JavaScript file,
|
||||
* or a const bound to one. A TypeScript program importing a JavaScript
|
||||
* package sees the package's inferred types (`jwtVerify` from jose
|
||||
* answers `any`), and its object destructuring reads the members of that
|
||||
* checked-dynamic value as JavaScript does — member reads on the same
|
||||
* value already compile. Other `any` sources keep the fence. */
|
||||
function javaScriptAnyOrigin(lowerer: Lowerer, node: ts.Expression, depth: number): boolean {
|
||||
let expr = node;
|
||||
while (ts.isParenthesizedExpression(expr) || ts.isAwaitExpression(expr)) expr = expr.expression;
|
||||
if (ts.isCallExpression(expr)) {
|
||||
const callee = ts.isPropertyAccessExpression(expr.expression)
|
||||
? expr.expression.name
|
||||
: expr.expression;
|
||||
if (!ts.isIdentifier(callee)) return false;
|
||||
const symbol = lowerer.resolveValueSymbol(callee);
|
||||
const declarations = symbol ? lowerer.checker.declarationsOf(symbol) : [];
|
||||
return (
|
||||
declarations.length > 0 &&
|
||||
declarations.every((d) => isJsSourceFile(d.getSourceFile()) && ts.isFunctionLike(d))
|
||||
);
|
||||
}
|
||||
if (ts.isIdentifier(expr) && depth === 0) {
|
||||
const symbol = lowerer.resolveValueSymbol(expr);
|
||||
const declaration = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined;
|
||||
return (
|
||||
declaration !== undefined &&
|
||||
ts.isVariableDeclaration(declaration) &&
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.initializer !== undefined &&
|
||||
(ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) !== 0 &&
|
||||
javaScriptAnyOrigin(lowerer, declaration.initializer, depth + 1)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Destructuring declarations, desugared as sugar over indexed/field
|
||||
* reads: the initializer evaluates ONCE into a hidden temp, then each
|
||||
* bound name declares (or assigns its pre-registered module global) from
|
||||
@@ -2107,7 +2144,12 @@ export function lowerDestructuringDecl(
|
||||
lowerer.anyOrigin(decl.initializer) &&
|
||||
!isJsSourceFile(decl.getSourceFile()) &&
|
||||
init.type.kind !== "record" &&
|
||||
init.type.kind !== "array"
|
||||
init.type.kind !== "array" &&
|
||||
!(
|
||||
init.type.kind === "dyn" &&
|
||||
ts.isObjectBindingPattern(decl.name) &&
|
||||
javaScriptAnyOrigin(lowerer, decl.initializer, 0)
|
||||
)
|
||||
) {
|
||||
lowerer.anyOpFence("destructuring", decl);
|
||||
}
|
||||
@@ -3029,6 +3071,10 @@ export function lowerBindingPattern(
|
||||
lowerMatchResultObjectPattern(lowerer, pattern, srcRef, srcType, isLet, out);
|
||||
return;
|
||||
}
|
||||
if (srcType.kind === "array" && isArrayPositionPattern(lowerer, pattern)) {
|
||||
lowerArrayPositionObjectPattern(lowerer, pattern, srcRef, srcType, isLet, out);
|
||||
return;
|
||||
}
|
||||
if (srcType.kind !== "record") {
|
||||
lowerer.unsupported(
|
||||
"SC1031",
|
||||
@@ -3359,19 +3405,8 @@ function lowerMatchResultObjectPattern(
|
||||
);
|
||||
}
|
||||
let value: IrExpr;
|
||||
if (/^(?:0|[1-9]\d{0,8})$/.test(propName)) {
|
||||
const index = Number(propName);
|
||||
const position: IrExpr = { kind: "numLit", value: index, type: F64, loc };
|
||||
value = lowerSafeIndexRead(lowerer, srcRef(), position, loc) ?? {
|
||||
kind: "arrayGet",
|
||||
arr: srcRef(),
|
||||
index: position,
|
||||
type: srcType.elem,
|
||||
loc,
|
||||
};
|
||||
if (el.initializer) {
|
||||
value = arrayPositionDefault(lowerer, el, value, srcRef, index, srcType.elem, out);
|
||||
}
|
||||
if (ARRAY_POSITION_KEY.test(propName)) {
|
||||
value = arrayPositionBinding(lowerer, el, srcRef, srcType, Number(propName), out);
|
||||
} else if (propName === "index" || propName === "input") {
|
||||
if (el.initializer) {
|
||||
lowerer.unsupported(
|
||||
@@ -3417,6 +3452,83 @@ function lowerMatchResultObjectPattern(
|
||||
}
|
||||
}
|
||||
|
||||
/** A canonical array index key (the numeric positions an object pattern
|
||||
* reads off an array). */
|
||||
const ARRAY_POSITION_KEY = /^(?:0|[1-9]\d{0,8})$/;
|
||||
|
||||
/** The positional read `{ <index>: x }` takes off an array: the element,
|
||||
* undefined past the end, with the binding default applied. */
|
||||
function arrayPositionBinding(
|
||||
lowerer: Lowerer,
|
||||
el: ts.BindingElement,
|
||||
srcRef: () => IrExpr,
|
||||
srcType: IrType & { kind: "array" },
|
||||
index: number,
|
||||
out: IrStmt[],
|
||||
): IrExpr {
|
||||
const loc = locOf(el);
|
||||
const position: IrExpr = { kind: "numLit", value: index, type: F64, loc };
|
||||
const value = lowerSafeIndexRead(lowerer, srcRef(), position, loc) ?? {
|
||||
kind: "arrayGet",
|
||||
arr: srcRef(),
|
||||
index: position,
|
||||
type: srcType.elem,
|
||||
loc,
|
||||
};
|
||||
return el.initializer
|
||||
? arrayPositionDefault(lowerer, el, value, srcRef, index, srcType.elem, out)
|
||||
: value;
|
||||
}
|
||||
|
||||
/** Whether every element of an object pattern over an array names a
|
||||
* numeric position or `length` (`const { 0: a, 1: b, length } =
|
||||
* s.split(".")`), with no rest element. */
|
||||
function isArrayPositionPattern(lowerer: Lowerer, pattern: ts.ObjectBindingPattern): boolean {
|
||||
return pattern.elements.nodes.every((el) => {
|
||||
if (el.name === undefined) return true;
|
||||
if (el.dotDotDotToken) return false;
|
||||
const prop = el.propertyName ?? el.name;
|
||||
if (
|
||||
!ts.isIdentifier(prop) &&
|
||||
!ts.isComputedPropertyName(prop) &&
|
||||
!ts.isStringLiteralLike(prop) &&
|
||||
!ts.isNumericLiteral(prop)
|
||||
)
|
||||
return false;
|
||||
const name = patternKeyNameOf(lowerer, prop as ts.PropertyName);
|
||||
return name !== null && (name === "length" || ARRAY_POSITION_KEY.test(name));
|
||||
});
|
||||
}
|
||||
|
||||
/** An object pattern of positions and `length` over an array: each
|
||||
* position is the read the equivalent array pattern makes, and `length`
|
||||
* the array's length (always present, so its default never runs). */
|
||||
function lowerArrayPositionObjectPattern(
|
||||
lowerer: Lowerer,
|
||||
pattern: ts.ObjectBindingPattern,
|
||||
srcRef: () => IrExpr,
|
||||
srcType: IrType & { kind: "array" },
|
||||
isLet: boolean,
|
||||
out: IrStmt[],
|
||||
): void {
|
||||
for (const el of pattern.elements.nodes) {
|
||||
if (el.name === undefined) continue;
|
||||
const name = patternKeyNameOf(lowerer, (el.propertyName ?? el.name) as ts.PropertyName)!;
|
||||
const value: IrExpr =
|
||||
name === "length"
|
||||
? {
|
||||
kind: "arrIntrinsic",
|
||||
method: "length",
|
||||
receiver: srcRef(),
|
||||
args: [],
|
||||
type: F64,
|
||||
loc: locOf(el),
|
||||
}
|
||||
: arrayPositionBinding(lowerer, el, srcRef, srcType, Number(name), out);
|
||||
lowerer.bindPatternTarget(el.name, value, isLet, out);
|
||||
}
|
||||
}
|
||||
|
||||
/** Decode one non-rest object-assignment property into its static source
|
||||
* field, destination expression, and optional default initializer. */
|
||||
function destructuringPropertyOf(
|
||||
@@ -7435,6 +7547,15 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm
|
||||
if (droppedPureStdlibStatement(lowerer, expr)) {
|
||||
return { kind: "block", body: [], loc: locOf(expr) };
|
||||
}
|
||||
if (
|
||||
stmtNode !== undefined &&
|
||||
ts.isExpressionStatement(stmtNode) &&
|
||||
ts.isBinaryExpression(expr) &&
|
||||
(expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
||||
expr.operatorToken.kind === ts.SyntaxKind.BarBarToken)
|
||||
) {
|
||||
return lowerDiscardedLogical(lowerer, expr);
|
||||
}
|
||||
const value = lowerer.lowerExpr(expr);
|
||||
// A bare unit-literal statement (`undefined;`): a pure no-op in JS,
|
||||
// and the IR has no bare-unit VALUE outside a union wrap — drop it
|
||||
@@ -7443,6 +7564,59 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm
|
||||
return { kind: "exprStmt", expr: value, loc: locOf(expr) };
|
||||
}
|
||||
|
||||
/** A discarded `a && b` / `a || b` statement (the minifier's `if`). The
|
||||
* value form lowers first, exactly as before. Only when it fails on the
|
||||
* logical's own result type — the operands have no common representation
|
||||
* (jose's `(!m || m[4] && m[1]) && invalid()` joins a boolean, a string
|
||||
* and undefined into the checker's `string`) — does the statement lower
|
||||
* as the control flow it is: `if (a) b;` / `if (!a) b;`, a condition test
|
||||
* that needs no result value. Diagnostics outside the statement, and
|
||||
* internal errors, keep the original failure. */
|
||||
function lowerDiscardedLogical(lowerer: Lowerer, expr: ts.BinaryExpression): IrStmt {
|
||||
const diagsBefore = lowerer.diags.length;
|
||||
try {
|
||||
const value = lowerer.lowerExpr(expr);
|
||||
if (value.kind === "unitLit") return { kind: "block", body: [], loc: locOf(expr) };
|
||||
return { kind: "exprStmt", expr: value, loc: locOf(expr) };
|
||||
} catch (e) {
|
||||
// A probe (a capture buffer is active) keeps its failure: its owner
|
||||
// picks another lowering, exactly as before.
|
||||
if (!(e instanceof PoisonError) || lowerer.diagSink !== null) throw e;
|
||||
const added = lowerer.diags.slice(diagsBefore);
|
||||
const file = expr.getSourceFile().fileName;
|
||||
const start = expr.getStart();
|
||||
const local =
|
||||
added.length > 0 &&
|
||||
added.every(
|
||||
(d) =>
|
||||
d.code !== "SC9001" &&
|
||||
d.loc.file === file &&
|
||||
d.loc.start >= start &&
|
||||
d.loc.end <= expr.end,
|
||||
);
|
||||
if (!local) throw e;
|
||||
lowerer.diags.length = diagsBefore;
|
||||
}
|
||||
const and = expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken;
|
||||
const cond = lowerer.lowerCondition(expr.left);
|
||||
if (cond.kind === "boolLit") {
|
||||
return cond.value === and
|
||||
? lowerExprStatement(lowerer, expr.right)
|
||||
: { kind: "block", body: [], loc: locOf(expr) };
|
||||
}
|
||||
const right = (): IrStmt[] =>
|
||||
lowerer.narrowingAliases(aliasTypeofNarrows(lowerer, expr.left, and), () =>
|
||||
and
|
||||
? withRuntimeOptionalNarrowed(lowerer, runtimeOptionalTrueIds(lowerer, expr.left), () => [
|
||||
lowerExprStatement(lowerer, expr.right),
|
||||
])
|
||||
: [lowerExprStatement(lowerer, expr.right)],
|
||||
);
|
||||
return and
|
||||
? { kind: "if", cond, then: right(), else_: [], loc: locOf(expr) }
|
||||
: { kind: "if", cond, then: [], else_: right(), loc: locOf(expr) };
|
||||
}
|
||||
|
||||
/** True for a statement-position expression whose evaluation cannot be
|
||||
* observed once the value is discarded:
|
||||
* - a property/element READ (`?.` included) whose receiver IS a
|
||||
|
||||
@@ -32,6 +32,7 @@ import { refuseWebAssemblyUse } from "./builtins/webassembly.js";
|
||||
import { discoverWasmModules, type WasmModuleReport } from "../../wasm/discover.js";
|
||||
import { resolveRequireRuntime } from "../runtime-resolve.js";
|
||||
import { lowerKeyObjectCall } from "./builtins/key-object.js";
|
||||
import { fenceWebCryptoMember, lowerWebCryptoCall } from "./builtins/webcrypto.js";
|
||||
import { hashStreamOf } from "./builtins/hash-stream.js";
|
||||
import { lowerNetAddressCall, lowerNetAddressNew } from "./builtins/net-address.js";
|
||||
import { dynUndefinedExpr, nodeThrowExpr, numLit, varRef } from "../../ir/build.js";
|
||||
@@ -5022,13 +5023,42 @@ export class Lowerer {
|
||||
return out;
|
||||
};
|
||||
const scanPattern = (name: ts.BindingName, init: ts.Expression): boolean => {
|
||||
if (ts.isObjectBindingPattern(name)) return scanRecordPattern(name, init);
|
||||
if (ts.isObjectBindingPattern(name)) {
|
||||
// `const { 0: a, 1: b, length } = parts` reads positions like the
|
||||
// equivalent array pattern: each may sit past the end.
|
||||
// Regex match results keep their own pattern lowering.
|
||||
const arrayType = this.mapTypeOf(this.typeOf(init));
|
||||
const initType = this.checker.getNonNullableType(this.typeOf(init));
|
||||
const initSymbol = initType.getAliasSymbol() ?? initType.getSymbol();
|
||||
const matchResult =
|
||||
initSymbol !== undefined &&
|
||||
(initSymbol.name === "RegExpExecArray" || initSymbol.name === "RegExpMatchArray");
|
||||
if (arrayType?.kind !== "array" || matchResult) return scanRecordPattern(name, init);
|
||||
const positions = name.elements.nodes.filter((el) => {
|
||||
const key = el.propertyName;
|
||||
return (
|
||||
key !== undefined && ts.isNumericLiteral(key) && /^(?:0|[1-9]\d{0,8})$/.test(key.text)
|
||||
);
|
||||
});
|
||||
return scanPositions(positions, init, arrayType);
|
||||
}
|
||||
if (!ts.isArrayBindingPattern(name)) return false;
|
||||
const sourceType = this.mapTypeOf(this.typeOf(init));
|
||||
if (sourceType?.kind !== "array") return false;
|
||||
return scanPositions(
|
||||
name.elements.nodes.filter((el): el is ts.BindingElement => !ts.isOmittedExpression(el)),
|
||||
init,
|
||||
sourceType,
|
||||
);
|
||||
};
|
||||
const scanPositions = (
|
||||
elements: readonly ts.BindingElement[],
|
||||
init: ts.Expression,
|
||||
sourceType: IrType & { kind: "array" },
|
||||
): boolean => {
|
||||
let changed = false;
|
||||
name.elements.nodes.forEach((el) => {
|
||||
if (ts.isOmittedExpression(el) || el.name === undefined || el.dotDotDotToken) return;
|
||||
elements.forEach((el) => {
|
||||
if (el.name === undefined || el.dotDotDotToken) return;
|
||||
if (ts.isIdentifier(el.name)) {
|
||||
const symbol = symbolOf(el.name);
|
||||
if (symbol && !el.initializer && !optionalSymbols.has(symbol)) {
|
||||
@@ -7904,7 +7934,11 @@ export class Lowerer {
|
||||
access: ts.PropertyAccessExpression | ts.ElementAccessExpression,
|
||||
use: "read" | "call",
|
||||
): IrExpr | null {
|
||||
return fenceStaticResponseMember(this, access, use) ?? fenceStaticBlobMember(this, access, use);
|
||||
return (
|
||||
fenceStaticResponseMember(this, access, use) ??
|
||||
fenceStaticBlobMember(this, access, use) ??
|
||||
fenceWebCryptoMember(this, access, use)
|
||||
);
|
||||
}
|
||||
|
||||
fenceUnsupportedFetchConstructorMember(
|
||||
@@ -11736,6 +11770,7 @@ export class Lowerer {
|
||||
lowerStaticReadableStreamReaderCall(this, expr) ??
|
||||
lowerStaticResponseCall(this, expr) ??
|
||||
lowerStaticBlobCall(this, expr) ??
|
||||
lowerWebCryptoCall(this, expr) ??
|
||||
lowerKeyObjectCall(this, expr) ??
|
||||
lowerNetAddressCall(this, expr) ??
|
||||
lowerFetchElementMethodCall(this, expr) ??
|
||||
|
||||
@@ -2162,6 +2162,20 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
|
||||
fns: ["blob.fileNew"],
|
||||
note: "a Blob with name and lastModified (Date.now() when absent)",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
id: "stdlib.webcrypto.CryptoKey",
|
||||
kind: "stdlib",
|
||||
name: "CryptoKey",
|
||||
fns: ["webcrypto.isCryptoKey"],
|
||||
note: "secret HMAC keys with type, extractable, algorithm and usages, and instanceof CryptoKey",
|
||||
},
|
||||
{
|
||||
id: "stdlib.atomics",
|
||||
kind: "stdlib",
|
||||
|
||||
@@ -125,6 +125,19 @@ export function jsOpenObjectType(
|
||||
: type;
|
||||
}
|
||||
|
||||
/** The WebCrypto interfaces whose values are checked-dynamic: the crypto
|
||||
* global, SubtleCrypto, CryptoKey handles and the algorithm dictionaries
|
||||
* (scr_webcrypto.c). */
|
||||
export const WEBCRYPTO_DYN_TYPES: ReadonlySet<string> = new Set([
|
||||
"Crypto",
|
||||
"SubtleCrypto",
|
||||
"CryptoKey",
|
||||
"Algorithm",
|
||||
"KeyAlgorithm",
|
||||
"HmacImportParams",
|
||||
"HmacKeyAlgorithm",
|
||||
]);
|
||||
|
||||
export const FETCH_AMBIENT_TYPES = [
|
||||
"Response",
|
||||
"Request",
|
||||
@@ -2459,6 +2472,23 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
) {
|
||||
return DYN;
|
||||
}
|
||||
// WebCrypto: the crypto global and its subtle object are dynamic
|
||||
// objects, CryptoKey values are native handles (scr_webcrypto.c), and
|
||||
// the algorithm dictionaries the methods take and keys report are plain
|
||||
// checked-dynamic objects.
|
||||
if (
|
||||
psym &&
|
||||
WEBCRYPTO_DYN_TYPES.has(psym.name) &&
|
||||
checker
|
||||
.declarationsOf(psym)
|
||||
.some(
|
||||
(d) =>
|
||||
(ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) &&
|
||||
ctx.isStdlibFile(d.getSourceFile()),
|
||||
)
|
||||
) {
|
||||
return DYN;
|
||||
}
|
||||
// node:crypto's KeyObject: secret keys are native handles (scr_lib.c)
|
||||
// whose getters and methods dispatch through the handle. The JsonWebKey
|
||||
// that export() returns is a plain checked-dynamic object.
|
||||
|
||||
@@ -554,6 +554,7 @@ async function compileExecutableNative(
|
||||
tls: features.tls,
|
||||
tlsCa: features.tlsCa,
|
||||
cipher: features.cipher,
|
||||
webcrypto: features.webcrypto,
|
||||
...(onArtifactReady === undefined ? {} : { onArtifactReady }),
|
||||
...(ffi === null
|
||||
? {}
|
||||
|
||||
@@ -2636,6 +2636,14 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"crypto.generateKeyCb": { argTypes: [DYN, DYN, null], result: VOID },
|
||||
"crypto.keyObjectInput": { argTypes: [DYN], result: BYTES_U8 },
|
||||
"crypto.isKeyObject": { argTypes: [DYN], result: BOOL },
|
||||
/** WebCrypto (scr_webcrypto.c): each method takes its argument vector as
|
||||
* 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.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 },
|
||||
"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 },
|
||||
|
||||
@@ -49,6 +49,8 @@ export interface RuntimeFeatures {
|
||||
tls: boolean;
|
||||
tlsCa: boolean;
|
||||
cipher: boolean;
|
||||
/** crypto.subtle importKey/sign/verify/digest: scr_webcrypto.c. */
|
||||
webcrypto: boolean;
|
||||
}
|
||||
|
||||
const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
@@ -407,6 +409,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
|
||||
tls: false,
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
};
|
||||
const keepGoing = (): boolean => stopAt === undefined || !features[stopAt];
|
||||
let tables: TypeTables | undefined;
|
||||
@@ -512,6 +515,8 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
|
||||
features.dynAsync = true;
|
||||
features.inspect = true;
|
||||
}
|
||||
// crypto.subtle and CryptoKey: scr_webcrypto.c.
|
||||
if (fn.startsWith("webcrypto.")) features.webcrypto = true;
|
||||
} else {
|
||||
switch (node.kind) {
|
||||
case "regexLit":
|
||||
|
||||
@@ -51,6 +51,7 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [
|
||||
["dns.", "the node:dns surface"],
|
||||
["tls.", "the node:tls surface"],
|
||||
["crypto.cipherNew", "crypto.createCipheriv and createDecipheriv"],
|
||||
["webcrypto.", "WebCrypto (crypto.subtle)"],
|
||||
["fs.watch", "fs.watch"],
|
||||
["watcher.", "fs.watch"],
|
||||
["test.", "the node:test surface"],
|
||||
|
||||
@@ -100,6 +100,7 @@ async function main(): Promise<number> {
|
||||
nodeTest: false,
|
||||
tls: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
tlsCa: false,
|
||||
},
|
||||
options.optimization ?? "release",
|
||||
|
||||
@@ -155,6 +155,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures {
|
||||
"tls",
|
||||
"tlsCa",
|
||||
"cipher",
|
||||
"webcrypto",
|
||||
].every((name) => typeof features[name] === "boolean");
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
|
||||
tls: false,
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
nativeFetch: false,
|
||||
netEffective: false,
|
||||
httpEffective: false,
|
||||
|
||||
@@ -5676,6 +5676,20 @@
|
||||
"code": "SC2020",
|
||||
"note": "WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations)"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.webcrypto.CryptoKey",
|
||||
"kind": "stdlib",
|
||||
"name": "CryptoKey",
|
||||
"status": "static",
|
||||
"note": "secret HMAC 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"
|
||||
},
|
||||
{
|
||||
"id": "syntax.async-generators",
|
||||
"kind": "syntax",
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6291-webcrypto-hmac-sign-verify.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6292-webcrypto-hmac-errors.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6293-webcrypto-digest.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6294-webcrypto-js-values.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6295-jose-hs256/main.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6296-array-position-patterns.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6297-library-runtime-idioms.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6331-jose-root-entry/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6332-fluent-this-declaration/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6333-fluent-this-declaration-js/main.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6334-discarded-logical-statements.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6335-discarded-logical-statements.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/webcrypto-refusals.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/fixtures/node-types/navigator.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/fixtures/node-types/webcrypto-hmac.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -113,6 +113,7 @@ const optional = [
|
||||
["scr_tls_ca.c", "tlsCaEffective"],
|
||||
["scr_tls.c", "tlsEffective"],
|
||||
["scr_cipher.c", "cipher"],
|
||||
["scr_webcrypto.c", "webcrypto"],
|
||||
["scr_fetch.c", "nativeFetch"],
|
||||
];
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ const OPTIONAL = [
|
||||
["scr_tls_ca.c", "tlsCaEffective"],
|
||||
["scr_tls.c", "tlsEffective"],
|
||||
["scr_cipher.c", "cipher"],
|
||||
["scr_webcrypto.c", "webcrypto"],
|
||||
["scr_fetch.c", "nativeFetch"],
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* scriptc runtime — node:crypto (scr_lib.c, scr_crypto_async.c). Included by
|
||||
/* scriptc runtime — node:crypto (scr_lib.c, scr_crypto_async.c) and the
|
||||
* WebCrypto HMAC slice (scr_webcrypto.c). Included by
|
||||
* scr_runtime.h, which says where new declarations go. */
|
||||
#ifndef SCR_CRYPTO_H
|
||||
#define SCR_CRYPTO_H
|
||||
@@ -193,6 +194,22 @@ size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t
|
||||
unsigned char out[64]);
|
||||
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
|
||||
* 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`. */
|
||||
ScrPromise *scr_webcrypto_import_key(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_sign(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_verify(ScrDyn *args);
|
||||
ScrPromise *scr_webcrypto_digest(ScrDyn *args);
|
||||
bool scr_webcrypto_is_crypto_key(const ScrDyn *value);
|
||||
|
||||
/* The composed `new crypto.X509Certificate(data).fingerprint` read (the
|
||||
* handle never materializes): the SHA-1 of the DER, uppercase
|
||||
* colon-separated — PEM or raw-DER input; anything else throws Node's
|
||||
|
||||
@@ -137,6 +137,7 @@ typedef enum {
|
||||
SCR_DYNH_CIPHER = 45, /* node:crypto Cipheriv (scr_cipher.c) */
|
||||
SCR_DYNH_CONSOLE = 32, /* stored global console value */
|
||||
SCR_DYNH_CONSOLE_INSTANCE = 38, /* node:console Console instance */
|
||||
SCR_DYNH_CRYPTO_KEY = 56, /* WebCrypto CryptoKey (scr_webcrypto.c) */
|
||||
SCR_DYNH_CUSTOM_EVENT = 40, /* ScrEvent with detail — new CustomEvent() */
|
||||
SCR_DYNH_DATE = 30, /* JavaScript Date with stable native identity */
|
||||
SCR_DYNH_DECIPHER = 46, /* node:crypto Decipheriv (scr_cipher.c) */
|
||||
|
||||
@@ -0,0 +1,829 @@
|
||||
/* WebCrypto's HMAC slice: crypto.subtle.importKey (raw HMAC keys),
|
||||
* sign, verify and the SHA-1/SHA-2 digests, plus the CryptoKey values
|
||||
* importKey answers.
|
||||
*
|
||||
* This unit is only the WebCrypto layer, modeled on Node 24's
|
||||
* lib/internal/crypto/webcrypto.js and mac.js: Web IDL argument
|
||||
* conversion, algorithm normalization, the CryptoKey handle, promise
|
||||
* wrapping and Node's error shapes. The MACs and digests themselves come
|
||||
* from node:crypto's digest core (scr_crypto_hmac_raw and
|
||||
* scr_crypto_digest_raw in scr_lib.c), and results settle through the
|
||||
* deferred crypto queue that node:crypto's callback forms use, so sign,
|
||||
* verify and digest settle on a later loop turn like Node's thread-pool
|
||||
* jobs while importKey settles at once.
|
||||
*
|
||||
* Every argument and validation failure rejects the returned promise.
|
||||
* Algorithms Node recognizes but this slice does not implement (RSA,
|
||||
* ECDSA, AES, SHA-3, ...) and the 'jwk' key format reject with a coded
|
||||
* SC2020 Error instead of silently diverging. The compiler links the unit
|
||||
* only into programs that call these methods (the webcrypto feature). */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── algorithm registry ───────────────────────────────────────────────
|
||||
* The names Node 24 registers per operation (util.js's
|
||||
* kAlgorithmDefinitions with the conditional algorithms its OpenSSL
|
||||
* build enables), matched case-insensitively. Only HMAC and the SHA-1/
|
||||
* SHA-2 digests are implemented. */
|
||||
static const char *const scr_wc_import_names[] = {
|
||||
"AES-CBC", "AES-CTR", "AES-GCM", "AES-KW", "AES-OCB",
|
||||
"Argon2d", "Argon2i", "Argon2id", "ChaCha20-Poly1305",
|
||||
"ECDH", "ECDSA", "Ed25519", "Ed448", "HKDF",
|
||||
"HMAC", "KMAC128", "KMAC256", "ML-DSA-44", "ML-DSA-65",
|
||||
"ML-DSA-87", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024", "MLKEM768-P256",
|
||||
"MLKEM768-X25519", "MLKEM1024-P384", "PBKDF2", "RSA-OAEP", "RSA-PSS",
|
||||
"RSASSA-PKCS1-v1_5", "X25519", "X448",
|
||||
};
|
||||
static const char *const scr_wc_sign_names[] = {
|
||||
"ECDSA", "Ed25519", "Ed448", "HMAC", "KMAC128", "KMAC256",
|
||||
"ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "RSA-PSS", "RSASSA-PKCS1-v1_5",
|
||||
};
|
||||
static const char *const scr_wc_digest_names[] = {
|
||||
"SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-384",
|
||||
"SHA3-512", "cSHAKE128", "cSHAKE256", "KT128", "KT256", "TurboSHAKE128",
|
||||
"TurboSHAKE256",
|
||||
};
|
||||
|
||||
#define SCR_WC_COUNT(names) (sizeof(names) / sizeof((names)[0]))
|
||||
|
||||
/* The implemented hashes: WebCrypto name and node:crypto digest name. */
|
||||
static const struct {
|
||||
const char *name;
|
||||
const char *native;
|
||||
} scr_wc_hashes[] = {
|
||||
{"SHA-1", "sha1"},
|
||||
{"SHA-256", "sha256"},
|
||||
{"SHA-384", "sha384"},
|
||||
{"SHA-512", "sha512"},
|
||||
};
|
||||
|
||||
static bool scr_wc_ascii_ieq(const ScrStr *s, const char *name) {
|
||||
size_t n = strlen(name);
|
||||
if (s->len != n) return false;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
unsigned char a = (unsigned char)s->data[i], b = (unsigned char)name[i];
|
||||
if (a >= 'a' && a <= 'z') a = (unsigned char)(a - 32);
|
||||
if (b >= 'a' && b <= 'z') b = (unsigned char)(b - 32);
|
||||
if (a != b) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The canonical registered spelling of `name`, or NULL. */
|
||||
static const char *scr_wc_lookup(const char *const *names, size_t count, const ScrStr *name) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
if (scr_wc_ascii_ieq(name, names[i])) return names[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *scr_wc_native_hash(const char *name) {
|
||||
for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_hashes); i++)
|
||||
if (strcmp(scr_wc_hashes[i].name, name) == 0) return scr_wc_hashes[i].native;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── errors ────────────────────────────────────────────────────────── */
|
||||
|
||||
static const char *const scr_wc_ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
|
||||
|
||||
static void scr_wc_type_error(const char *code, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
static void scr_wc_type_error(const char *code, const char *fmt, ...) {
|
||||
char message[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int n = vsnprintf(message, sizeof message, fmt, ap);
|
||||
va_end(ap);
|
||||
if (n < 0) n = 0;
|
||||
if ((size_t)n >= sizeof message) n = (int)sizeof message - 1;
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, message, (size_t)n, code);
|
||||
}
|
||||
|
||||
/* An operation or algorithm Node supports that this slice does not. */
|
||||
static void scr_wc_unsupported(const char *method, const char *what) {
|
||||
char message[256];
|
||||
int n = snprintf(message, sizeof message, "crypto.subtle.%s with %s is not supported yet", method,
|
||||
what);
|
||||
if ((size_t)n >= sizeof message) n = (int)sizeof message - 1;
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)n, "SC2020");
|
||||
}
|
||||
|
||||
static void scr_wc_unsupported_algorithm(const char *method, const char *name) {
|
||||
char what[96];
|
||||
snprintf(what, sizeof what, "the %s algorithm", name);
|
||||
scr_wc_unsupported(method, what);
|
||||
}
|
||||
|
||||
/* ── Web IDL conversions ──────────────────────────────────────────────
|
||||
* Each answers NULL/false with the TypeError pending. `prefix` is
|
||||
* "Failed to execute '<method>' on 'SubtleCrypto'". */
|
||||
|
||||
static bool scr_wc_is_object(const ScrDyn *v) {
|
||||
switch (v->kind) {
|
||||
case SCR_DYN_UNDEF:
|
||||
case SCR_DYN_NULL:
|
||||
case SCR_DYN_BOOL:
|
||||
case SCR_DYN_NUM:
|
||||
case SCR_DYN_STR:
|
||||
case SCR_DYN_BIGINT:
|
||||
case SCR_DYN_SYMBOL:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* A typed reference capsule as its plain view; other values retained. */
|
||||
static ScrDyn *scr_wc_plain(ScrDyn *v) {
|
||||
if (v->kind == SCR_DYN_TYPED_REF) return scr_dyn_typed_ref_materialize(v);
|
||||
return scr_dyn_retain(v);
|
||||
}
|
||||
|
||||
/* BufferSource: the bytes of a TypedArray, DataView, Buffer or
|
||||
* ArrayBuffer as a u8 view (+1). */
|
||||
static ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *value) {
|
||||
ScrDyn *plain = scr_wc_plain(value);
|
||||
if (!plain) return NULL;
|
||||
ScrBytes *view = NULL;
|
||||
if (plain->kind == SCR_DYN_BYTES || scr_array_buffer_is(plain)) {
|
||||
view = scr_bytes_buffer_source(plain);
|
||||
} else {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE",
|
||||
"%s: %s argument is not instance of ArrayBuffer, Buffer, TypedArray, or "
|
||||
"DataView.",
|
||||
prefix, scr_wc_ordinals[index]);
|
||||
}
|
||||
scr_dyn_release(plain);
|
||||
return view;
|
||||
}
|
||||
|
||||
/* DOMString conversion (+1). */
|
||||
static ScrStr *scr_wc_dom_string(const ScrDyn *value) { return scr_dyn_string_coerce_js(value); }
|
||||
|
||||
static const char *const scr_wc_key_formats[] = {
|
||||
"raw", "raw-public", "raw-seed", "raw-secret", "raw-private", "pkcs8", "spki", "jwk",
|
||||
};
|
||||
|
||||
/* KeyFormat: the canonical enum spelling, or NULL. */
|
||||
static const char *scr_wc_key_format(const char *prefix, ScrDyn *value) {
|
||||
ScrStr *s = scr_wc_dom_string(value);
|
||||
if (!s) return NULL;
|
||||
for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_key_formats); i++) {
|
||||
if (s->len == strlen(scr_wc_key_formats[i]) &&
|
||||
memcmp(s->data, scr_wc_key_formats[i], s->len) == 0) {
|
||||
scr_str_release(s);
|
||||
return scr_wc_key_formats[i];
|
||||
}
|
||||
}
|
||||
scr_wc_type_error("ERR_INVALID_ARG_VALUE",
|
||||
"%s: 1st argument '%.*s' is not a valid enum value of type KeyFormat.", prefix,
|
||||
(int)(s->len > 200 ? 200 : s->len), s->data);
|
||||
scr_str_release(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Node's canonical usage order; the bit is the index. */
|
||||
static const char *const scr_wc_usages[] = {
|
||||
"encrypt", "decrypt", "sign", "verify",
|
||||
"deriveKey", "deriveBits", "wrapKey", "unwrapKey",
|
||||
"encapsulateKey", "encapsulateBits", "decapsulateKey", "decapsulateBits",
|
||||
};
|
||||
enum { SCR_WC_USAGE_SIGN = 1u << 2, SCR_WC_USAGE_VERIFY = 1u << 3 };
|
||||
|
||||
/* sequence<KeyUsage> over an array: the usage mask (-1 on failure). */
|
||||
static long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value) {
|
||||
ScrDyn *plain = scr_wc_plain(value);
|
||||
if (!plain) return -1;
|
||||
if (plain->kind != SCR_DYN_ARR) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: %s argument cannot be converted to sequence.",
|
||||
prefix, scr_wc_ordinals[index]);
|
||||
scr_dyn_release(plain);
|
||||
return -1;
|
||||
}
|
||||
long mask = 0;
|
||||
double n = scr_dyn_arr_len(plain);
|
||||
for (double i = 0; i < n; i++) {
|
||||
ScrDyn *item = scr_dyn_arr_at(plain, i);
|
||||
ScrStr *s = scr_wc_dom_string(item);
|
||||
scr_dyn_release(item);
|
||||
if (!s) {
|
||||
scr_dyn_release(plain);
|
||||
return -1;
|
||||
}
|
||||
size_t k = 0;
|
||||
while (k < SCR_WC_COUNT(scr_wc_usages) &&
|
||||
!(s->len == strlen(scr_wc_usages[k]) && memcmp(s->data, scr_wc_usages[k], s->len) == 0))
|
||||
k++;
|
||||
if (k == SCR_WC_COUNT(scr_wc_usages)) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_VALUE",
|
||||
"%s: %s argument[%.0f] '%.*s' is not a valid enum value of type KeyUsage.",
|
||||
prefix, scr_wc_ordinals[index], i, (int)(s->len > 200 ? 200 : s->len),
|
||||
s->data);
|
||||
scr_str_release(s);
|
||||
scr_dyn_release(plain);
|
||||
return -1;
|
||||
}
|
||||
scr_str_release(s);
|
||||
mask |= 1L << k;
|
||||
}
|
||||
scr_dyn_release(plain);
|
||||
return mask;
|
||||
}
|
||||
|
||||
/* ── algorithm normalization ──────────────────────────────────────────
|
||||
* normalizeAlgorithm(algorithm, op) for the members this slice reads:
|
||||
* the canonical name, and for HMAC imports the hash and length. */
|
||||
typedef struct {
|
||||
const char *name; /* canonical registered name */
|
||||
const char *hash; /* HMAC import: the normalized hash name */
|
||||
bool has_length;
|
||||
double length;
|
||||
} ScrWcAlgorithm;
|
||||
|
||||
static const char scr_wc_normalize_prefix[] = "Failed to normalize algorithm";
|
||||
|
||||
static void scr_wc_missing_member(const char *dict, const char *member) {
|
||||
scr_wc_type_error("ERR_MISSING_OPTION",
|
||||
"%s: passed algorithm cannot be converted to '%s' because '%s' is required in "
|
||||
"'%s'.",
|
||||
scr_wc_normalize_prefix, dict, member, dict);
|
||||
}
|
||||
|
||||
/* Reads `key` off an algorithm dictionary (+1; undefined when absent). */
|
||||
static ScrDyn *scr_wc_member(const ScrDyn *dict, const char *key) {
|
||||
if (dict->kind != SCR_DYN_OBJ) return scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *v = scr_dyn_obj_read(dict, key, strlen(key));
|
||||
return v ? v : scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
/* The name an AlgorithmIdentifier carries (+1), with the Algorithm
|
||||
* dictionary's missing-name TypeError. `dict` names the dictionary the
|
||||
* value converts to. */
|
||||
static ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict) {
|
||||
if (!scr_wc_is_object(identifier)) return scr_wc_dom_string(identifier);
|
||||
ScrDyn *plain = scr_wc_plain(identifier);
|
||||
if (!plain) return NULL;
|
||||
ScrDyn *name = scr_wc_member(plain, "name");
|
||||
scr_dyn_release(plain);
|
||||
ScrStr *out = NULL;
|
||||
if (name->kind == SCR_DYN_UNDEF) scr_wc_missing_member(dict, "name");
|
||||
else out = scr_wc_dom_string(name);
|
||||
scr_dyn_release(name);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* The digest a HashAlgorithmIdentifier names: canonical name, or NULL
|
||||
* with NotSupportedError / the SC2020 refusal pending. */
|
||||
static const char *scr_wc_digest_algorithm(ScrDyn *identifier, const char *method) {
|
||||
ScrStr *name = scr_wc_algorithm_name(identifier, "Algorithm");
|
||||
if (!name) return NULL;
|
||||
const char *canonical = scr_wc_lookup(scr_wc_digest_names, SCR_WC_COUNT(scr_wc_digest_names), name);
|
||||
scr_str_release(name);
|
||||
if (!canonical) {
|
||||
scr_throw_domex("NotSupportedError", "Unrecognized algorithm name");
|
||||
return NULL;
|
||||
}
|
||||
if (!scr_wc_native_hash(canonical)) {
|
||||
scr_wc_unsupported_algorithm(method, canonical);
|
||||
return NULL;
|
||||
}
|
||||
return canonical;
|
||||
}
|
||||
|
||||
/* HmacImportParams over an object: hash (required, a SHA name), then the
|
||||
* optional [EnforceRange] unsigned long length. */
|
||||
static bool scr_wc_hmac_import_params(ScrDyn *identifier, ScrWcAlgorithm *out) {
|
||||
if (!scr_wc_is_object(identifier)) {
|
||||
scr_wc_missing_member("HmacImportParams", "hash");
|
||||
return false;
|
||||
}
|
||||
ScrDyn *plain = scr_wc_plain(identifier);
|
||||
if (!plain) return false;
|
||||
ScrDyn *hash = scr_wc_member(plain, "hash");
|
||||
bool ok = true;
|
||||
if (hash->kind == SCR_DYN_UNDEF) {
|
||||
scr_wc_missing_member("HmacImportParams", "hash");
|
||||
ok = false;
|
||||
}
|
||||
ScrDyn *hash_value = NULL;
|
||||
if (ok) {
|
||||
/* HashAlgorithmIdentifier converts to an object or a DOMString, then
|
||||
* ensureSHA reads the name: a string, or the object's name member. */
|
||||
ScrStr *hash_name = NULL;
|
||||
if (scr_wc_is_object(hash)) {
|
||||
hash_value = scr_dyn_retain(hash);
|
||||
ScrDyn *hp = scr_wc_plain(hash);
|
||||
ScrDyn *member = hp ? scr_wc_member(hp, "name") : NULL;
|
||||
if (hp) scr_dyn_release(hp);
|
||||
if (!member) ok = false;
|
||||
else {
|
||||
if (member->kind == SCR_DYN_STR) hash_name = scr_str_retain(member->v.str);
|
||||
scr_dyn_release(member);
|
||||
}
|
||||
} else {
|
||||
hash_name = scr_wc_dom_string(hash);
|
||||
if (!hash_name) ok = false;
|
||||
else hash_value = scr_dyn_new_str(hash_name);
|
||||
}
|
||||
bool sha = hash_name && hash_name->len >= 3 &&
|
||||
(hash_name->data[0] | 32) == 's' && (hash_name->data[1] | 32) == 'h' &&
|
||||
(hash_name->data[2] | 32) == 'a';
|
||||
if (hash_name) scr_str_release(hash_name);
|
||||
if (ok && !sha) {
|
||||
scr_throw_domex("NotSupportedError", "Only SHA hashes are supported in HmacImportParams");
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
ScrDyn *length = scr_wc_member(plain, "length");
|
||||
if (length->kind != SCR_DYN_UNDEF) {
|
||||
double x;
|
||||
if (!scr_dyn_number_coerce_js(length, &x)) ok = false;
|
||||
else if (x != x || x == 1.0 / 0.0 || x == -1.0 / 0.0) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE",
|
||||
"%s: length in passed algorithm is not a finite number.",
|
||||
scr_wc_normalize_prefix);
|
||||
ok = false;
|
||||
} else {
|
||||
x = trunc(x);
|
||||
if (x == 0) x = 0;
|
||||
if (x < 0 || x > 4294967295.0) {
|
||||
scr_wc_type_error("ERR_OUT_OF_RANGE",
|
||||
"%s: length in passed algorithm is outside the expected range of 0 to "
|
||||
"4294967295.",
|
||||
scr_wc_normalize_prefix);
|
||||
ok = false;
|
||||
} else if (x == 0) {
|
||||
scr_throw_domex("DataError", "HmacImportParams.length cannot be 0");
|
||||
ok = false;
|
||||
} else {
|
||||
out->has_length = true;
|
||||
out->length = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
scr_dyn_release(length);
|
||||
}
|
||||
if (ok) {
|
||||
out->hash = scr_wc_digest_algorithm(hash_value, "importKey");
|
||||
if (!out->hash) ok = false;
|
||||
}
|
||||
if (hash_value) scr_dyn_release(hash_value);
|
||||
scr_dyn_release(hash);
|
||||
scr_dyn_release(plain);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* normalizeAlgorithm for importKey, sign/verify (`names`) — false with
|
||||
* the error pending. Only HMAC parameters are read; any other recognized
|
||||
* algorithm is refused by the caller. */
|
||||
static bool scr_wc_normalize(ScrDyn *identifier, const char *const *names, size_t count,
|
||||
bool import, ScrWcAlgorithm *out) {
|
||||
memset(out, 0, sizeof *out);
|
||||
ScrStr *name = scr_wc_algorithm_name(identifier, "Algorithm");
|
||||
if (!name) return false;
|
||||
out->name = scr_wc_lookup(names, count, name);
|
||||
scr_str_release(name);
|
||||
if (!out->name) {
|
||||
scr_throw_domex("NotSupportedError", "Unrecognized algorithm name");
|
||||
return false;
|
||||
}
|
||||
if (import && strcmp(out->name, "HMAC") == 0) return scr_wc_hmac_import_params(identifier, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ── CryptoKey ─────────────────────────────────────────────────────────
|
||||
* A secret HMAC key: the bytes, the hash, the bit length, the usage mask
|
||||
* and the extractable flag. The algorithm and usages getters answer the
|
||||
* same object on every read (Node caches its clones), built on first
|
||||
* read. The bytes are cleared when the last reference goes. */
|
||||
typedef struct ScrCryptoKey {
|
||||
size_t rc;
|
||||
const char *hash; /* WebCrypto hash name */
|
||||
double length; /* bits */
|
||||
unsigned usages;
|
||||
bool extractable;
|
||||
ScrDyn *algorithm; /* cached getter results, or NULL */
|
||||
ScrDyn *usage_list;
|
||||
size_t len;
|
||||
unsigned char data[];
|
||||
} ScrCryptoKey;
|
||||
|
||||
static void *scr_crypto_key_retain(void *p) {
|
||||
((ScrCryptoKey *)p)->rc++;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void scr_crypto_key_release(void *p) {
|
||||
ScrCryptoKey *key = p;
|
||||
if (!key || --key->rc != 0) return;
|
||||
volatile unsigned char *bytes = key->data;
|
||||
for (size_t i = 0; i < key->len; i++) bytes[i] = 0;
|
||||
if (key->algorithm) scr_dyn_release(key->algorithm);
|
||||
if (key->usage_list) scr_dyn_release(key->usage_list);
|
||||
free(key);
|
||||
}
|
||||
|
||||
static ScrCryptoKey *scr_crypto_key_of(const ScrDyn *value) {
|
||||
if (value->kind != SCR_DYN_HANDLE || value->v.handle.tag != SCR_DYNH_CRYPTO_KEY) return NULL;
|
||||
return value->v.handle.ptr;
|
||||
}
|
||||
|
||||
bool scr_webcrypto_is_crypto_key(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_TYPED_REF) return false;
|
||||
return scr_crypto_key_of(value) != NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_wc_cstr(const char *s) {
|
||||
ScrStr *str = scr_str_new(s, strlen(s));
|
||||
ScrDyn *out = scr_dyn_new_str(str);
|
||||
scr_str_release(str);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_crypto_key_algorithm(ScrCryptoKey *key) {
|
||||
if (!key->algorithm) {
|
||||
ScrDyn *hash = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(hash, "name", 4, scr_wc_cstr(key->hash));
|
||||
ScrDyn *algorithm = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(algorithm, "name", 4, scr_wc_cstr("HMAC"));
|
||||
scr_dyn_obj_set(algorithm, "length", 6, scr_dyn_new_num(key->length));
|
||||
scr_dyn_obj_set(algorithm, "hash", 4, hash);
|
||||
key->algorithm = algorithm;
|
||||
}
|
||||
return scr_dyn_retain(key->algorithm);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_crypto_key_usages(ScrCryptoKey *key) {
|
||||
if (!key->usage_list) {
|
||||
ScrDyn *list = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_usages); i++)
|
||||
if (key->usages & (1u << i)) scr_dyn_arr_push(list, scr_wc_cstr(scr_wc_usages[i]));
|
||||
key->usage_list = list;
|
||||
}
|
||||
return scr_dyn_retain(key->usage_list);
|
||||
}
|
||||
|
||||
static bool scr_wc_name_is(const char *name, size_t len, const char *literal) {
|
||||
return len == strlen(literal) && memcmp(name, literal, len) == 0;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_crypto_key_get(void *h, const char *name, size_t len) {
|
||||
ScrCryptoKey *key = h;
|
||||
if (scr_wc_name_is(name, len, "type")) return scr_wc_cstr("secret");
|
||||
if (scr_wc_name_is(name, len, "extractable")) return scr_dyn_new_bool(key->extractable);
|
||||
if (scr_wc_name_is(name, len, "algorithm")) return scr_crypto_key_algorithm(key);
|
||||
if (scr_wc_name_is(name, len, "usages")) return scr_crypto_key_usages(key);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* The four members are prototype getters without setters: an assignment
|
||||
* in strict code throws Node's TypeError. */
|
||||
static bool scr_crypto_key_set(void *h, const char *name, size_t len, const ScrDyn *value) {
|
||||
(void)h;
|
||||
(void)value;
|
||||
if (!scr_wc_name_is(name, len, "type") && !scr_wc_name_is(name, len, "extractable") &&
|
||||
!scr_wc_name_is(name, len, "algorithm") && !scr_wc_name_is(name, len, "usages"))
|
||||
return false;
|
||||
char message[128];
|
||||
int n = snprintf(message, sizeof message,
|
||||
"Cannot set property %.*s of #<CryptoKey> which has only a getter", (int)len,
|
||||
name);
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, (size_t)n);
|
||||
return true;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_crypto_key_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args,
|
||||
size_t argc, const char *what) {
|
||||
(void)h;
|
||||
(void)self;
|
||||
(void)args;
|
||||
(void)argc;
|
||||
if (strcmp(method, "toString") == 0) return scr_wc_cstr("[object CryptoKey]");
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, what);
|
||||
scr_jb_puts(&b, " is not a function");
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* key[Symbol.toStringTag] answers "CryptoKey". */
|
||||
static ScrDyn *scr_crypto_key_symbol_get(void *h, const ScrDyn *key) {
|
||||
(void)h;
|
||||
if (key->kind != SCR_DYN_SYMBOL || !key->v.symbol.description) return NULL;
|
||||
ScrStr *description = key->v.symbol.description(key->v.symbol.value);
|
||||
bool tag = description && description->len == 18 &&
|
||||
memcmp(description->data, "Symbol.toStringTag", 18) == 0;
|
||||
if (description) scr_str_release(description);
|
||||
return tag ? scr_wc_cstr("CryptoKey") : NULL;
|
||||
}
|
||||
|
||||
/* No inspect_view: Node's custom inspector prints the members one level
|
||||
* deeper than a plain object would, which the shared handle inspection
|
||||
* cannot reproduce, so util.inspect refuses a CryptoKey ('not supported
|
||||
* yet') rather than printing a different shape. */
|
||||
static const ScrDynHandleOps scr_crypto_key_ops = {
|
||||
.cls = "CryptoKey",
|
||||
.retain = scr_crypto_key_retain,
|
||||
.release = scr_crypto_key_release,
|
||||
.invoke = scr_crypto_key_invoke,
|
||||
.get = scr_crypto_key_get,
|
||||
.set = scr_crypto_key_set,
|
||||
.json_empty_object = true,
|
||||
.to_string_tag = "CryptoKey",
|
||||
.symbol_get = scr_crypto_key_symbol_get,
|
||||
};
|
||||
|
||||
static ScrDyn *scr_crypto_key_new(const unsigned char *data, size_t len, const char *hash,
|
||||
double length, unsigned usages, bool extractable) {
|
||||
static SCR_TL bool installed;
|
||||
if (!installed) {
|
||||
installed = true;
|
||||
scr_dyn_handle_install(SCR_DYNH_CRYPTO_KEY, &scr_crypto_key_ops);
|
||||
}
|
||||
ScrCryptoKey *key = malloc(sizeof *key + (len ? len : 1));
|
||||
if (!key) scr_trap("scriptc: out of memory\n");
|
||||
key->rc = 1;
|
||||
key->hash = hash;
|
||||
key->length = length;
|
||||
key->usages = usages;
|
||||
key->extractable = extractable;
|
||||
key->algorithm = NULL;
|
||||
key->usage_list = NULL;
|
||||
key->len = len;
|
||||
if (len) memcpy(key->data, data, len);
|
||||
ScrDyn *box = scr_dyn_new_handle(key, SCR_DYNH_CRYPTO_KEY);
|
||||
scr_crypto_key_release(key);
|
||||
return box;
|
||||
}
|
||||
|
||||
/* ── the operations ───────────────────────────────────────────────────
|
||||
* Each takes its argument vector as one dyn array (borrowed) and answers
|
||||
* a +1 promise. */
|
||||
|
||||
static ScrPromise *scr_wc_rejected(void) {
|
||||
ScrPromise *p = scr_promise_new();
|
||||
scr_promise_reject_pending(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* The argument count check every method starts with (after the receiver
|
||||
* check the compiler's call shape already guarantees). */
|
||||
static bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t required) {
|
||||
size_t argc = (size_t)scr_dyn_arr_len(args);
|
||||
if (argc >= required) return true;
|
||||
scr_wc_type_error("ERR_MISSING_ARGS", "%s: %zu arguments required, but only %zu present.", prefix,
|
||||
required, argc);
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
ScrDyn *v[5];
|
||||
} ScrWcArgs;
|
||||
|
||||
static void scr_wc_args_take(ScrWcArgs *a, const ScrDyn *args, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) a->v[i] = scr_dyn_arr_at(args, (double)i);
|
||||
}
|
||||
|
||||
static void scr_wc_args_drop(ScrWcArgs *a, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) scr_dyn_release(a->v[i]);
|
||||
}
|
||||
|
||||
/* importKey(format, keyData, algorithm, extractable, keyUsages): Node's
|
||||
* conversion order, then macImportKey's checks and importKeySync's empty
|
||||
* usage check. */
|
||||
static ScrDyn *scr_wc_import(const ScrDyn *args) {
|
||||
static const char prefix[] = "Failed to execute 'importKey' on 'SubtleCrypto'";
|
||||
if (!scr_wc_required(prefix, args, 5)) return NULL;
|
||||
ScrWcArgs a;
|
||||
scr_wc_args_take(&a, args, 5);
|
||||
ScrDyn *result = NULL;
|
||||
ScrBytes *data = NULL;
|
||||
const char *format = scr_wc_key_format(prefix, a.v[0]);
|
||||
if (!format) goto done;
|
||||
bool jwk = strcmp(format, "jwk") == 0;
|
||||
/* The JsonWebKey dictionary is not implemented; that format is refused
|
||||
* after the remaining arguments convert. */
|
||||
if (!jwk) {
|
||||
data = scr_wc_buffer_source(prefix, 1, a.v[1]);
|
||||
if (!data) goto done;
|
||||
}
|
||||
if (!scr_wc_is_object(a.v[2])) {
|
||||
ScrStr *s = scr_wc_dom_string(a.v[2]);
|
||||
if (!s) goto done;
|
||||
scr_str_release(s);
|
||||
}
|
||||
bool extractable = scr_dyn_truthy(a.v[3]);
|
||||
long usages = scr_wc_key_usages(prefix, 4, a.v[4]);
|
||||
if (usages < 0) goto done;
|
||||
ScrWcAlgorithm algorithm;
|
||||
if (!scr_wc_normalize(a.v[2], scr_wc_import_names, SCR_WC_COUNT(scr_wc_import_names), true,
|
||||
&algorithm))
|
||||
goto done;
|
||||
if (strcmp(algorithm.name, "HMAC") != 0) {
|
||||
scr_wc_unsupported_algorithm("importKey", algorithm.name);
|
||||
goto done;
|
||||
}
|
||||
if (usages & ~(long)(SCR_WC_USAGE_SIGN | SCR_WC_USAGE_VERIFY)) {
|
||||
scr_throw_domex("SyntaxError", "Unsupported key usage for HMAC key");
|
||||
goto done;
|
||||
}
|
||||
if (jwk) {
|
||||
scr_wc_unsupported("importKey", "the 'jwk' key format");
|
||||
goto done;
|
||||
}
|
||||
if (strcmp(format, "raw") != 0 && strcmp(format, "raw-secret") != 0) {
|
||||
char message[96];
|
||||
snprintf(message, sizeof message, "Unable to import HMAC using %s format", format);
|
||||
scr_throw_domex("NotSupportedError", message);
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
ScrBytes *view = data;
|
||||
SCR_BYTES_SNAPSHOT(view);
|
||||
size_t len = (size_t)scr_bytes_byte_len(view);
|
||||
if (len == 0) {
|
||||
scr_throw_domex("DataError", "Zero-length key is not supported");
|
||||
goto done;
|
||||
}
|
||||
double length = (double)len * 8;
|
||||
unsigned char last = view->data[len - 1];
|
||||
if (algorithm.has_length) {
|
||||
double bytes = floor(algorithm.length / 8) + floor((7 + fmod(algorithm.length, 8)) / 8);
|
||||
if (bytes != (double)len) {
|
||||
scr_throw_domex("DataError", "Invalid key length");
|
||||
goto done;
|
||||
}
|
||||
/* A length that is not a whole number of bytes clears the unused
|
||||
* low bits of the final byte (truncateToBitLength). */
|
||||
int extra = (int)fmod(algorithm.length, 8);
|
||||
if (extra) last = (unsigned char)(last & (0xff << (8 - extra)));
|
||||
length = algorithm.length;
|
||||
}
|
||||
if (usages == 0) {
|
||||
scr_throw_domex("SyntaxError", "Usages cannot be empty when importing a secret key.");
|
||||
goto done;
|
||||
}
|
||||
result = scr_crypto_key_new(view->data, len, algorithm.hash, length, (unsigned)usages,
|
||||
extractable);
|
||||
scr_crypto_key_of(result)->data[len - 1] = last;
|
||||
}
|
||||
done:
|
||||
if (data) scr_bytes_release(data);
|
||||
scr_wc_args_drop(&a, 5);
|
||||
return result;
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_import_key(ScrDyn *args) {
|
||||
ScrDyn *key = scr_wc_import(args);
|
||||
if (!key) return scr_wc_rejected();
|
||||
return scr_promise_settled_ref(key, &scr_dyn_retain_v, &scr_dyn_release_v, NULL);
|
||||
}
|
||||
|
||||
/* sign(algorithm, key, data) / verify(algorithm, key, signature, data):
|
||||
* the HMAC over `data`, or NULL with the error pending. *mac_len is the
|
||||
* digest length. */
|
||||
static bool scr_wc_sign_verify(const ScrDyn *args, bool verify, unsigned char mac[64],
|
||||
size_t *mac_len, ScrBytes **signature) {
|
||||
const char *prefix = verify ? "Failed to execute 'verify' on 'SubtleCrypto'"
|
||||
: "Failed to execute 'sign' on 'SubtleCrypto'";
|
||||
const char *method = verify ? "verify" : "sign";
|
||||
size_t n = verify ? 4 : 3;
|
||||
if (!scr_wc_required(prefix, args, n)) return false;
|
||||
ScrWcArgs a;
|
||||
scr_wc_args_take(&a, args, n);
|
||||
bool ok = false;
|
||||
ScrBytes *data = NULL;
|
||||
if (!scr_wc_is_object(a.v[0])) {
|
||||
ScrStr *s = scr_wc_dom_string(a.v[0]);
|
||||
if (!s) goto done;
|
||||
scr_str_release(s);
|
||||
}
|
||||
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
|
||||
if (!key) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
if (verify) {
|
||||
*signature = scr_wc_buffer_source(prefix, 2, a.v[2]);
|
||||
if (!*signature) goto done;
|
||||
}
|
||||
data = scr_wc_buffer_source(prefix, verify ? 3 : 2, a.v[verify ? 3 : 2]);
|
||||
if (!data) goto done;
|
||||
ScrWcAlgorithm algorithm;
|
||||
if (!scr_wc_normalize(a.v[0], scr_wc_sign_names, SCR_WC_COUNT(scr_wc_sign_names), false,
|
||||
&algorithm))
|
||||
goto done;
|
||||
if (strcmp(algorithm.name, "HMAC") != 0) {
|
||||
scr_wc_unsupported_algorithm(method, algorithm.name);
|
||||
goto done;
|
||||
}
|
||||
if (!(key->usages & (verify ? SCR_WC_USAGE_VERIFY : SCR_WC_USAGE_SIGN))) {
|
||||
char message[64];
|
||||
snprintf(message, sizeof message, "Unable to use this key to %s", method);
|
||||
scr_throw_domex("InvalidAccessError", message);
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
ScrBytes *view = data;
|
||||
SCR_BYTES_SNAPSHOT(view);
|
||||
*mac_len = scr_crypto_hmac_raw(scr_wc_native_hash(key->hash), key->data, key->len, view->data,
|
||||
(size_t)scr_bytes_byte_len(view), mac);
|
||||
}
|
||||
ok = true;
|
||||
done:
|
||||
if (data) scr_bytes_release(data);
|
||||
scr_wc_args_drop(&a, n);
|
||||
return ok;
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_sign(ScrDyn *args) {
|
||||
unsigned char mac[64];
|
||||
size_t mac_len = 0;
|
||||
if (!scr_wc_sign_verify(args, false, mac, &mac_len, NULL)) return scr_wc_rejected();
|
||||
ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)mac_len);
|
||||
memcpy(out->data, mac, mac_len);
|
||||
ScrPromise *p = scr_promise_new();
|
||||
scr_crypto_defer_bytes(out, NULL, scr_crypto_promise_callback(p),
|
||||
scr_crypto_promise_settle_array_buffer);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* The verify result's adapter: the deferred value is a dyn boolean. */
|
||||
static void scr_wc_settle_verified(ScrClosure *cb, ScrError *error, ScrBytes *value) {
|
||||
(void)error;
|
||||
ScrPromise *p;
|
||||
memcpy(&p, &cb->caps[0]->slot, sizeof p);
|
||||
ScrDyn *verified = (ScrDyn *)value;
|
||||
scr_promise_fulfill_bool(p, scr_dyn_truthy(verified));
|
||||
scr_dyn_release(verified);
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_verify(ScrDyn *args) {
|
||||
unsigned char mac[64];
|
||||
size_t mac_len = 0;
|
||||
ScrBytes *signature = NULL;
|
||||
if (!scr_wc_sign_verify(args, true, mac, &mac_len, &signature)) {
|
||||
if (signature) scr_bytes_release(signature);
|
||||
return scr_wc_rejected();
|
||||
}
|
||||
bool equal;
|
||||
{
|
||||
ScrBytes *view = signature;
|
||||
SCR_BYTES_SNAPSHOT(view);
|
||||
/* A signature of another length is simply not valid; equal lengths
|
||||
* compare in constant time. */
|
||||
size_t sig_len = (size_t)scr_bytes_byte_len(view);
|
||||
equal = sig_len == mac_len;
|
||||
unsigned char diff = 0;
|
||||
for (size_t i = 0; equal && i < mac_len; i++) diff |= (unsigned char)(view->data[i] ^ mac[i]);
|
||||
equal = equal && diff == 0;
|
||||
}
|
||||
scr_bytes_release(signature);
|
||||
ScrPromise *p = scr_promise_new();
|
||||
scr_crypto_defer_dyn(scr_dyn_new_bool(equal), NULL, scr_crypto_promise_callback(p),
|
||||
scr_wc_settle_verified);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* digest(algorithm, data). */
|
||||
ScrPromise *scr_webcrypto_digest(ScrDyn *args) {
|
||||
static const char prefix[] = "Failed to execute 'digest' on 'SubtleCrypto'";
|
||||
if (!scr_wc_required(prefix, args, 2)) return scr_wc_rejected();
|
||||
ScrWcArgs a;
|
||||
scr_wc_args_take(&a, args, 2);
|
||||
ScrBytes *data = NULL;
|
||||
ScrBytes *out = NULL;
|
||||
if (!scr_wc_is_object(a.v[0])) {
|
||||
ScrStr *s = scr_wc_dom_string(a.v[0]);
|
||||
if (!s) goto done;
|
||||
scr_str_release(s);
|
||||
}
|
||||
data = scr_wc_buffer_source(prefix, 1, a.v[1]);
|
||||
if (!data) goto done;
|
||||
const char *hash = scr_wc_digest_algorithm(a.v[0], "digest");
|
||||
if (!hash) goto done;
|
||||
{
|
||||
ScrBytes *view = data;
|
||||
SCR_BYTES_SNAPSHOT(view);
|
||||
unsigned char digest[64];
|
||||
size_t len = scr_crypto_digest_raw(scr_wc_native_hash(hash), view->data,
|
||||
(size_t)scr_bytes_byte_len(view), digest);
|
||||
out = scr_bytes_new(SCR_BYTES_U8, (double)len);
|
||||
memcpy(out->data, digest, len);
|
||||
}
|
||||
done:
|
||||
if (data) scr_bytes_release(data);
|
||||
scr_wc_args_drop(&a, 2);
|
||||
if (!out) return scr_wc_rejected();
|
||||
ScrPromise *p = scr_promise_new();
|
||||
scr_crypto_defer_bytes(out, NULL, scr_crypto_promise_callback(p),
|
||||
scr_crypto_promise_settle_array_buffer);
|
||||
return p;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// WebCrypto HMAC through the crypto global: importKey('raw') for SHA-1 and
|
||||
// SHA-2 keys, sign and verify (a wrong or short signature answers false),
|
||||
// the CryptoKey members, instanceof CryptoKey, the key sources importKey
|
||||
// and sign accept, and that results settle asynchronously.
|
||||
const encoder = new TextEncoder();
|
||||
const secret = encoder.encode("scriptc-webcrypto-secret");
|
||||
const data = encoder.encode("header.payload");
|
||||
|
||||
function hex(buffer: ArrayBuffer): string {
|
||||
return Buffer.from(buffer).toString("hex");
|
||||
}
|
||||
|
||||
const key = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
secret,
|
||||
{ name: "HMAC", hash: "SHA-256" },
|
||||
false,
|
||||
["sign", "verify"],
|
||||
);
|
||||
console.log(key.type, key.extractable, key.usages.join(","));
|
||||
const algorithm = key.algorithm as HmacKeyAlgorithm;
|
||||
console.log(algorithm.name, algorithm.length, algorithm.hash.name);
|
||||
console.log(key instanceof CryptoKey, key.algorithm === key.algorithm, key.usages === key.usages);
|
||||
|
||||
const signature = await crypto.subtle.sign("HMAC", key, data);
|
||||
console.log(signature.byteLength, hex(signature));
|
||||
console.log(await crypto.subtle.verify("HMAC", key, signature, data));
|
||||
console.log(await crypto.subtle.verify({ name: "HMAC" }, key, signature, encoder.encode("header.payloaD")));
|
||||
const tampered = new Uint8Array(signature);
|
||||
tampered[0] ^= 1;
|
||||
console.log(await crypto.subtle.verify("HMAC", key, tampered, data));
|
||||
console.log(await crypto.subtle.verify("HMAC", key, signature.slice(0, 16), data));
|
||||
console.log(await crypto.subtle.verify("HMAC", key, new Uint8Array(0), data));
|
||||
|
||||
for (const hash of ["SHA-1", "SHA-384", "SHA-512"]) {
|
||||
const k = await crypto.subtle.importKey("raw", secret, { name: "HMAC", hash }, true, ["sign"]);
|
||||
const a = k.algorithm as HmacKeyAlgorithm;
|
||||
const mac = await crypto.subtle.sign({ name: "hmac" }, k, data);
|
||||
console.log(hash, a.hash.name, a.length, k.extractable, k.usages.join(","), mac.byteLength, hex(mac));
|
||||
}
|
||||
|
||||
// Names normalize case-insensitively; usages come back in Node's order.
|
||||
const lower = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
secret,
|
||||
{ name: "hmac", hash: { name: "sha-256" } },
|
||||
true,
|
||||
["verify", "sign"],
|
||||
);
|
||||
const lowerAlgorithm = lower.algorithm as HmacKeyAlgorithm;
|
||||
console.log(lowerAlgorithm.name, lowerAlgorithm.hash.name, lower.usages.join(","));
|
||||
|
||||
// An explicit length in bits must match the key bytes.
|
||||
const sized = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
secret,
|
||||
{ name: "HMAC", hash: "SHA-256", length: secret.length * 8 },
|
||||
false,
|
||||
["verify"],
|
||||
);
|
||||
console.log((sized.algorithm as HmacKeyAlgorithm).length, sized.usages.join(","));
|
||||
|
||||
// Key and data sources: ArrayBuffer, Buffer, DataView and subarray views.
|
||||
const fromBuffer = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
secret.slice().buffer,
|
||||
{ name: "HMAC", hash: "SHA-256" },
|
||||
false,
|
||||
["sign"],
|
||||
);
|
||||
console.log(hex(await crypto.subtle.sign("HMAC", fromBuffer, Buffer.from("header.payload"))) === hex(signature));
|
||||
const padded = encoder.encode("xxheader.payloadyy");
|
||||
console.log(hex(await crypto.subtle.sign("HMAC", key, padded.subarray(2, 16))) === hex(signature));
|
||||
console.log(hex(await crypto.subtle.sign("HMAC", key, new DataView(data.buffer))) === hex(signature));
|
||||
|
||||
// The data is read when sign is called.
|
||||
const mutable = encoder.encode("header.payload");
|
||||
const pending = crypto.subtle.sign("HMAC", key, mutable);
|
||||
mutable[0] = 0;
|
||||
console.log(hex(await pending) === hex(signature));
|
||||
|
||||
// Results settle after the synchronous code that started them.
|
||||
const order: string[] = [];
|
||||
const signed = crypto.subtle.sign("HMAC", key, data).then(() => {
|
||||
order.push("sign");
|
||||
});
|
||||
const imported = crypto.subtle
|
||||
.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"])
|
||||
.then(() => {
|
||||
order.push("importKey");
|
||||
});
|
||||
order.push("sync");
|
||||
await Promise.all([signed, imported]);
|
||||
console.log(order.join(","));
|
||||
@@ -0,0 +1,83 @@
|
||||
// WebCrypto HMAC failures reject with Node's TypeErrors (argument count,
|
||||
// enum values, buffer sources, CryptoKey arguments, required members) and
|
||||
// DOMExceptions (usages, key length, algorithm names, key access).
|
||||
const encoder = new TextEncoder();
|
||||
const secret = encoder.encode("scriptc-webcrypto-secret");
|
||||
const data = encoder.encode("payload");
|
||||
|
||||
function report(label: string, error: Error): void {
|
||||
if (error instanceof DOMException) {
|
||||
const dom = error as DOMException;
|
||||
console.log(label, dom.name, dom.code, dom.message, "DOMException");
|
||||
} else {
|
||||
const coded = error as Error & { code?: string };
|
||||
console.log(label, coded.name, coded.code, coded.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function attempt(label: string, run: () => Promise<unknown>): Promise<void> {
|
||||
try {
|
||||
await run();
|
||||
console.log(label, "resolved");
|
||||
} catch (e) {
|
||||
report(label, e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function attemptVerify(label: string, run: () => Promise<boolean>): Promise<void> {
|
||||
try {
|
||||
console.log(label, "resolved", await run());
|
||||
} catch (e) {
|
||||
report(label, e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
const signOnly = await crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
||||
const verifyOnly = await crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-384" }, false, ["verify"]);
|
||||
|
||||
await attempt("usage", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign", "encrypt"]),
|
||||
);
|
||||
await attempt("empty usages", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, []),
|
||||
);
|
||||
await attempt("zero-length key", () =>
|
||||
crypto.subtle.importKey("raw", new Uint8Array(0), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]),
|
||||
);
|
||||
await attempt("length mismatch", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256", length: 64 }, false, ["sign"]),
|
||||
);
|
||||
await attempt("length zero", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256", length: 0 }, false, ["sign"]),
|
||||
);
|
||||
await attempt("missing hash", () =>
|
||||
crypto.subtle.importKey("raw", secret, "HMAC", false, ["sign"]),
|
||||
);
|
||||
await attempt("md5 hash", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "MD5" }, false, ["sign"]),
|
||||
);
|
||||
await attempt("unknown sha", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-999" }, false, ["sign"]),
|
||||
);
|
||||
await attempt("unknown algorithm", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "NOPE", hash: "SHA-256" }, false, ["sign"]),
|
||||
);
|
||||
await attempt("pkcs8 format", () =>
|
||||
crypto.subtle.importKey("pkcs8", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]),
|
||||
);
|
||||
await attempt("string key", () =>
|
||||
crypto.subtle.importKey("raw", "secret" as unknown as BufferSource, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]),
|
||||
);
|
||||
|
||||
await attempt("sign with verify key", () => crypto.subtle.sign("HMAC", verifyOnly, data));
|
||||
await attemptVerify("verify with sign key", () => crypto.subtle.verify("HMAC", signOnly, new Uint8Array(32), data));
|
||||
await attempt("sign without key", () =>
|
||||
crypto.subtle.sign("HMAC", secret as unknown as CryptoKey, data),
|
||||
);
|
||||
await attempt("sign unknown algorithm", () => crypto.subtle.sign("HMAC-SHA256", signOnly, data));
|
||||
await attempt("sign string data", () =>
|
||||
crypto.subtle.sign("HMAC", signOnly, "payload" as unknown as BufferSource),
|
||||
);
|
||||
await attemptVerify("verify string signature", () =>
|
||||
crypto.subtle.verify("HMAC", verifyOnly, "sig" as unknown as BufferSource, data),
|
||||
);
|
||||
@@ -0,0 +1,35 @@
|
||||
// crypto.subtle.digest over the SHA-1 and SHA-2 hashes: string and object
|
||||
// algorithm identifiers, typed-array, Buffer and ArrayBuffer input, and
|
||||
// Node's rejections for unknown names and non-buffer data.
|
||||
const encoder = new TextEncoder();
|
||||
const input = encoder.encode("scriptc digest input");
|
||||
|
||||
function hex(buffer: ArrayBuffer): string {
|
||||
return Buffer.from(buffer).toString("hex");
|
||||
}
|
||||
|
||||
for (const name of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) {
|
||||
const digest = await crypto.subtle.digest(name, input);
|
||||
console.log(name, digest.byteLength, hex(digest));
|
||||
}
|
||||
console.log(hex(await crypto.subtle.digest({ name: "sha-256" }, Buffer.from("scriptc digest input"))));
|
||||
console.log(hex(await crypto.subtle.digest("SHA-256", input.slice().buffer)));
|
||||
console.log(hex(await crypto.subtle.digest("SHA-256", new Uint8Array(0))));
|
||||
|
||||
async function attempt(label: string, run: () => Promise<unknown>): Promise<void> {
|
||||
try {
|
||||
await run();
|
||||
console.log(label, "resolved");
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException) {
|
||||
const error = e as DOMException;
|
||||
console.log(label, error.name, error.code, error.message, "DOMException");
|
||||
} else {
|
||||
const error = e as Error & { code?: string };
|
||||
console.log(label, error.name, error.code, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await attempt("md5", () => crypto.subtle.digest("MD5", input));
|
||||
await attempt("string data", () => crypto.subtle.digest("SHA-256", "text" as unknown as BufferSource));
|
||||
@@ -0,0 +1,52 @@
|
||||
// WebCrypto from JavaScript: keys and algorithms carried as dynamic
|
||||
// values, the CryptoKey brand checks libraries use (Symbol.toStringTag,
|
||||
// Object.prototype.toString and instanceof), JSON.stringify of a key, and
|
||||
// rejections for missing arguments and non-key values.
|
||||
const encoder = new TextEncoder();
|
||||
const secret = encoder.encode("dynamic-secret");
|
||||
const table = {
|
||||
HS256: { name: "HMAC", hash: "SHA-256" },
|
||||
HS512: { name: "HMAC", hash: "SHA-512" },
|
||||
};
|
||||
|
||||
async function importFor(alg, usage) {
|
||||
const params = table[alg];
|
||||
return crypto.subtle.importKey("raw", secret, params, false, [usage]);
|
||||
}
|
||||
|
||||
const key = await importFor("HS256", "sign");
|
||||
console.log(key[Symbol.toStringTag], Object.prototype.toString.call(key), String(key));
|
||||
console.log(key instanceof CryptoKey, secret instanceof CryptoKey, {} instanceof CryptoKey);
|
||||
console.log(JSON.stringify(key), typeof key, key.type, key.algorithm.hash.name, key.usages.includes("sign"));
|
||||
const sig = new Uint8Array(await crypto.subtle.sign(table.HS256, key, encoder.encode("a.b")));
|
||||
console.log(sig.length, Buffer.from(sig).toString("base64url"));
|
||||
const verifier = await importFor("HS256", "verify");
|
||||
let verified = false;
|
||||
try {
|
||||
verified = await crypto.subtle.verify(table.HS256, verifier, sig, encoder.encode("a.b"));
|
||||
} catch {}
|
||||
console.log(verified);
|
||||
const other = await importFor("HS512", "verify");
|
||||
console.log(await crypto.subtle.verify(table.HS512, other, sig, encoder.encode("a.b")));
|
||||
|
||||
async function attempt(label, run) {
|
||||
try {
|
||||
await run();
|
||||
console.log(label, "resolved");
|
||||
} catch (e) {
|
||||
console.log(label, e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
await attempt("too few", () => crypto.subtle.sign("HMAC", key));
|
||||
await attempt("import too few", () => crypto.subtle.importKey("raw", secret, table.HS256, false));
|
||||
await attempt("verify too few", () => crypto.subtle.verify("HMAC", key, sig));
|
||||
await attempt("digest too few", () => crypto.subtle.digest("SHA-256"));
|
||||
const lookalike = JSON.parse('{"type":"secret","usages":["sign"]}');
|
||||
await attempt("not a key", () => crypto.subtle.sign("HMAC", lookalike, secret));
|
||||
await attempt("bad format", () => crypto.subtle.importKey("nope", secret, table.HS256, false, ["sign"]));
|
||||
await attempt("bad usage", () => crypto.subtle.importKey("raw", secret, table.HS256, false, ["bogus"]));
|
||||
await attempt("usages not a sequence", () => crypto.subtle.importKey("raw", secret, table.HS256, false, "sign"));
|
||||
await attempt("algorithm without name", () => crypto.subtle.sign({ hash: "SHA-256" }, key, secret));
|
||||
await attempt("hash object without name", () =>
|
||||
crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: {} }, false, ["sign"]),
|
||||
);
|
||||
@@ -0,0 +1,103 @@
|
||||
// The real jose@6.2.12 HS256 path: SignJWT and jwtVerify from jose's
|
||||
// public jose/jwt/sign, jose/jwt/verify and jose/errors entry points, over
|
||||
// crypto.subtle importKey, sign and verify. node_modules/jose vendors the
|
||||
// dist/webapi files those entry points load and their declarations,
|
||||
// unmodified (MIT, LICENSE.md), with a package.json that exports only
|
||||
// those entry points.
|
||||
import { SignJWT } from "jose/jwt/sign";
|
||||
import { jwtVerify } from "jose/jwt/verify";
|
||||
import * as errors from "jose/errors";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const secret = encoder.encode("scriptc-jose-hs256-shared-secret");
|
||||
const now = new Date(1_800_000_000_000);
|
||||
const iat = 1_799_999_000;
|
||||
|
||||
async function sign(claims, alg = "HS256", key = secret) {
|
||||
const jwt = new SignJWT(claims);
|
||||
jwt.setProtectedHeader({ alg, typ: "JWT" });
|
||||
jwt.setIssuedAt(iat);
|
||||
jwt.setIssuer("urn:scriptc:issuer");
|
||||
jwt.setAudience("urn:scriptc:audience");
|
||||
jwt.setSubject("alice");
|
||||
return jwt.sign(key);
|
||||
}
|
||||
|
||||
async function verify(label, token, options = {}, key = secret) {
|
||||
try {
|
||||
const { payload, protectedHeader } = await jwtVerify(token, key, {
|
||||
algorithms: ["HS256"],
|
||||
currentDate: now,
|
||||
...options,
|
||||
});
|
||||
console.log(label, "ok", JSON.stringify(protectedHeader), JSON.stringify(payload));
|
||||
} catch (e) {
|
||||
console.log(label, e.code, e.message, e instanceof errors.JOSEError, e.name, e.claim, e.reason);
|
||||
}
|
||||
}
|
||||
|
||||
const token = await sign({ role: "admin", exp: iat + 3600 });
|
||||
console.log(token);
|
||||
await verify("valid", token, { issuer: "urn:scriptc:issuer", audience: "urn:scriptc:audience" });
|
||||
await verify("bytes token", encoder.encode(token));
|
||||
await verify("wrong secret", token, {}, encoder.encode("another-secret"));
|
||||
const [header, body, signature] = token.split(".");
|
||||
const forged = Buffer.from(JSON.stringify({ role: "root", sub: "mallory" })).toString("base64url");
|
||||
await verify("tampered payload", `${header}.${forged}.${signature}`);
|
||||
await verify("tampered signature", `${header}.${body}.${signature.slice(0, -2)}AA`);
|
||||
await verify("malformed", "not-a-jwt");
|
||||
await verify("bad signature encoding", `${header}.${body}.***`);
|
||||
await verify("expired", token, { currentDate: new Date((iat + 7200) * 1000) });
|
||||
await verify("clock tolerance", token, { currentDate: new Date((iat + 3610) * 1000), clockTolerance: 30 });
|
||||
await verify("not yet valid", await sign({ nbf: iat + 600 }));
|
||||
await verify("issuer", token, { issuer: "urn:other" });
|
||||
await verify("audience", token, { audience: "urn:other" });
|
||||
await verify("required claim", token, { requiredClaims: ["jti"] });
|
||||
await verify("alg not allowed", token, { algorithms: ["HS512"] });
|
||||
await verify("max token age", token, { maxTokenAge: 600 });
|
||||
|
||||
const hs384 = await sign({ scope: "read" }, "HS384");
|
||||
await verify("hs384", hs384, { algorithms: ["HS384"] });
|
||||
const hs512 = await sign({ scope: "write" }, "HS512");
|
||||
await verify("hs512", hs512, { algorithms: ["HS256", "HS512"] });
|
||||
await verify("hs512 as hs256 only", hs512);
|
||||
await verify("none", `${Buffer.from('{"alg":"none"}').toString("base64url")}.${body}.`, { algorithms: ["none"] });
|
||||
|
||||
// The same key imported once and reused as a CryptoKey.
|
||||
const cryptoKey = await crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
|
||||
await verify("crypto key", token, {}, cryptoKey);
|
||||
const signingKey = await crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-512" }, false, ["sign"]);
|
||||
await verify("wrong key hash", token, {}, signingKey);
|
||||
|
||||
// Chained builder calls: jose's declarations type every setter's result as
|
||||
// `this`, implemented by the JWTClaimsBuilder base SignJWT extends.
|
||||
const chained = await new SignJWT({ role: "reader" })
|
||||
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
|
||||
.setIssuedAt(iat)
|
||||
.setIssuer("urn:scriptc:issuer")
|
||||
.setAudience(["urn:scriptc:audience", "urn:other"])
|
||||
.setSubject("bob")
|
||||
.setJti("jti-2")
|
||||
.setNotBefore(iat - 60)
|
||||
.setExpirationTime(iat + 60)
|
||||
.sign(secret);
|
||||
console.log(chained);
|
||||
await verify("chained", chained, { audience: "urn:scriptc:audience" });
|
||||
|
||||
// String durations: jose's secs() parses them.
|
||||
for (const maxTokenAge of ["10 minutes", "1 hour", "1.5h", "2 days", "+20m", "1 week", "1y", "900 secs", "3 HOURS", "2h ago", "-2h ago", "10 fortnights", ""]) {
|
||||
await verify(`max token age ${JSON.stringify(maxTokenAge)}`, token, { maxTokenAge });
|
||||
}
|
||||
for (const clockTolerance of ["30s", "5 sec", "1 min from now", "1 min ago"]) {
|
||||
await verify(`clock tolerance ${clockTolerance}`, token, { currentDate: new Date((iat + 3620) * 1000), clockTolerance });
|
||||
}
|
||||
const relative = new SignJWT({ role: "reader" });
|
||||
relative.setProtectedHeader({ alg: "HS256" }).setIssuedAt("-1 minute").setExpirationTime("2h");
|
||||
const claims = JSON.parse(Buffer.from((await relative.sign(secret)).split(".")[1], "base64url").toString());
|
||||
const clock = Math.floor(Date.now() / 1000);
|
||||
console.log(Math.abs(claims.exp - clock - 7200) <= 2, Math.abs(claims.iat - clock + 60) <= 2);
|
||||
try {
|
||||
new SignJWT({}).setExpirationTime("soon");
|
||||
} catch (e) {
|
||||
console.log(e.name, e.message);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Filip Skokan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import type * as t from '../types.d.ts';
|
||||
/** @param payload Initial JWT Claims Set. Defaults to an empty object. */
|
||||
declare const SignJWT_base: new (payload?: t.JWTPayload) => t.ProduceJWT;
|
||||
/** Builds and signs Compact JWS-formatted JSON Web Tokens. */
|
||||
export declare class SignJWT extends SignJWT_base {
|
||||
#private;
|
||||
/**
|
||||
* Sets the JWS Protected Header. May only be called once.
|
||||
*
|
||||
* @param protectedHeader JWS Protected Header. Must contain an "alg" (JWS Algorithm) property.
|
||||
*/
|
||||
setProtectedHeader(protectedHeader: t.JWTHeaderParameters): this;
|
||||
/**
|
||||
* Signs and returns the JWT.
|
||||
*
|
||||
* @param key Private key or shared secret to sign the JWT with. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
sign(key: t.KeyInput, options?: t.SignOptions): Promise<string>;
|
||||
}
|
||||
export {};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import type * as t from '../types.d.ts';
|
||||
/** JWS verification and JWT Claims Set validation options. */
|
||||
export interface JWTVerifyOptions extends t.VerifyOptions, t.JWTClaimVerificationOptions {
|
||||
}
|
||||
/**
|
||||
* Resolves a JWT verification key. No token components have been authenticated when this function
|
||||
* is called.
|
||||
*/
|
||||
export interface JWTVerifyGetKey<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array> extends t.GetKeyFunction<t.CompactJWSHeaderParameters, t.FlattenedJWSInput, KeyType | t.KeyObject | t.JWK> {
|
||||
}
|
||||
/**
|
||||
* Verifies a Compact JWS-formatted JWT and validates its Claims Set.
|
||||
*
|
||||
* @param key Public key or shared secret to verify the JWT with. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
* @param options JWT Verification and JWT Claims Set validation options.
|
||||
*/
|
||||
export declare function jwtVerify<PayloadType = t.JWTPayload>(jwt: string | Uint8Array, key: t.KeyInput, options?: JWTVerifyOptions): Promise<t.JWTVerifyResult<PayloadType>>;
|
||||
/**
|
||||
* Verifies the JWT signature and claims, returning the resolved key.
|
||||
*
|
||||
* @param getKey Function resolving a public key or shared secret to verify the JWT with. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
* @param options JWT Verification and JWT Claims Set validation options.
|
||||
*/
|
||||
export declare function jwtVerify<PayloadType = t.JWTPayload, KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array>(jwt: string | Uint8Array, getKey: JWTVerifyGetKey<KeyType>, options?: JWTVerifyOptions): Promise<t.JWTVerifyResult<PayloadType> & t.ResolvedKey<KeyType>>;
|
||||
/**
|
||||
* Accepts a key or key resolver. Use this overload when forwarding either form; the result includes
|
||||
* `key` only when a resolver was used.
|
||||
*
|
||||
* @param key Key, or function resolving a key, to verify the JWT with. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
* @param options JWT Verification and JWT Claims Set validation options.
|
||||
*/
|
||||
export declare function jwtVerify<PayloadType = t.JWTPayload>(jwt: string | Uint8Array, key: t.KeyInput | JWTVerifyGetKey, options?: JWTVerifyOptions): Promise<t.JWTVerifyResult<PayloadType> & Partial<t.ResolvedKey>>;
|
||||
+736
@@ -0,0 +1,736 @@
|
||||
/** Supported JWS "alg" (Algorithm) Header Parameter values. Availability depends on the runtime. */
|
||||
export type JWSAlgorithm =
|
||||
| 'HS256'
|
||||
| 'HS384'
|
||||
| 'HS512'
|
||||
| 'RS256'
|
||||
| 'RS384'
|
||||
| 'RS512'
|
||||
| 'PS256'
|
||||
| 'PS384'
|
||||
| 'PS512'
|
||||
| 'ES256'
|
||||
| 'ES384'
|
||||
| 'ES512'
|
||||
| 'EdDSA'
|
||||
| 'Ed25519'
|
||||
| 'ML-DSA-44'
|
||||
| 'ML-DSA-65'
|
||||
| 'ML-DSA-87'
|
||||
| (string & {})
|
||||
|
||||
/** Supported JWE "alg" (Algorithm) Header Parameter values. Availability depends on the runtime. */
|
||||
export type JWEKeyManagementAlgorithm =
|
||||
| 'dir'
|
||||
| 'A128KW'
|
||||
| 'A192KW'
|
||||
| 'A256KW'
|
||||
| 'A128GCMKW'
|
||||
| 'A192GCMKW'
|
||||
| 'A256GCMKW'
|
||||
| 'ECDH-ES'
|
||||
| 'ECDH-ES+A128KW'
|
||||
| 'ECDH-ES+A192KW'
|
||||
| 'ECDH-ES+A256KW'
|
||||
| 'RSA-OAEP'
|
||||
| 'RSA-OAEP-256'
|
||||
| 'RSA-OAEP-384'
|
||||
| 'RSA-OAEP-512'
|
||||
| 'PBES2-HS256+A128KW'
|
||||
| 'PBES2-HS384+A192KW'
|
||||
| 'PBES2-HS512+A256KW'
|
||||
| (string & {})
|
||||
|
||||
/**
|
||||
* Supported JWE "enc" (Encryption Algorithm) Header Parameter values. Availability depends on the
|
||||
* runtime.
|
||||
*/
|
||||
export type JWEContentEncryptionAlgorithm =
|
||||
| 'A128CBC-HS256'
|
||||
| 'A192CBC-HS384'
|
||||
| 'A256CBC-HS512'
|
||||
| 'A128GCM'
|
||||
| 'A192GCM'
|
||||
| 'A256GCM'
|
||||
| (string & {})
|
||||
|
||||
/** JWK "kty" (Key Type) Parameter values supported by this module. */
|
||||
export type JWKKeyType = 'EC' | 'RSA' | 'OKP' | 'AKP' | 'oct' | (string & {})
|
||||
|
||||
/**
|
||||
* Generic JSON Web Key Parameters.
|
||||
*
|
||||
* > Note: This is declared as a type alias rather than an interface so that it satisfies the implicit index
|
||||
* > signature of the `JsonWebKey` types shipped by `@types/node` and `lib.dom`.
|
||||
*/
|
||||
export type JWKParameters = {
|
||||
/** JWK "kty" (Key Type) Parameter */
|
||||
kty?: JWKKeyType
|
||||
/** JWK "alg" (Algorithm) Parameter */
|
||||
alg?: JWSAlgorithm | JWEKeyManagementAlgorithm | JWEContentEncryptionAlgorithm
|
||||
/** Permitted key operations. */
|
||||
key_ops?: string[]
|
||||
/** Whether the key may be exported. */
|
||||
ext?: boolean
|
||||
|
||||
/** JWK "use" (Public Key Use) Parameter */
|
||||
use?: 'sig' | 'enc' | (string & {})
|
||||
/** X.509 certificate chain. */
|
||||
x5c?: string[]
|
||||
/** X.509 certificate SHA-1 thumbprint. */
|
||||
x5t?: string
|
||||
/** X.509 certificate SHA-256 thumbprint. */
|
||||
'x5t#S256'?: string
|
||||
/** X.509 certificate URL. */
|
||||
x5u?: string
|
||||
|
||||
/** JWK "kid" (Key ID) Parameter */
|
||||
kid?: string
|
||||
}
|
||||
|
||||
/** Convenience interface for public OKP JSON Web Keys. */
|
||||
export interface JWK_OKP_Public extends JWKParameters {
|
||||
/** Key pair subtype. */
|
||||
crv: string
|
||||
/** Public key. */
|
||||
x: string
|
||||
}
|
||||
|
||||
/** Convenience interface for private OKP JSON Web Keys. */
|
||||
export interface JWK_OKP_Private extends JWK_OKP_Public {
|
||||
/** Private key. */
|
||||
d: string
|
||||
}
|
||||
|
||||
/** Convenience interface for public AKP JSON Web Keys. */
|
||||
export interface JWK_AKP_Public extends JWKParameters {
|
||||
/** JWK "alg" (Algorithm) Parameter */
|
||||
alg: string
|
||||
|
||||
/** AKP JWK "pub" (The Public key) Parameter */
|
||||
pub: string
|
||||
}
|
||||
|
||||
/** Convenience interface for private AKP JSON Web Keys. */
|
||||
export interface JWK_AKP_Private extends JWK_AKP_Public {
|
||||
/** AKP JWK "priv" (The Private Key) Parameter */
|
||||
priv: string
|
||||
}
|
||||
|
||||
/** Convenience interface for public EC JSON Web Keys. */
|
||||
export interface JWK_EC_Public extends JWKParameters {
|
||||
/** Curve. */
|
||||
crv: string
|
||||
/** Public key X coordinate. */
|
||||
x: string
|
||||
/** Public key Y coordinate. */
|
||||
y: string
|
||||
}
|
||||
|
||||
/** Convenience interface for private EC JSON Web Keys. */
|
||||
export interface JWK_EC_Private extends JWK_EC_Public {
|
||||
/** Private key. */
|
||||
d: string
|
||||
}
|
||||
|
||||
/** Convenience interface for public RSA JSON Web Keys. */
|
||||
export interface JWK_RSA_Public extends JWKParameters {
|
||||
/** Public exponent. */
|
||||
e: string
|
||||
/** Modulus. */
|
||||
n: string
|
||||
}
|
||||
|
||||
/** Convenience interface for private RSA JSON Web Keys. */
|
||||
export interface JWK_RSA_Private extends JWK_RSA_Public {
|
||||
/** Private exponent. */
|
||||
d: string
|
||||
/** First factor CRT exponent. */
|
||||
dp: string
|
||||
/** Second factor CRT exponent. */
|
||||
dq: string
|
||||
/** First prime factor. */
|
||||
p: string
|
||||
/** Second prime factor. */
|
||||
q: string
|
||||
/** First CRT coefficient. */
|
||||
qi: string
|
||||
}
|
||||
|
||||
/** Convenience interface for "oct" JSON Web Keys. */
|
||||
export interface JWK_oct extends JWKParameters {
|
||||
/** Symmetric key value. */
|
||||
k: string
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON Web Key ({@link https://www.rfc-editor.org/info/rfc7517/ JWK}). "RSA", "EC", "OKP", "AKP",
|
||||
* and "oct" key types are supported.
|
||||
*
|
||||
* > Note: This is declared as a type alias rather than an interface so that it satisfies the implicit index
|
||||
* > signature of the `JsonWebKey` types shipped by `@types/node` and `lib.dom`. It spells out the
|
||||
* > {@link JWKParameters} members rather than intersecting them so that every JWK member is documented
|
||||
* > in one place.
|
||||
*/
|
||||
export type JWK = {
|
||||
/** JWK "kty" (Key Type) Parameter */
|
||||
kty?: JWKKeyType
|
||||
/** JWK "alg" (Algorithm) Parameter */
|
||||
alg?: JWSAlgorithm | JWEKeyManagementAlgorithm | JWEContentEncryptionAlgorithm
|
||||
/** Permitted key operations. */
|
||||
key_ops?: string[]
|
||||
/** Whether the key may be exported. */
|
||||
ext?: boolean
|
||||
|
||||
/** JWK "use" (Public Key Use) Parameter */
|
||||
use?: 'sig' | 'enc' | (string & {})
|
||||
/** X.509 certificate chain. */
|
||||
x5c?: string[]
|
||||
/** X.509 certificate SHA-1 thumbprint. */
|
||||
x5t?: string
|
||||
/** X.509 certificate SHA-256 thumbprint. */
|
||||
'x5t#S256'?: string
|
||||
/** X.509 certificate URL. */
|
||||
x5u?: string
|
||||
|
||||
/** JWK "kid" (Key ID) Parameter */
|
||||
kid?: string
|
||||
/** EC curve or OKP key pair subtype. */
|
||||
crv?: string
|
||||
/** Private RSA exponent, EC key, or OKP key. */
|
||||
d?: string
|
||||
/** RSA first factor CRT exponent. */
|
||||
dp?: string
|
||||
/** RSA second factor CRT exponent. */
|
||||
dq?: string
|
||||
/** RSA public exponent. */
|
||||
e?: string
|
||||
/** Symmetric key value. */
|
||||
k?: string
|
||||
/** RSA modulus. */
|
||||
n?: string
|
||||
/** RSA first prime factor. */
|
||||
p?: string
|
||||
/** RSA second prime factor. */
|
||||
q?: string
|
||||
/** RSA first CRT coefficient. */
|
||||
qi?: string
|
||||
/** EC public key X coordinate or OKP public key. */
|
||||
x?: string
|
||||
/** EC public key Y coordinate. */
|
||||
y?: string
|
||||
|
||||
/** AKP JWK "pub" (Public Key) Parameter */
|
||||
pub?: string
|
||||
|
||||
/** AKP JWK "priv" (Private key) Parameter */
|
||||
priv?: string
|
||||
/** Additional RSA prime factors. */
|
||||
oth?: Array<{
|
||||
/** Factor CRT exponent. */
|
||||
d?: string
|
||||
/** Prime factor. */
|
||||
r?: string
|
||||
/** Factor CRT coefficient. */
|
||||
t?: string
|
||||
}>
|
||||
}
|
||||
|
||||
/** Discriminated union of supported JSON Web Key shapes, narrowed by the "kty" (Key Type) Parameter. */
|
||||
// Intersect "kty" into each member separately: typedoc omits the parentheses in `X & (A | B)`.
|
||||
export type AnyJWK =
|
||||
| (JWK_EC_Private & { kty: 'EC' })
|
||||
| (JWK_EC_Public & { kty: 'EC' })
|
||||
| (JWK_RSA_Private & { kty: 'RSA' })
|
||||
| (JWK_RSA_Public & { kty: 'RSA' })
|
||||
| (JWK_OKP_Private & { kty: 'OKP' })
|
||||
| (JWK_OKP_Public & { kty: 'OKP' })
|
||||
| (JWK_AKP_Private & { kty: 'AKP' })
|
||||
| (JWK_AKP_Public & { kty: 'AKP' })
|
||||
| (JWK_oct & { kty: 'oct' })
|
||||
|
||||
/** Key or secret input accepted by all sign, verify, encrypt, and decrypt operations. */
|
||||
export type KeyInput = CryptoKey | KeyObject | JWK | Uint8Array
|
||||
|
||||
/** Dynamic key resolver for consuming operations. */
|
||||
export interface GetKeyFunction<IProtectedHeader, IToken, KeyTypes extends KeyInput = KeyInput> {
|
||||
/**
|
||||
* Resolves a key for an unverified token. Throw if no suitable key can be resolved.
|
||||
*
|
||||
* @param token The consumed JWE or JWS token; none of its components have been verified.
|
||||
*/
|
||||
(protectedHeader: IProtectedHeader, token: IToken): Promise<KeyTypes> | KeyTypes
|
||||
}
|
||||
|
||||
/** Flattened JWS verification input. */
|
||||
export interface FlattenedJWSInput {
|
||||
/** JWS Unprotected Header as a JSON object. Not integrity protected; omit when empty. */
|
||||
header?: JWSHeaderParameters
|
||||
|
||||
/** Base64url-encoded payload; with `b64: false`, supply an unencoded string or Uint8Array. */
|
||||
payload: string | Uint8Array
|
||||
|
||||
/** Base64url-encoded UTF-8 JWS Protected Header. Integrity protected; omit when empty. */
|
||||
protected?: string
|
||||
|
||||
/** Base64url-encoded signature or MAC. */
|
||||
signature: string
|
||||
}
|
||||
|
||||
/** General JWS verification input. */
|
||||
export interface GeneralJWSInput {
|
||||
/** Base64url-encoded payload; with `b64: false`, supply an unencoded string or Uint8Array. */
|
||||
payload: string | Uint8Array
|
||||
|
||||
/**
|
||||
* The "signatures" member value MUST be an array of JSON objects. Each object represents a
|
||||
* signature or MAC over the JWS Payload and the JWS Protected Header.
|
||||
*/
|
||||
signatures: Omit<FlattenedJWSInput, 'payload'>[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattened JWS JSON Serialization token. The payload is an empty string when the
|
||||
* {@link https://www.rfc-editor.org/info/rfc7797/ unencoded payload option} is used.
|
||||
*/
|
||||
export interface FlattenedJWS extends Partial<FlattenedJWSInput> {
|
||||
payload: string
|
||||
signature: string
|
||||
}
|
||||
|
||||
/**
|
||||
* General JWS JSON Serialization token. The payload is an empty string when the
|
||||
* {@link https://www.rfc-editor.org/info/rfc7797/ unencoded payload option} is used.
|
||||
*/
|
||||
export interface GeneralJWS {
|
||||
payload: string
|
||||
signatures: Omit<FlattenedJWSInput, 'payload'>[]
|
||||
}
|
||||
|
||||
/** Header Parameters common to JWE and JWS. */
|
||||
export interface JoseHeaderParameters {
|
||||
/** "kid" (Key ID) Header Parameter */
|
||||
kid?: string
|
||||
|
||||
/** X.509 certificate SHA-1 thumbprint. */
|
||||
x5t?: string
|
||||
|
||||
/** X.509 certificate chain. */
|
||||
x5c?: string[]
|
||||
|
||||
/** X.509 certificate URL. */
|
||||
x5u?: string
|
||||
|
||||
/** JWK Set URL. */
|
||||
jku?: string
|
||||
|
||||
/** Public JWK only; private and symmetric key parameters are not permitted. */
|
||||
jwk?: Omit<JWK, 'd' | 'dp' | 'dq' | 'k' | 'p' | 'q' | 'qi' | 'priv' | 'oth'>
|
||||
|
||||
/** "typ" (Type) Header Parameter */
|
||||
typ?: string
|
||||
|
||||
/** Content type. */
|
||||
cty?: string
|
||||
}
|
||||
|
||||
/** Recognized JWS Header Parameters; additional members may also be present. */
|
||||
export interface JWSHeaderParameters extends JoseHeaderParameters {
|
||||
/** JWS "alg" (Algorithm) Header Parameter */
|
||||
alg?: JWSAlgorithm
|
||||
|
||||
/**
|
||||
* Controls payload encoding and the JWS signing input as defined by
|
||||
* {@link https://www.rfc-editor.org/info/rfc7797/ RFC7797}. Set to `false` and list `b64` in
|
||||
* `crit` to use an unencoded payload.
|
||||
*/
|
||||
b64?: boolean
|
||||
|
||||
/** Extension parameters that must be recognized. */
|
||||
crit?: string[]
|
||||
|
||||
/** Any other JWS Header member. */
|
||||
[propName: string]: unknown
|
||||
}
|
||||
|
||||
/** Recognized JWE Key Management-related Header Parameters. */
|
||||
export interface JWEKeyManagementHeaderParameters {
|
||||
/** ECDH-ES Agreement PartyUInfo bytes, used in ConcatKDF and added to the JOSE header. */
|
||||
apu?: Uint8Array
|
||||
|
||||
/** ECDH-ES Agreement PartyVInfo bytes, used in ConcatKDF and added to the JOSE header. */
|
||||
apv?: Uint8Array
|
||||
|
||||
/** PBES2 PBKDF2 iteration count, added to the JOSE header. */
|
||||
p2c?: number
|
||||
|
||||
/** @deprecated For testing and vector validation only. */
|
||||
p2s?: Uint8Array
|
||||
/** @deprecated For testing and vector validation only. */
|
||||
iv?: Uint8Array
|
||||
/** @deprecated For testing and vector validation only. */
|
||||
epk?: CryptoKey | KeyObject
|
||||
}
|
||||
|
||||
/** Flattened JWE JSON Serialization token. */
|
||||
export interface FlattenedJWE {
|
||||
/**
|
||||
* Base64url-encoded additional authenticated data; integrity protected but not encrypted. Omit
|
||||
* when empty.
|
||||
*/
|
||||
aad?: string
|
||||
|
||||
/** Base64url-encoded ciphertext. */
|
||||
ciphertext: string
|
||||
|
||||
/** Base64url-encoded encrypted key. Omit when empty. */
|
||||
encrypted_key?: string
|
||||
|
||||
/**
|
||||
* JWE Per-Recipient Unprotected Header as a JSON object. Not integrity protected; omit when
|
||||
* empty.
|
||||
*/
|
||||
header?: JWEHeaderParameters
|
||||
|
||||
/** Base64url-encoded initialization vector. Omit when empty. */
|
||||
iv?: string
|
||||
|
||||
/** Base64url-encoded UTF-8 JWE Protected Header. Integrity protected; omit when empty. */
|
||||
protected?: string
|
||||
|
||||
/** Base64url-encoded authentication tag. Omit when empty. */
|
||||
tag?: string
|
||||
|
||||
/** JWE Shared Unprotected Header as a JSON object. Not integrity protected; omit when empty. */
|
||||
unprotected?: JWEHeaderParameters
|
||||
}
|
||||
|
||||
/** General JWE JSON Serialization token. */
|
||||
export interface GeneralJWE extends Omit<FlattenedJWE, 'encrypted_key' | 'header'> {
|
||||
recipients: Pick<FlattenedJWE, 'encrypted_key' | 'header'>[]
|
||||
}
|
||||
|
||||
/** Recognized JWE Header Parameters; additional members may also be present. */
|
||||
export interface JWEHeaderParameters extends JoseHeaderParameters {
|
||||
/** JWE "alg" (Algorithm) Header Parameter */
|
||||
alg?: JWEKeyManagementAlgorithm
|
||||
|
||||
/** JWE "enc" (Encryption Algorithm) Header Parameter */
|
||||
enc?: JWEContentEncryptionAlgorithm
|
||||
|
||||
/** Extension parameters that must be recognized. */
|
||||
crit?: string[]
|
||||
|
||||
/**
|
||||
* JWE compression algorithm. Only `"DEF"` (DEFLATE) is supported, requiring the runtime's
|
||||
* `CompressionStream` / `DecompressionStream` APIs.
|
||||
*/
|
||||
zip?: 'DEF' | (string & {})
|
||||
|
||||
/** Any other JWE Header member. */
|
||||
[propName: string]: unknown
|
||||
}
|
||||
|
||||
/** Shared "crit" option for signing, verification, encryption, and decryption. */
|
||||
export interface CritOption {
|
||||
/**
|
||||
* Recognized "crit" (Critical) Header Parameter names. Set each value to `true` to require
|
||||
* integrity protection, or `false` when protection is optional. The JWS `b64` extension is always
|
||||
* recognized and processed.
|
||||
*
|
||||
* > Warning: Other extensions are only checked for syntax and optional integrity protection. Their presence
|
||||
* > is not required by this option. You must check their presence and process them according to the
|
||||
* > profile's validation steps after the operation succeeds.
|
||||
*/
|
||||
crit?: {
|
||||
[propName: string]: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/** JWE Decryption options. */
|
||||
export interface DecryptOptions extends CritOption {
|
||||
/**
|
||||
* Accepted JWE "alg" (Algorithm) Header Parameter values. Defaults to all algorithms applicable
|
||||
* to the key or secret except PBES2, which must be explicitly allowed.
|
||||
*/
|
||||
keyManagementAlgorithms?: JWEKeyManagementAlgorithm[]
|
||||
|
||||
/**
|
||||
* Accepted JWE "enc" (Encryption Algorithm) Header Parameter values. Defaults to all algorithms
|
||||
* applicable to the key or secret.
|
||||
*/
|
||||
contentEncryptionAlgorithms?: JWEContentEncryptionAlgorithm[]
|
||||
|
||||
/**
|
||||
* Maximum "p2c" (PBES2 Count) Header Parameter value, limiting PBKDF2 iterations and their
|
||||
* computational expense. Defaults to 10000; must be a positive safe integer or `Infinity` to
|
||||
* disable the limit.
|
||||
*/
|
||||
maxPBES2Count?: number
|
||||
|
||||
/**
|
||||
* Maximum decompressed plaintext size in bytes. Defaults to 250000; `0` rejects compressed JWEs,
|
||||
* and `Infinity` disables the limit. Other values must be positive safe integers.
|
||||
*/
|
||||
maxDecompressedLength?: number
|
||||
}
|
||||
|
||||
/** JWE Encryption options. */
|
||||
export interface EncryptOptions extends CritOption {}
|
||||
|
||||
/** JWT Claims Set verification options. */
|
||||
export interface JWTClaimVerificationOptions {
|
||||
/** Expected JWT "aud" (Audience) Claim value(s). Requires the claim to be present. */
|
||||
audience?: string | string[]
|
||||
|
||||
/**
|
||||
* Clock skew tolerance in seconds or a duration string (e.g. "5 seconds"). Applies to the "nbf"
|
||||
* (Not Before) and "exp" (Expiration Time) claims, and to "iat" (Issued At) when
|
||||
* {@link maxTokenAge} is set.
|
||||
*/
|
||||
clockTolerance?: string | number
|
||||
|
||||
/** Expected JWT "iss" (Issuer) Claim value(s). Requires the claim to be present. */
|
||||
issuer?: string | string[]
|
||||
|
||||
/**
|
||||
* Maximum time since the JWT "iat" (Issued At) Claim, in seconds or a duration string (e.g. "2
|
||||
* hours"). Requires the claim to be present.
|
||||
*/
|
||||
maxTokenAge?: string | number
|
||||
|
||||
/** Expected JWT "sub" (Subject) Claim value. Requires the claim to be present. */
|
||||
subject?: string
|
||||
|
||||
/** Expected JWT "typ" (Type) Header Parameter value. Requires the parameter to be present. */
|
||||
typ?: string
|
||||
|
||||
/** Date for NumericDate comparisons. Defaults to `new Date()`. */
|
||||
currentDate?: Date
|
||||
|
||||
/**
|
||||
* Additional claim names required in the JWT Claims Set. The {@link issuer}, {@link audience},
|
||||
* {@link subject}, and {@link maxTokenAge} options independently require "iss", "aud", "sub", and
|
||||
* "iat", respectively.
|
||||
*/
|
||||
requiredClaims?: string[]
|
||||
}
|
||||
|
||||
/** JWS Verification options. */
|
||||
export interface VerifyOptions extends CritOption {
|
||||
/**
|
||||
* Accepted JWS "alg" (Algorithm) Header Parameter values. Defaults to all algorithms applicable
|
||||
* to the key or secret. Unsecured JWTs (`alg: "none"`) are never accepted.
|
||||
*/
|
||||
algorithms?: JWSAlgorithm[]
|
||||
}
|
||||
|
||||
/** JWS Signing options. */
|
||||
export interface SignOptions extends CritOption {}
|
||||
|
||||
/** Recognized JWT Claims Set members; additional members may also be present. */
|
||||
export interface JWTPayload {
|
||||
/** JWT Issuer */
|
||||
iss?: string
|
||||
|
||||
/** JWT Subject */
|
||||
sub?: string
|
||||
|
||||
/** JWT Audience */
|
||||
aud?: string | string[]
|
||||
|
||||
/** JWT ID */
|
||||
jti?: string
|
||||
|
||||
/** Not valid before this Unix timestamp in seconds. */
|
||||
nbf?: number
|
||||
|
||||
/** Expiration Unix timestamp in seconds. */
|
||||
exp?: number
|
||||
|
||||
/** Issued-at Unix timestamp in seconds. */
|
||||
iat?: number
|
||||
|
||||
/** Any other JWT Claim Set member. */
|
||||
[propName: string]: unknown
|
||||
}
|
||||
|
||||
/** Flattened JWE JSON Serialization decryption result. */
|
||||
export interface FlattenedDecryptResult {
|
||||
/** JWE Additional Authenticated Data, integrity protected but not encrypted. */
|
||||
additionalAuthenticatedData?: Uint8Array
|
||||
|
||||
/** Plaintext. */
|
||||
plaintext: Uint8Array
|
||||
|
||||
/** JWE Protected Header. */
|
||||
protectedHeader?: JWEHeaderParameters
|
||||
|
||||
/** JWE Shared Unprotected Header. Not integrity protected. */
|
||||
sharedUnprotectedHeader?: JWEHeaderParameters
|
||||
|
||||
/** JWE Per-Recipient Unprotected Header. Not integrity protected. */
|
||||
unprotectedHeader?: JWEHeaderParameters
|
||||
}
|
||||
|
||||
/** General JWE JSON Serialization decryption result. */
|
||||
export interface GeneralDecryptResult extends FlattenedDecryptResult {}
|
||||
|
||||
/** Compact JWE decryption result. */
|
||||
export interface CompactDecryptResult {
|
||||
/** Plaintext. */
|
||||
plaintext: Uint8Array
|
||||
|
||||
/** JWE Protected Header. */
|
||||
protectedHeader: CompactJWEHeaderParameters
|
||||
}
|
||||
|
||||
/** Flattened JWS JSON Serialization verification result. */
|
||||
export interface FlattenedVerifyResult {
|
||||
/** JWS Payload. */
|
||||
payload: Uint8Array
|
||||
|
||||
/** JWS Protected Header. */
|
||||
protectedHeader?: JWSHeaderParameters
|
||||
|
||||
/** JWS Unprotected Header. Not integrity protected. */
|
||||
unprotectedHeader?: JWSHeaderParameters
|
||||
}
|
||||
|
||||
/** General JWS JSON Serialization verification result. */
|
||||
export interface GeneralVerifyResult extends FlattenedVerifyResult {}
|
||||
|
||||
/** Compact JWS verification result. */
|
||||
export interface CompactVerifyResult {
|
||||
/** JWS Payload. */
|
||||
payload: Uint8Array
|
||||
|
||||
/** JWS Protected Header. */
|
||||
protectedHeader: CompactJWSHeaderParameters
|
||||
}
|
||||
|
||||
/** Signed JSON Web Token (JWT) verification result. */
|
||||
export interface JWTVerifyResult<PayloadType = JWTPayload> {
|
||||
/** JWT Claims Set. */
|
||||
payload: PayloadType &
|
||||
JWTPayload &
|
||||
([PayloadType] extends [object] ? unknown : unknown extends PayloadType ? unknown : never)
|
||||
|
||||
/** JWS Protected Header. */
|
||||
protectedHeader: JWTHeaderParameters
|
||||
}
|
||||
|
||||
/** Encrypted JSON Web Token (JWT) decryption result. */
|
||||
export interface JWTDecryptResult<PayloadType = JWTPayload> {
|
||||
/** JWT Claims Set. */
|
||||
payload: PayloadType &
|
||||
JWTPayload &
|
||||
([PayloadType] extends [object] ? unknown : unknown extends PayloadType ? unknown : never)
|
||||
|
||||
/** JWE Protected Header. */
|
||||
protectedHeader: CompactJWEHeaderParameters
|
||||
}
|
||||
|
||||
/** Key resolver result metadata. */
|
||||
export interface ResolvedKey<KeyType extends CryptoKey | Uint8Array = CryptoKey | Uint8Array> {
|
||||
/** The resolved key, present only when a key resolver is used. */
|
||||
key: KeyType
|
||||
}
|
||||
|
||||
/** Recognized Compact JWS Header Parameters; additional members may also be present. */
|
||||
export interface CompactJWSHeaderParameters extends JWSHeaderParameters {
|
||||
alg: JWSAlgorithm
|
||||
}
|
||||
|
||||
/** Recognized signed JWT Header Parameters; additional members may also be present. */
|
||||
export interface JWTHeaderParameters extends CompactJWSHeaderParameters {
|
||||
b64?: boolean
|
||||
}
|
||||
|
||||
/** Recognized Compact JWE Header Parameters; additional members may also be present. */
|
||||
export interface CompactJWEHeaderParameters extends JWEHeaderParameters {
|
||||
alg: JWEKeyManagementAlgorithm
|
||||
enc: JWEContentEncryptionAlgorithm
|
||||
}
|
||||
|
||||
/** JSON Web Key Set. */
|
||||
export interface JSONWebKeySet {
|
||||
keys: JWK[]
|
||||
}
|
||||
|
||||
/** Node.js {@link !KeyObject} representation accepted as key input. */
|
||||
export interface KeyObject {
|
||||
type: string
|
||||
}
|
||||
|
||||
/** The runtime's Web Crypto {@link !CryptoKey} representation accepted as key input. */
|
||||
export type CryptoKey = typeof globalThis extends {
|
||||
crypto: { subtle: { generateKey(...args: any[]): Promise<infer R> } }
|
||||
}
|
||||
? Extract<R, { type: string }>
|
||||
: CryptoKeyStructuralFallback
|
||||
|
||||
/** Structural fallback used when the host {@link !CryptoKey} type cannot be inferred. */
|
||||
export interface CryptoKeyStructuralFallback {
|
||||
readonly algorithm: { name: string }
|
||||
readonly extractable: boolean
|
||||
readonly type: string
|
||||
readonly usages: string[]
|
||||
}
|
||||
|
||||
/** Shared fluent API for JWT-producing classes. */
|
||||
export interface ProduceJWT {
|
||||
/**
|
||||
* Set the "iss" (Issuer) Claim.
|
||||
*
|
||||
* @param issuer "Issuer" Claim value to set on the JWT Claims Set.
|
||||
*/
|
||||
setIssuer(issuer: string): this
|
||||
|
||||
/**
|
||||
* Set the "sub" (Subject) Claim.
|
||||
*
|
||||
* @param subject "sub" (Subject) Claim value to set on the JWT Claims Set.
|
||||
*/
|
||||
setSubject(subject: string): this
|
||||
|
||||
/**
|
||||
* Set the "aud" (Audience) Claim.
|
||||
*
|
||||
* @param audience "aud" (Audience) Claim value to set on the JWT Claims Set.
|
||||
*/
|
||||
setAudience(audience: string | string[]): this
|
||||
|
||||
/**
|
||||
* Set the "jti" (JWT ID) Claim.
|
||||
*
|
||||
* @param jwtId "jti" (JWT ID) Claim value to set on the JWT Claims Set.
|
||||
*/
|
||||
setJti(jwtId: string): this
|
||||
|
||||
/**
|
||||
* Set the "nbf" (Not Before) Claim. Numbers are Unix timestamps in seconds; Dates are converted
|
||||
* to seconds. Strings are relative to now, using seconds, minutes, hours, days, weeks, or years
|
||||
* (365.25 days; no months). Prefix `-` or suffix `ago` subtracts the duration.
|
||||
*
|
||||
* @param input "nbf" (Not Before) Claim value as a timestamp, Date, or relative duration.
|
||||
*/
|
||||
setNotBefore(input: number | string | Date): this
|
||||
|
||||
/**
|
||||
* Set the "exp" (Expiration Time) Claim. Accepts a Unix timestamp in seconds, a Date, or a
|
||||
* duration relative to now using the same formats as {@link setNotBefore}.
|
||||
*
|
||||
* @param input "exp" (Expiration Time) Claim value as a timestamp, Date, or relative duration.
|
||||
*/
|
||||
setExpirationTime(input: number | string | Date): this
|
||||
|
||||
/**
|
||||
* Set the "iat" (Issued At) Claim. Defaults to the current Unix timestamp in seconds. Accepts a
|
||||
* Unix timestamp in seconds, a Date, or a duration relative to now using the same formats as
|
||||
* {@link setNotBefore}.
|
||||
*
|
||||
* @param input "iat" (Issued At) Claim value as a timestamp, Date, or relative duration.
|
||||
*/
|
||||
setIssuedAt(input?: number | string | Date): this
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
import type * as t from '../types.d.ts';
|
||||
/** Stable error codes used by this module. */
|
||||
export type JOSEErrorCode = 'ERR_JOSE_ALG_NOT_ALLOWED' | 'ERR_JOSE_GENERIC' | 'ERR_JOSE_NOT_SUPPORTED' | 'ERR_JWE_DECRYPTION_FAILED' | 'ERR_JWE_INVALID' | 'ERR_JWK_INVALID' | 'ERR_JWKS_INVALID' | 'ERR_JWKS_MULTIPLE_MATCHING_KEYS' | 'ERR_JWKS_NO_MATCHING_KEY' | 'ERR_JWKS_TIMEOUT' | 'ERR_JWS_INVALID' | 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED' | 'ERR_JWT_CLAIM_VALIDATION_FAILED' | 'ERR_JWT_EXPIRED' | 'ERR_JWT_INVALID';
|
||||
/** Shared details of JWT claim or header validation failures. */
|
||||
export interface JWTClaimValidationFailure {
|
||||
/** Claim or header that failed validation. */
|
||||
claim: string;
|
||||
/** Reason code for the validation failure. */
|
||||
reason: JWTClaimValidationReason;
|
||||
/** The parsed JWT Claims Set; validation of other claims may be incomplete. */
|
||||
payload: t.JWTPayload;
|
||||
}
|
||||
/** Reason codes produced by JWT Claims Set validation. */
|
||||
export type JWTClaimValidationReason = 'check_failed' | 'invalid' | 'mismatch' | 'missing' | 'unspecified' | (string & {});
|
||||
/** Base class for JOSE errors. */
|
||||
export declare class JOSEError extends Error {
|
||||
/** Stable code identifying the error class. */
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
/** Stable code identifying the error class. Use {@link AnyJOSEError} to narrow subclasses by code. */
|
||||
code: JOSEErrorCode | (string & {});
|
||||
constructor(message?: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Thrown when JWT claim or header validation fails. Expiration is reported separately as
|
||||
* {@link JWTExpired}.
|
||||
*/
|
||||
export declare class JWTClaimValidationFailed extends JOSEError implements JWTClaimValidationFailure {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
/** The {@link JWTClaimValidationFailure} details carried by this error. */
|
||||
cause: JWTClaimValidationFailure;
|
||||
/** Claim or header that failed validation. */
|
||||
claim: string;
|
||||
/** Reason code for the validation failure. */
|
||||
reason: JWTClaimValidationReason;
|
||||
/**
|
||||
* The parsed JWT Claims Set; validation of other claims may be incomplete. With
|
||||
* {@link jwt/verify.jwtVerify jwtVerify} and {@link jwt/decrypt.jwtDecrypt jwtDecrypt}, token
|
||||
* authentication precedes claim validation. {@link jwt/unsecured.UnsecuredJWT.decode} does not
|
||||
* authenticate tokens.
|
||||
*/
|
||||
payload: t.JWTPayload;
|
||||
constructor(message: string, payload: t.JWTPayload, claim?: string, reason?: JWTClaimValidationReason);
|
||||
}
|
||||
/** Thrown when a JWT has expired or exceeds the configured maximum token age. */
|
||||
export declare class JWTExpired extends JOSEError implements JWTClaimValidationFailure {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
/** The {@link JWTClaimValidationFailure} details carried by this error. */
|
||||
cause: JWTClaimValidationFailure;
|
||||
/** Claim or header that failed validation. */
|
||||
claim: string;
|
||||
/** Reason code for the validation failure. */
|
||||
reason: JWTClaimValidationReason;
|
||||
/**
|
||||
* The parsed JWT Claims Set; validation of other claims may be incomplete. With
|
||||
* {@link jwt/verify.jwtVerify jwtVerify} and {@link jwt/decrypt.jwtDecrypt jwtDecrypt}, token
|
||||
* authentication precedes claim validation. {@link jwt/unsecured.UnsecuredJWT.decode} does not
|
||||
* authenticate tokens.
|
||||
*/
|
||||
payload: t.JWTPayload;
|
||||
constructor(message: string, payload: t.JWTPayload, claim?: string, reason?: JWTClaimValidationReason);
|
||||
}
|
||||
/** Thrown when an algorithm is disallowed by configuration. */
|
||||
export declare class JOSEAlgNotAllowed extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
}
|
||||
/** Thrown when a feature or algorithm is unsupported. */
|
||||
export declare class JOSENotSupported extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
}
|
||||
/** Thrown when JWE ciphertext decryption or authentication fails. */
|
||||
export declare class JWEDecryptionFailed extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
constructor(message?: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
/** Thrown when a JWE is invalid. */
|
||||
export declare class JWEInvalid extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
}
|
||||
/** Thrown when a JWS is invalid. */
|
||||
export declare class JWSInvalid extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
}
|
||||
/** Thrown when a JWT is invalid. */
|
||||
export declare class JWTInvalid extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
}
|
||||
/** Thrown when a JWK is invalid. */
|
||||
export declare class JWKInvalid extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
}
|
||||
/** Thrown when a JWKS is invalid. */
|
||||
export declare class JWKSInvalid extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
}
|
||||
/** Thrown when no keys match in a JWKS. */
|
||||
export declare class JWKSNoMatchingKey extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
constructor(message?: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
/** Thrown when multiple keys match in a JWKS. */
|
||||
export declare class JWKSMultipleMatchingKeys extends JOSEError {
|
||||
/**
|
||||
* Yields public keys matching the JWS Header so verification can be attempted with each. See the
|
||||
* {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} and
|
||||
* {@link jwks/local.createLocalJWKSet createLocalJWKSet} examples. Empty on manually constructed
|
||||
* errors.
|
||||
*/
|
||||
[Symbol.asyncIterator]: () => AsyncIterableIterator<t.CryptoKey>;
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
constructor(message?: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
/** Thrown when fetching a remote JWKS times out. */
|
||||
export declare class JWKSTimeout extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
constructor(message?: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
/** Thrown when JWS signature verification fails. */
|
||||
export declare class JWSSignatureVerificationFailed extends JOSEError {
|
||||
static code: JOSEErrorCode | (string & {});
|
||||
code: JOSEErrorCode | (string & {});
|
||||
constructor(message?: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
/** Errors thrown during JWT Claims Set validation. */
|
||||
export type JWTClaimValidationError = JWTClaimValidationFailed | JWTExpired;
|
||||
/**
|
||||
* Discriminated union of specific {@link JOSEError} subclasses, narrowed by `code`. Excludes base
|
||||
* {@link JOSEError} instances; use `instanceof JOSEError` to catch all module errors.
|
||||
*/
|
||||
export type AnyJOSEError = (JOSEAlgNotAllowed & {
|
||||
code: 'ERR_JOSE_ALG_NOT_ALLOWED';
|
||||
}) | (JOSENotSupported & {
|
||||
code: 'ERR_JOSE_NOT_SUPPORTED';
|
||||
}) | (JWEDecryptionFailed & {
|
||||
code: 'ERR_JWE_DECRYPTION_FAILED';
|
||||
}) | (JWEInvalid & {
|
||||
code: 'ERR_JWE_INVALID';
|
||||
}) | (JWKInvalid & {
|
||||
code: 'ERR_JWK_INVALID';
|
||||
}) | (JWKSInvalid & {
|
||||
code: 'ERR_JWKS_INVALID';
|
||||
}) | (JWKSMultipleMatchingKeys & {
|
||||
code: 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
||||
}) | (JWKSNoMatchingKey & {
|
||||
code: 'ERR_JWKS_NO_MATCHING_KEY';
|
||||
}) | (JWKSTimeout & {
|
||||
code: 'ERR_JWKS_TIMEOUT';
|
||||
}) | (JWSInvalid & {
|
||||
code: 'ERR_JWS_INVALID';
|
||||
}) | (JWSSignatureVerificationFailed & {
|
||||
code: 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED';
|
||||
}) | (JWTClaimValidationFailed & {
|
||||
code: 'ERR_JWT_CLAIM_VALIDATION_FAILED';
|
||||
}) | (JWTExpired & {
|
||||
code: 'ERR_JWT_EXPIRED';
|
||||
}) | (JWTInvalid & {
|
||||
code: 'ERR_JWT_INVALID';
|
||||
});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { createCompactSignature } from "../lib/jws_sign.js";
|
||||
import { JWTInvalid } from "../util/errors.js";
|
||||
import { JWTClaimsBuilder, jwtData } from "../lib/jwt_claims_set.js";
|
||||
import { assertNotSet } from "../lib/validate.js";
|
||||
const SignJWT_base = JWTClaimsBuilder;
|
||||
class SignJWT extends SignJWT_base {
|
||||
#protectedHeader;
|
||||
setProtectedHeader(protectedHeader) {
|
||||
return assertNotSet(this.#protectedHeader, "setProtectedHeader"), this.#protectedHeader = protectedHeader, this;
|
||||
}
|
||||
async sign(key, options) {
|
||||
return createCompactSignature(jwtData(this), this.#protectedHeader, options?.crit, key, () => {
|
||||
throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
|
||||
});
|
||||
}
|
||||
}
|
||||
export {
|
||||
SignJWT
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { prepareVerify, verifyCompact } from "../lib/jws_verify.js";
|
||||
import { validateClaimsSet } from "../lib/jwt_claims_set.js";
|
||||
import { JWTInvalid } from "../util/errors.js";
|
||||
async function jwtVerify(jwt, key, options) {
|
||||
const [verified, b64] = await verifyCompact(jwt, prepareVerify(options), key);
|
||||
if (!b64)
|
||||
throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
|
||||
const payload = validateClaimsSet(verified.protectedHeader, verified.payload, options);
|
||||
return { ...verified, payload };
|
||||
}
|
||||
export {
|
||||
jwtVerify
|
||||
};
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
const encoder = new TextEncoder(), decoder = new TextDecoder(), strictDecoder = new TextDecoder("utf-8", { fatal: !0 }), MAX_INT32 = 2 ** 32;
|
||||
function concat(...buffers) {
|
||||
const size = buffers.reduce((acc, { length }) => acc + length, 0), buf = new Uint8Array(size);
|
||||
let i = 0;
|
||||
for (const buffer of buffers)
|
||||
buf.set(buffer, i), i += buffer.length;
|
||||
return buf;
|
||||
}
|
||||
function writeUInt32BE(buf, value, offset) {
|
||||
if (value < 0 || value >= MAX_INT32)
|
||||
throw new RangeError(`value must be >= 0 and <= ${MAX_INT32 - 1}. Received ${value}`);
|
||||
buf.set([value >>> 24, value >>> 16, value >>> 8, value & 255], offset);
|
||||
}
|
||||
function uint64be(value) {
|
||||
const high = Math.floor(value / MAX_INT32), low = value % MAX_INT32, buf = new Uint8Array(8);
|
||||
return writeUInt32BE(buf, high, 0), writeUInt32BE(buf, low, 4), buf;
|
||||
}
|
||||
function uint32be(value) {
|
||||
const buf = new Uint8Array(4);
|
||||
return writeUInt32BE(buf, value), buf;
|
||||
}
|
||||
const NON_ASCII = /[^\x00-\x7f]/;
|
||||
function encode(string) {
|
||||
if (typeof string == "string" && string.length >= 128) {
|
||||
if (NON_ASCII.test(string))
|
||||
throw new TypeError("non-ASCII string encountered in encode()");
|
||||
return encoder.encode(string);
|
||||
}
|
||||
const bytes = new Uint8Array(string.length);
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
const code = string.charCodeAt(i);
|
||||
if (code > 127)
|
||||
throw new TypeError("non-ASCII string encountered in encode()");
|
||||
bytes[i] = code;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
function encodeBase64(input, url = !1) {
|
||||
if (Uint8Array.prototype.toBase64)
|
||||
return input.toBase64({ alphabet: url ? "base64url" : "base64", omitPadding: url });
|
||||
const CHUNK_SIZE = 32768, arr = [];
|
||||
for (let i = 0; i < input.length; i += CHUNK_SIZE)
|
||||
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
||||
const encoded = btoa(arr.join(""));
|
||||
return url ? encoded.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") : encoded;
|
||||
}
|
||||
function decodeBase64(encoded, url = !1) {
|
||||
if (Uint8Array.fromBase64)
|
||||
return Uint8Array.fromBase64(encoded, { alphabet: url ? "base64url" : "base64" });
|
||||
if (url) {
|
||||
if (encoded.includes("+") || encoded.includes("/"))
|
||||
throw new TypeError("Invalid base64url");
|
||||
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
||||
}
|
||||
const binary = atob(encoded), bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++)
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
return bytes;
|
||||
}
|
||||
async function digest(algorithm, data) {
|
||||
const subtleDigest = `SHA-${algorithm.slice(-3)}`;
|
||||
return new Uint8Array(await crypto.subtle.digest(subtleDigest, data));
|
||||
}
|
||||
export {
|
||||
concat,
|
||||
decodeBase64,
|
||||
decoder,
|
||||
digest,
|
||||
encode,
|
||||
encodeBase64,
|
||||
encoder,
|
||||
strictDecoder,
|
||||
uint32be,
|
||||
uint64be
|
||||
};
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import { JOSENotSupported } from "../util/errors.js";
|
||||
import { table } from "./key_descriptor.js";
|
||||
const sig = [["verify"], ["sign"]];
|
||||
function hmac(bits) {
|
||||
const subtle = { name: "HMAC", hash: `SHA-${bits}` };
|
||||
return { kty: ["oct"], secret: !0, subtle, signing: subtle, usages: sig };
|
||||
}
|
||||
function rsa(bits, saltLength) {
|
||||
const subtle = { name: saltLength ? "RSA-PSS" : "RSASSA-PKCS1-v1_5", hash: `SHA-${bits}` };
|
||||
return {
|
||||
kty: ["RSA"],
|
||||
subtle,
|
||||
signing: saltLength ? { ...subtle, saltLength } : subtle,
|
||||
usages: sig,
|
||||
minRsaBits: 2048
|
||||
};
|
||||
}
|
||||
function ecdsa(crv, bits) {
|
||||
return {
|
||||
kty: ["EC"],
|
||||
crv,
|
||||
subtle: { name: "ECDSA", namedCurve: crv },
|
||||
signing: { name: "ECDSA", hash: `SHA-${bits}` },
|
||||
usages: sig
|
||||
};
|
||||
}
|
||||
function eddsa() {
|
||||
const subtle = { name: "Ed25519" };
|
||||
return {
|
||||
kty: ["OKP"],
|
||||
crv: "Ed25519",
|
||||
subtle,
|
||||
signing: subtle,
|
||||
usages: sig
|
||||
};
|
||||
}
|
||||
function mldsa(bits) {
|
||||
const subtle = { name: `ML-DSA-${bits}` };
|
||||
return {
|
||||
kty: ["AKP"],
|
||||
subtle,
|
||||
signing: subtle,
|
||||
usages: sig
|
||||
};
|
||||
}
|
||||
const JWS = table({
|
||||
HS256: hmac(256),
|
||||
HS384: hmac(384),
|
||||
HS512: hmac(512),
|
||||
RS256: rsa(256),
|
||||
RS384: rsa(384),
|
||||
RS512: rsa(512),
|
||||
PS256: rsa(256, 32),
|
||||
PS384: rsa(384, 48),
|
||||
PS512: rsa(512, 64),
|
||||
ES256: ecdsa("P-256", 256),
|
||||
ES384: ecdsa("P-384", 384),
|
||||
ES512: ecdsa("P-521", 512),
|
||||
EdDSA: eddsa(),
|
||||
Ed25519: eddsa(),
|
||||
"ML-DSA-44": mldsa(44),
|
||||
"ML-DSA-65": mldsa(65),
|
||||
"ML-DSA-87": mldsa(87)
|
||||
});
|
||||
function jwsAlgorithm(alg) {
|
||||
const entry = typeof alg == "string" ? JWS[alg] : void 0;
|
||||
if (!entry)
|
||||
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
||||
return entry;
|
||||
}
|
||||
export {
|
||||
JWS,
|
||||
jwsAlgorithm
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { encode as b64u } from "../util/base64url.js";
|
||||
import { jwsAlgorithm } from "./jws_algorithms.js";
|
||||
import { isDisjoint, serializeJoseHeader, validateB64, validateCrit, validateCritDuplicates, JWS_RECOGNIZED } from "./validate.js";
|
||||
import { JWSInvalid } from "../util/errors.js";
|
||||
import { concat, encode, encoder } from "./buffer_utils.js";
|
||||
import { prepareKey, rawKey, checkModulusLength } from "./key.js";
|
||||
async function createSignature(input, key, rejectUnencoded) {
|
||||
let [payload, protectedHeader, unprotectedHeader, crit] = input, protectedHeaderString = "";
|
||||
if (protectedHeader !== void 0) {
|
||||
const normalized = serializeJoseHeader(JWSInvalid, protectedHeader);
|
||||
protectedHeader = normalized[0], protectedHeaderString = b64u(normalized[1]);
|
||||
}
|
||||
if (unprotectedHeader !== void 0 && (unprotectedHeader = serializeJoseHeader(JWSInvalid, unprotectedHeader)[0]), !protectedHeader && !unprotectedHeader)
|
||||
throw new JWSInvalid("either setProtectedHeader or setUnprotectedHeader must be called before #sign()");
|
||||
if (!isDisjoint(protectedHeader, unprotectedHeader))
|
||||
throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
|
||||
const joseHeader = { ...protectedHeader, ...unprotectedHeader };
|
||||
validateCritDuplicates(JWSInvalid, protectedHeader);
|
||||
const b64 = validateB64(protectedHeader, validateCrit(JWSInvalid, JWS_RECOGNIZED, crit, protectedHeader, joseHeader));
|
||||
b64 || rejectUnencoded?.();
|
||||
const { alg } = joseHeader;
|
||||
if (typeof alg != "string" || !alg)
|
||||
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
|
||||
const entry = jwsAlgorithm(alg);
|
||||
let payloadS = "", payloadB = payload, data;
|
||||
if (b64) {
|
||||
const encoded = input[4];
|
||||
encoded ? (payloadS = encoded[0] ??= b64u(payload), payloadB = encoded[1] ??= encode(payloadS)) : (payloadS = b64u(payload), data = encoder.encode(`${protectedHeaderString}.${payloadS}`));
|
||||
}
|
||||
data ??= concat(encode(protectedHeaderString), encode("."), payloadB);
|
||||
const k = await rawKey(await prepareKey(entry, key, "sign"), entry.subtle, "sign");
|
||||
entry.minRsaBits && checkModulusLength(entry.alg, k);
|
||||
const jws = {
|
||||
signature: b64u(new Uint8Array(await crypto.subtle.sign(entry.signing, k, data))),
|
||||
payload: payloadS
|
||||
};
|
||||
return protectedHeader && (jws.protected = protectedHeaderString), unprotectedHeader && (jws.header = unprotectedHeader), [jws, b64];
|
||||
}
|
||||
async function createCompactSignature(payload, protectedHeader, crit, key, rejectUnencoded) {
|
||||
const [jws] = await createSignature([payload, protectedHeader, void 0, crit], key, rejectUnencoded);
|
||||
return `${jws.protected}.${jws.payload}.${jws.signature}`;
|
||||
}
|
||||
export {
|
||||
createCompactSignature,
|
||||
createSignature
|
||||
};
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { jwsAlgorithm } from "./jws_algorithms.js";
|
||||
import { JOSEAlgNotAllowed, JWSInvalid, JWSSignatureVerificationFailed } from "../util/errors.js";
|
||||
import { concat, decoder, encoder, encode } from "./buffer_utils.js";
|
||||
import { decodeBase64url, encodeBase64url, parseJoseHeader, isDisjoint, isObject, validateB64, validateCrit, validateAlgorithms, JWS_RECOGNIZED } from "./validate.js";
|
||||
import { prepareKey, rawKey, checkModulusLength } from "./key.js";
|
||||
function snapshotJws(jws, sharedPayload) {
|
||||
const encodedProtected = jws.protected, inputHeader = jws.header, header = isObject(inputHeader) ? { ...inputHeader } : inputHeader;
|
||||
let payload = sharedPayload ? sharedPayload[0] : jws.payload;
|
||||
!sharedPayload && payload instanceof Uint8Array && (payload = new Uint8Array(payload));
|
||||
const signature = jws.signature, snapshot = { payload, signature };
|
||||
if (encodedProtected !== void 0 && (snapshot.protected = encodedProtected), inputHeader !== void 0 && (snapshot.header = header), encodedProtected === void 0 && header === void 0)
|
||||
throw new JWSInvalid('Flattened JWS must have either of the "protected" or "header" members');
|
||||
if (encodedProtected !== void 0 && typeof encodedProtected != "string")
|
||||
throw new JWSInvalid("JWS Protected Header incorrect type");
|
||||
if (payload === void 0)
|
||||
throw new JWSInvalid("JWS Payload missing");
|
||||
if (typeof signature != "string")
|
||||
throw new JWSInvalid("JWS Signature missing or incorrect type");
|
||||
if (header !== void 0 && !isObject(header))
|
||||
throw new JWSInvalid("JWS Unprotected Header incorrect type");
|
||||
return snapshot;
|
||||
}
|
||||
function prepareVerify(options) {
|
||||
return [options && validateAlgorithms("algorithms", options.algorithms), options?.crit];
|
||||
}
|
||||
function parseProtectedHeader(encodedProtected) {
|
||||
return encodedProtected === void 0 ? {} : parseJoseHeader(encodedProtected, JWSInvalid, "JWS Protected Header is invalid");
|
||||
}
|
||||
function encodeJsonUnencodedPayload(payload) {
|
||||
const invalid = /[\p{Cs}\p{Cn}]/u.exec(payload)?.[0];
|
||||
if (invalid !== void 0)
|
||||
throw new JWSInvalid(/\p{Cs}/u.test(invalid) ? "JWS Payload must be a well-formed Unicode string" : "JWS Payload must not contain unassigned Unicode code points");
|
||||
return encoder.encode(payload);
|
||||
}
|
||||
function encodeCompactUnencodedPayload(payload) {
|
||||
try {
|
||||
return encode(payload);
|
||||
} catch {
|
||||
throw new JWSInvalid("JWS Compact Serialization payload must use only ASCII characters");
|
||||
}
|
||||
}
|
||||
async function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedProtected) {
|
||||
const { protected: encodedProtected, header, payload: inputPayload } = jws, parsedProt = parsedProtected ?? parseProtectedHeader(encodedProtected);
|
||||
if (!isDisjoint(parsedProt, header))
|
||||
throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
|
||||
const joseHeader = { ...parsedProt, ...header }, b64 = validateB64(parsedProt, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader)), { alg } = joseHeader;
|
||||
if (typeof alg != "string" || !alg)
|
||||
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
|
||||
if (shared[0] && !shared[0].has(alg))
|
||||
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
|
||||
if (b64) {
|
||||
if (typeof inputPayload != "string")
|
||||
throw new JWSInvalid("JWS Payload must be a string");
|
||||
} else if (typeof inputPayload != "string" && !(inputPayload instanceof Uint8Array))
|
||||
throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance");
|
||||
const signingPayload = b64 || typeof inputPayload != "string" ? inputPayload : encodeUnencodedPayload(inputPayload);
|
||||
let resolvedKey = !1;
|
||||
typeof key == "function" && (key = await key(parsedProt, jws), resolvedKey = !0);
|
||||
const entry = jwsAlgorithm(alg), data = concat(encodedProtected !== void 0 ? encode(encodedProtected) : new Uint8Array(), encode("."), typeof signingPayload == "string" ? shared[2] ??= encodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload), signature = decodeBase64url(jws.signature, "signature", JWSInvalid), k = await prepareKey(entry, key, "verify"), cryptoKey = await rawKey(k, entry.subtle, "verify");
|
||||
entry.minRsaBits && checkModulusLength(entry.alg, cryptoKey);
|
||||
let verified = !1;
|
||||
try {
|
||||
verified = await crypto.subtle.verify(entry.signing, cryptoKey, signature, data);
|
||||
} catch {
|
||||
}
|
||||
if (!verified)
|
||||
throw new JWSSignatureVerificationFailed();
|
||||
const result = { payload: typeof signingPayload == "string" ? decodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload };
|
||||
return encodedProtected !== void 0 && (result.protectedHeader = parsedProt), header !== void 0 && (result.unprotectedHeader = header), resolvedKey ? [{ ...result, key: k }, b64] : [result, b64];
|
||||
}
|
||||
async function verifyCompact(jws, shared, key) {
|
||||
if (jws instanceof Uint8Array && (jws = decoder.decode(jws)), typeof jws != "string")
|
||||
throw new JWSInvalid("Compact JWS must be a string or Uint8Array");
|
||||
const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split(".");
|
||||
if (length !== 3)
|
||||
throw new JWSInvalid("Invalid Compact JWS");
|
||||
return verifySignature({ payload, protected: protectedHeader, signature }, shared, key, encodeCompactUnencodedPayload);
|
||||
}
|
||||
export {
|
||||
encodeJsonUnencodedPayload,
|
||||
parseProtectedHeader,
|
||||
prepareVerify,
|
||||
snapshotJws,
|
||||
verifyCompact,
|
||||
verifySignature
|
||||
};
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from "../util/errors.js";
|
||||
import { encoder, strictDecoder } from "./buffer_utils.js";
|
||||
import { isObject } from "./validate.js";
|
||||
const epoch = (date) => Math.floor(date.getTime() / 1e3), multipliers = {
|
||||
s: 1,
|
||||
m: 60,
|
||||
h: 3600,
|
||||
d: 86400,
|
||||
w: 604800,
|
||||
y: 31557600
|
||||
}, REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i, checkFailed = "check_failed";
|
||||
function invalidDuration() {
|
||||
throw new TypeError("Invalid time period format");
|
||||
}
|
||||
function secs(str) {
|
||||
typeof str != "string" && invalidDuration();
|
||||
const matched = REGEX.exec(str);
|
||||
(!matched || matched[4] && matched[1]) && invalidDuration();
|
||||
const value = parseFloat(matched[2]), numericDate2 = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
|
||||
return Number.isFinite(numericDate2) || invalidDuration(), matched[1] === "-" || matched[4] === "ago" ? -numericDate2 : numericDate2;
|
||||
}
|
||||
function validateInput(label, input) {
|
||||
if (!Number.isFinite(input))
|
||||
throw new TypeError(`Invalid ${label} input`);
|
||||
return input;
|
||||
}
|
||||
function validateStringClaim(claim, value) {
|
||||
if (typeof value != "string")
|
||||
throw new TypeError(`"${claim}" claim must be a string`);
|
||||
}
|
||||
function validateAudienceClaim(value) {
|
||||
if (typeof value != "string" && (!Array.isArray(value) || Array.from(value).some((member) => typeof member != "string")))
|
||||
throw new TypeError('"aud" claim must be a string or an array of strings');
|
||||
}
|
||||
function numericDate(value, label) {
|
||||
return typeof value == "number" ? validateInput(label, value) : value instanceof Date ? validateInput(label, epoch(value)) : epoch(/* @__PURE__ */ new Date()) + secs(value);
|
||||
}
|
||||
const normalizeTyp = (value) => {
|
||||
const normalized = value.toLowerCase();
|
||||
return value.includes("/") ? normalized : `application/${normalized}`;
|
||||
}, checkAudiencePresence = (audPayload, audOption) => typeof audPayload == "string" ? audOption.includes(audPayload) : Array.isArray(audPayload) ? audOption.some((aud) => audPayload.includes(aud)) : !1;
|
||||
function validateNumericDate(payload, claim, required = !1) {
|
||||
const value = payload[claim];
|
||||
if (!(value === void 0 && !required)) {
|
||||
if (typeof value != "number")
|
||||
throw new JWTClaimValidationFailed(`"${claim}" claim must be a number`, payload, claim, "invalid");
|
||||
return value;
|
||||
}
|
||||
}
|
||||
function unexpectedClaim(payload, claim) {
|
||||
throw new JWTClaimValidationFailed(`unexpected "${claim}" claim value`, payload, claim, checkFailed);
|
||||
}
|
||||
function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(strictDecoder.decode(encodedPayload));
|
||||
} catch {
|
||||
}
|
||||
if (!isObject(payload))
|
||||
throw new JWTInvalid("JWT Claims Set must be a top-level JSON object");
|
||||
const { typ } = options;
|
||||
if (typ !== void 0 && (typeof protectedHeader.typ != "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ)))
|
||||
throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, "typ", checkFailed);
|
||||
const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options, presenceCheck = [...requiredClaims];
|
||||
maxTokenAge !== void 0 && presenceCheck.push("iat"), audience !== void 0 && presenceCheck.push("aud"), subject !== void 0 && presenceCheck.push("sub"), issuer !== void 0 && presenceCheck.push("iss");
|
||||
for (const claim of new Set(presenceCheck.reverse()))
|
||||
if (!Object.hasOwn(payload, claim))
|
||||
throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, "missing");
|
||||
issuer !== void 0 && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss) && unexpectedClaim(payload, "iss"), subject !== void 0 && payload.sub !== subject && unexpectedClaim(payload, "sub"), audience !== void 0 && !checkAudiencePresence(payload.aud, typeof audience == "string" ? [audience] : audience) && unexpectedClaim(payload, "aud");
|
||||
const { clockTolerance } = options;
|
||||
let tolerance = 0;
|
||||
if (typeof clockTolerance == "string")
|
||||
tolerance = secs(clockTolerance);
|
||||
else if (clockTolerance !== void 0) {
|
||||
if (typeof clockTolerance != "number")
|
||||
throw new TypeError("Invalid clockTolerance option type");
|
||||
tolerance = clockTolerance;
|
||||
}
|
||||
validateInput("clockTolerance option", tolerance);
|
||||
const { currentDate } = options, now = validateInput("currentDate option", epoch(currentDate === void 0 ? /* @__PURE__ */ new Date() : currentDate)), iat = validateNumericDate(payload, "iat", maxTokenAge !== void 0), nbf = validateNumericDate(payload, "nbf");
|
||||
if (nbf !== void 0 && nbf > now + tolerance)
|
||||
throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, "nbf", checkFailed);
|
||||
const exp = validateNumericDate(payload, "exp");
|
||||
if (exp !== void 0 && exp <= now - tolerance)
|
||||
throw new JWTExpired('"exp" claim timestamp check failed', payload, "exp", checkFailed);
|
||||
if (maxTokenAge !== void 0) {
|
||||
const age = now - iat, max = validateInput("maxTokenAge option", typeof maxTokenAge == "number" ? maxTokenAge : secs(maxTokenAge));
|
||||
if (age - tolerance > max)
|
||||
throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, "iat", checkFailed);
|
||||
if (age < -tolerance)
|
||||
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, "iat", checkFailed);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
let producerPayloads;
|
||||
function producerPayload(producer) {
|
||||
return producerPayloads.get(producer);
|
||||
}
|
||||
function jwtData(producer) {
|
||||
const payload = producerPayload(producer);
|
||||
for (const claim of ["iat", "nbf", "exp"]) {
|
||||
const value = payload[claim];
|
||||
if (typeof value == "number" && !Number.isFinite(value))
|
||||
throw new TypeError(`"${claim}" claim must be a finite number`);
|
||||
}
|
||||
return encoder.encode(JSON.stringify(payload));
|
||||
}
|
||||
function jwtClaim(producer, claim) {
|
||||
return producerPayload(producer)[claim];
|
||||
}
|
||||
class JWTClaimsBuilder {
|
||||
constructor(payload = {}) {
|
||||
if (!isObject(payload))
|
||||
throw new TypeError("JWT Claims Set MUST be an object");
|
||||
(producerPayloads ||= /* @__PURE__ */ new WeakMap()).set(this, structuredClone(payload));
|
||||
}
|
||||
setIssuer(value) {
|
||||
return validateStringClaim("iss", value), producerPayload(this).iss = value, this;
|
||||
}
|
||||
setSubject(value) {
|
||||
return validateStringClaim("sub", value), producerPayload(this).sub = value, this;
|
||||
}
|
||||
setAudience(value) {
|
||||
return validateAudienceClaim(value), producerPayload(this).aud = value, this;
|
||||
}
|
||||
setJti(value) {
|
||||
return validateStringClaim("jti", value), producerPayload(this).jti = value, this;
|
||||
}
|
||||
setNotBefore(value) {
|
||||
return producerPayload(this).nbf = numericDate(value, "setNotBefore"), this;
|
||||
}
|
||||
setExpirationTime(value) {
|
||||
return producerPayload(this).exp = numericDate(value, "setExpirationTime"), this;
|
||||
}
|
||||
setIssuedAt(value) {
|
||||
const payload = producerPayload(this);
|
||||
return value === void 0 ? payload.iat = epoch(/* @__PURE__ */ new Date()) : typeof value == "string" ? payload.iat = validateInput("setIssuedAt", epoch(/* @__PURE__ */ new Date()) + secs(value)) : payload.iat = numericDate(value, "setIssuedAt"), this;
|
||||
}
|
||||
}
|
||||
export {
|
||||
JWTClaimsBuilder,
|
||||
jwtClaim,
|
||||
jwtData,
|
||||
secs,
|
||||
validateClaimsSet
|
||||
};
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import { isObject } from "./validate.js";
|
||||
import { decode } from "../util/base64url.js";
|
||||
import { JOSENotSupported } from "../util/errors.js";
|
||||
const tag = (key) => key[Symbol.toStringTag], jwkMatchesOp = (entry, key, usage) => {
|
||||
const { alg } = entry;
|
||||
if (key.use !== void 0) {
|
||||
const expected = usage === "sign" || usage === "verify" ? "sig" : "enc";
|
||||
if (key.use !== expected)
|
||||
throw new TypeError(`Invalid key for this operation, its "use" must be "${expected}" when present`);
|
||||
}
|
||||
if (key.alg !== void 0 && key.alg !== alg)
|
||||
throw new TypeError(`Invalid key for this operation, its "alg" must be "${alg}" when present`);
|
||||
if (Array.isArray(key.key_ops)) {
|
||||
const expectedKeyOp = usage === "encrypt" || usage === "decrypt" ? entry.ops?.[usage === "encrypt" ? 0 : 1] : usage;
|
||||
if (expectedKeyOp && !key.key_ops.includes(expectedKeyOp))
|
||||
throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${expectedKeyOp}" when present`);
|
||||
}
|
||||
};
|
||||
async function prepareKey(entry, key, usage) {
|
||||
const { alg, secret } = entry, privateKey = usage === "decrypt" || usage === "sign";
|
||||
if (secret && key instanceof Uint8Array)
|
||||
return key;
|
||||
let normalized, keyObject;
|
||||
if (isObject(key)) {
|
||||
if (normalized = normalizeJwk(key), typeof normalized.kty != "string")
|
||||
throw invalidKeyType(alg, key, secret);
|
||||
if (!(secret ? normalized.kty === "oct" && typeof normalized.k == "string" : normalized.kty !== "oct" && (privateKey ? normalized.kty === "AKP" && typeof normalized.priv == "string" || typeof normalized.d == "string" : normalized.d === void 0 && normalized.priv === void 0)))
|
||||
throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present' : `JSON Web Key for this operation must be a ${privateKey ? "private" : "public"} JWK`);
|
||||
if (jwkMatchesOp(entry, normalized, usage), normalized.kty === "oct")
|
||||
return decode(normalized.k);
|
||||
if (!Object.isFrozen(key)) {
|
||||
const { key_ops } = key;
|
||||
Array.isArray(key_ops) && Object.freeze(key_ops), Object.freeze(key);
|
||||
}
|
||||
} else {
|
||||
if (!isKeyLike(key))
|
||||
throw invalidKeyType(alg, key, secret);
|
||||
const expectedType = secret ? "secret" : privateKey ? "private" : "public";
|
||||
if (key.type !== expectedType && (secret || ["secret", "public", "private"].includes(key.type)))
|
||||
throw new TypeError(`${tag(key)} instances must be of type "${expectedType}" for the ${alg} algorithm`);
|
||||
if (isCryptoKey(key))
|
||||
return key;
|
||||
if (keyObject = key, keyObject.type === "secret")
|
||||
return keyObject.export();
|
||||
}
|
||||
cache ||= /* @__PURE__ */ new WeakMap();
|
||||
const cacheKey = key;
|
||||
let cached = cache.get(cacheKey);
|
||||
if (cached?.[alg])
|
||||
return cached[alg];
|
||||
if (cached || cache.set(cacheKey, cached = {}), keyObject && typeof keyObject.toCryptoKey == "function") {
|
||||
const isPublic = keyObject.type === "public", crv = nist[keyObject.asymmetricKeyDetails?.namedCurve], params = entry.resolve?.({ crv, asymmetricKeyType: keyObject.asymmetricKeyType }) ?? entry.subtle;
|
||||
return cached[alg] = keyObject.toCryptoKey(params, isPublic, entry.usages[isPublic ? 0 : 1]);
|
||||
}
|
||||
return normalized ??= keyObject.export({ format: "jwk" }), normalized.alg = alg, cached[alg] = await jwkToKey(entry, normalized);
|
||||
}
|
||||
let cache;
|
||||
const nist = {
|
||||
__proto__: null,
|
||||
prime256v1: "P-256",
|
||||
secp384r1: "P-384",
|
||||
secp521r1: "P-521"
|
||||
};
|
||||
function assertCryptoKey(key) {
|
||||
if (!isCryptoKey(key))
|
||||
throw new Error("CryptoKey instance expected");
|
||||
}
|
||||
const isCryptoKey = (key) => {
|
||||
if (key?.[Symbol.toStringTag] === "CryptoKey")
|
||||
return !0;
|
||||
try {
|
||||
return key instanceof CryptoKey;
|
||||
} catch {
|
||||
return !1;
|
||||
}
|
||||
}, isKeyObject = (key) => key?.[Symbol.toStringTag] === "KeyObject", isKeyLike = (key) => isCryptoKey(key) || isKeyObject(key);
|
||||
function message(msg, actual, ...types) {
|
||||
if (types.length > 2) {
|
||||
const last = types.pop();
|
||||
msg += `one of type ${types.join(", ")}, or ${last}.`;
|
||||
} else types.length === 2 ? msg += `one of type ${types[0]} or ${types[1]}.` : msg += `of type ${types[0]}.`;
|
||||
return actual == null ? msg += ` Received ${actual}` : typeof actual == "function" && actual.name ? msg += ` Received function ${actual.name}` : typeof actual == "object" && actual != null && actual.constructor?.name && (msg += ` Received an instance of ${actual.constructor.name}`), msg;
|
||||
}
|
||||
const invalidKeyInput = (actual, ...types) => message("Key must be ", actual, ...types);
|
||||
function invalidKeyType(alg, actual, secret) {
|
||||
const types = ["CryptoKey", "KeyObject", "JSON Web Key"];
|
||||
return secret && types.push("Uint8Array"), new TypeError(message(`Key for the ${alg} algorithm must be `, actual, ...types));
|
||||
}
|
||||
const unusable = (name, prop = "algorithm.name") => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
||||
function checkUsage(key, usage) {
|
||||
if (usage && !key.usages.includes(usage))
|
||||
throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
||||
}
|
||||
function checkModulusLength(alg, key) {
|
||||
const { modulusLength } = key.algorithm;
|
||||
if (typeof modulusLength != "number" || modulusLength < 2048)
|
||||
throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
||||
}
|
||||
function checkCryptoKey(key, expected, usage) {
|
||||
const algorithm = key.algorithm;
|
||||
if (algorithm.name !== expected.name)
|
||||
throw unusable(expected.name);
|
||||
if (expected.hash && algorithm.hash?.name !== expected.hash)
|
||||
throw unusable(expected.hash, "algorithm.hash");
|
||||
if (expected.namedCurve && algorithm.namedCurve !== expected.namedCurve)
|
||||
throw unusable(expected.namedCurve, "algorithm.namedCurve");
|
||||
if (expected.length !== void 0 && algorithm.length !== expected.length)
|
||||
throw unusable(expected.length, "algorithm.length");
|
||||
checkUsage(key, usage);
|
||||
}
|
||||
function snapshotJwk(jwk) {
|
||||
return { __proto__: null, ...jwk };
|
||||
}
|
||||
function normalizeJwk(jwk) {
|
||||
const normalized = snapshotJwk(jwk);
|
||||
if (normalized.ext !== void 0 && typeof normalized.ext != "boolean")
|
||||
throw new TypeError('"ext" (Extractable) Parameter must be a boolean');
|
||||
if (normalized.key_ops !== void 0) {
|
||||
const value = normalized.key_ops, keyOps = Array.isArray(value) ? [...value] : void 0;
|
||||
if (!keyOps || keyOps.some((operation) => typeof operation != "string") || new Set(keyOps).size !== keyOps.length)
|
||||
throw new TypeError('"key_ops" (Key Operations) Parameter must be an array of unique strings');
|
||||
normalized.key_ops = keyOps;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
function validateExtractableOption(extractable) {
|
||||
if (extractable !== void 0 && typeof extractable != "boolean")
|
||||
throw new TypeError('"extractable" option must be a boolean');
|
||||
return extractable;
|
||||
}
|
||||
async function jwkToKey(entry, jwk, extractable) {
|
||||
if (!entry.kty.includes(jwk.kty))
|
||||
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
||||
const algorithm = entry.resolve?.({ kty: jwk.kty, crv: jwk.crv }) ?? entry.subtle, isPrivate = !!(jwk.d || jwk.priv), keyData = { ...jwk, ext: extractable ?? jwk.ext };
|
||||
return keyData.kty !== "AKP" && delete keyData.alg, delete keyData.use, crypto.subtle.importKey("jwk", keyData, algorithm, keyData.ext ?? !isPrivate, jwk.key_ops ?? entry.usages[isPrivate ? 1 : 0]);
|
||||
}
|
||||
async function rawKey(key, expected, usage, extractable = !1) {
|
||||
return key instanceof Uint8Array && (key = await crypto.subtle.importKey("raw", key, expected, extractable, [usage])), checkCryptoKey(key, expected, usage), key;
|
||||
}
|
||||
export {
|
||||
assertCryptoKey,
|
||||
checkCryptoKey,
|
||||
checkModulusLength,
|
||||
checkUsage,
|
||||
invalidKeyInput,
|
||||
isCryptoKey,
|
||||
isKeyLike,
|
||||
isKeyObject,
|
||||
jwkToKey,
|
||||
normalizeJwk,
|
||||
prepareKey,
|
||||
rawKey,
|
||||
snapshotJwk,
|
||||
validateExtractableOption
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
function table(entries) {
|
||||
const out = { __proto__: null };
|
||||
for (const alg in entries)
|
||||
out[alg] = { ...entries[alg], alg };
|
||||
return out;
|
||||
}
|
||||
export {
|
||||
table
|
||||
};
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import { JOSENotSupported, JWSInvalid } from "../util/errors.js";
|
||||
import { decode } from "../util/base64url.js";
|
||||
import { encode, strictDecoder } from "./buffer_utils.js";
|
||||
function assertUint8Array(input, label) {
|
||||
if (!(input instanceof Uint8Array))
|
||||
throw new TypeError(`${label} must be an instance of Uint8Array`);
|
||||
}
|
||||
function isObject(input) {
|
||||
if (typeof input != "object" || input === null || Object.prototype.toString.call(input) !== "[object Object]")
|
||||
return !1;
|
||||
const prototype = Object.getPrototypeOf(input);
|
||||
return prototype === null || Object.getPrototypeOf(prototype) === null;
|
||||
}
|
||||
function isJwkSet(input) {
|
||||
return isObject(input) && Array.isArray(input.keys) && Array.from(input.keys).every(isObject);
|
||||
}
|
||||
function isDisjoint(...headers) {
|
||||
const parameters = /* @__PURE__ */ new Set();
|
||||
for (const header of headers)
|
||||
if (header)
|
||||
for (const parameter of Object.keys(header)) {
|
||||
if (parameters.has(parameter))
|
||||
return !1;
|
||||
parameters.add(parameter);
|
||||
}
|
||||
return !0;
|
||||
}
|
||||
function assertNotSet(value, name) {
|
||||
if (value !== void 0)
|
||||
throw new TypeError(`${name} can only be called once`);
|
||||
}
|
||||
function decodeBase64url(value, label, ErrorClass) {
|
||||
try {
|
||||
return decode(value);
|
||||
} catch {
|
||||
throw new ErrorClass(`Failed to base64url decode the ${label}`);
|
||||
}
|
||||
}
|
||||
function encodeBase64url(value, label, ErrorClass) {
|
||||
try {
|
||||
return encode(value);
|
||||
} catch {
|
||||
throw new ErrorClass(`The ${label} is not a valid base64url string`);
|
||||
}
|
||||
}
|
||||
function parseJoseHeader(b64, ErrorClass, message) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(strictDecoder.decode(decode(b64)));
|
||||
} catch {
|
||||
throw new ErrorClass(message);
|
||||
}
|
||||
if (!isObject(parsed))
|
||||
throw new ErrorClass(message);
|
||||
return parsed;
|
||||
}
|
||||
const JWS_RECOGNIZED = { __proto__: null, b64: !0 }, JWE_RECOGNIZED = { __proto__: null };
|
||||
function validateAlgorithms(option, algorithms) {
|
||||
if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s != "string")))
|
||||
throw new TypeError(`"${option}" option must be an array of strings`);
|
||||
return algorithms === void 0 ? void 0 : new Set(algorithms);
|
||||
}
|
||||
function validateCritDuplicates(Err, protectedHeader) {
|
||||
const { crit } = protectedHeader ?? {};
|
||||
if (Array.isArray(crit) && new Set(crit).size !== crit.length)
|
||||
throw new Err('"crit" (Critical) Header Parameter MUST NOT contain duplicate values');
|
||||
}
|
||||
function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) {
|
||||
if (joseHeader.crit !== void 0 && protectedHeader?.crit === void 0)
|
||||
throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected');
|
||||
if (!protectedHeader || protectedHeader.crit === void 0)
|
||||
return [];
|
||||
if (!Array.isArray(protectedHeader.crit) || protectedHeader.crit.length === 0 || protectedHeader.crit.some((input) => typeof input != "string" || input.length === 0))
|
||||
throw new Err('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present');
|
||||
const recognized = recognizedOption === void 0 ? recognizedDefault : { __proto__: null, ...recognizedOption, ...recognizedDefault };
|
||||
for (const parameter of protectedHeader.crit) {
|
||||
if (!(parameter in recognized))
|
||||
throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`);
|
||||
if (!Object.hasOwn(joseHeader, parameter) || joseHeader[parameter] === void 0)
|
||||
throw new Err(`Extension Header Parameter "${parameter}" is missing`);
|
||||
if (recognized[parameter] && (!Object.hasOwn(protectedHeader, parameter) || protectedHeader[parameter] === void 0))
|
||||
throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`);
|
||||
}
|
||||
return protectedHeader.crit;
|
||||
}
|
||||
function validateB64(protectedHeader, extensions) {
|
||||
if (extensions.includes("b64")) {
|
||||
const b64 = protectedHeader.b64;
|
||||
if (typeof b64 != "boolean")
|
||||
throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean');
|
||||
return b64;
|
||||
}
|
||||
return !0;
|
||||
}
|
||||
function serializeJoseHeader(Err, header) {
|
||||
let serialized, parsed;
|
||||
try {
|
||||
serialized = JSON.stringify(header), parsed = JSON.parse(serialized);
|
||||
} catch (cause) {
|
||||
throw new Err("JOSE Header is not valid JSON", { cause });
|
||||
}
|
||||
if (!isObject(parsed))
|
||||
throw new Err("JOSE Header is not a JSON object");
|
||||
return [parsed, serialized];
|
||||
}
|
||||
export {
|
||||
JWE_RECOGNIZED,
|
||||
JWS_RECOGNIZED,
|
||||
assertNotSet,
|
||||
assertUint8Array,
|
||||
decodeBase64url,
|
||||
encodeBase64url,
|
||||
isDisjoint,
|
||||
isJwkSet,
|
||||
isObject,
|
||||
parseJoseHeader,
|
||||
serializeJoseHeader,
|
||||
validateAlgorithms,
|
||||
validateB64,
|
||||
validateCrit,
|
||||
validateCritDuplicates
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { encoder, decoder, encodeBase64, decodeBase64 } from "../lib/buffer_utils.js";
|
||||
const invalid = "The input to be decoded is not correctly encoded.";
|
||||
function decode(input) {
|
||||
try {
|
||||
return decodeBase64(typeof input == "string" ? input : decoder.decode(input), !0);
|
||||
} catch (cause) {
|
||||
throw new TypeError(invalid, { cause });
|
||||
}
|
||||
}
|
||||
function encode(input) {
|
||||
return encodeBase64(typeof input == "string" ? encoder.encode(input) : input, !0);
|
||||
}
|
||||
export {
|
||||
decode,
|
||||
encode
|
||||
};
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
class JOSEError extends Error {
|
||||
static code = "ERR_JOSE_GENERIC";
|
||||
code = "ERR_JOSE_GENERIC";
|
||||
constructor(message, options) {
|
||||
super(message, options), this.name = this.constructor.name, Error.captureStackTrace?.(this, this.constructor);
|
||||
}
|
||||
}
|
||||
class JWTClaimValidationFailed extends JOSEError {
|
||||
static code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
|
||||
code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
|
||||
claim;
|
||||
reason;
|
||||
payload;
|
||||
constructor(message, payload, claim = "unspecified", reason = "unspecified") {
|
||||
super(message, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;
|
||||
}
|
||||
}
|
||||
class JWTExpired extends JOSEError {
|
||||
static code = "ERR_JWT_EXPIRED";
|
||||
code = "ERR_JWT_EXPIRED";
|
||||
claim;
|
||||
reason;
|
||||
payload;
|
||||
constructor(message, payload, claim = "unspecified", reason = "unspecified") {
|
||||
super(message, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;
|
||||
}
|
||||
}
|
||||
class JOSEAlgNotAllowed extends JOSEError {
|
||||
static code = "ERR_JOSE_ALG_NOT_ALLOWED";
|
||||
code = "ERR_JOSE_ALG_NOT_ALLOWED";
|
||||
}
|
||||
class JOSENotSupported extends JOSEError {
|
||||
static code = "ERR_JOSE_NOT_SUPPORTED";
|
||||
code = "ERR_JOSE_NOT_SUPPORTED";
|
||||
}
|
||||
class JWEDecryptionFailed extends JOSEError {
|
||||
static code = "ERR_JWE_DECRYPTION_FAILED";
|
||||
code = "ERR_JWE_DECRYPTION_FAILED";
|
||||
constructor(message = "decryption operation failed", options) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
class JWEInvalid extends JOSEError {
|
||||
static code = "ERR_JWE_INVALID";
|
||||
code = "ERR_JWE_INVALID";
|
||||
}
|
||||
class JWSInvalid extends JOSEError {
|
||||
static code = "ERR_JWS_INVALID";
|
||||
code = "ERR_JWS_INVALID";
|
||||
}
|
||||
class JWTInvalid extends JOSEError {
|
||||
static code = "ERR_JWT_INVALID";
|
||||
code = "ERR_JWT_INVALID";
|
||||
}
|
||||
class JWKInvalid extends JOSEError {
|
||||
static code = "ERR_JWK_INVALID";
|
||||
code = "ERR_JWK_INVALID";
|
||||
}
|
||||
class JWKSInvalid extends JOSEError {
|
||||
static code = "ERR_JWKS_INVALID";
|
||||
code = "ERR_JWKS_INVALID";
|
||||
}
|
||||
class JWKSNoMatchingKey extends JOSEError {
|
||||
static code = "ERR_JWKS_NO_MATCHING_KEY";
|
||||
code = "ERR_JWKS_NO_MATCHING_KEY";
|
||||
constructor(message = "no applicable key found in the JSON Web Key Set", options) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
class JWKSMultipleMatchingKeys extends JOSEError {
|
||||
[Symbol.asyncIterator] = async function* () {
|
||||
};
|
||||
static code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
|
||||
code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
|
||||
constructor(message = "multiple matching keys found in the JSON Web Key Set", options) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
class JWKSTimeout extends JOSEError {
|
||||
static code = "ERR_JWKS_TIMEOUT";
|
||||
code = "ERR_JWKS_TIMEOUT";
|
||||
constructor(message = "request timed out", options) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
class JWSSignatureVerificationFailed extends JOSEError {
|
||||
static code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
|
||||
code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
|
||||
constructor(message = "signature verification failed", options) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
export {
|
||||
JOSEAlgNotAllowed,
|
||||
JOSEError,
|
||||
JOSENotSupported,
|
||||
JWEDecryptionFailed,
|
||||
JWEInvalid,
|
||||
JWKInvalid,
|
||||
JWKSInvalid,
|
||||
JWKSMultipleMatchingKeys,
|
||||
JWKSNoMatchingKey,
|
||||
JWKSTimeout,
|
||||
JWSInvalid,
|
||||
JWSSignatureVerificationFailed,
|
||||
JWTClaimValidationFailed,
|
||||
JWTExpired,
|
||||
JWTInvalid
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "jose",
|
||||
"version": "6.2.12",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
"./jwt/*": {
|
||||
"types": "./dist/types/jwt/*.d.ts",
|
||||
"default": "./dist/webapi/jwt/*.js"
|
||||
},
|
||||
"./errors": {
|
||||
"types": "./dist/types/util/errors.d.ts",
|
||||
"default": "./dist/webapi/util/errors.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Object patterns over arrays that name positions and `length` (jose's
|
||||
// `const { 0: header, 1: payload, 2: signature, length } = jws.split(".")`):
|
||||
// each position is the element or undefined past the end, defaults apply
|
||||
// to missing positions, and `length` is the array's length.
|
||||
const { 0: header, 1: payload, 2: signature, length } = "aaa.bbb.ccc".split(".");
|
||||
console.log(header, payload, signature, length);
|
||||
|
||||
const short = "aaa.bbb".split(".");
|
||||
const { 0: first, 2: third, length: count } = short;
|
||||
console.log(first, third === undefined, count);
|
||||
|
||||
const { 1: second = "fallback", 3: fourth = "missing" } = short;
|
||||
console.log(second, fourth);
|
||||
|
||||
function parts(values: number[]): string {
|
||||
const { 0: a, 1: b, length: n } = values;
|
||||
return `${a}:${b}:${n}`;
|
||||
}
|
||||
console.log(parts([1, 2, 3]), parts([7]));
|
||||
|
||||
let renamed = "";
|
||||
for (const { 0: key, 1: value } of [["x", "1"], ["y", "2"]]) renamed += `${key}=${value};`;
|
||||
console.log(renamed);
|
||||
@@ -0,0 +1,34 @@
|
||||
// Runtime idioms from jose's dist build: the Uint8Array base64/hex
|
||||
// feature probes (absent in Node 24), String.fromCharCode.apply over byte
|
||||
// chunks and number lists (the btoa fallback), and structuredClone of a
|
||||
// parameter whose default is `{}` called with differently shaped objects.
|
||||
console.log(typeof Uint8Array.prototype.toBase64, typeof Uint8Array.prototype.toHex);
|
||||
console.log(typeof Uint8Array.prototype.setFromBase64, typeof Uint8Array.prototype.setFromHex);
|
||||
|
||||
function encodeBase64(input) {
|
||||
if (Uint8Array.prototype.toBase64) return input.toBase64();
|
||||
const CHUNK_SIZE = 4, arr = [];
|
||||
for (let i = 0; i < input.length; i += CHUNK_SIZE)
|
||||
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
||||
return btoa(arr.join(""));
|
||||
}
|
||||
console.log(encodeBase64(new TextEncoder().encode("hello, base64 chunks")));
|
||||
console.log(encodeBase64(new Uint8Array([0, 255, 128, 1, 2])));
|
||||
|
||||
const bytes = new Uint8Array([104, 105]);
|
||||
console.log(String.fromCharCode.apply(null, bytes), String.fromCharCode.apply(undefined, [72, 73]));
|
||||
const untyped = JSON.parse("[79, 75]");
|
||||
console.log(String.fromCharCode.apply(null, untyped), String.fromCharCode.apply(null, []).length);
|
||||
|
||||
let store;
|
||||
class Builder {
|
||||
constructor(payload = {}) {
|
||||
(store ||= new WeakMap()).set(this, structuredClone(payload));
|
||||
}
|
||||
claims() {
|
||||
return JSON.stringify(store.get(this));
|
||||
}
|
||||
}
|
||||
console.log(new Builder({ role: "admin", exp: 5 }).claims());
|
||||
console.log(new Builder({ scope: "read" }).claims());
|
||||
console.log(new Builder().claims());
|
||||
@@ -0,0 +1,110 @@
|
||||
// The real jose@6.2.12 through its root entry, from TypeScript, the way a
|
||||
// Next.js proxy uses it: `import { jwtVerify, errors } from "jose"`.
|
||||
// node_modules/jose vendors the published package's dist/ (every webapi
|
||||
// module the root entry loads, and its declarations), package.json and
|
||||
// LICENSE.md unmodified (MIT). Loading the root runs jwks/remote.js, which
|
||||
// reads Node's navigator global at module load. Covers chained SignJWT
|
||||
// builders, string durations (jose's secs()), destructured jwtVerify
|
||||
// results and `instanceof errors.JOSEError` brand checks.
|
||||
import { SignJWT, jwtVerify, errors, cryptoRuntime, decodeJwt } from "jose";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const secret = encoder.encode("scriptc-jose-root-shared-secret");
|
||||
const now = 1_800_000_000;
|
||||
const iat = 1_799_999_000;
|
||||
|
||||
interface Options {
|
||||
issuer?: string;
|
||||
audience?: string;
|
||||
algorithms?: string[];
|
||||
at?: number;
|
||||
maxTokenAge?: string;
|
||||
clockTolerance?: string;
|
||||
}
|
||||
|
||||
async function check(label: string, token: string, options: Options = {}): Promise<void> {
|
||||
try {
|
||||
const { payload, protectedHeader } = await jwtVerify(token, secret, {
|
||||
algorithms: options.algorithms ?? ["HS256"],
|
||||
currentDate: new Date((options.at ?? now) * 1000),
|
||||
issuer: options.issuer,
|
||||
audience: options.audience,
|
||||
maxTokenAge: options.maxTokenAge,
|
||||
clockTolerance: options.clockTolerance,
|
||||
});
|
||||
const role = payload.role;
|
||||
console.log(label, "ok", protectedHeader.alg, payload.sub ?? null, role, payload.exp ?? null);
|
||||
} catch (error) {
|
||||
if (error instanceof errors.JOSEError) {
|
||||
console.log(label, error.code, error.message, error instanceof errors.JWTExpired);
|
||||
} else if (error instanceof TypeError) {
|
||||
console.log(label, "TypeError", error.message);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(cryptoRuntime, errors.JWTExpired.code, errors.JWSSignatureVerificationFailed.code);
|
||||
|
||||
const token = await new SignJWT({ role: "admin" })
|
||||
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
|
||||
.setIssuedAt(iat)
|
||||
.setIssuer("urn:scriptc:issuer")
|
||||
.setAudience("urn:scriptc:audience")
|
||||
.setSubject("alice")
|
||||
.setJti("jti-1")
|
||||
.setNotBefore(iat - 60)
|
||||
.setExpirationTime(iat + 3600)
|
||||
.sign(secret);
|
||||
console.log(token);
|
||||
console.log(JSON.stringify(decodeJwt(token)));
|
||||
|
||||
await check("valid", token, { issuer: "urn:scriptc:issuer", audience: "urn:scriptc:audience" });
|
||||
await check("issuer", token, { issuer: "urn:other" });
|
||||
await check("audience", token, { audience: "urn:other" });
|
||||
await check("expired", token, { at: iat + 7200 });
|
||||
await check("wrong secret", await new SignJWT({ role: "admin" }).setProtectedHeader({ alg: "HS256" }).sign(encoder.encode("x")));
|
||||
await check("malformed", "not.a.jwt");
|
||||
await check("alg", token, { algorithms: ["HS512"] });
|
||||
|
||||
// String durations: maxTokenAge and clockTolerance against the fixed date.
|
||||
await check("max age 10 minutes", token, { maxTokenAge: "10 minutes" });
|
||||
await check("max age 1 hour", token, { maxTokenAge: "1 hour" });
|
||||
await check("max age 2 days", token, { maxTokenAge: "2 days" });
|
||||
await check("max age 1.5h", token, { maxTokenAge: "1.5h" });
|
||||
await check("max age +20m", token, { maxTokenAge: "+20m" });
|
||||
await check("max age 1 week", token, { maxTokenAge: "1 week" });
|
||||
await check("max age 1y", token, { maxTokenAge: "1y" });
|
||||
await check("max age 900 secs", token, { maxTokenAge: "900 secs" });
|
||||
await check("max age 3 HOURS", token, { maxTokenAge: "3 HOURS" });
|
||||
await check("tolerance 30s", token, { at: iat + 3620, clockTolerance: "30s" });
|
||||
await check("tolerance 5 sec", token, { at: iat + 3620, clockTolerance: "5 sec" });
|
||||
await check("tolerance 1 min from now", token, { at: iat + 3620, clockTolerance: "1 min from now" });
|
||||
await check("tolerance 1 min ago", token, { at: iat + 3620, clockTolerance: "1 min ago" });
|
||||
await check("bad duration", token, { maxTokenAge: "10 fortnights" });
|
||||
await check("signed ago", token, { maxTokenAge: "-2h ago" });
|
||||
await check("empty duration", token, { maxTokenAge: "" });
|
||||
|
||||
// String durations on the builder are relative to the clock: check the
|
||||
// distance from now instead of printing it.
|
||||
const relative = await new SignJWT({ role: "reader" })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setIssuedAt("-1 minute")
|
||||
.setNotBefore("0s")
|
||||
.setExpirationTime("2h")
|
||||
.sign(secret);
|
||||
const claims = decodeJwt(relative);
|
||||
const clock = Math.floor(Date.now() / 1000);
|
||||
console.log(
|
||||
Math.abs((claims.exp ?? 0) - clock - 7200) <= 2,
|
||||
Math.abs((claims.iat ?? 0) - clock + 60) <= 2,
|
||||
Math.abs((claims.nbf ?? 0) - clock) <= 2,
|
||||
);
|
||||
const { payload } = await jwtVerify(relative, secret, { maxTokenAge: "5 minutes" });
|
||||
console.log(payload.role);
|
||||
try {
|
||||
new SignJWT({}).setExpirationTime("soon");
|
||||
} catch (error) {
|
||||
console.log((error as Error).name, (error as Error).message);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Filip Skokan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
export { compactDecrypt } from './jwe/compact/decrypt.js';
|
||||
export type { CompactDecryptGetKey } from './jwe/compact/decrypt.js';
|
||||
export { flattenedDecrypt } from './jwe/flattened/decrypt.js';
|
||||
export type { FlattenedDecryptGetKey } from './jwe/flattened/decrypt.js';
|
||||
export { generalDecrypt } from './jwe/general/decrypt.js';
|
||||
export type { GeneralDecryptGetKey } from './jwe/general/decrypt.js';
|
||||
export { GeneralEncrypt } from './jwe/general/encrypt.js';
|
||||
export type { Recipient } from './jwe/general/encrypt.js';
|
||||
export { compactVerify } from './jws/compact/verify.js';
|
||||
export type { CompactVerifyGetKey } from './jws/compact/verify.js';
|
||||
export { flattenedVerify } from './jws/flattened/verify.js';
|
||||
export type { FlattenedVerifyGetKey } from './jws/flattened/verify.js';
|
||||
export { generalVerify } from './jws/general/verify.js';
|
||||
export type { GeneralVerifyGetKey } from './jws/general/verify.js';
|
||||
export { jwtVerify } from './jwt/verify.js';
|
||||
export type { JWTVerifyOptions, JWTVerifyGetKey } from './jwt/verify.js';
|
||||
export { jwtDecrypt } from './jwt/decrypt.js';
|
||||
export type { JWTDecryptOptions, JWTDecryptGetKey } from './jwt/decrypt.js';
|
||||
export { CompactEncrypt } from './jwe/compact/encrypt.js';
|
||||
export { FlattenedEncrypt } from './jwe/flattened/encrypt.js';
|
||||
export { CompactSign } from './jws/compact/sign.js';
|
||||
export { FlattenedSign } from './jws/flattened/sign.js';
|
||||
export { GeneralSign } from './jws/general/sign.js';
|
||||
export type { Signature } from './jws/general/sign.js';
|
||||
export { SignJWT } from './jwt/sign.js';
|
||||
export { EncryptJWT } from './jwt/encrypt.js';
|
||||
export { calculateJwkThumbprint, calculateJwkThumbprintUri } from './jwk/thumbprint.js';
|
||||
export { EmbeddedJWK } from './jwk/embedded.js';
|
||||
export { createLocalJWKSet } from './jwks/local.js';
|
||||
export type { LocalJWKSet } from './jwks/local.js';
|
||||
export { createRemoteJWKSet, jwksCache, customFetch } from './jwks/remote.js';
|
||||
export type { RemoteJWKSet, RemoteJWKSetOptions, JWKSCacheInput, ExportedJWKSCache, FetchImplementation, } from './jwks/remote.js';
|
||||
export { UnsecuredJWT } from './jwt/unsecured.js';
|
||||
export type { UnsecuredResult } from './jwt/unsecured.js';
|
||||
export { exportPKCS8, exportSPKI, exportJWK } from './key/export.js';
|
||||
export { importSPKI, importPKCS8, importX509, importJWK } from './key/import.js';
|
||||
export type { KeyImportOptions } from './key/import.js';
|
||||
export { decodeProtectedHeader } from './util/decode_protected_header.js';
|
||||
export { decodeJwt } from './util/decode_jwt.js';
|
||||
export type { ProtectedHeaderParameters } from './util/decode_protected_header.js';
|
||||
import * as errors from './util/errors.js';
|
||||
export { errors };
|
||||
export { generateKeyPair } from './key/generate_key_pair.js';
|
||||
export type { GenerateKeyPairAlgorithm, GenerateKeyPairResult, GenerateKeyPairOptions, } from './key/generate_key_pair.js';
|
||||
export { generateSecret } from './key/generate_secret.js';
|
||||
export type { GenerateSecretAlgorithm, GenerateSecretOptions } from './key/generate_secret.js';
|
||||
import * as base64url from './util/base64url.js';
|
||||
export { base64url };
|
||||
export type { AnyJWK, CompactDecryptResult, CompactJWEHeaderParameters, CompactJWSHeaderParameters, CompactVerifyResult, CritOption, CryptoKey, DecryptOptions, EncryptOptions, FlattenedDecryptResult, FlattenedJWE, FlattenedJWS, FlattenedJWSInput, FlattenedVerifyResult, GeneralDecryptResult, GeneralJWE, GeneralJWS, GeneralJWSInput, GeneralVerifyResult, GetKeyFunction, JoseHeaderParameters, JSONWebKeySet, JWEContentEncryptionAlgorithm, JWEHeaderParameters, JWEKeyManagementAlgorithm, JWEKeyManagementHeaderParameters, JWK_AKP_Private, JWK_AKP_Public, JWK_EC_Private, JWK_EC_Public, JWK_oct, JWK_OKP_Private, JWK_OKP_Public, JWK_RSA_Private, JWK_RSA_Public, JWK, JWKKeyType, JWKParameters, JWSAlgorithm, JWSHeaderParameters, JWTClaimVerificationOptions, JWTDecryptResult, JWTHeaderParameters, JWTPayload, JWTVerifyResult, KeyInput, KeyObject, ProduceJWT, ResolvedKey, SignOptions, VerifyOptions, } from './types.d.ts';
|
||||
/**
|
||||
* Identified the loaded runtime-specific build in prior releases; now always `"WebCryptoAPI"`.
|
||||
*
|
||||
* @deprecated Remove any runtime branching on this value; it is always `"WebCryptoAPI"`.
|
||||
*/
|
||||
export declare const cryptoRuntime = "WebCryptoAPI";
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Resolves a key for Compact JWE decryption from unverified headers and token data. */
|
||||
export interface CompactDecryptGetKey<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array> extends t.GetKeyFunction<t.CompactJWEHeaderParameters, t.FlattenedJWE, KeyType | t.KeyObject | t.JWK> {
|
||||
}
|
||||
/**
|
||||
* Decrypts a Compact JWE.
|
||||
*
|
||||
* @param key Private key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
export declare function compactDecrypt(jwe: string | Uint8Array, key: t.KeyInput, options?: t.DecryptOptions): Promise<t.CompactDecryptResult>;
|
||||
/**
|
||||
* Decrypts a Compact JWE with a dynamically resolved key, included in the result.
|
||||
*
|
||||
* @param getKey Resolves a private key or shared secret from unverified token data.
|
||||
*/
|
||||
export declare function compactDecrypt<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array>(jwe: string | Uint8Array, getKey: CompactDecryptGetKey<KeyType>, options?: t.DecryptOptions): Promise<t.CompactDecryptResult & t.ResolvedKey<KeyType>>;
|
||||
/**
|
||||
* Decrypts a Compact JWE with a key or key resolver. The result includes `key` only when a resolver
|
||||
* is used.
|
||||
*
|
||||
* @param key Private key or shared secret, or a function resolving one.
|
||||
*/
|
||||
export declare function compactDecrypt(jwe: string | Uint8Array, key: t.KeyInput | CompactDecryptGetKey, options?: t.DecryptOptions): Promise<t.CompactDecryptResult & Partial<t.ResolvedKey>>;
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Builds and encrypts Compact JWE strings. */
|
||||
export declare class CompactEncrypt {
|
||||
#private;
|
||||
/**
|
||||
* Creates a Compact JWE encryptor.
|
||||
*
|
||||
* @param plaintext Binary representation of the plaintext to encrypt.
|
||||
*/
|
||||
constructor(plaintext: Uint8Array);
|
||||
/**
|
||||
* Sets a content encryption key instead of generating a random one for the JWE "enc" algorithm.
|
||||
* May only be called once.
|
||||
*
|
||||
* @deprecated Use only for testing and vector validation.
|
||||
*/
|
||||
setContentEncryptionKey(cek: Uint8Array): this;
|
||||
/**
|
||||
* Sets the content encryption IV instead of generating a random one for the JWE "enc" algorithm.
|
||||
* May only be called once.
|
||||
*
|
||||
* @deprecated Use only for testing and vector validation.
|
||||
*/
|
||||
setInitializationVector(iv: Uint8Array): this;
|
||||
/** Sets the JWE Protected Header. May only be called once. */
|
||||
setProtectedHeader(protectedHeader: t.CompactJWEHeaderParameters): this;
|
||||
/**
|
||||
* Sets key management inputs such as ECDH-ES "apu"/"apv" or PBES2 "p2c". Use this method instead
|
||||
* of header setters; the resulting parameters are added to the JOSE header. May only be called
|
||||
* once.
|
||||
*/
|
||||
setKeyManagementParameters(parameters: t.JWEKeyManagementHeaderParameters): this;
|
||||
/**
|
||||
* Encrypts the plaintext as a Compact JWE.
|
||||
*
|
||||
* @param key Public key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
encrypt(key: t.KeyInput, options?: t.EncryptOptions): Promise<string>;
|
||||
}
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Resolves a key for Flattened JWE decryption from unverified headers and token data. */
|
||||
export interface FlattenedDecryptGetKey<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array> extends t.GetKeyFunction<t.JWEHeaderParameters | undefined, t.FlattenedJWE, KeyType | t.KeyObject | t.JWK> {
|
||||
}
|
||||
/**
|
||||
* Decrypts a Flattened JWE.
|
||||
*
|
||||
* @param key Private key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
export declare function flattenedDecrypt(jwe: t.FlattenedJWE, key: t.KeyInput, options?: t.DecryptOptions): Promise<t.FlattenedDecryptResult>;
|
||||
/**
|
||||
* Decrypts a Flattened JWE with a dynamically resolved key, included in the result.
|
||||
*
|
||||
* @param getKey Resolves a private key or shared secret from unverified token data.
|
||||
*/
|
||||
export declare function flattenedDecrypt<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array>(jwe: t.FlattenedJWE, getKey: FlattenedDecryptGetKey<KeyType>, options?: t.DecryptOptions): Promise<t.FlattenedDecryptResult & t.ResolvedKey<KeyType>>;
|
||||
/**
|
||||
* Decrypts a Flattened JWE with a key or key resolver. The result includes `key` only when a
|
||||
* resolver is used.
|
||||
*
|
||||
* @param key Private key or shared secret, or a function resolving one.
|
||||
*/
|
||||
export declare function flattenedDecrypt(jwe: t.FlattenedJWE, key: t.KeyInput | FlattenedDecryptGetKey, options?: t.DecryptOptions): Promise<t.FlattenedDecryptResult & Partial<t.ResolvedKey>>;
|
||||
Generated
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Builds and encrypts Flattened JWE objects. */
|
||||
export declare class FlattenedEncrypt {
|
||||
#private;
|
||||
/**
|
||||
* Creates a Flattened JWE encryptor.
|
||||
*
|
||||
* @param plaintext Binary representation of the plaintext to encrypt.
|
||||
*/
|
||||
constructor(plaintext: Uint8Array);
|
||||
/**
|
||||
* Sets key management inputs such as ECDH-ES "apu"/"apv" or PBES2 "p2c". Use this method instead
|
||||
* of header setters; the resulting parameters are added to the JOSE header. May only be called
|
||||
* once.
|
||||
*/
|
||||
setKeyManagementParameters(parameters: t.JWEKeyManagementHeaderParameters): this;
|
||||
/** Sets the JWE Protected Header. May only be called once. */
|
||||
setProtectedHeader(protectedHeader: t.JWEHeaderParameters): this;
|
||||
/** Sets the JWE Shared Unprotected Header. May only be called once. */
|
||||
setSharedUnprotectedHeader(sharedUnprotectedHeader: t.JWEHeaderParameters): this;
|
||||
/** Sets the JWE Per-Recipient Unprotected Header. May only be called once. */
|
||||
setUnprotectedHeader(unprotectedHeader: t.JWEHeaderParameters): this;
|
||||
/** Sets additional data to authenticate without encrypting it. */
|
||||
setAdditionalAuthenticatedData(aad: Uint8Array): this;
|
||||
/**
|
||||
* Sets a content encryption key instead of generating a random one for the JWE "enc" algorithm.
|
||||
* May only be called once.
|
||||
*
|
||||
* @deprecated Use only for testing and vector validation.
|
||||
*/
|
||||
setContentEncryptionKey(cek: Uint8Array): this;
|
||||
/**
|
||||
* Sets the content encryption IV instead of generating a random one for the JWE "enc" algorithm.
|
||||
* May only be called once.
|
||||
*
|
||||
* @deprecated Use only for testing and vector validation.
|
||||
*/
|
||||
setInitializationVector(iv: Uint8Array): this;
|
||||
/**
|
||||
* Encrypts the plaintext as a Flattened JWE.
|
||||
*
|
||||
* @param key Public key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
encrypt(key: t.KeyInput, options?: t.EncryptOptions): Promise<t.FlattenedJWE>;
|
||||
}
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Resolves a key for General JWE decryption from unverified headers and token data. */
|
||||
export interface GeneralDecryptGetKey<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array> extends t.GetKeyFunction<t.JWEHeaderParameters | undefined, t.FlattenedJWE, KeyType | t.KeyObject | t.JWK> {
|
||||
}
|
||||
/**
|
||||
* Decrypts a General JWE.
|
||||
*
|
||||
* > Note: Returns plaintext and headers from the first recipient that decrypts successfully. Other
|
||||
* > recipients may be inspected to enforce serialization rules, but their headers are not included in
|
||||
* > the result. Rely only on the returned data.
|
||||
*
|
||||
* @param key Private key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
export declare function generalDecrypt(jwe: t.GeneralJWE, key: t.KeyInput, options?: t.DecryptOptions): Promise<t.GeneralDecryptResult>;
|
||||
/**
|
||||
* Decrypts a General JWE with a dynamically resolved key, included in the result.
|
||||
*
|
||||
* @param getKey Resolves a private key or shared secret from unverified token data.
|
||||
*/
|
||||
export declare function generalDecrypt<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array>(jwe: t.GeneralJWE, getKey: GeneralDecryptGetKey<KeyType>, options?: t.DecryptOptions): Promise<t.GeneralDecryptResult & t.ResolvedKey<KeyType>>;
|
||||
/**
|
||||
* Decrypts a General JWE with a key or key resolver. The result includes `key` only when a resolver
|
||||
* is used.
|
||||
*
|
||||
* @param key Private key or shared secret, or a function resolving one.
|
||||
*/
|
||||
export declare function generalDecrypt(jwe: t.GeneralJWE, key: t.KeyInput | GeneralDecryptGetKey, options?: t.DecryptOptions): Promise<t.GeneralDecryptResult & Partial<t.ResolvedKey>>;
|
||||
Generated
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Configures an individual recipient in a General JWE. */
|
||||
export interface Recipient {
|
||||
/** Sets the JWE Per-Recipient Unprotected Header. May only be called once. */
|
||||
setUnprotectedHeader(unprotectedHeader: t.JWEHeaderParameters): Recipient;
|
||||
/**
|
||||
* Sets key management inputs such as ECDH-ES "apu"/"apv" or PBES2 "p2c". Use this method instead
|
||||
* of header setters; the resulting parameters are added to the JOSE header. May only be called
|
||||
* once.
|
||||
*/
|
||||
setKeyManagementParameters(parameters: t.JWEKeyManagementHeaderParameters): Recipient;
|
||||
/**
|
||||
* Adds another recipient to the enclosing {@link GeneralEncrypt} and returns its configuration.
|
||||
*
|
||||
* @param key Public key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
addRecipient(key: t.KeyInput, options?: t.CritOption): Recipient;
|
||||
/**
|
||||
* Encrypts for all recipients on the enclosing {@link GeneralEncrypt}, using their configured
|
||||
* keys.
|
||||
*/
|
||||
encrypt(): Promise<t.GeneralJWE>;
|
||||
/** Returns the enclosing {@link GeneralEncrypt} instance. */
|
||||
done(): GeneralEncrypt;
|
||||
}
|
||||
/** Builds and encrypts General JWE objects. */
|
||||
export declare class GeneralEncrypt {
|
||||
#private;
|
||||
/**
|
||||
* Creates a General JWE encryptor.
|
||||
*
|
||||
* @param plaintext Binary representation of the plaintext to encrypt.
|
||||
*/
|
||||
constructor(plaintext: Uint8Array);
|
||||
/**
|
||||
* Adds a recipient and returns its configuration.
|
||||
*
|
||||
* @param key Public key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
addRecipient(key: t.KeyInput, options?: t.CritOption): Recipient;
|
||||
/** Sets the JWE Protected Header. May only be called once. */
|
||||
setProtectedHeader(protectedHeader: t.JWEHeaderParameters): this;
|
||||
/** Sets the JWE Shared Unprotected Header. May only be called once. */
|
||||
setSharedUnprotectedHeader(sharedUnprotectedHeader: t.JWEHeaderParameters): this;
|
||||
/** Sets additional data to authenticate without encrypting it. */
|
||||
setAdditionalAuthenticatedData(aad: Uint8Array): this;
|
||||
/** Encrypts the plaintext as a General JWE. */
|
||||
encrypt(): Promise<t.GeneralJWE>;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import type * as t from '../types.d.ts';
|
||||
/**
|
||||
* Resolves a verification key from an embedded "jwk" (JSON Web Key) Header Parameter. This key
|
||||
* resolver opts JWS and JWT verification into trusting a public key supplied by the token. Use the
|
||||
* verification function's `algorithms` option to restrict accepted algorithms.
|
||||
*
|
||||
* @param token The consumed JWS token.
|
||||
* @returns The public key from the JWS "jwk" (JSON Web Key) Header Parameter.
|
||||
*/
|
||||
export declare function EmbeddedJWK(protectedHeader?: t.JWSHeaderParameters, token?: t.FlattenedJWSInput): Promise<t.CryptoKey>;
|
||||
Generated
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
import type * as t from '../types.d.ts';
|
||||
/**
|
||||
* Calculates a base64url-encoded JWK Thumbprint. CryptoKey inputs must be extractable.
|
||||
*
|
||||
* @param key Key to calculate the thumbprint for.
|
||||
* @param digestAlgorithm Digest algorithm. Defaults to "sha256".
|
||||
*/
|
||||
export declare function calculateJwkThumbprint(key: t.JWK | t.CryptoKey | t.KeyObject, digestAlgorithm?: 'sha256' | 'sha384' | 'sha512'): Promise<string>;
|
||||
/**
|
||||
* Calculates a JWK Thumbprint URI. CryptoKey inputs must be extractable.
|
||||
*
|
||||
* @param key Key to calculate the thumbprint for.
|
||||
* @param digestAlgorithm Digest algorithm. Defaults to "sha256".
|
||||
*/
|
||||
export declare function calculateJwkThumbprintUri(key: t.CryptoKey | t.KeyObject | t.JWK, digestAlgorithm?: 'sha256' | 'sha384' | 'sha512'): Promise<string>;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import type * as t from '../types.d.ts';
|
||||
/** A key resolver created by {@link createLocalJWKSet}. */
|
||||
export interface LocalJWKSet {
|
||||
(protectedHeader?: t.JWSHeaderParameters, token?: t.FlattenedJWSInput): Promise<t.CryptoKey>;
|
||||
/** Returns a structured clone of the original JSON Web Key Set. */
|
||||
jwks: () => t.JSONWebKeySet;
|
||||
}
|
||||
/**
|
||||
* Creates a resolver for a locally available JSON Web Key Set. Selection uses the header's "alg"
|
||||
* (Algorithm) and "kid" (Key ID), and respects the JWK's "use" (Public Key Use) and "key_ops" (Key
|
||||
* Operations). Exactly one key must match.
|
||||
*
|
||||
* > Note: Only public signature verification keys are supported, not public encryption keys.
|
||||
*
|
||||
* @param jwks JSON Web Key Set formatted object.
|
||||
*/
|
||||
export declare function createLocalJWKSet(jwks: t.JSONWebKeySet): LocalJWKSet;
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import type * as t from '../types.d.ts';
|
||||
/**
|
||||
* Configures a custom fetch implementation for remote JWKS retrieval.
|
||||
*
|
||||
* > Note: Fetch-like libraries may have incompatible TypeScript signatures even when they accept the
|
||||
* > supplied arguments at runtime.
|
||||
*/
|
||||
export declare const customFetch: unique symbol;
|
||||
/** Custom fetch function. Must return HTTP 200 with a JSON JWKS. See {@link customFetch}. */
|
||||
export type FetchImplementation = (
|
||||
/** JWKS URL, passed as the fetch resource. */
|
||||
url: string,
|
||||
/** Options passed to fetch. */
|
||||
options: {
|
||||
headers: Headers;
|
||||
method: 'GET';
|
||||
/** See {@link !Request.redirect} */
|
||||
redirect: 'manual';
|
||||
signal: AbortSignal;
|
||||
}) => Promise<Response>;
|
||||
/**
|
||||
* Symbol used to configure an externally persisted remote JWKS cache.
|
||||
*
|
||||
* > Warning: Only trusted application code must be allowed to write this cache; its keys are trusted for
|
||||
* > signature verification.
|
||||
*/
|
||||
export declare const jwksCache: unique symbol;
|
||||
/** Remote JWKS resolver options. */
|
||||
export interface RemoteJWKSetOptions {
|
||||
/**
|
||||
* Timeout (in milliseconds) for the HTTP request. When reached the request will be aborted and
|
||||
* the verification will fail. Must be a non-negative integer. Default is 5000 (5 seconds).
|
||||
*/
|
||||
timeoutDuration?: number;
|
||||
/**
|
||||
* Time in milliseconds after a successful fetch before a missing key can trigger another fetch.
|
||||
* Must not be `NaN`. Defaults to 30000 (30 seconds).
|
||||
*/
|
||||
cooldownDuration?: number;
|
||||
/**
|
||||
* Maximum age of cached keys in milliseconds. Defaults to 600000 (10 minutes); `Infinity`
|
||||
* disables expiry. Must not be `NaN`.
|
||||
*/
|
||||
cacheMaxAge?: number | typeof Infinity;
|
||||
/** Headers to be sent with the HTTP request. */
|
||||
headers?: Record<string, string>;
|
||||
/** See {@link jwksCache}. */
|
||||
[jwksCache]?: JWKSCacheInput;
|
||||
/** See {@link customFetch}. */
|
||||
[customFetch]?: FetchImplementation;
|
||||
}
|
||||
/** Shape of an externally persisted remote JWKS cache. */
|
||||
export interface ExportedJWKSCache {
|
||||
/** Current cached JSON Web Key Set */
|
||||
jwks: t.JSONWebKeySet;
|
||||
/** Last successful fetch, in milliseconds since Unix epoch. */
|
||||
uat: number;
|
||||
}
|
||||
/** Values accepted by the {@link jwksCache} option. */
|
||||
export type JWKSCacheInput = ExportedJWKSCache | Record<string, never>;
|
||||
/** A key resolver created by {@link createRemoteJWKSet}. */
|
||||
export interface RemoteJWKSet {
|
||||
(protectedHeader?: t.JWSHeaderParameters, token?: t.FlattenedJWSInput): Promise<t.CryptoKey>;
|
||||
/** Whether the cooldown window following the last successful fetch is still in effect. */
|
||||
readonly coolingDown: boolean;
|
||||
/**
|
||||
* Whether the currently cached JSON Web Key Set is within its
|
||||
* {@link RemoteJWKSetOptions.cacheMaxAge}.
|
||||
*/
|
||||
readonly fresh: boolean;
|
||||
/** Whether a JSON Web Key Set fetch is currently in flight. */
|
||||
readonly reloading: boolean;
|
||||
/**
|
||||
* Triggers a JSON Web Key Set fetch, bypassing
|
||||
* {@link RemoteJWKSetOptions.cooldownDuration the cooldown}.
|
||||
*/
|
||||
reload: () => Promise<void>;
|
||||
/**
|
||||
* Returns a structured clone of the cached JSON Web Key Set, or `undefined` before keys have been
|
||||
* fetched or seeded via {@link jwksCache}.
|
||||
*/
|
||||
jwks: () => t.JSONWebKeySet | undefined;
|
||||
}
|
||||
/**
|
||||
* Creates a resolver for a JSON Web Key Set available at an HTTP(S) URL. Fetches the JSON Web Key
|
||||
* Set when the cache is missing or stale. An unmatched key triggers another fetch only when
|
||||
* `cooldownDuration` has elapsed since the last successful fetch. Selection uses the header's "alg"
|
||||
* and "kid" and respects the JWK's "use" and "key_ops". Exactly one key must match.
|
||||
*
|
||||
* > Note: Only public signature verification keys are supported, not public encryption keys.
|
||||
*
|
||||
* @param url URL to fetch the JSON Web Key Set from.
|
||||
*/
|
||||
export declare function createRemoteJWKSet(url: URL, options?: RemoteJWKSetOptions): RemoteJWKSet;
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Builds and signs Compact JWS strings. */
|
||||
export declare class CompactSign {
|
||||
#private;
|
||||
/**
|
||||
* Creates a Compact JWS signer.
|
||||
*
|
||||
* @param payload Binary representation of the payload to sign.
|
||||
*/
|
||||
constructor(payload: Uint8Array);
|
||||
/** Sets the JWS Protected Header. May only be called once. */
|
||||
setProtectedHeader(protectedHeader: t.CompactJWSHeaderParameters): this;
|
||||
/**
|
||||
* Signs the payload as a Compact JWS.
|
||||
*
|
||||
* @param key Private key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
sign(key: t.KeyInput, options?: t.SignOptions): Promise<string>;
|
||||
}
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Resolves a key for Compact JWS verification from unverified headers and token data. */
|
||||
export interface CompactVerifyGetKey<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array> extends t.GetKeyFunction<t.CompactJWSHeaderParameters, t.FlattenedJWSInput, KeyType | t.KeyObject | t.JWK> {
|
||||
}
|
||||
/**
|
||||
* Verifies a Compact JWS signature and decodes its payload.
|
||||
*
|
||||
* @param key Public key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
export declare function compactVerify(jws: string | Uint8Array, key: t.KeyInput, options?: t.VerifyOptions): Promise<t.CompactVerifyResult>;
|
||||
/**
|
||||
* Verifies a Compact JWS signature and decodes its payload with a dynamically resolved key,
|
||||
* included in the result.
|
||||
*
|
||||
* @param getKey Resolves a public key or shared secret from unverified token data.
|
||||
*/
|
||||
export declare function compactVerify<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array>(jws: string | Uint8Array, getKey: CompactVerifyGetKey<KeyType>, options?: t.VerifyOptions): Promise<t.CompactVerifyResult & t.ResolvedKey<KeyType>>;
|
||||
/**
|
||||
* Verifies a Compact JWS and decodes its payload using a key or key resolver. The result includes
|
||||
* `key` only when a resolver is used.
|
||||
*
|
||||
* @param key Public key or shared secret, or a function resolving one.
|
||||
*/
|
||||
export declare function compactVerify(jws: string | Uint8Array, key: t.KeyInput | CompactVerifyGetKey, options?: t.VerifyOptions): Promise<t.CompactVerifyResult & Partial<t.ResolvedKey>>;
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Builds and signs Flattened JWS objects. */
|
||||
export declare class FlattenedSign {
|
||||
#private;
|
||||
/**
|
||||
* Creates a Flattened JWS signer.
|
||||
*
|
||||
* @param payload Binary representation of the payload to sign.
|
||||
*/
|
||||
constructor(payload: Uint8Array);
|
||||
/** Sets the JWS Protected Header. May only be called once. */
|
||||
setProtectedHeader(protectedHeader: t.JWSHeaderParameters): this;
|
||||
/** Sets the JWS Unprotected Header. May only be called once. */
|
||||
setUnprotectedHeader(unprotectedHeader: t.JWSHeaderParameters): this;
|
||||
/**
|
||||
* Signs the payload as a Flattened JWS.
|
||||
*
|
||||
* @param key Private key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
sign(key: t.KeyInput, options?: t.SignOptions): Promise<t.FlattenedJWS>;
|
||||
}
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
import type * as t from '../../types.d.ts';
|
||||
/** Resolves a key for Flattened JWS verification from unverified headers and token data. */
|
||||
export interface FlattenedVerifyGetKey<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array> extends t.GetKeyFunction<t.JWSHeaderParameters, t.FlattenedJWSInput, KeyType | t.KeyObject | t.JWK> {
|
||||
}
|
||||
/**
|
||||
* Verifies a Flattened JWS signature and decodes its payload.
|
||||
*
|
||||
* @param key Public key or shared secret. See
|
||||
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
|
||||
*/
|
||||
export declare function flattenedVerify(jws: t.FlattenedJWSInput, key: t.KeyInput, options?: t.VerifyOptions): Promise<t.FlattenedVerifyResult>;
|
||||
/**
|
||||
* Verifies a Flattened JWS signature and decodes its payload with a dynamically resolved key,
|
||||
* included in the result.
|
||||
*
|
||||
* @param getKey Resolves a public key or shared secret from unverified token data.
|
||||
*/
|
||||
export declare function flattenedVerify<KeyType extends t.CryptoKey | Uint8Array = t.CryptoKey | Uint8Array>(jws: t.FlattenedJWSInput, getKey: FlattenedVerifyGetKey<KeyType>, options?: t.VerifyOptions): Promise<t.FlattenedVerifyResult & t.ResolvedKey<KeyType>>;
|
||||
/**
|
||||
* Verifies a Flattened JWS and decodes its payload using a key or key resolver. The result includes
|
||||
* `key` only when a resolver is used.
|
||||
*
|
||||
* @param key Public key or shared secret, or a function resolving one.
|
||||
*/
|
||||
export declare function flattenedVerify(jws: t.FlattenedJWSInput, key: t.KeyInput | FlattenedVerifyGetKey, options?: t.VerifyOptions): Promise<t.FlattenedVerifyResult & Partial<t.ResolvedKey>>;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user