@blamejs/core 0.12.35 → 0.12.37
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/README.md +2 -1
- package/index.js +1 -0
- package/lib/cose.js +212 -4
- package/lib/scitt.js +243 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.37 (2026-05-24) — **`b.scitt.signStatement` / `b.scitt.verifyStatement` — SCITT signed statements over COSE (RFC 9052 + RFC 9597).** A SCITT signed statement is a signed, attributable claim about an artifact — a signed SBOM, a build attestation, a release approval. It is a COSE_Sign1 (b.cose) whose integrity-protected CWT_Claims header (label 15, RFC 9597) binds the issuer (who makes the statement) and the subject (the artifact it is about); the artifact, or a hash/reference to it, is the payload. signStatement places iss/sub in the protected header and declares the payload media type; verifyStatement checks the COSE signature (the algorithm allowlist is mandatory) and refuses any statement that lacks the iss/sub binding, with optional expected-issuer/subject matching. Signing uses the same algorithms as b.cose — classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) plus ML-DSA-87 (PQC-forward). This is the issuer side of SCITT, buildable today on finalized RFCs; the transparency receipt (an inclusion proof from a transparency service, the COSE Receipts draft) is not yet shipped — a statement produced here is the input a transparency service registers, and the receipt format is the part still in flux. It opts in when COSE Receipts publishes. **Added:** *`b.scitt.signStatement(payload, opts)` / `b.scitt.verifyStatement(statement, opts)`* — `signStatement` produces a COSE_Sign1 whose protected CWT_Claims header (label 15) carries `iss` (`opts.issuer`) and `sub` (`opts.subject`), with the payload media type declared via `opts.contentType` and extra CWT claims allowed by integer label (iss/sub cannot be overridden through `opts.claims`). `verifyStatement` verifies the signature through `b.cose.verify` (passing `opts.algorithms` as the mandatory allowlist), then requires a CWT_Claims header with both `iss` and `sub` — a bare COSE_Sign1 with no such binding is refused with `scitt/missing-cwt-claims` — and enforces `expectedIssuer` / `expectedSubject` when given. Returns `{ payload, issuer, subject, cwtClaims, alg, protectedHeaders, unprotectedHeaders }`. Because the identity binding lives in the integrity-protected header it is covered by the signature and cannot be substituted without detection. **Changed:** *`b.cose.sign` accepts `protectedHeaders` and a media-type-string `contentType`* — `opts.protectedHeaders` (a numeric-keyed object or Map) adds extra integrity-protected header parameters — the CWT_Claims map (label 15) is the SCITT case. Label 1 (alg) is reserved and managed via `opts.alg`; setting it through `protectedHeaders` is refused with `cose/reserved-header`. `opts.contentType` now accepts a media-type string (RFC 9052 §3.1 tstr form, e.g. `"application/spdx+json"`) in addition to a CoAP Content-Format uint; a string was previously dropped.
|
|
12
|
+
|
|
13
|
+
- v0.12.36 (2026-05-24) — **`b.cose.encrypt0` / `b.cose.decrypt0` — COSE_Encrypt0 single-recipient AEAD (RFC 9052 §5.2).** Completes the COSE family with encryption alongside the v0.12.33 signing: COSE_Encrypt0 is the single-recipient AEAD container where the recipient already holds the symmetric key (direct mode). The default algorithm is ChaCha20/Poly1305 (COSE alg 24) — AES-GCM stays opt-in, since hard-rule #2 forbids AES-GCM as a default. The Enc_structure (`["Encrypt0", protected, external_aad]`) is bound as the AEAD associated data so the algorithm + any external context are authenticated, and the authentication tag is appended to the ciphertext per COSE. Composes the in-tree `b.cbor` codec and `node:crypto` AEAD. **Added:** *`b.cose.encrypt0(plaintext, opts)` / `b.cose.decrypt0(coseEncrypt0, opts)`* — `encrypt0` produces a tagged COSE_Encrypt0 with `alg` in the protected header and a random 12-byte IV in the unprotected header (label 5); `alg` is `"ChaCha20-Poly1305"` (default), `"A256GCM"`, or `"A128GCM"`, with the key length enforced (32 / 16 bytes). `decrypt0` reads the algorithm from the protected header (must be in the required `opts.algorithms` allowlist), reconstructs the Enc_structure as the AEAD AAD, and returns `{ plaintext, alg, protectedHeaders, unprotectedHeaders }`; a wrong key, tampered ciphertext, or `external_aad` mismatch fails AEAD authentication and is refused with `cose/decrypt-failed`. `external_aad` binds request context into the tag.
|
|
14
|
+
|
|
11
15
|
- v0.12.35 (2026-05-24) — **`b.eat` — Entity Attestation Token (RFC 9711) over `b.cwt`.** An EAT is the token a Relying Party asks a device or software entity to produce to prove what it is and what state it is in — a freshness nonce, a Universal Entity ID, OEM / hardware identifiers, debug status, software measurements, and nested submodule attestations. `b.eat` is the RFC 9711 profile over the v0.12.34 `b.cwt`: it maps the EAT claim names to their IANA CWT claim-key integer labels and adds the attestation-specific verification on top of the CWT signature + time checks. The central control is the verifier-nonce binding: when the Relying Party supplies a fresh `expectedNonce`, the token's `eat_nonce` (claim 10) must match it (constant-time compare) — without it a captured attestation replays forever. `verify` also enforces a debug-status policy (`requireDebugDisabled` refuses an `enabled` or absent `dbgstat`) and pins the `eat_profile`. RFC 9711 is a finalized standard; signing follows `b.cwt` / `b.cose` (ES256/384/512 + EdDSA interoperable today, ML-DSA-87 PQC-forward). **Added:** *`b.eat.sign(claims, opts)` / `b.eat.verify(eat, opts)`* — `sign` maps EAT claim names (`nonce`, `ueid`, `oemid`, `hwmodel`, `dbgstat`, `eat_profile`, `swname`/`swversion`, `measurements`, `submods`, …) to their RFC 9711 integer labels and accepts the `dbgstat` enum by name (`disabled-since-boot` → 2); standard CWT claims (`iss` / `exp` / …) pass through. `verify` returns `{ claims, raw, alg, protectedHeaders }` with the labels mapped back to friendly names and `dbgstat` decoded to its enum name. Attestation enforcement: `expectedNonce` requires a matching `eat_nonce` (refused `eat/nonce-mismatch`, missing `eat/nonce-missing` — `eat_nonce` may be a single byte string or an array for multiple verifiers), `requireDebugDisabled` refuses a non-disabled `dbgstat` (`eat/debug-not-disabled`), and `expectedProfile` pins `eat_profile`. The signature, algorithm allowlist, and `exp`/`nbf` checks delegate to `b.cwt` / `b.cose`. · *`b.cwt.sign` accepts a `Map`* — `b.cwt.sign` now takes either a plain object (string keys, standard claims mapped by name) or a `Map`, which preserves integer claim keys verbatim — profiles like `b.eat` resolve their claim names to integer labels and pass them through without the keys being stringified. The plain-object path is unchanged.
|
|
12
16
|
|
|
13
17
|
- v0.12.34 (2026-05-24) — **`b.cwt` — CBOR Web Token (RFC 8392) sign / verify over `b.cose`.** A CWT is the CBOR-native counterpart to JWT — a signed claims set for constrained / IoT, FIDO attestation, and verifiable-credential contexts. `b.cwt` composes the v0.12.33 `b.cose` (COSE_Sign1 signature + mandatory algorithm allowlist) and v0.12.32 `b.cbor` (deterministic claims encoding) and layers the standard-claim handling on top: `sign` takes a friendly claims object, maps the standard claims to their RFC 8392 §3.1.1 integer labels (iss=1, sub=2, aud=3, exp=4, nbf=5, iat=6, cti=7), and signs; `verify` checks the COSE signature, decodes the claims, and enforces the time + identity claims — a passed `exp` (with clock-skew tolerance), a future `nbf`, and an `iss` / `aud` mismatch against the expected values are each refused. Signing algorithms follow `b.cose`: classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) and ML-DSA-87 (PQC-forward). RFC 8392 is a finalized standard, so CWTs produced here interoperate with other COSE/CWT implementations. **Added:** *`b.cwt.sign(claims, opts)` / `b.cwt.verify(cwt, opts)`* — `sign` maps standard claim names to integer labels and keeps custom claims verbatim; `exp` / `nbf` / `iat` must be non-negative integer NumericDates. `opts.tagged` wraps the COSE_Sign1 in the CWT CBOR tag 61 (RFC 8392 §6); `verify` accepts tagged or bare input. `verify` returns `{ claims, raw, alg, protectedHeaders }` — `claims` is the friendly object (labels mapped back to names), `raw` the integer-keyed Map. Standard-claim enforcement: `exp` past `now + clockSkewSec` (default 60s) is refused with `cwt/expired`, `nbf` beyond `now - skew` with `cwt/not-yet-valid`, and `expectedIssuer` / `expectedAudience` mismatches with `cwt/issuer-mismatch` / `cwt/audience-mismatch` (aud may be a single value or an array). `opts.now` overrides the clock for testing. The signature itself is verified by `b.cose.verify`, so a tampered token fails there.
|
package/README.md
CHANGED
|
@@ -126,9 +126,10 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
126
126
|
- **JSON / SQL / schema** — `b.safeJson` (with `maxKeys` cap defending CVE-2026-21717 V8 HashDoS), `b.safeBuffer`, `b.safeSql`, `b.safeSchema`
|
|
127
127
|
- **URL + path** — `b.safeUrl` (IDN mixed-script / homograph refuse); `b.safeJsonPath` (refuses filter `?(...)`, deep-scan `$..`, script-shape `(@.x)` for safe Postgres JSONB ops)
|
|
128
128
|
- **Binary codec** — `b.cbor` bounded deterministic CBOR (RFC 8949 §4.2): depth/size caps, indefinite-length + reserved-info + tag + duplicate-key refusal, `requireDeterministic` canonical-form check; the in-tree substrate under COSE / CWT / SCITT / WebAuthn attestation
|
|
129
|
-
- **COSE signing** — `b.cose` COSE_Sign1 sign/verify (RFC 9052) over `b.cbor`: classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) plus ML-DSA-87 (PQC-forward, draft id); bounded + alg-allowlisted + crit-bypass-checked verification; the signed-statement substrate under SCITT / CWT / C2PA
|
|
129
|
+
- **COSE signing + encryption** — `b.cose` COSE_Sign1 sign/verify + COSE_Encrypt0 (RFC 9052) over `b.cbor`: classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) plus ML-DSA-87 (PQC-forward, draft id); bounded + alg-allowlisted + crit-bypass-checked verification; single-recipient AEAD (ChaCha20/Poly1305 default, AES-GCM opt-in) with Enc_structure-bound AAD; the signed-statement substrate under SCITT / CWT / C2PA
|
|
130
130
|
- **CBOR Web Token** — `b.cwt` CWT sign/verify (RFC 8392) over `b.cose`: standard-claim mapping (iss/sub/aud/exp/nbf/iat/cti) + `exp`/`nbf` clock-skew enforcement + `iss`/`aud` matching; the CBOR-native JWT for constrained / IoT / FIDO / verifiable-credential contexts
|
|
131
131
|
- **Entity Attestation Token** — `b.eat` EAT sign/verify (RFC 9711) over `b.cwt`: device + software attestation claims (ueid / oemid / hwmodel / measurements / submods) with verifier-nonce freshness binding, `dbgstat` debug-status policy, and `eat_profile` pinning
|
|
132
|
+
- **SCITT signed statements** — `b.scitt` sign/verify a signed, attributable claim about an artifact (signed SBOM, build attestation, release approval) over `b.cose`: the issuer + subject bind in the integrity-protected CWT_Claims header (RFC 9597); verification refuses any statement missing the iss/sub binding. The issuer side, on finalized RFCs; the transparency receipt (COSE Receipts draft) opts in on publication
|
|
132
133
|
- **Document parsers** — `b.parsers` (XML / TOML / YAML / .env); `b.config` (schema-validated env)
|
|
133
134
|
- **File-type detection** — `b.fileType` magic-byte content classification with deny-on-upload categories (image / document / archive / executable / etc.)
|
|
134
135
|
### Content-safety gates
|
package/index.js
CHANGED
package/lib/cose.js
CHANGED
|
@@ -63,6 +63,7 @@ var HDR_ALG = 1;
|
|
|
63
63
|
var HDR_CRIT = 2; // header label: crit
|
|
64
64
|
var HDR_CONTENT_TYPE = 3; // header label: content type
|
|
65
65
|
var HDR_KID = 4; // header label: kid
|
|
66
|
+
var HDR_CWT_CLAIMS = 15; // allow:raw-byte-literal — RFC 9597 CWT Claims header label (carries SCITT iss/sub)
|
|
66
67
|
|
|
67
68
|
// COSE algorithm identifiers. ML-DSA-87 is a NON-FINAL requested
|
|
68
69
|
// assignment (draft-ietf-cose-dilithium) — pinned deliberately, re-open
|
|
@@ -83,7 +84,7 @@ var SIGNABLE = ["ML-DSA-87", "ES256", "ES384", "ES512", "EdDSA"];
|
|
|
83
84
|
|
|
84
85
|
// Header labels this verifier understands — a `crit` entry naming any
|
|
85
86
|
// other label is refused (RFC 9052 §3.1 crit-bypass defense).
|
|
86
|
-
var UNDERSTOOD_LABELS = [HDR_ALG, HDR_CRIT, HDR_CONTENT_TYPE, HDR_KID];
|
|
87
|
+
var UNDERSTOOD_LABELS = [HDR_ALG, HDR_CRIT, HDR_CONTENT_TYPE, HDR_KID, HDR_CWT_CLAIMS];
|
|
87
88
|
|
|
88
89
|
function _toKeyObject(key, kind) {
|
|
89
90
|
if (key && typeof key === "object" && typeof key.asymmetricKeyType === "string") return key;
|
|
@@ -138,9 +139,10 @@ function _toBeSigned(protectedBstr, externalAad, payload) {
|
|
|
138
139
|
* alg: string, // "ES256" | "ES384" | "ES512" | "EdDSA" | "ML-DSA-87"
|
|
139
140
|
* privateKey: object, // matching KeyObject or PEM
|
|
140
141
|
* kid?: string, // → unprotected header label 4
|
|
141
|
-
* contentType?: number,
|
|
142
|
+
* contentType?: number|string, // → protected header label 3 (CoAP Content-Format uint or media-type string)
|
|
142
143
|
* externalAad?: Buffer, // default empty — bound into the signature
|
|
143
144
|
* unprotectedHeaders?: object, // extra unprotected map entries (numeric keys)
|
|
145
|
+
* protectedHeaders?: object, // extra INTEGRITY-PROTECTED map entries (numeric keys); label 1 (alg) is reserved
|
|
144
146
|
* }
|
|
145
147
|
*
|
|
146
148
|
* @example
|
|
@@ -150,7 +152,7 @@ function _toBeSigned(protectedBstr, externalAad, payload) {
|
|
|
150
152
|
*/
|
|
151
153
|
async function sign(payload, opts) {
|
|
152
154
|
validateOpts.requireObject(opts, "cose.sign", CoseError);
|
|
153
|
-
validateOpts(opts, ["alg", "privateKey", "kid", "contentType", "externalAad", "unprotectedHeaders"], "cose.sign");
|
|
155
|
+
validateOpts(opts, ["alg", "privateKey", "kid", "contentType", "externalAad", "unprotectedHeaders", "protectedHeaders"], "cose.sign");
|
|
154
156
|
if (SIGNABLE.indexOf(opts.alg) === -1) {
|
|
155
157
|
throw new CoseError("cose/unsignable-alg",
|
|
156
158
|
"cose.sign: alg must be one of " + SIGNABLE.join(" / ") +
|
|
@@ -165,7 +167,31 @@ async function sign(payload, opts) {
|
|
|
165
167
|
|
|
166
168
|
var protMap = new Map();
|
|
167
169
|
protMap.set(HDR_ALG, algId);
|
|
168
|
-
|
|
170
|
+
// Content type (RFC 9052 §3.1): a uint (CoAP Content-Format) or a
|
|
171
|
+
// media-type string (tstr) — a SCITT signed statement declares its
|
|
172
|
+
// payload media type as a string here.
|
|
173
|
+
if (typeof opts.contentType === "number" || typeof opts.contentType === "string") {
|
|
174
|
+
protMap.set(HDR_CONTENT_TYPE, opts.contentType);
|
|
175
|
+
}
|
|
176
|
+
// Extra integrity-protected headers (e.g. CWT_Claims label 15 for a
|
|
177
|
+
// SCITT signed statement). alg (label 1) is managed via opts.alg and
|
|
178
|
+
// cannot be overridden here — a caller that needs a different alg
|
|
179
|
+
// names it in opts.alg.
|
|
180
|
+
if (opts.protectedHeaders && typeof opts.protectedHeaders === "object") {
|
|
181
|
+
var pk = opts.protectedHeaders instanceof Map
|
|
182
|
+
? Array.from(opts.protectedHeaders.keys())
|
|
183
|
+
: Object.keys(opts.protectedHeaders);
|
|
184
|
+
for (var pi = 0; pi < pk.length; pi++) {
|
|
185
|
+
var plabel = Number(pk[pi]);
|
|
186
|
+
if (plabel === HDR_ALG) {
|
|
187
|
+
throw new CoseError("cose/reserved-header",
|
|
188
|
+
"cose.sign: protectedHeaders must not set label 1 (alg) — pass opts.alg instead");
|
|
189
|
+
}
|
|
190
|
+
var pval = opts.protectedHeaders instanceof Map
|
|
191
|
+
? opts.protectedHeaders.get(pk[pi]) : opts.protectedHeaders[pk[pi]];
|
|
192
|
+
protMap.set(plabel, pval);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
169
195
|
var protectedBstr = cbor.encode(protMap);
|
|
170
196
|
|
|
171
197
|
var unprot = new Map();
|
|
@@ -330,10 +356,192 @@ async function verify(coseSign1, opts) {
|
|
|
330
356
|
};
|
|
331
357
|
}
|
|
332
358
|
|
|
359
|
+
// ---- COSE_Encrypt0 (RFC 9052 §5.2) — single-recipient AEAD ----
|
|
360
|
+
|
|
361
|
+
var COSE_ENCRYPT0_TAG = 16; // allow:raw-byte-literal — RFC 9052 COSE_Encrypt0 CBOR tag
|
|
362
|
+
var HDR_IV = 5; // RFC 9052 §3.1 unprotected header label: IV
|
|
363
|
+
var AEAD_TAG_LEN = 16; // allow:raw-byte-literal — AEAD authentication tag length (bytes)
|
|
364
|
+
|
|
365
|
+
// AEAD algorithm: COSE id → node cipher + key / IV sizes. ChaCha20/
|
|
366
|
+
// Poly1305 (24) is the default; AES-GCM is opt-in (project hard-rule
|
|
367
|
+
// #2 forbids AES-GCM as a default).
|
|
368
|
+
var AEAD_NAME_TO_ID = { "ChaCha20-Poly1305": 24, "A256GCM": 3, "A128GCM": 1 }; // allow:raw-byte-literal — COSE AEAD algorithm identifiers (RFC 9053), not sizes
|
|
369
|
+
var AEAD_ID_TO_NAME = {};
|
|
370
|
+
Object.keys(AEAD_NAME_TO_ID).forEach(function (k) { AEAD_ID_TO_NAME[AEAD_NAME_TO_ID[k]] = k; });
|
|
371
|
+
|
|
372
|
+
function _aeadParams(algId) {
|
|
373
|
+
switch (algId) {
|
|
374
|
+
case 24: return { cipher: "chacha20-poly1305", keyLen: 32, ivLen: 12 }; // allow:raw-byte-literal — ChaCha20/Poly1305 key+IV sizes
|
|
375
|
+
case 3: return { cipher: "aes-256-gcm", keyLen: 32, ivLen: 12 }; // allow:raw-byte-literal — AES-256-GCM key+IV sizes
|
|
376
|
+
case 1: return { cipher: "aes-128-gcm", keyLen: 16, ivLen: 12 }; // allow:raw-byte-literal — AES-128-GCM key+IV sizes
|
|
377
|
+
default:
|
|
378
|
+
throw new CoseError("cose/unknown-alg", "cose: unrecognized AEAD COSE alg id " + algId);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Enc_structure (§5.3) = [ "Encrypt0", body_protected (bstr), external_aad (bstr) ]
|
|
383
|
+
// — deterministically CBOR-encoded, used as the AEAD associated data.
|
|
384
|
+
function _encStructure(protectedBstr, externalAad) {
|
|
385
|
+
return cbor.encode(["Encrypt0", protectedBstr, externalAad]);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* @primitive b.cose.encrypt0
|
|
390
|
+
* @signature b.cose.encrypt0(plaintext, opts)
|
|
391
|
+
* @since 0.12.36
|
|
392
|
+
* @status stable
|
|
393
|
+
* @related b.cose.decrypt0, b.cose.sign
|
|
394
|
+
*
|
|
395
|
+
* Encrypt bytes into a tagged COSE_Encrypt0 (RFC 9052 §5.2), a
|
|
396
|
+
* single-recipient AEAD container where the recipient already holds
|
|
397
|
+
* the symmetric key (direct mode). Default algorithm is
|
|
398
|
+
* <code>ChaCha20-Poly1305</code>; <code>A256GCM</code> / <code>A128GCM</code>
|
|
399
|
+
* are opt-in. The Enc_structure is bound as the AEAD associated data,
|
|
400
|
+
* and the authentication tag is appended to the ciphertext per COSE.
|
|
401
|
+
*
|
|
402
|
+
* @opts
|
|
403
|
+
* {
|
|
404
|
+
* alg: string, // "ChaCha20-Poly1305" (default) | "A256GCM" | "A128GCM"
|
|
405
|
+
* key: Buffer, // symmetric key (32 bytes for ChaCha/A256GCM, 16 for A128GCM)
|
|
406
|
+
* iv?: Buffer, // 12-byte IV (random if omitted)
|
|
407
|
+
* externalAad?: Buffer, // bound into the AEAD tag
|
|
408
|
+
* unprotectedHeaders?: object,
|
|
409
|
+
* }
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* var enc = b.cose.encrypt0(Buffer.from("secret"), { alg: "ChaCha20-Poly1305", key: k });
|
|
413
|
+
*/
|
|
414
|
+
function encrypt0(plaintext, opts) {
|
|
415
|
+
validateOpts.requireObject(opts, "cose.encrypt0", CoseError);
|
|
416
|
+
validateOpts(opts, ["alg", "key", "iv", "externalAad", "unprotectedHeaders"], "cose.encrypt0");
|
|
417
|
+
var alg = opts.alg || "ChaCha20-Poly1305";
|
|
418
|
+
if (!(alg in AEAD_NAME_TO_ID)) {
|
|
419
|
+
throw new CoseError("cose/unknown-alg", "cose.encrypt0: alg must be one of " + Object.keys(AEAD_NAME_TO_ID).join(" / "));
|
|
420
|
+
}
|
|
421
|
+
var algId = AEAD_NAME_TO_ID[alg];
|
|
422
|
+
var p = _aeadParams(algId);
|
|
423
|
+
var key = _bstr(opts.key);
|
|
424
|
+
if (key.length !== p.keyLen) throw new CoseError("cose/bad-key", "cose.encrypt0: " + alg + " requires a " + p.keyLen + "-byte key");
|
|
425
|
+
var iv = opts.iv != null ? _bstr(opts.iv) : nodeCrypto.randomBytes(p.ivLen);
|
|
426
|
+
if (iv.length !== p.ivLen) throw new CoseError("cose/bad-iv", "cose.encrypt0: " + alg + " requires a " + p.ivLen + "-byte IV");
|
|
427
|
+
|
|
428
|
+
var protMap = new Map(); protMap.set(HDR_ALG, algId);
|
|
429
|
+
var protectedBstr = cbor.encode(protMap);
|
|
430
|
+
var aad = _encStructure(protectedBstr, opts.externalAad == null ? Buffer.alloc(0) : _bstr(opts.externalAad));
|
|
431
|
+
|
|
432
|
+
var cipher = nodeCrypto.createCipheriv(p.cipher, key, iv, { authTagLength: AEAD_TAG_LEN });
|
|
433
|
+
cipher.setAAD(aad);
|
|
434
|
+
var ct = Buffer.concat([cipher.update(_bstr(plaintext)), cipher.final()]);
|
|
435
|
+
var ciphertext = Buffer.concat([ct, cipher.getAuthTag()]); // COSE appends the auth tag to the ciphertext
|
|
436
|
+
|
|
437
|
+
var unprot = new Map(); unprot.set(HDR_IV, iv);
|
|
438
|
+
if (opts.unprotectedHeaders && typeof opts.unprotectedHeaders === "object") {
|
|
439
|
+
var uk = Object.keys(opts.unprotectedHeaders);
|
|
440
|
+
for (var i = 0; i < uk.length; i++) {
|
|
441
|
+
var label = Number(uk[i]);
|
|
442
|
+
// The IV (label 5) is managed via opts.iv and must match the IV
|
|
443
|
+
// the AEAD used — refuse an override that would emit a token whose
|
|
444
|
+
// stored IV disagrees with the one it was encrypted under.
|
|
445
|
+
if (label === HDR_IV) {
|
|
446
|
+
throw new CoseError("cose/reserved-header",
|
|
447
|
+
"cose.encrypt0: unprotectedHeaders must not set label 5 (IV) — pass opts.iv instead");
|
|
448
|
+
}
|
|
449
|
+
unprot.set(label, opts.unprotectedHeaders[uk[i]]);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return cbor.encode(new cbor.Tag(COSE_ENCRYPT0_TAG, [protectedBstr, unprot, ciphertext]));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @primitive b.cose.decrypt0
|
|
457
|
+
* @signature b.cose.decrypt0(coseEncrypt0, opts)
|
|
458
|
+
* @since 0.12.36
|
|
459
|
+
* @status stable
|
|
460
|
+
* @related b.cose.encrypt0
|
|
461
|
+
*
|
|
462
|
+
* Decrypt a COSE_Encrypt0 and return the plaintext. The algorithm is
|
|
463
|
+
* read from the protected header and must be in
|
|
464
|
+
* <code>opts.algorithms</code>; the Enc_structure is reconstructed as
|
|
465
|
+
* the AEAD associated data and authentication failure (wrong key /
|
|
466
|
+
* tampered ciphertext or AAD) is refused.
|
|
467
|
+
*
|
|
468
|
+
* @opts
|
|
469
|
+
* {
|
|
470
|
+
* key: Buffer, // symmetric key
|
|
471
|
+
* algorithms: string[], // required — accepted AEAD algs (allowlist)
|
|
472
|
+
* externalAad?: Buffer, // must match what was encrypted
|
|
473
|
+
* maxBytes?: number,
|
|
474
|
+
* maxDepth?: number,
|
|
475
|
+
* }
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* var pt = b.cose.decrypt0(enc, { key: k, algorithms: ["ChaCha20-Poly1305"] }).plaintext;
|
|
479
|
+
*/
|
|
480
|
+
function decrypt0(coseEncrypt0, opts) {
|
|
481
|
+
validateOpts.requireObject(opts, "cose.decrypt0", CoseError);
|
|
482
|
+
validateOpts(opts, ["key", "algorithms", "externalAad", "maxBytes", "maxDepth"], "cose.decrypt0");
|
|
483
|
+
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
484
|
+
throw new CoseError("cose/algorithms-required", "cose.decrypt0: opts.algorithms is required (no defaults — name the accepted algorithms)");
|
|
485
|
+
}
|
|
486
|
+
var decoded = cbor.decode(_bstr(coseEncrypt0), { allowedTags: [COSE_ENCRYPT0_TAG], maxBytes: opts.maxBytes, maxDepth: opts.maxDepth });
|
|
487
|
+
var arr = (decoded instanceof cbor.Tag && decoded.tag === COSE_ENCRYPT0_TAG) ? decoded.value : decoded;
|
|
488
|
+
if (!Array.isArray(arr) || arr.length !== 3) {
|
|
489
|
+
throw new CoseError("cose/malformed", "cose.decrypt0: not a COSE_Encrypt0 (expected a 3-element array)");
|
|
490
|
+
}
|
|
491
|
+
var protectedBstr = arr[0], unprotected = arr[1], ciphertext = arr[2];
|
|
492
|
+
if (!Buffer.isBuffer(protectedBstr) || !Buffer.isBuffer(ciphertext)) {
|
|
493
|
+
throw new CoseError("cose/malformed", "cose.decrypt0: protected header and ciphertext must be byte strings");
|
|
494
|
+
}
|
|
495
|
+
if (!(unprotected instanceof Map)) {
|
|
496
|
+
throw new CoseError("cose/malformed", "cose.decrypt0: unprotected header must be a CBOR map");
|
|
497
|
+
}
|
|
498
|
+
var protMap = protectedBstr.length === 0 ? new Map()
|
|
499
|
+
: cbor.decode(protectedBstr, { maxBytes: opts.maxBytes, maxDepth: opts.maxDepth });
|
|
500
|
+
if (!(protMap instanceof Map)) {
|
|
501
|
+
throw new CoseError("cose/malformed", "cose.decrypt0: protected header is not a CBOR map");
|
|
502
|
+
}
|
|
503
|
+
var algId = protMap.get(HDR_ALG);
|
|
504
|
+
var algName = AEAD_ID_TO_NAME[algId];
|
|
505
|
+
if (algName === undefined) {
|
|
506
|
+
throw new CoseError("cose/unknown-alg", "cose.decrypt0: unrecognized AEAD alg id " + algId);
|
|
507
|
+
}
|
|
508
|
+
if (opts.algorithms.indexOf(algName) === -1) {
|
|
509
|
+
throw new CoseError("cose/alg-not-allowed", "cose.decrypt0: alg '" + algName + "' is not in the allowlist");
|
|
510
|
+
}
|
|
511
|
+
var p = _aeadParams(algId);
|
|
512
|
+
var key = _bstr(opts.key);
|
|
513
|
+
if (key.length !== p.keyLen) throw new CoseError("cose/bad-key", "cose.decrypt0: " + algName + " requires a " + p.keyLen + "-byte key");
|
|
514
|
+
var iv = unprotected.get(HDR_IV);
|
|
515
|
+
if (!Buffer.isBuffer(iv) || iv.length !== p.ivLen) {
|
|
516
|
+
throw new CoseError("cose/bad-iv", "cose.decrypt0: missing or wrong-length IV (unprotected label 5)");
|
|
517
|
+
}
|
|
518
|
+
if (ciphertext.length < AEAD_TAG_LEN) {
|
|
519
|
+
throw new CoseError("cose/malformed", "cose.decrypt0: ciphertext shorter than the AEAD tag");
|
|
520
|
+
}
|
|
521
|
+
var tag = ciphertext.subarray(ciphertext.length - AEAD_TAG_LEN);
|
|
522
|
+
var ct = ciphertext.subarray(0, ciphertext.length - AEAD_TAG_LEN);
|
|
523
|
+
var aad = _encStructure(protectedBstr, opts.externalAad == null ? Buffer.alloc(0) : _bstr(opts.externalAad));
|
|
524
|
+
|
|
525
|
+
var decipher = nodeCrypto.createDecipheriv(p.cipher, key, iv, { authTagLength: AEAD_TAG_LEN });
|
|
526
|
+
decipher.setAAD(aad);
|
|
527
|
+
decipher.setAuthTag(tag);
|
|
528
|
+
var pt;
|
|
529
|
+
try {
|
|
530
|
+
pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
531
|
+
} catch (_e) {
|
|
532
|
+
throw new CoseError("cose/decrypt-failed", "cose.decrypt0: AEAD authentication failed (wrong key, tampered ciphertext, or AAD mismatch)");
|
|
533
|
+
}
|
|
534
|
+
return { plaintext: pt, alg: algName, protectedHeaders: protMap, unprotectedHeaders: unprotected };
|
|
535
|
+
}
|
|
536
|
+
|
|
333
537
|
module.exports = {
|
|
334
538
|
sign: sign,
|
|
335
539
|
verify: verify,
|
|
540
|
+
encrypt0: encrypt0,
|
|
541
|
+
decrypt0: decrypt0,
|
|
336
542
|
ALGORITHMS: ALG_NAME_TO_ID,
|
|
543
|
+
AEAD_ALGORITHMS: AEAD_NAME_TO_ID,
|
|
337
544
|
COSE_SIGN1_TAG: COSE_SIGN1_TAG,
|
|
545
|
+
COSE_ENCRYPT0_TAG: COSE_ENCRYPT0_TAG,
|
|
338
546
|
CoseError: CoseError,
|
|
339
547
|
};
|
package/lib/scitt.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.scitt
|
|
4
|
+
* @nav Crypto
|
|
5
|
+
* @title SCITT signed statements
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* A SCITT (Supply Chain Integrity, Transparency, and Trust) signed
|
|
9
|
+
* statement is a <code>b.cose</code> COSE_Sign1 that makes a signed,
|
|
10
|
+
* attributable claim <em>about an artifact</em> — a signed SBOM, a
|
|
11
|
+
* build attestation, a release approval. The artifact (or a hash /
|
|
12
|
+
* reference to it) is the payload; the issuer and the subject are
|
|
13
|
+
* carried in the integrity-protected <strong>CWT_Claims</strong>
|
|
14
|
+
* header (label 15, RFC 9597): <code>iss</code> (label 1) is who
|
|
15
|
+
* makes the statement, <code>sub</code> (label 2) is the artifact the
|
|
16
|
+
* statement is about. This module builds and verifies that envelope
|
|
17
|
+
* over <code>b.cose</code> + <code>b.cbor</code>.
|
|
18
|
+
*
|
|
19
|
+
* <code>b.scitt.signStatement(payload, opts)</code> produces the
|
|
20
|
+
* COSE_Sign1, placing <code>iss</code> / <code>sub</code> (plus any
|
|
21
|
+
* extra CWT claims) in the protected CWT_Claims header and declaring
|
|
22
|
+
* the payload media type as the COSE content type.
|
|
23
|
+
* <code>b.scitt.verifyStatement(statement, opts)</code> verifies the
|
|
24
|
+
* signature (delegating the mandatory algorithm allowlist to
|
|
25
|
+
* <code>b.cose.verify</code>), then enforces that a CWT_Claims header
|
|
26
|
+
* with both <code>iss</code> and <code>sub</code> is present —
|
|
27
|
+
* refusing a statement that omits the issuer/subject binding — and
|
|
28
|
+
* optionally checks them against expected values.
|
|
29
|
+
*
|
|
30
|
+
* The signing algorithms are exactly <code>b.cose</code>'s: the
|
|
31
|
+
* classical ES256/384/512 + EdDSA (final COSE ids, interoperable
|
|
32
|
+
* today) and ML-DSA-87 (PQC-forward, draft COSE id). Because the
|
|
33
|
+
* identity binding lives in the protected header it is covered by the
|
|
34
|
+
* signature and cannot be substituted without detection.
|
|
35
|
+
*
|
|
36
|
+
* <strong>Scope.</strong> This is the <em>issuer half</em> of SCITT —
|
|
37
|
+
* producing and verifying signed statements, which is buildable today
|
|
38
|
+
* on finalized RFCs (RFC 9052 COSE, RFC 9597 CWT_Claims header, RFC
|
|
39
|
+
* 8392 iss/sub). The <em>transparency receipt</em> (an inclusion proof
|
|
40
|
+
* from an append-only transparency service, COSE Receipts /
|
|
41
|
+
* draft-ietf-cose-merkle-tree-proofs) and the transparency-service
|
|
42
|
+
* registration protocol (draft-ietf-scitt-*) are deferred until those
|
|
43
|
+
* drafts publish — a signed statement produced here is the input a
|
|
44
|
+
* transparency service registers, and the receipt format is the part
|
|
45
|
+
* still in flux. Re-open on COSE-Receipts publication.
|
|
46
|
+
*
|
|
47
|
+
* @card
|
|
48
|
+
* SCITT signed statements (RFC 9052 COSE + RFC 9597 CWT_Claims) — a
|
|
49
|
+
* signed, issuer/subject-bound claim about an artifact (SBOM /
|
|
50
|
+
* attestation / approval). Composes b.cose; transparency receipts
|
|
51
|
+
* deferred to the COSE-Receipts draft.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
var cose = require("./cose");
|
|
55
|
+
var validateOpts = require("./validate-opts");
|
|
56
|
+
var { defineClass } = require("./framework-error");
|
|
57
|
+
|
|
58
|
+
var ScittError = defineClass("ScittError", { alwaysPermanent: true });
|
|
59
|
+
|
|
60
|
+
// CWT_Claims header label (RFC 9597) and the two SCITT-required claim
|
|
61
|
+
// labels inside it (RFC 8392 §3.1.1): iss = who states, sub = about what.
|
|
62
|
+
var HDR_CWT_CLAIMS = 15;
|
|
63
|
+
var CLAIM_ISS = 1;
|
|
64
|
+
var CLAIM_SUB = 2;
|
|
65
|
+
|
|
66
|
+
function _requireNonEmptyString(v, name) {
|
|
67
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
68
|
+
throw new ScittError("scitt/bad-" + name,
|
|
69
|
+
"scitt.signStatement: opts." + name + " is required and must be a non-empty string");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @primitive b.scitt.signStatement
|
|
75
|
+
* @signature b.scitt.signStatement(payload, opts)
|
|
76
|
+
* @since 0.12.37
|
|
77
|
+
* @status experimental
|
|
78
|
+
* @compliance soc2, cra
|
|
79
|
+
* @related b.scitt.verifyStatement, b.cose.sign
|
|
80
|
+
*
|
|
81
|
+
* Produce a SCITT signed statement: a COSE_Sign1 over
|
|
82
|
+
* <code>payload</code> (the artifact bytes, or a hash / reference to
|
|
83
|
+
* it) whose integrity-protected CWT_Claims header (label 15) binds the
|
|
84
|
+
* issuer (<code>iss</code>) and subject (<code>sub</code>). Declare the
|
|
85
|
+
* payload media type via <code>contentType</code> so a consumer knows
|
|
86
|
+
* how to interpret it.
|
|
87
|
+
*
|
|
88
|
+
* @opts
|
|
89
|
+
* {
|
|
90
|
+
* alg: string, // b.cose alg: "ES256" | … | "ML-DSA-87"
|
|
91
|
+
* privateKey: object, // matching KeyObject or PEM
|
|
92
|
+
* issuer: string, // → CWT_Claims iss (label 1) — who makes the statement
|
|
93
|
+
* subject: string, // → CWT_Claims sub (label 2) — the artifact the statement is about
|
|
94
|
+
* contentType?: number|string, // payload media type (e.g. "application/spdx+json")
|
|
95
|
+
* claims?: object, // extra CWT claims by integer label, merged into CWT_Claims
|
|
96
|
+
* kid?: string, // → unprotected header label 4
|
|
97
|
+
* externalAad?: Buffer, // bound into the signature
|
|
98
|
+
* }
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* var stmt = await b.scitt.signStatement(sbomBytes, {
|
|
102
|
+
* alg: "ES256", privateKey: issuerKey,
|
|
103
|
+
* issuer: "https://builder.example", subject: "pkg:npm/widget@1.2.3",
|
|
104
|
+
* contentType: "application/spdx+json",
|
|
105
|
+
* });
|
|
106
|
+
*/
|
|
107
|
+
async function signStatement(payload, opts) {
|
|
108
|
+
validateOpts.requireObject(opts, "scitt.signStatement", ScittError);
|
|
109
|
+
validateOpts(opts,
|
|
110
|
+
["alg", "privateKey", "issuer", "subject", "contentType", "claims", "kid", "externalAad"],
|
|
111
|
+
"scitt.signStatement");
|
|
112
|
+
_requireNonEmptyString(opts.issuer, "issuer");
|
|
113
|
+
_requireNonEmptyString(opts.subject, "subject");
|
|
114
|
+
|
|
115
|
+
var cwtClaims = new Map();
|
|
116
|
+
cwtClaims.set(CLAIM_ISS, opts.issuer);
|
|
117
|
+
cwtClaims.set(CLAIM_SUB, opts.subject);
|
|
118
|
+
// Extra CWT claims (e.g. iat = 6, a registration-policy claim) keyed
|
|
119
|
+
// by their integer label. iss / sub are managed via opts.issuer /
|
|
120
|
+
// opts.subject and cannot be overridden here.
|
|
121
|
+
if (opts.claims && typeof opts.claims === "object") {
|
|
122
|
+
var ck = opts.claims instanceof Map ? Array.from(opts.claims.keys()) : Object.keys(opts.claims);
|
|
123
|
+
for (var i = 0; i < ck.length; i++) {
|
|
124
|
+
var label = Number(ck[i]);
|
|
125
|
+
if (!Number.isInteger(label)) {
|
|
126
|
+
throw new ScittError("scitt/bad-claim-label",
|
|
127
|
+
"scitt.signStatement: claims keys must be integer CWT claim labels");
|
|
128
|
+
}
|
|
129
|
+
if (label === CLAIM_ISS || label === CLAIM_SUB) {
|
|
130
|
+
throw new ScittError("scitt/reserved-claim",
|
|
131
|
+
"scitt.signStatement: set iss / sub via opts.issuer / opts.subject, not opts.claims");
|
|
132
|
+
}
|
|
133
|
+
var val = opts.claims instanceof Map ? opts.claims.get(ck[i]) : opts.claims[ck[i]];
|
|
134
|
+
cwtClaims.set(label, val);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
var protectedHeaders = {};
|
|
139
|
+
protectedHeaders[HDR_CWT_CLAIMS] = cwtClaims;
|
|
140
|
+
|
|
141
|
+
return cose.sign(payload, {
|
|
142
|
+
alg: opts.alg,
|
|
143
|
+
privateKey: opts.privateKey,
|
|
144
|
+
kid: opts.kid,
|
|
145
|
+
contentType: opts.contentType,
|
|
146
|
+
externalAad: opts.externalAad,
|
|
147
|
+
protectedHeaders: protectedHeaders,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @primitive b.scitt.verifyStatement
|
|
153
|
+
* @signature b.scitt.verifyStatement(statement, opts)
|
|
154
|
+
* @since 0.12.37
|
|
155
|
+
* @status experimental
|
|
156
|
+
* @compliance soc2, cra
|
|
157
|
+
* @related b.scitt.signStatement, b.cose.verify
|
|
158
|
+
*
|
|
159
|
+
* Verify a SCITT signed statement and return its payload + identity
|
|
160
|
+
* binding. The COSE signature is checked through
|
|
161
|
+
* <code>b.cose.verify</code> (the algorithm allowlist is mandatory); a
|
|
162
|
+
* statement that does not carry a CWT_Claims header with both
|
|
163
|
+
* <code>iss</code> and <code>sub</code> is refused — that binding is
|
|
164
|
+
* what makes it a SCITT statement rather than a bare COSE_Sign1.
|
|
165
|
+
* <code>expectedIssuer</code> / <code>expectedSubject</code>, when
|
|
166
|
+
* given, must match.
|
|
167
|
+
*
|
|
168
|
+
* @opts
|
|
169
|
+
* {
|
|
170
|
+
* algorithms: string[], // required — accepted alg names (allowlist)
|
|
171
|
+
* publicKey?: object, // verification key (KeyObject / PEM)
|
|
172
|
+
* keyResolver?: function, // (protectedHeaders, unprotectedHeaders) → key
|
|
173
|
+
* expectedIssuer?: string, // require iss === this
|
|
174
|
+
* expectedSubject?: string, // require sub === this
|
|
175
|
+
* externalAad?: Buffer, // must match what was signed
|
|
176
|
+
* maxBytes?: number, // forwarded to b.cose.verify → b.cbor.decode
|
|
177
|
+
* maxDepth?: number,
|
|
178
|
+
* }
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* var out = await b.scitt.verifyStatement(stmt, {
|
|
182
|
+
* algorithms: ["ES256"], publicKey: issuerPub,
|
|
183
|
+
* expectedSubject: "pkg:npm/widget@1.2.3",
|
|
184
|
+
* });
|
|
185
|
+
* // → { payload: <Buffer>, issuer, subject, cwtClaims: Map, alg, protectedHeaders, unprotectedHeaders }
|
|
186
|
+
*/
|
|
187
|
+
async function verifyStatement(statement, opts) {
|
|
188
|
+
validateOpts.requireObject(opts, "scitt.verifyStatement", ScittError);
|
|
189
|
+
validateOpts(opts,
|
|
190
|
+
["algorithms", "publicKey", "keyResolver", "expectedIssuer", "expectedSubject",
|
|
191
|
+
"externalAad", "maxBytes", "maxDepth"],
|
|
192
|
+
"scitt.verifyStatement");
|
|
193
|
+
|
|
194
|
+
var out = await cose.verify(statement, {
|
|
195
|
+
algorithms: opts.algorithms,
|
|
196
|
+
publicKey: opts.publicKey,
|
|
197
|
+
keyResolver: opts.keyResolver,
|
|
198
|
+
externalAad: opts.externalAad,
|
|
199
|
+
maxBytes: opts.maxBytes,
|
|
200
|
+
maxDepth: opts.maxDepth,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
var cwtClaims = out.protectedHeaders.get(HDR_CWT_CLAIMS);
|
|
204
|
+
if (!(cwtClaims instanceof Map)) {
|
|
205
|
+
throw new ScittError("scitt/missing-cwt-claims",
|
|
206
|
+
"scitt.verifyStatement: no CWT_Claims header (label 15) — not a SCITT signed statement");
|
|
207
|
+
}
|
|
208
|
+
var issuer = cwtClaims.get(CLAIM_ISS);
|
|
209
|
+
var subject = cwtClaims.get(CLAIM_SUB);
|
|
210
|
+
if (issuer === undefined || issuer === null) {
|
|
211
|
+
throw new ScittError("scitt/missing-issuer",
|
|
212
|
+
"scitt.verifyStatement: CWT_Claims has no iss (label 1)");
|
|
213
|
+
}
|
|
214
|
+
if (subject === undefined || subject === null) {
|
|
215
|
+
throw new ScittError("scitt/missing-subject",
|
|
216
|
+
"scitt.verifyStatement: CWT_Claims has no sub (label 2)");
|
|
217
|
+
}
|
|
218
|
+
if (opts.expectedIssuer !== undefined && issuer !== opts.expectedIssuer) {
|
|
219
|
+
throw new ScittError("scitt/issuer-mismatch",
|
|
220
|
+
"scitt.verifyStatement: iss does not match expectedIssuer");
|
|
221
|
+
}
|
|
222
|
+
if (opts.expectedSubject !== undefined && subject !== opts.expectedSubject) {
|
|
223
|
+
throw new ScittError("scitt/subject-mismatch",
|
|
224
|
+
"scitt.verifyStatement: sub does not match expectedSubject");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
payload: out.payload,
|
|
229
|
+
issuer: issuer,
|
|
230
|
+
subject: subject,
|
|
231
|
+
cwtClaims: cwtClaims,
|
|
232
|
+
alg: out.alg,
|
|
233
|
+
protectedHeaders: out.protectedHeaders,
|
|
234
|
+
unprotectedHeaders: out.unprotectedHeaders,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = {
|
|
239
|
+
signStatement: signStatement,
|
|
240
|
+
verifyStatement: verifyStatement,
|
|
241
|
+
CWT_CLAIMS_LABEL: HDR_CWT_CLAIMS,
|
|
242
|
+
ScittError: ScittError,
|
|
243
|
+
};
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:06167082-579c-4ea8-9d7e-30209b1be393",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-25T01:10:14.224Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.12.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.12.37",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.12.
|
|
25
|
+
"version": "0.12.37",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.12.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.12.37",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.12.
|
|
57
|
+
"ref": "@blamejs/core@0.12.37",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|