@ggui-ai/gadget-signing 0.6.2 → 0.7.0
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/dist/index.d.ts +52 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +66 -60
- package/dist/testing/index.d.ts +92 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +237 -0
- package/package.json +32 -1
package/dist/index.d.ts
CHANGED
|
@@ -110,8 +110,11 @@ export declare function isGadgetSignature(value: unknown): value is GadgetSignat
|
|
|
110
110
|
export declare function canonicalJson(value: unknown): string;
|
|
111
111
|
/**
|
|
112
112
|
* Derive a stable public-key identifier from a 32-byte Ed25519 public key.
|
|
113
|
-
* `
|
|
114
|
-
*
|
|
113
|
+
* `base64url(sha256(publicKey))[:16]` — RFC 4648 §5 URL-safe alphabet
|
|
114
|
+
* (`-`/`_`, no padding), because keyIds travel as URL path segments
|
|
115
|
+
* (`DELETE /author-keys/{keyId}`) and as storage row-key components;
|
|
116
|
+
* standard base64 would put `/` in ~22% of ids. Used as the registry's
|
|
117
|
+
* stable handle for a stored author public key.
|
|
115
118
|
*/
|
|
116
119
|
export declare function derivePublicKeyId(publicKey: Uint8Array): string;
|
|
117
120
|
/**
|
|
@@ -248,14 +251,41 @@ export interface VerifyBundleSigstoreInput {
|
|
|
248
251
|
readonly issuer?: string;
|
|
249
252
|
};
|
|
250
253
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
+
* TUF mirror serving the trust-root metadata + targets. When unset,
|
|
255
|
+
* the upstream verifier resolves against the Sigstore public-good
|
|
256
|
+
* mirror. Point this (together with {@link tufRootPath}) at an
|
|
257
|
+
* alternative TUF repository for private sigstore deployments and
|
|
258
|
+
* hermetic tests — verification never contacts Fulcio/Rekor
|
|
259
|
+
* directly, so the TUF trust root is THE knob that selects which
|
|
260
|
+
* signing infrastructure a verifier trusts.
|
|
254
261
|
*/
|
|
255
|
-
readonly
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
262
|
+
readonly tufMirrorURL?: string;
|
|
263
|
+
/**
|
|
264
|
+
* Path to the initial TUF `root.json` that anchors trust in
|
|
265
|
+
* {@link tufMirrorURL}. Required for any non-public-good mirror
|
|
266
|
+
* (the upstream client only ships seeds for the public-good
|
|
267
|
+
* repository); ignored once a cached root exists under
|
|
268
|
+
* {@link tufCachePath}.
|
|
269
|
+
*/
|
|
270
|
+
readonly tufRootPath?: string;
|
|
271
|
+
/**
|
|
272
|
+
* Writable cache directory for the sigstore TUF trust root. The
|
|
273
|
+
* upstream verifier refreshes TUF metadata into this directory
|
|
274
|
+
* before verifying; when unset it falls back to the platform
|
|
275
|
+
* app-data directory (derived from `$XDG_DATA_HOME` / `$HOME`),
|
|
276
|
+
* which read-only filesystems reject at `mkdir` time — serverless
|
|
277
|
+
* runtimes typically only allow writes under `/tmp`, so point this
|
|
278
|
+
* at e.g. `/tmp/sigstore-js` there.
|
|
279
|
+
*/
|
|
280
|
+
readonly tufCachePath?: string;
|
|
281
|
+
/**
|
|
282
|
+
* Reuse cached TUF metadata without a remote refresh while the
|
|
283
|
+
* cached copy is still valid. Keeps the trust-root network
|
|
284
|
+
* round-trip off warm verification paths; a missing, expired, or
|
|
285
|
+
* corrupt cache falls back to a normal remote refresh, so this is
|
|
286
|
+
* a latency knob — never a correctness one.
|
|
287
|
+
*/
|
|
288
|
+
readonly tufForceCache?: boolean;
|
|
259
289
|
}
|
|
260
290
|
/**
|
|
261
291
|
* Verify a sigstore-signed gadget bundle.
|
|
@@ -282,10 +312,19 @@ export interface VerifyBundleSigstoreInput {
|
|
|
282
312
|
export declare function verifyBundleSigstore(input: VerifyBundleSigstoreInput): Promise<VerifyResult>;
|
|
283
313
|
/**
|
|
284
314
|
* Extract the Fulcio leaf cert's base64 raw bytes from a
|
|
285
|
-
* {@link SigstoreSignature}'s serialized cosign bundle
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
315
|
+
* {@link SigstoreSignature}'s serialized cosign bundle. Two
|
|
316
|
+
* `verificationMaterial` shapes exist on the wire:
|
|
317
|
+
*
|
|
318
|
+
* - `certificate.rawBytes` — the single-leaf shape bundle v0.3
|
|
319
|
+
* emits. What real `sigstore.sign()` produces today (surfaced by
|
|
320
|
+
* this path's first real signing run, 2026-08-10 — the extractor
|
|
321
|
+
* previously only knew the chain shape and returned `undefined`
|
|
322
|
+
* for every genuinely-signed bundle).
|
|
323
|
+
* - `x509CertificateChain.certificates[0].rawBytes` — the v0.1/v0.2
|
|
324
|
+
* chain shape, still valid on the wire for externally-produced
|
|
325
|
+
* bundles.
|
|
326
|
+
*
|
|
327
|
+
* Returns `undefined` if the bundle is malformed or carries neither.
|
|
289
328
|
*
|
|
290
329
|
* Lives here — next to {@link verifyBundleSigstore} — so cosign
|
|
291
330
|
* bundle-format knowledge has ONE home. Registries persist the
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import { signAsync, verifyAsync, getPublicKeyAsync, utils } from "@noble/ed25519";
|
|
32
32
|
import { sha384, sha256 } from "@noble/hashes/sha2";
|
|
33
33
|
import { bundleFromJSON, bundleToJSON, } from "@sigstore/bundle";
|
|
34
|
+
import { X509Certificate } from "@sigstore/core";
|
|
34
35
|
import * as sigstoreClient from "sigstore";
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
36
37
|
// Canonical type guards.
|
|
@@ -91,6 +92,18 @@ function bytesToBase64(bytes) {
|
|
|
91
92
|
// `btoa` exists in browsers and modern Node (>=16) globals.
|
|
92
93
|
return btoa(binary);
|
|
93
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* base64url (RFC 4648 §5, unpadded) — the URL- and filename-safe
|
|
97
|
+
* alphabet. Used for identifiers that travel as URL path segments or
|
|
98
|
+
* become row-key/filename components ({@link derivePublicKeyId});
|
|
99
|
+
* signatures and digests stay standard base64 (JSON-body-only).
|
|
100
|
+
*/
|
|
101
|
+
function bytesToBase64Url(bytes) {
|
|
102
|
+
return bytesToBase64(bytes)
|
|
103
|
+
.replaceAll("+", "-")
|
|
104
|
+
.replaceAll("/", "_")
|
|
105
|
+
.replace(/=+$/, "");
|
|
106
|
+
}
|
|
94
107
|
function base64ToBytes(b64) {
|
|
95
108
|
const binary = atob(b64);
|
|
96
109
|
const out = new Uint8Array(binary.length);
|
|
@@ -141,15 +154,18 @@ function canonicalSort(value) {
|
|
|
141
154
|
// ---------------------------------------------------------------------------
|
|
142
155
|
/**
|
|
143
156
|
* Derive a stable public-key identifier from a 32-byte Ed25519 public key.
|
|
144
|
-
* `
|
|
145
|
-
*
|
|
157
|
+
* `base64url(sha256(publicKey))[:16]` — RFC 4648 §5 URL-safe alphabet
|
|
158
|
+
* (`-`/`_`, no padding), because keyIds travel as URL path segments
|
|
159
|
+
* (`DELETE /author-keys/{keyId}`) and as storage row-key components;
|
|
160
|
+
* standard base64 would put `/` in ~22% of ids. Used as the registry's
|
|
161
|
+
* stable handle for a stored author public key.
|
|
146
162
|
*/
|
|
147
163
|
export function derivePublicKeyId(publicKey) {
|
|
148
164
|
if (publicKey.length !== 32) {
|
|
149
165
|
throw new Error(`derivePublicKeyId: expected 32-byte Ed25519 public key, got ${publicKey.length}`);
|
|
150
166
|
}
|
|
151
167
|
const digest = sha256(publicKey);
|
|
152
|
-
return
|
|
168
|
+
return bytesToBase64Url(digest).slice(0, 16);
|
|
153
169
|
}
|
|
154
170
|
/**
|
|
155
171
|
* Derive the Ed25519 public key from a 32-byte private key. Deterministic
|
|
@@ -372,7 +388,7 @@ function classifySigstoreSigningError(err) {
|
|
|
372
388
|
* `signature.bundle` JSON).
|
|
373
389
|
*/
|
|
374
390
|
export async function verifyBundleSigstore(input) {
|
|
375
|
-
const { bundleBytes, signature, expectedIdentity } = input;
|
|
391
|
+
const { bundleBytes, signature, expectedIdentity, tufMirrorURL, tufRootPath, tufCachePath, tufForceCache, } = input;
|
|
376
392
|
// 1. Fast tamper check.
|
|
377
393
|
const recomputed = sha384(bundleBytes);
|
|
378
394
|
const recomputedB64 = bytesToBase64(recomputed);
|
|
@@ -425,6 +441,18 @@ export async function verifyBundleSigstore(input) {
|
|
|
425
441
|
// 4. Run the full upstream verify.
|
|
426
442
|
try {
|
|
427
443
|
const verifyOpts = {};
|
|
444
|
+
if (tufMirrorURL !== undefined) {
|
|
445
|
+
verifyOpts.tufMirrorURL = tufMirrorURL;
|
|
446
|
+
}
|
|
447
|
+
if (tufRootPath !== undefined) {
|
|
448
|
+
verifyOpts.tufRootPath = tufRootPath;
|
|
449
|
+
}
|
|
450
|
+
if (tufCachePath !== undefined) {
|
|
451
|
+
verifyOpts.tufCachePath = tufCachePath;
|
|
452
|
+
}
|
|
453
|
+
if (tufForceCache !== undefined) {
|
|
454
|
+
verifyOpts.tufForceCache = tufForceCache;
|
|
455
|
+
}
|
|
428
456
|
if (expectedIdentity) {
|
|
429
457
|
if (typeof expectedIdentity.subject === "string") {
|
|
430
458
|
// Use email-shaped vs URI-shaped routing per upstream's two
|
|
@@ -455,10 +483,19 @@ export async function verifyBundleSigstore(input) {
|
|
|
455
483
|
}
|
|
456
484
|
/**
|
|
457
485
|
* Extract the Fulcio leaf cert's base64 raw bytes from a
|
|
458
|
-
* {@link SigstoreSignature}'s serialized cosign bundle
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
486
|
+
* {@link SigstoreSignature}'s serialized cosign bundle. Two
|
|
487
|
+
* `verificationMaterial` shapes exist on the wire:
|
|
488
|
+
*
|
|
489
|
+
* - `certificate.rawBytes` — the single-leaf shape bundle v0.3
|
|
490
|
+
* emits. What real `sigstore.sign()` produces today (surfaced by
|
|
491
|
+
* this path's first real signing run, 2026-08-10 — the extractor
|
|
492
|
+
* previously only knew the chain shape and returned `undefined`
|
|
493
|
+
* for every genuinely-signed bundle).
|
|
494
|
+
* - `x509CertificateChain.certificates[0].rawBytes` — the v0.1/v0.2
|
|
495
|
+
* chain shape, still valid on the wire for externally-produced
|
|
496
|
+
* bundles.
|
|
497
|
+
*
|
|
498
|
+
* Returns `undefined` if the bundle is malformed or carries neither.
|
|
462
499
|
*
|
|
463
500
|
* Lives here — next to {@link verifyBundleSigstore} — so cosign
|
|
464
501
|
* bundle-format knowledge has ONE home. Registries persist the
|
|
@@ -486,6 +523,14 @@ export function extractSigstoreLeafCertPem(signature) {
|
|
|
486
523
|
if (verificationMaterial === null || typeof verificationMaterial !== "object") {
|
|
487
524
|
return undefined;
|
|
488
525
|
}
|
|
526
|
+
// Bundle v0.3 single-leaf shape first — what real signing emits.
|
|
527
|
+
const certificate = verificationMaterial.certificate;
|
|
528
|
+
if (certificate !== null && typeof certificate === "object") {
|
|
529
|
+
const rawBytes = certificate.rawBytes;
|
|
530
|
+
if (typeof rawBytes === "string" && rawBytes.length > 0)
|
|
531
|
+
return rawBytes;
|
|
532
|
+
}
|
|
533
|
+
// v0.1/v0.2 chain shape — leaf first.
|
|
489
534
|
const chain = verificationMaterial
|
|
490
535
|
.x509CertificateChain;
|
|
491
536
|
if (chain === null || typeof chain !== "object")
|
|
@@ -507,9 +552,15 @@ export function extractSigstoreLeafCertPem(signature) {
|
|
|
507
552
|
* by the RegExp-identity pre-check; production verification still goes
|
|
508
553
|
* through the upstream verifier for the actual cryptographic check.
|
|
509
554
|
*
|
|
555
|
+
* Parsing delegates to `@sigstore/core`'s `X509Certificate` — the
|
|
556
|
+
* SAME parser the upstream verifier uses — so the SAN this pre-check
|
|
557
|
+
* sees can never drift from the SAN verification enforces. (A prior
|
|
558
|
+
* hand-rolled DER scan over-captured on real certificates —
|
|
559
|
+
* discovered on the first genuinely Fulcio-issued cert it ever saw,
|
|
560
|
+
* 2026-08-10 — and was replaced wholesale rather than patched.)
|
|
561
|
+
*
|
|
510
562
|
* Returns `undefined` if the bundle has no cert (e.g. publicKey-only
|
|
511
|
-
* bundle)
|
|
512
|
-
* defers to the upstream verifier for the trust-chain semantics.
|
|
563
|
+
* bundle), the cert fails to parse, or it carries no URI/email SAN.
|
|
513
564
|
*/
|
|
514
565
|
function extractSANFromBundle(bundle) {
|
|
515
566
|
const material = bundle.verificationMaterial;
|
|
@@ -526,59 +577,14 @@ function extractSANFromBundle(bundle) {
|
|
|
526
577
|
}
|
|
527
578
|
if (!certB64)
|
|
528
579
|
return undefined;
|
|
529
|
-
// Very lightweight SAN extraction: decode DER, locate the SAN
|
|
530
|
-
// extension OID (2.5.29.17), and pull the first URI/email-shaped
|
|
531
|
-
// ASCII run. This is intentionally tolerant — exact parsing happens
|
|
532
|
-
// in `@sigstore/verify` downstream.
|
|
533
|
-
let der;
|
|
534
580
|
try {
|
|
535
|
-
|
|
581
|
+
const cert = X509Certificate.parse(Buffer.from(certB64, "base64"));
|
|
582
|
+
// `subjectAltName` is the URI GeneralName when present, else the
|
|
583
|
+
// rfc822Name — the exact preference order the verifier's identity
|
|
584
|
+
// policy uses.
|
|
585
|
+
return cert.subjectAltName;
|
|
536
586
|
}
|
|
537
587
|
catch {
|
|
538
588
|
return undefined;
|
|
539
589
|
}
|
|
540
|
-
// OID 2.5.29.17 (subjectAltName) DER prefix: 06 03 55 1d 11.
|
|
541
|
-
const sanOid = [0x06, 0x03, 0x55, 0x1d, 0x11];
|
|
542
|
-
let idx = -1;
|
|
543
|
-
for (let i = 0; i < der.length - sanOid.length; i++) {
|
|
544
|
-
let match = true;
|
|
545
|
-
for (let j = 0; j < sanOid.length; j++) {
|
|
546
|
-
if (der[i + j] !== sanOid[j]) {
|
|
547
|
-
match = false;
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
if (match) {
|
|
552
|
-
idx = i;
|
|
553
|
-
break;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
if (idx === -1)
|
|
557
|
-
return undefined;
|
|
558
|
-
// After the OID DER there's a BOOLEAN (critical, optional) then an
|
|
559
|
-
// OCTET STRING wrapping a SEQUENCE of GeneralName. Scan forward for
|
|
560
|
-
// the first printable ASCII run containing a URI or email shape.
|
|
561
|
-
const tail = der.subarray(idx + sanOid.length);
|
|
562
|
-
let buf = "";
|
|
563
|
-
const uriPattern = /[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]+|[\w.+-]+@[\w.-]+/;
|
|
564
|
-
for (let i = 0; i < tail.length; i++) {
|
|
565
|
-
const b = tail[i];
|
|
566
|
-
if (b >= 0x20 && b <= 0x7e) {
|
|
567
|
-
buf += String.fromCharCode(b);
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
if (buf.length >= 3) {
|
|
571
|
-
const m = buf.match(uriPattern);
|
|
572
|
-
if (m)
|
|
573
|
-
return m[0];
|
|
574
|
-
}
|
|
575
|
-
buf = "";
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (buf.length >= 3) {
|
|
579
|
-
const m = buf.match(uriPattern);
|
|
580
|
-
if (m)
|
|
581
|
-
return m[0];
|
|
582
|
-
}
|
|
583
|
-
return undefined;
|
|
584
590
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { initializeCA, initializeCTLog, initializeTLog } from '@sigstore/mock';
|
|
2
|
+
type CA = Awaited<ReturnType<typeof initializeCA>>;
|
|
3
|
+
type CTLog = Awaited<ReturnType<typeof initializeCTLog>>;
|
|
4
|
+
type TLog = Awaited<ReturnType<typeof initializeTLog>>;
|
|
5
|
+
/** Options for {@link startSigstoreMockStack}. */
|
|
6
|
+
export interface SigstoreMockStackOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Mount the Fulcio signing-cert endpoint. Default `true`. Pass
|
|
9
|
+
* `false` to exercise CA-unreachable signing failure paths.
|
|
10
|
+
*/
|
|
11
|
+
readonly fulcio?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Mount the Rekor create-entry endpoint. Default `true`. Pass
|
|
14
|
+
* `false` to exercise transparency-log failure paths.
|
|
15
|
+
*/
|
|
16
|
+
readonly rekor?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Claims for {@link SigstoreMockStack.identityToken}. */
|
|
19
|
+
export interface MockIdentityClaims {
|
|
20
|
+
/**
|
|
21
|
+
* OIDC subject — becomes the issued certificate's SAN (as a URI
|
|
22
|
+
* GeneralName; see the module docstring's mock-fidelity note).
|
|
23
|
+
*/
|
|
24
|
+
readonly sub?: string;
|
|
25
|
+
/** OIDC issuer claim — lands in the cert's issuer extension. */
|
|
26
|
+
readonly iss?: string;
|
|
27
|
+
/** Additional claims (email, GitHub Actions workflow claims, …). */
|
|
28
|
+
readonly [claim: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** Handle returned by {@link startSigstoreMockStack}. */
|
|
31
|
+
export interface SigstoreMockStack {
|
|
32
|
+
/** Base URL of the mock Fulcio instance. */
|
|
33
|
+
readonly fulcioURL: string;
|
|
34
|
+
/** Base URL of the mock Rekor instance. */
|
|
35
|
+
readonly rekorURL: string;
|
|
36
|
+
/**
|
|
37
|
+
* Endpoint overrides for `signBundleSigstore` — spread into its
|
|
38
|
+
* `endpoints` input.
|
|
39
|
+
*/
|
|
40
|
+
readonly signEndpoints: {
|
|
41
|
+
readonly fulcioURL: string;
|
|
42
|
+
readonly rekorURL: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* TUF trust-root overrides for `verifyBundleSigstore` — spread into
|
|
46
|
+
* its input (or a registry publish op's `sigstoreTuf` deps slot) so
|
|
47
|
+
* verification resolves the MOCK trust root instead of the
|
|
48
|
+
* public-good one.
|
|
49
|
+
*
|
|
50
|
+
* Includes `tufForceCache: true`, and load-bearingly so: the mock
|
|
51
|
+
* TUF mirror's nock interceptors are single-use, so exactly ONE
|
|
52
|
+
* remote metadata refresh succeeds per stack (discovered on this
|
|
53
|
+
* fixture's first multi-verify run — the second refresh died with
|
|
54
|
+
* `error refreshing TUF metadata`). The first verify populates the
|
|
55
|
+
* cache over the mock network; every later verify reuses it — which
|
|
56
|
+
* is also precisely the warm-start path serverless verifiers run in
|
|
57
|
+
* production, so the cache-reuse semantics get real coverage for
|
|
58
|
+
* free.
|
|
59
|
+
*/
|
|
60
|
+
readonly tuf: {
|
|
61
|
+
readonly tufMirrorURL: string;
|
|
62
|
+
readonly tufRootPath: string;
|
|
63
|
+
readonly tufCachePath: string;
|
|
64
|
+
readonly tufForceCache: true;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Subject the zero-argument {@link identityToken} carries — handy
|
|
68
|
+
* for identity-policy assertions.
|
|
69
|
+
*/
|
|
70
|
+
readonly defaultSubject: string;
|
|
71
|
+
/** Issuer the zero-argument {@link identityToken} carries. */
|
|
72
|
+
readonly defaultIssuer: string;
|
|
73
|
+
/**
|
|
74
|
+
* Mint an unsigned JWT-shaped OIDC token the mock Fulcio accepts.
|
|
75
|
+
* The mock decodes claims without verifying a signature.
|
|
76
|
+
*/
|
|
77
|
+
identityToken(claims?: MockIdentityClaims): string;
|
|
78
|
+
/**
|
|
79
|
+
* Remove ALL nock interceptors registered in this process (including
|
|
80
|
+
* other live stacks — one stack per suite), restore real network
|
|
81
|
+
* access, and delete the TUF repo's cache directory.
|
|
82
|
+
*/
|
|
83
|
+
teardown(): void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Boot a full in-process sigstore mock stack: Fulcio + Rekor
|
|
87
|
+
* interceptors plus a TUF repository serving a trusted root built from
|
|
88
|
+
* the same keys. See the module docstring for the trust wiring.
|
|
89
|
+
*/
|
|
90
|
+
export declare function startSigstoreMockStack(options?: SigstoreMockStackOptions): Promise<SigstoreMockStack>;
|
|
91
|
+
export type { CA as MockCA, CTLog as MockCTLog, TLog as MockTLog };
|
|
92
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AA2DA,OAAO,EAEL,YAAY,EACZ,eAAe,EACf,cAAc,EAEf,MAAM,gBAAgB,CAAC;AAkBxB,KAAK,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AACnD,KAAK,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC;AACzD,KAAK,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC;AAGvD,kDAAkD;AAClD,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE;QACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,GAAG,EAAE;QACZ,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC;KAC9B,CAAC;IACF;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,aAAa,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACnD;;;;OAIG;IACH,QAAQ,IAAI,IAAI,CAAC;CAClB;AAaD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA6H5B;AAgDD,YAAY,EAAE,EAAE,IAAI,MAAM,EAAE,KAAK,IAAI,SAAS,EAAE,IAAI,IAAI,QAAQ,EAAE,CAAC"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic sigstore test infrastructure for `@ggui-ai/gadget-signing`
|
|
3
|
+
* consumers.
|
|
4
|
+
*
|
|
5
|
+
* `startSigstoreMockStack()` boots in-process mock Fulcio + Rekor HTTP
|
|
6
|
+
* endpoints (nock interceptors over `@sigstore/mock`'s CA / CTLog /
|
|
7
|
+
* TLog primitives) **plus** a mock TUF repository (via
|
|
8
|
+
* `@tufjs/repo-mock`) that serves a `trusted_root.json` built from the
|
|
9
|
+
* SAME key material the mock services sign with. The result: the real
|
|
10
|
+
* `signBundleSigstore` and `verifyBundleSigstore` code paths execute
|
|
11
|
+
* end-to-end — real ephemeral key generation, real cert issuance, real
|
|
12
|
+
* transparency-log entries, real bundle serialization, real TUF
|
|
13
|
+
* trust-root resolution, real cryptographic verification — with only
|
|
14
|
+
* the network endpoints and the root of trust substituted, all through
|
|
15
|
+
* the upstream `sigstore` package's public option surface
|
|
16
|
+
* (`fulcioURL` / `rekorURL` on sign; `tufMirrorURL` / `tufRootPath` /
|
|
17
|
+
* `tufCachePath` on verify). Nothing in the sign/verify pipeline is
|
|
18
|
+
* stubbed.
|
|
19
|
+
*
|
|
20
|
+
* Subpath export — consumers
|
|
21
|
+
* `import { startSigstoreMockStack } from '@ggui-ai/gadget-signing/testing'`.
|
|
22
|
+
* Requires `@sigstore/mock`, `@tufjs/repo-mock`, `nock`, and
|
|
23
|
+
* `@sigstore/protobuf-specs` at test-runtime (declared as optional
|
|
24
|
+
* peerDeps; add them to devDependencies alongside this package).
|
|
25
|
+
* `@tufjs/repo-mock` is pinned to exactly 4.0.1 on purpose: 4.0.2
|
|
26
|
+
* moves its nock dependency to v14, which would load a second,
|
|
27
|
+
* differently-patched `http` interceptor next to `@sigstore/mock`'s
|
|
28
|
+
* nock v13 in the same process. At 4.0.1 both resolve one shared nock.
|
|
29
|
+
*
|
|
30
|
+
* Known mock-fidelity limits (upstream `@sigstore/mock` behavior —
|
|
31
|
+
* document divergence from the public-good instance, don't paper over
|
|
32
|
+
* it):
|
|
33
|
+
*
|
|
34
|
+
* - The mock CA writes the certificate SAN as a **URI** GeneralName
|
|
35
|
+
* regardless of the subject's shape, so email-based identity
|
|
36
|
+
* policies (`certificateIdentityEmail`) can never match a
|
|
37
|
+
* mock-issued cert. Use URI-shaped subjects in identity-policy
|
|
38
|
+
* tests; the email routing itself is covered by the
|
|
39
|
+
* option-threading seam tests.
|
|
40
|
+
* - The mock Fulcio decodes (but does not cryptographically verify)
|
|
41
|
+
* the OIDC token, so {@link SigstoreMockStack.identityToken}
|
|
42
|
+
* mints unsigned JWT-shaped tokens.
|
|
43
|
+
*
|
|
44
|
+
* Interceptors are process-global (nock patches `http`), so run ONE
|
|
45
|
+
* stack per suite and call {@link SigstoreMockStack.teardown} in
|
|
46
|
+
* `afterAll`. While a stack is live the stack is HERMETIC: real
|
|
47
|
+
* network egress over `http.ClientRequest` is disabled (loopback
|
|
48
|
+
* excepted), so a request to any unmocked host fails fast and
|
|
49
|
+
* deterministically instead of silently escaping to live DNS — a
|
|
50
|
+
* `rekor: false` stack answers in milliseconds with nock's
|
|
51
|
+
* disallowed-net-connect error rather than waiting on a real
|
|
52
|
+
* NXDOMAIN. Undici-based `fetch` traffic is NOT intercepted (nor
|
|
53
|
+
* blocked) — only the `http.ClientRequest` path the sigstore/TUF
|
|
54
|
+
* clients use — so an in-process HTTP server under test keeps
|
|
55
|
+
* working alongside a live stack. `teardown()` restores real network
|
|
56
|
+
* access.
|
|
57
|
+
*/
|
|
58
|
+
import { createHash, generateKeyPairSync } from 'node:crypto';
|
|
59
|
+
import { join } from 'node:path';
|
|
60
|
+
import { fulcioHandler, initializeCA, initializeCTLog, initializeTLog, rekorHandler, } from '@sigstore/mock';
|
|
61
|
+
import { TrustedRoot } from '@sigstore/protobuf-specs';
|
|
62
|
+
import { createRequire } from 'node:module';
|
|
63
|
+
import nock from 'nock';
|
|
64
|
+
// `@tufjs/repo-mock` ships CJS with a transpiled `exports.default`.
|
|
65
|
+
// Under native Node ESM a default-import binds the whole `exports`
|
|
66
|
+
// object (Node does not unwrap transpiled `.default`), so
|
|
67
|
+
// `mocktuf(...)` throws `TypeError: mocktuf is not a function` in the
|
|
68
|
+
// shipped artifact even though vitest's interop keeps in-repo tests
|
|
69
|
+
// green. `createRequire` reads the CJS shape identically in every
|
|
70
|
+
// runtime — pinned by `dist-esm-interop.integration.test.ts`, which
|
|
71
|
+
// imports the BUILT artifact under plain `node`.
|
|
72
|
+
const cjsRequire = createRequire(import.meta.url);
|
|
73
|
+
const repoMock = cjsRequire('@tufjs/repo-mock');
|
|
74
|
+
const mocktuf = repoMock.default;
|
|
75
|
+
/**
|
|
76
|
+
* Default SAN subject for minted identity tokens. URI-shaped on
|
|
77
|
+
* purpose — the mock CA writes URI SANs (module docstring).
|
|
78
|
+
*/
|
|
79
|
+
const DEFAULT_SUBJECT = 'https://gadgets.ggui.test/e2e-signer';
|
|
80
|
+
/** Mirrors the mock Fulcio's default issuer claim. */
|
|
81
|
+
const DEFAULT_ISSUER = 'https://fake.oidcissuer.com';
|
|
82
|
+
/** Per-process counter so parallel suites get distinct mock hosts. */
|
|
83
|
+
let stackCounter = 0;
|
|
84
|
+
/**
|
|
85
|
+
* Boot a full in-process sigstore mock stack: Fulcio + Rekor
|
|
86
|
+
* interceptors plus a TUF repository serving a trusted root built from
|
|
87
|
+
* the same keys. See the module docstring for the trust wiring.
|
|
88
|
+
*/
|
|
89
|
+
export async function startSigstoreMockStack(options = {}) {
|
|
90
|
+
const id = (stackCounter += 1);
|
|
91
|
+
const fulcioURL = `https://fulcio.mock-${id}.ggui.test`;
|
|
92
|
+
const rekorURL = `https://rekor.mock-${id}.ggui.test`;
|
|
93
|
+
const tufMirrorURL = `https://tuf.mock-${id}.ggui.test`;
|
|
94
|
+
// One P-256 keypair roots the whole stack (CA + CTLog + TLog) —
|
|
95
|
+
// mirrors `@sigstore/mock`'s own top-level `mockFulcio` /
|
|
96
|
+
// `mockRekor` composition, which shares a keypair between the CA
|
|
97
|
+
// and its CTLog.
|
|
98
|
+
const keyPair = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
|
99
|
+
const ctlog = await initializeCTLog(keyPair);
|
|
100
|
+
const ca = await initializeCA(keyPair, ctlog);
|
|
101
|
+
const tlog = await initializeTLog(rekorURL, keyPair);
|
|
102
|
+
// Hermetic: while the stack is live, requests to any host without a
|
|
103
|
+
// registered interceptor fail fast with nock's NetConnectNotAllowed
|
|
104
|
+
// error instead of escaping to real DNS/network (empirically: an
|
|
105
|
+
// unmocked `.test` host cost ~3s of live NXDOMAIN lookups per
|
|
106
|
+
// attempt before this). Loopback stays open for in-process HTTP
|
|
107
|
+
// servers under test.
|
|
108
|
+
nock.disableNetConnect();
|
|
109
|
+
nock.enableNetConnect((host) => host.startsWith('127.0.0.1') || host.startsWith('localhost'));
|
|
110
|
+
if (options.fulcio !== false) {
|
|
111
|
+
mount(fulcioURL, fulcioHandler(ca, { strict: true }));
|
|
112
|
+
}
|
|
113
|
+
if (options.rekor !== false) {
|
|
114
|
+
mount(rekorURL, rekorHandler(tlog, { strict: true }));
|
|
115
|
+
}
|
|
116
|
+
// Trust material for verification — the SAME roots the mock services
|
|
117
|
+
// sign with, projected onto the `trusted_root.json` shape the TUF
|
|
118
|
+
// client resolves. Built in the protobuf-specs JSON encoding (base64
|
|
119
|
+
// bytes, enum names, ISO timestamps) and round-tripped through the
|
|
120
|
+
// `TrustedRoot` codec so a field drift fails loudly here rather than
|
|
121
|
+
// deep inside the verifier. (The installed protobuf-specs build
|
|
122
|
+
// exposes only `fromJSON`/`toJSON` — no `fromPartial` — discovered
|
|
123
|
+
// on this fixture's first run.)
|
|
124
|
+
//
|
|
125
|
+
// Validity windows are backdated one minute so cert-chain checks sit
|
|
126
|
+
// comfortably inside the window even under clock skew between this
|
|
127
|
+
// init and the moment a test signs.
|
|
128
|
+
const validityStart = new Date(Date.now() - 60_000).toISOString();
|
|
129
|
+
const trustedRootJSON = JSON.stringify(TrustedRoot.toJSON(TrustedRoot.fromJSON({
|
|
130
|
+
mediaType: 'application/vnd.dev.sigstore.trustedroot+json;version=0.1',
|
|
131
|
+
certificateAuthorities: [
|
|
132
|
+
{
|
|
133
|
+
subject: { commonName: 'sigstore', organization: 'sigstore.mock' },
|
|
134
|
+
uri: fulcioURL,
|
|
135
|
+
certChain: {
|
|
136
|
+
certificates: [{ rawBytes: toBase64(ca.rootCertificate) }],
|
|
137
|
+
},
|
|
138
|
+
validFor: { start: validityStart },
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
tlogs: [
|
|
142
|
+
{
|
|
143
|
+
baseUrl: rekorURL,
|
|
144
|
+
logId: {
|
|
145
|
+
keyId: createHash('sha256').update(tlog.publicKey).digest('base64'),
|
|
146
|
+
},
|
|
147
|
+
hashAlgorithm: 'SHA2_256',
|
|
148
|
+
publicKey: {
|
|
149
|
+
rawBytes: tlog.publicKey.toString('base64'),
|
|
150
|
+
keyDetails: 'PKIX_ECDSA_P256_SHA_256',
|
|
151
|
+
validFor: { start: validityStart },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
ctlogs: [
|
|
156
|
+
{
|
|
157
|
+
baseUrl: `${fulcioURL}/ctlog`,
|
|
158
|
+
logId: { keyId: toBase64(ctlog.logID) },
|
|
159
|
+
hashAlgorithm: 'SHA2_256',
|
|
160
|
+
publicKey: {
|
|
161
|
+
rawBytes: ctlog.publicKey.toString('base64'),
|
|
162
|
+
keyDetails: 'PKIX_ECDSA_P256_SHA_256',
|
|
163
|
+
validFor: { start: validityStart },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
timestampAuthorities: [],
|
|
168
|
+
})));
|
|
169
|
+
const target = {
|
|
170
|
+
name: 'trusted_root.json',
|
|
171
|
+
content: Buffer.from(trustedRootJSON),
|
|
172
|
+
};
|
|
173
|
+
// `metadataPathPrefix: ''` matches `@sigstore/tuf`'s mirror layout
|
|
174
|
+
// (metadata at the mirror root, targets under /targets).
|
|
175
|
+
const tufRepo = mocktuf(target, { baseURL: tufMirrorURL, metadataPathPrefix: '' });
|
|
176
|
+
return {
|
|
177
|
+
fulcioURL,
|
|
178
|
+
rekorURL,
|
|
179
|
+
signEndpoints: { fulcioURL, rekorURL },
|
|
180
|
+
tuf: {
|
|
181
|
+
tufMirrorURL: tufRepo.baseURL,
|
|
182
|
+
tufCachePath: tufRepo.cachePath,
|
|
183
|
+
tufRootPath: join(tufRepo.cachePath, 'root.json'),
|
|
184
|
+
tufForceCache: true,
|
|
185
|
+
},
|
|
186
|
+
defaultSubject: DEFAULT_SUBJECT,
|
|
187
|
+
defaultIssuer: DEFAULT_ISSUER,
|
|
188
|
+
identityToken(claims = {}) {
|
|
189
|
+
return mintUnsignedJwt({
|
|
190
|
+
sub: DEFAULT_SUBJECT,
|
|
191
|
+
iss: DEFAULT_ISSUER,
|
|
192
|
+
...claims,
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
teardown() {
|
|
196
|
+
nock.cleanAll();
|
|
197
|
+
nock.enableNetConnect();
|
|
198
|
+
tufRepo.teardown();
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Register a persistent nock interceptor for a `@sigstore/mock`
|
|
204
|
+
* service handler. Persistence matters: a suite signs more than once
|
|
205
|
+
* per stack, and nock interceptors are single-use by default.
|
|
206
|
+
*/
|
|
207
|
+
function mount(baseURL, handler) {
|
|
208
|
+
nock(baseURL)
|
|
209
|
+
.persist()
|
|
210
|
+
.post(handler.path)
|
|
211
|
+
.reply(async (_uri, requestBody) => {
|
|
212
|
+
const raw = typeof requestBody === 'string' ? requestBody : JSON.stringify(requestBody);
|
|
213
|
+
const { statusCode, response, contentType } = await handler.fn(raw);
|
|
214
|
+
return [statusCode, response, { 'Content-Type': contentType ?? 'text/plain' }];
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Mint an unsigned JWT-shaped token (`<b64url-header>.<b64url-payload>.`)
|
|
219
|
+
* that JWT decoders parse without signature verification — all the
|
|
220
|
+
* mock Fulcio does with it.
|
|
221
|
+
*/
|
|
222
|
+
function mintUnsignedJwt(payload) {
|
|
223
|
+
const header = base64Url(JSON.stringify({ alg: 'none', typ: 'JWT' }));
|
|
224
|
+
const body = base64Url(JSON.stringify(payload));
|
|
225
|
+
return `${header}.${body}.`;
|
|
226
|
+
}
|
|
227
|
+
function base64Url(value) {
|
|
228
|
+
return Buffer.from(value)
|
|
229
|
+
.toString('base64')
|
|
230
|
+
.replace(/=+$/u, '')
|
|
231
|
+
.replace(/\+/gu, '-')
|
|
232
|
+
.replace(/\//gu, '_');
|
|
233
|
+
}
|
|
234
|
+
/** Base64-encode an ArrayBufferView's exact byte range. */
|
|
235
|
+
function toBase64(view) {
|
|
236
|
+
return Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString('base64');
|
|
237
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/gadget-signing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Gadget bundle signing + verification for the ggui gadget marketplace. Ed25519 author-key path + sigstore/cosign keyless path. Pure-TS @noble crypto for Ed25519 — browser-safe.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -25,16 +25,47 @@
|
|
|
25
25
|
"types": "./dist/index.d.ts",
|
|
26
26
|
"import": "./dist/index.js",
|
|
27
27
|
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./testing": {
|
|
30
|
+
"types": "./dist/testing/index.d.ts",
|
|
31
|
+
"import": "./dist/testing/index.js",
|
|
32
|
+
"default": "./dist/testing/index.js"
|
|
28
33
|
}
|
|
29
34
|
},
|
|
30
35
|
"dependencies": {
|
|
31
36
|
"@noble/ed25519": "^2.2.0",
|
|
32
37
|
"@noble/hashes": "^1.6.0",
|
|
33
38
|
"@sigstore/bundle": "^4.0.0",
|
|
39
|
+
"@sigstore/core": "^3.2.1",
|
|
34
40
|
"sigstore": "^4.0.0"
|
|
35
41
|
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@sigstore/mock": "~0.12.1",
|
|
44
|
+
"@sigstore/protobuf-specs": "^0.5.1",
|
|
45
|
+
"@tufjs/repo-mock": "4.0.1",
|
|
46
|
+
"nock": "^13.5.6"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"@sigstore/mock": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"@sigstore/protobuf-specs": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
55
|
+
"@tufjs/repo-mock": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"nock": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
36
62
|
"devDependencies": {
|
|
63
|
+
"@sigstore/mock": "~0.12.1",
|
|
64
|
+
"@sigstore/protobuf-specs": "^0.5.1",
|
|
65
|
+
"@sigstore/verify": "^3.1.1",
|
|
66
|
+
"@tufjs/repo-mock": "4.0.1",
|
|
37
67
|
"@types/node": "^24.0.0",
|
|
68
|
+
"nock": "^13.5.6",
|
|
38
69
|
"typescript": "^5.0.0",
|
|
39
70
|
"vitest": "^3.2.6"
|
|
40
71
|
},
|