@blamejs/core 0.14.25 → 0.14.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/agent-envelope-mac.js +104 -0
- package/lib/agent-event-bus.js +105 -4
- package/lib/agent-posture-chain.js +8 -42
- package/lib/atomic-file.js +33 -3
- package/lib/audit.js +31 -23
- package/lib/auth/oauth.js +25 -5
- package/lib/auth/openid-federation.js +108 -47
- package/lib/auth/sd-jwt-vc.js +16 -3
- package/lib/break-glass.js +153 -3
- package/lib/compliance.js +147 -4
- package/lib/crypto-field.js +87 -1
- package/lib/dsr.js +378 -52
- package/lib/error-page.js +14 -1
- package/lib/file-upload.js +52 -7
- package/lib/framework-error.js +3 -1
- package/lib/gate-contract.js +53 -0
- package/lib/http-client.js +23 -9
- package/lib/mail-server-jmap.js +117 -12
- package/lib/middleware/body-parser.js +71 -25
- package/lib/middleware/csrf-protect.js +19 -8
- package/lib/object-store/azure-blob.js +28 -2
- package/lib/observability.js +87 -0
- package/lib/otel-export.js +25 -1
- package/lib/parsers/safe-xml.js +47 -7
- package/lib/queue-local.js +23 -1
- package/lib/queue.js +7 -0
- package/lib/redact.js +68 -11
- package/lib/redis-client.js +160 -31
- package/lib/request-helpers.js +7 -0
- package/lib/router.js +212 -5
- package/lib/ssrf-guard.js +51 -4
- package/lib/static.js +132 -27
- package/lib/vault/rotate.js +64 -44
- package/lib/websocket.js +19 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/compliance.js
CHANGED
|
@@ -50,6 +50,18 @@ var audit = lazyRequire(function () { return require("./audit"); });
|
|
|
50
50
|
var retentionMod = lazyRequire(function () { return require("./retention"); });
|
|
51
51
|
var db = lazyRequire(function () { return require("./db"); });
|
|
52
52
|
var cryptoField = lazyRequire(function () { return require("./crypto-field"); });
|
|
53
|
+
var redact = lazyRequire(function () { return require("./redact"); });
|
|
54
|
+
|
|
55
|
+
// Postures whose floor implies an outbound-DLP gate (b.redact's
|
|
56
|
+
// classifier presets cover exactly these regimes). Pinning one of these
|
|
57
|
+
// does NOT auto-install outbound DLP — the compliance coordinator holds
|
|
58
|
+
// no httpClient / mail / webhook handles — so set() emits a one-time
|
|
59
|
+
// `compliance.posture.outbound_dlp_unwired` warning when none is wired,
|
|
60
|
+
// so the gap is grep-able in the audit chain instead of a silent paper-
|
|
61
|
+
// compliance hole (CWE-200 / CWE-201 outbound data exposure).
|
|
62
|
+
var OUTBOUND_DLP_FLOOR_POSTURES = Object.freeze([
|
|
63
|
+
"hipaa", "pci-dss", "gdpr", "soc2", "fapi-2.0", "fapi-2.0-message-signing",
|
|
64
|
+
]);
|
|
53
65
|
|
|
54
66
|
// Recognised posture names. Aligns with the compliance-posture
|
|
55
67
|
// vocabulary every guard / retention floor / etc. accepts. Operators
|
|
@@ -445,6 +457,24 @@ function set(posture) {
|
|
|
445
457
|
"warning");
|
|
446
458
|
}
|
|
447
459
|
}
|
|
460
|
+
|
|
461
|
+
// Outbound-DLP wiring signal. A posture whose floor implies an
|
|
462
|
+
// outbound-DLP gate is being pinned, but set() cannot install the
|
|
463
|
+
// interceptors itself (no httpClient / mail / webhook handles). Warn
|
|
464
|
+
// once when nothing is wired so the gap is visible in the audit chain
|
|
465
|
+
// rather than a silent paper-compliance hole. Fires at most once per
|
|
466
|
+
// pin (set() is idempotent for the same posture).
|
|
467
|
+
if (OUTBOUND_DLP_FLOOR_POSTURES.indexOf(posture) !== -1) {
|
|
468
|
+
var dlpInstalled = false;
|
|
469
|
+
try { dlpInstalled = redact().isOutboundDlpInstalled() === true; }
|
|
470
|
+
catch (_e) { dlpInstalled = false; }
|
|
471
|
+
if (!dlpInstalled) {
|
|
472
|
+
_emitAudit("compliance.posture.outbound_dlp_unwired",
|
|
473
|
+
{ posture: posture,
|
|
474
|
+
recommendation: "compliance.set does not auto-install outbound DLP — it holds no httpClient / mail / webhook handles. Call b.redact.installForPosture('" + posture + "', { httpClient, mail, webhook }) with your primitive instances so outbound payloads are classified (CWE-200 / CWE-201)." },
|
|
475
|
+
"warning");
|
|
476
|
+
}
|
|
477
|
+
}
|
|
448
478
|
}
|
|
449
479
|
|
|
450
480
|
// _applyPostureCascade — walks every primitive that
|
|
@@ -948,6 +978,25 @@ function describe(posture) {
|
|
|
948
978
|
// + DPDP §12 + LGPD-BR Art. 18 + PIPL-CN
|
|
949
979
|
// Art. 47 all require effective erasure;
|
|
950
980
|
// leftover index residue defeats it.
|
|
981
|
+
// sealEnvelopeFloor — minimum field-level seal envelope a
|
|
982
|
+
// sealed-column table may declare under
|
|
983
|
+
// this posture: "plain" (vault.seal, no
|
|
984
|
+
// AAD), "aad" (AEAD-bound to table/row/
|
|
985
|
+
// column via b.vault.aad), or "per-row-key"
|
|
986
|
+
// (K_row crypto-shred). cryptoField.
|
|
987
|
+
// registerTable refuses a table whose
|
|
988
|
+
// declared envelope is below the floor when
|
|
989
|
+
// this posture is the globally-pinned one.
|
|
990
|
+
// PCI-DSS Req. 3.5/3.6 (PAN render
|
|
991
|
+
// unreadable, key-management binding) and
|
|
992
|
+
// HIPAA 45 CFR 164.312(a)(2)(iv) +
|
|
993
|
+
// 164.312(e)(2)(ii) (encryption that
|
|
994
|
+
// resists ciphertext relocation, CWE-311 /
|
|
995
|
+
// CWE-326) need an AAD-bound envelope at
|
|
996
|
+
// minimum so a DB-write attacker cannot
|
|
997
|
+
// copy a sealed cell between rows. Absent
|
|
998
|
+
// on a posture → no floor (back-compat;
|
|
999
|
+
// plain envelopes keep registering).
|
|
951
1000
|
//
|
|
952
1001
|
// This table is the single source-of-truth — duplicating values into
|
|
953
1002
|
// per-primitive defaults would drift the moment a regulator updates.
|
|
@@ -957,12 +1006,22 @@ var POSTURE_DEFAULTS = Object.freeze({
|
|
|
957
1006
|
auditChainSignedRequired: true,
|
|
958
1007
|
tlsMinVersion: "TLSv1.3",
|
|
959
1008
|
requireVacuumAfterErase: true,
|
|
1009
|
+
// 45 CFR 164.312(a)(2)(iv) + (e)(2)(ii) — ePHI encryption must
|
|
1010
|
+
// resist ciphertext relocation; a plain vault.seal cell can be
|
|
1011
|
+
// copied between rows undetected (CWE-311 / CWE-326). AAD-bound
|
|
1012
|
+
// envelope is the floor.
|
|
1013
|
+
sealEnvelopeFloor: "aad",
|
|
960
1014
|
}),
|
|
961
1015
|
"pci-dss": Object.freeze({
|
|
962
1016
|
backupEncryptionRequired: true,
|
|
963
1017
|
auditChainSignedRequired: true,
|
|
964
1018
|
tlsMinVersion: "TLSv1.3",
|
|
965
1019
|
requireVacuumAfterErase: false,
|
|
1020
|
+
// PCI-DSS v4 Req. 3.5 (PAN unreadable) + Req. 3.6 (key-management
|
|
1021
|
+
// binding) — the seal must bind cardholder data to its storage
|
|
1022
|
+
// location so a relocated ciphertext fails to verify. AAD-bound
|
|
1023
|
+
// envelope is the floor.
|
|
1024
|
+
sealEnvelopeFloor: "aad",
|
|
966
1025
|
}),
|
|
967
1026
|
"gdpr": Object.freeze({
|
|
968
1027
|
backupEncryptionRequired: false, // GDPR Art. 32 says "appropriate" — not mandatory floor
|
|
@@ -1357,10 +1416,13 @@ var POSTURE_DEFAULTS = Object.freeze({
|
|
|
1357
1416
|
* where `set()` would over-pin the process.
|
|
1358
1417
|
*
|
|
1359
1418
|
* Recognised keys per posture include `backupEncryptionRequired`,
|
|
1360
|
-
* `auditChainSignedRequired`, `tlsMinVersion`,
|
|
1361
|
-
* `requireVacuumAfterErase` — the floors
|
|
1362
|
-
* `b.audit`, the TLS minimum-version gate,
|
|
1363
|
-
* residual-erasure pass.
|
|
1419
|
+
* `auditChainSignedRequired`, `tlsMinVersion`,
|
|
1420
|
+
* `requireVacuumAfterErase`, and `sealEnvelopeFloor` — the floors
|
|
1421
|
+
* enforced by `b.backup`, `b.audit`, the TLS minimum-version gate,
|
|
1422
|
+
* `b.cryptoField`'s residual-erasure pass, and `b.cryptoField`'s
|
|
1423
|
+
* field-level seal-envelope gate. Keys not declared for a posture
|
|
1424
|
+
* return `null` (no floor), so reading `sealEnvelopeFloor` for a
|
|
1425
|
+
* posture that doesn't pin one is the back-compat no-op signal.
|
|
1364
1426
|
*
|
|
1365
1427
|
* @example
|
|
1366
1428
|
* b.compliance.postureDefault("hipaa", "tlsMinVersion");
|
|
@@ -1615,10 +1677,91 @@ function isCrossBorderRegulated(posture) {
|
|
|
1615
1677
|
return CROSS_BORDER_REGULATED_POSTURES.indexOf(posture) !== -1;
|
|
1616
1678
|
}
|
|
1617
1679
|
|
|
1680
|
+
// Region-tag wildcards. Both spellings mean "no residency constraint"
|
|
1681
|
+
// across the framework — the external-db gate uses "unrestricted" as
|
|
1682
|
+
// its default + wildcard, while the local db-query / external-db row
|
|
1683
|
+
// gates also accept "global" as the region-neutral row tag. Normalizing
|
|
1684
|
+
// folds both to "unrestricted" so callers reason about one wildcard.
|
|
1685
|
+
var _REGION_WILDCARDS = Object.freeze(["global", "unrestricted", "any", "*"]);
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* @primitive b.compliance.normalizeRegionTag
|
|
1689
|
+
* @signature b.compliance.normalizeRegionTag(tag)
|
|
1690
|
+
* @since 0.14.27
|
|
1691
|
+
* @compliance gdpr
|
|
1692
|
+
* @related b.compliance.isRegionCompatible, b.compliance.isCrossBorderRegulated
|
|
1693
|
+
*
|
|
1694
|
+
* Canonicalize an operator-supplied residency region tag so the same
|
|
1695
|
+
* region declared as `"EU"`, `"eu"`, or `" Eu "` compares equal. Lower-
|
|
1696
|
+
* cases and trims the tag; folds the no-constraint wildcards
|
|
1697
|
+
* (`"global"` / `"unrestricted"` / `"any"` / `"*"`) to `"unrestricted"`.
|
|
1698
|
+
* Returns `null` for non-string / empty input.
|
|
1699
|
+
*
|
|
1700
|
+
* This is an ADDITIVE helper composed OVER the residency write gates
|
|
1701
|
+
* (`b.db.from` local, `b.externalDb.query` backend/replica) — it does
|
|
1702
|
+
* not change the gate internals. Callers normalize their tags with it
|
|
1703
|
+
* BEFORE handing them to the gate so case / wildcard drift (`"EU"` vs
|
|
1704
|
+
* `"eu"` vs `"global"`) doesn't read as a region mismatch.
|
|
1705
|
+
*
|
|
1706
|
+
* @example
|
|
1707
|
+
* b.compliance.normalizeRegionTag("EU"); // → "eu"
|
|
1708
|
+
* b.compliance.normalizeRegionTag(" eu "); // → "eu"
|
|
1709
|
+
* b.compliance.normalizeRegionTag("global"); // → "unrestricted"
|
|
1710
|
+
* b.compliance.normalizeRegionTag("unrestricted"); // → "unrestricted"
|
|
1711
|
+
* b.compliance.normalizeRegionTag(null); // → null
|
|
1712
|
+
*/
|
|
1713
|
+
function normalizeRegionTag(tag) {
|
|
1714
|
+
if (typeof tag !== "string") return null;
|
|
1715
|
+
var t = tag.trim().toLowerCase();
|
|
1716
|
+
if (t.length === 0) return null;
|
|
1717
|
+
if (_REGION_WILDCARDS.indexOf(t) !== -1) return "unrestricted";
|
|
1718
|
+
return t;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* @primitive b.compliance.isRegionCompatible
|
|
1723
|
+
* @signature b.compliance.isRegionCompatible(a, b)
|
|
1724
|
+
* @since 0.14.27
|
|
1725
|
+
* @compliance gdpr
|
|
1726
|
+
* @related b.compliance.normalizeRegionTag, b.compliance.isCrossBorderRegulated
|
|
1727
|
+
*
|
|
1728
|
+
* Returns `true` when two residency region tags are compatible for a
|
|
1729
|
+
* same-region write/replication after normalization: identical
|
|
1730
|
+
* normalized regions are compatible, and a wildcard (`"global"` /
|
|
1731
|
+
* `"unrestricted"`) on EITHER side is compatible. Different concrete
|
|
1732
|
+
* regions (`"eu"` vs `"us"`) are NOT compatible — a cross-border
|
|
1733
|
+
* transfer the operator must opt into explicitly at the gate.
|
|
1734
|
+
*
|
|
1735
|
+
* Mirrors the residency gate's compatibility rule (identical-or-
|
|
1736
|
+
* wildcard) but over NORMALIZED tags, so it is case- and wildcard-drift
|
|
1737
|
+
* insensitive. ADDITIVE helper composed over the gate — it does not
|
|
1738
|
+
* change `_residencyCompatible` in db-query.js / external-db.js.
|
|
1739
|
+
* Missing/non-string tags on either side normalize to `null`, treated
|
|
1740
|
+
* as "no constraint" → compatible (matches the gate's
|
|
1741
|
+
* `!primaryTag || !replicaTag` short-circuit).
|
|
1742
|
+
*
|
|
1743
|
+
* @example
|
|
1744
|
+
* b.compliance.isRegionCompatible("EU", "eu"); // → true
|
|
1745
|
+
* b.compliance.isRegionCompatible("eu", "global"); // → true
|
|
1746
|
+
* b.compliance.isRegionCompatible("unrestricted", "us"); // → true
|
|
1747
|
+
* b.compliance.isRegionCompatible("eu", "us"); // → false
|
|
1748
|
+
* b.compliance.isRegionCompatible("EU", null); // → true
|
|
1749
|
+
*/
|
|
1750
|
+
function isRegionCompatible(a, b) {
|
|
1751
|
+
var na = normalizeRegionTag(a);
|
|
1752
|
+
var nb = normalizeRegionTag(b);
|
|
1753
|
+
if (na === null || nb === null) return true; // no constraint either side
|
|
1754
|
+
if (na === nb) return true; // identical region (post-normalize)
|
|
1755
|
+
if (na === "unrestricted" || nb === "unrestricted") return true; // wildcard either side
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1618
1759
|
module.exports = {
|
|
1619
1760
|
set: set,
|
|
1620
1761
|
current: current,
|
|
1621
1762
|
isCrossBorderRegulated: isCrossBorderRegulated,
|
|
1763
|
+
normalizeRegionTag: normalizeRegionTag,
|
|
1764
|
+
isRegionCompatible: isRegionCompatible,
|
|
1622
1765
|
CROSS_BORDER_REGULATED_POSTURES: CROSS_BORDER_REGULATED_POSTURES,
|
|
1623
1766
|
assert: assert,
|
|
1624
1767
|
clear: clear,
|
package/lib/crypto-field.js
CHANGED
|
@@ -155,6 +155,25 @@ var perRowResidency = Object.create(null);
|
|
|
155
155
|
// { tableName: { keySize, info } }
|
|
156
156
|
var perRowKeyTables = Object.create(null);
|
|
157
157
|
|
|
158
|
+
// Seal-envelope strength ranking. A regulated posture can declare a
|
|
159
|
+
// sealEnvelopeFloor in b.compliance POSTURE_DEFAULTS; registerTable
|
|
160
|
+
// refuses a table that seals columns under a weaker envelope than the
|
|
161
|
+
// floor when that posture is the globally-pinned one. Higher rank =
|
|
162
|
+
// stronger binding:
|
|
163
|
+
// plain — vault.seal: XChaCha20-Poly1305 under the vault root,
|
|
164
|
+
// no AAD; a DB-write attacker can copy a cell to another
|
|
165
|
+
// row undetected (CWE-311 / CWE-326).
|
|
166
|
+
// aad — vault.aad.seal: AEAD-bound to (table,row,column,
|
|
167
|
+
// schemaVersion); a relocated cell fails Poly1305.
|
|
168
|
+
// per-row-key — K_row crypto-shred: aad binding PLUS a per-row key,
|
|
169
|
+
// so destroying the row-secret renders residue
|
|
170
|
+
// mathematically undecryptable.
|
|
171
|
+
var SEAL_ENVELOPE_RANK = Object.freeze({
|
|
172
|
+
"plain": 0,
|
|
173
|
+
"aad": 1,
|
|
174
|
+
"per-row-key": 2,
|
|
175
|
+
});
|
|
176
|
+
|
|
158
177
|
// The framework registry table that holds each row's AAD-sealed
|
|
159
178
|
// row-secret. Named once so the seal-side AAD (materializePerRowKey),
|
|
160
179
|
// the read-side AAD (unsealRow's K_row fetch), and rotate's reseal all
|
|
@@ -232,6 +251,14 @@ function isRowSealed(value) {
|
|
|
232
251
|
* hash namespaces. Subsequent `sealRow` / `unsealRow` / `eraseRow`
|
|
233
252
|
* calls dispatch through this registry.
|
|
234
253
|
*
|
|
254
|
+
* Seal-envelope floor: when a compliance posture that declares a
|
|
255
|
+
* `sealEnvelopeFloor` is globally pinned (`b.compliance.set` — today
|
|
256
|
+
* `hipaa` / `pci-dss` require at least an AAD-bound envelope), a table
|
|
257
|
+
* that seals columns under a weaker envelope throws
|
|
258
|
+
* `crypto-field/seal-envelope-below-floor` here at registration so the
|
|
259
|
+
* operator catches the under-protected schema at boot. Unpinned and
|
|
260
|
+
* non-regulated deployments register unchanged.
|
|
261
|
+
*
|
|
235
262
|
* @opts
|
|
236
263
|
* sealedFields: string[], // column names sealed via vault.seal
|
|
237
264
|
* derivedHashes: { [hashCol]: { from: string, normalize?: fn } },
|
|
@@ -289,8 +316,25 @@ function registerTable(name, opts) {
|
|
|
289
316
|
"'salted-sha3' or 'hmac-shake256', got " + JSON.stringify(colMode));
|
|
290
317
|
}
|
|
291
318
|
}
|
|
319
|
+
var sealedFields = Array.isArray(opts.sealedFields) ? opts.sealedFields.slice() : [];
|
|
320
|
+
// Seal-envelope floor gate. Only fires when ALL hold:
|
|
321
|
+
// (1) a posture is globally pinned (b.compliance.set) — read via
|
|
322
|
+
// compliance().current(), the same source the residency write
|
|
323
|
+
// gates read; an UNPINNED deployment is untouched (back-compat),
|
|
324
|
+
// (2) that posture declares a sealEnvelopeFloor in POSTURE_DEFAULTS
|
|
325
|
+
// (only regulated regimes do — hipaa / pci-dss), and
|
|
326
|
+
// (3) the table actually seals columns under an envelope WEAKER than
|
|
327
|
+
// the floor.
|
|
328
|
+
// A non-sealing table, an unpinned deployment, or a posture without a
|
|
329
|
+
// floor all pass through exactly as before. Config-time / entry-point
|
|
330
|
+
// tier: THROW so the operator catches the under-protected schema at
|
|
331
|
+
// boot rather than shipping PHI/PCI under a relocatable plain seal
|
|
332
|
+
// (CWE-311 / CWE-326).
|
|
333
|
+
if (sealedFields.length > 0) {
|
|
334
|
+
_assertSealEnvelopeFloor(name, aadOn);
|
|
335
|
+
}
|
|
292
336
|
schemas[name] = {
|
|
293
|
-
sealedFields:
|
|
337
|
+
sealedFields: sealedFields,
|
|
294
338
|
derivedHashes: derivedHashes,
|
|
295
339
|
hashNamespaces: Object.assign({}, opts.hashNamespaces || {}),
|
|
296
340
|
aad: aadOn,
|
|
@@ -300,6 +344,48 @@ function registerTable(name, opts) {
|
|
|
300
344
|
};
|
|
301
345
|
}
|
|
302
346
|
|
|
347
|
+
// _assertSealEnvelopeFloor — config-time guard for registerTable. Reads
|
|
348
|
+
// the globally-pinned posture (compliance().current()) and its declared
|
|
349
|
+
// sealEnvelopeFloor; throws when `table` seals columns under a weaker
|
|
350
|
+
// envelope. No-op when no posture is pinned, the posture declares no
|
|
351
|
+
// floor, or compliance isn't loaded — so unpinned/non-regulated
|
|
352
|
+
// deployments register exactly as before.
|
|
353
|
+
function _assertSealEnvelopeFloor(table, aadOn) {
|
|
354
|
+
var posture;
|
|
355
|
+
var floor;
|
|
356
|
+
try {
|
|
357
|
+
var c = compliance();
|
|
358
|
+
posture = c.current();
|
|
359
|
+
if (typeof posture !== "string" || posture.length === 0) return;
|
|
360
|
+
floor = c.postureDefault(posture, "sealEnvelopeFloor");
|
|
361
|
+
} catch (_e) {
|
|
362
|
+
// compliance not loaded / unavailable — record nothing, gate nothing.
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (typeof floor !== "string" || !Object.prototype.hasOwnProperty.call(SEAL_ENVELOPE_RANK, floor)) {
|
|
366
|
+
return; // posture pins no recognised floor → back-compat pass-through
|
|
367
|
+
}
|
|
368
|
+
// Declared envelope for this table: per-row-key beats aad beats plain.
|
|
369
|
+
// declarePerRowKey may run before or after registerTable; honour it
|
|
370
|
+
// when it ran first.
|
|
371
|
+
var declared = perRowKeyTables[table] ? "per-row-key" : (aadOn ? "aad" : "plain");
|
|
372
|
+
if (SEAL_ENVELOPE_RANK[declared] < SEAL_ENVELOPE_RANK[floor]) {
|
|
373
|
+
throw new CryptoFieldError("crypto-field/seal-envelope-below-floor",
|
|
374
|
+
"registerTable: table '" + table + "' seals columns under the '" +
|
|
375
|
+
declared + "' envelope, but the pinned compliance posture '" +
|
|
376
|
+
posture + "' requires at least '" + floor + "'. " +
|
|
377
|
+
(floor === "aad"
|
|
378
|
+
? "Pass registerTable({ aad: true, rowIdField: <pk> }) so each " +
|
|
379
|
+
"cell is AEAD-bound to (table, row, column) and cannot be " +
|
|
380
|
+
"relocated between rows"
|
|
381
|
+
: "Call b.cryptoField.declarePerRowKey('" + table + "', ...) " +
|
|
382
|
+
"before registerTable so each row gets a crypto-shred K_row") +
|
|
383
|
+
" (CWE-311 / CWE-326). Unpinned or non-regulated deployments are " +
|
|
384
|
+
"unaffected; this gate fires only under a posture that declares a " +
|
|
385
|
+
"sealEnvelopeFloor.");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
303
389
|
// Derived-hash digest width for the keyed (hmac-shake256) mode: 32
|
|
304
390
|
// bytes -> 64 hex chars.
|
|
305
391
|
var DERIVED_HASH_BYTES = 32;
|