@pipobscure/bundle 0.0.1

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.
Files changed (98) hide show
  1. package/HISTORY.md +1924 -0
  2. package/README.md +623 -0
  3. package/bundle.run +0 -0
  4. package/dist/api.d.ts +147 -0
  5. package/dist/api.d.ts.map +1 -0
  6. package/dist/api.js +174 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/archive.d.ts +115 -0
  9. package/dist/archive.d.ts.map +1 -0
  10. package/dist/archive.js +188 -0
  11. package/dist/archive.js.map +1 -0
  12. package/dist/audit.d.ts +78 -0
  13. package/dist/audit.d.ts.map +1 -0
  14. package/dist/audit.js +119 -0
  15. package/dist/audit.js.map +1 -0
  16. package/dist/cli.d.ts +23 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +555 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/files.d.ts +53 -0
  21. package/dist/files.d.ts.map +1 -0
  22. package/dist/files.js +118 -0
  23. package/dist/files.js.map +1 -0
  24. package/dist/index.d.ts +10 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +35 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/launch.d.ts +97 -0
  29. package/dist/launch.d.ts.map +1 -0
  30. package/dist/launch.js +267 -0
  31. package/dist/launch.js.map +1 -0
  32. package/dist/main.d.ts +3 -0
  33. package/dist/main.d.ts.map +1 -0
  34. package/dist/main.js +19 -0
  35. package/dist/main.js.map +1 -0
  36. package/dist/manifest.d.ts +139 -0
  37. package/dist/manifest.d.ts.map +1 -0
  38. package/dist/manifest.js +504 -0
  39. package/dist/manifest.js.map +1 -0
  40. package/dist/oidc.d.ts +40 -0
  41. package/dist/oidc.d.ts.map +1 -0
  42. package/dist/oidc.js +320 -0
  43. package/dist/oidc.js.map +1 -0
  44. package/dist/preload.d.ts +14 -0
  45. package/dist/preload.d.ts.map +1 -0
  46. package/dist/preload.js +38 -0
  47. package/dist/preload.js.map +1 -0
  48. package/dist/provider.d.ts +83 -0
  49. package/dist/provider.d.ts.map +1 -0
  50. package/dist/provider.js +206 -0
  51. package/dist/provider.js.map +1 -0
  52. package/dist/record.d.ts +2 -0
  53. package/dist/record.d.ts.map +1 -0
  54. package/dist/record.js +23 -0
  55. package/dist/record.js.map +1 -0
  56. package/dist/recorder.d.ts +64 -0
  57. package/dist/recorder.d.ts.map +1 -0
  58. package/dist/recorder.js +111 -0
  59. package/dist/recorder.js.map +1 -0
  60. package/dist/register.d.ts +2 -0
  61. package/dist/register.d.ts.map +1 -0
  62. package/dist/register.js +28 -0
  63. package/dist/register.js.map +1 -0
  64. package/dist/sea.d.ts +97 -0
  65. package/dist/sea.d.ts.map +1 -0
  66. package/dist/sea.js +220 -0
  67. package/dist/sea.js.map +1 -0
  68. package/dist/sigstore.d.ts +112 -0
  69. package/dist/sigstore.d.ts.map +1 -0
  70. package/dist/sigstore.js +385 -0
  71. package/dist/sigstore.js.map +1 -0
  72. package/dist/skill.d.ts +36 -0
  73. package/dist/skill.d.ts.map +1 -0
  74. package/dist/skill.js +108 -0
  75. package/dist/skill.js.map +1 -0
  76. package/package.json +84 -0
  77. package/shell-base +2 -0
  78. package/skills/audit-bundle/SKILL.md +271 -0
  79. package/src/api.ts +293 -0
  80. package/src/archive.ts +312 -0
  81. package/src/audit.ts +206 -0
  82. package/src/cli.ts +575 -0
  83. package/src/files.ts +156 -0
  84. package/src/index.ts +114 -0
  85. package/src/launch.ts +336 -0
  86. package/src/main.ts +20 -0
  87. package/src/manifest.ts +615 -0
  88. package/src/oidc.ts +372 -0
  89. package/src/preload.ts +40 -0
  90. package/src/provider.ts +270 -0
  91. package/src/record.ts +25 -0
  92. package/src/recorder.ts +166 -0
  93. package/src/register.ts +30 -0
  94. package/src/sea.ts +341 -0
  95. package/src/sigstore.ts +492 -0
  96. package/src/skill.ts +132 -0
  97. package/src/types/node-vfs.d.ts +90 -0
  98. package/src/types/node-zip.d.ts +85 -0
package/dist/api.d.ts ADDED
@@ -0,0 +1,147 @@
1
+ import type { Writable } from 'node:stream';
2
+ import { type EmitResult, type Signer } from './archive.ts';
3
+ import { type VerificationResult, type VerifyOptions, type ArchiveSource, type ManifestFields } from './manifest.ts';
4
+ /** What a build wrote, and what it signed. */
5
+ export interface BuildResult extends EmitResult {
6
+ /** Where the archive was written, or null when it went to a stream. */
7
+ output: string | null;
8
+ /** Member names in the finished archive, excluding `AUTHORITY.PEM`. */
9
+ members: string[];
10
+ /** Size of the written file in bytes, when it went to a path. */
11
+ size?: number | undefined;
12
+ }
13
+ /** Where a build's bytes go: a path, a caller's stream, or stdout. */
14
+ interface Destination {
15
+ /** Where to write the archive. */
16
+ output?: string | undefined;
17
+ /** An open stream to write to instead. The caller closes it. */
18
+ stream?: Writable | undefined;
19
+ }
20
+ export interface CreateOptions extends Destination {
21
+ /** Base directory the file list is relative to (default: '.'). */
22
+ base?: string | undefined;
23
+ /** Member names, relative to `base`. */
24
+ files: string[];
25
+ /** A launcher or binary to prepend, making the result self-running. */
26
+ prefix?: string | undefined;
27
+ hashAlg?: string | undefined;
28
+ signAlg?: string | undefined;
29
+ /** Sign as it is built. Both must be given together. */
30
+ key?: Buffer | string | undefined;
31
+ chain?: string | undefined;
32
+ /** A two-phase signer, instead of `key`/`chain`. */
33
+ signer?: Signer | undefined;
34
+ }
35
+ export interface SignOptions extends Destination {
36
+ /** Path to the archive whose members are re-emitted and signed. */
37
+ source: string;
38
+ prefix?: string | undefined;
39
+ /** Make the output executable; implied by `prefix`. */
40
+ executable?: boolean | undefined;
41
+ hashAlg?: string | undefined;
42
+ signAlg?: string | undefined;
43
+ key?: Buffer | string | undefined;
44
+ chain?: string | undefined;
45
+ signer?: Signer | undefined;
46
+ }
47
+ export interface VerifyBundleOptions extends VerifyOptions {
48
+ /**
49
+ * Extra trusted roots, as PEM text or as paths to PEM files — the
50
+ * convenience form of `extraRoots`, which takes PEM text only.
51
+ */
52
+ roots?: string[] | undefined;
53
+ }
54
+ export interface RunOptions {
55
+ /** Extra trusted roots, as PEM text or paths to PEM files. */
56
+ roots?: string[] | undefined;
57
+ identity?: string | undefined;
58
+ issuer?: string | undefined;
59
+ /** Run an archive whose signature is good but whose chain is unanchored. */
60
+ allowUntrusted?: boolean | undefined;
61
+ /** Arguments handed to the application inside the archive. */
62
+ args?: string[] | undefined;
63
+ /** Extra environment for the child, merged over `process.env`. */
64
+ env?: NodeJS.ProcessEnv | undefined;
65
+ /** How the child's stdio is wired (default: 'inherit'). */
66
+ stdio?: 'inherit' | 'pipe' | undefined;
67
+ }
68
+ export interface RunResult {
69
+ /** The child's exit status, or null when it was killed by a signal. */
70
+ status: number | null;
71
+ signal: NodeJS.Signals | null;
72
+ /** Captured only when `stdio` was 'pipe'. */
73
+ stdout?: string | undefined;
74
+ stderr?: string | undefined;
75
+ }
76
+ /** What an archive says about itself, with no trust decision attached. */
77
+ export interface Inspection {
78
+ /** Member names, excluding `AUTHORITY.PEM`. */
79
+ members: string[];
80
+ /** Whether the archive carries a signature marker at all. */
81
+ signed: boolean;
82
+ /** The whole-file hash the marker records, hex. */
83
+ hash?: string | undefined;
84
+ /** Names of the unsigned attributes carried beside the signature. */
85
+ fields: string[];
86
+ /** The manifest's declared algorithms and certificate chain. */
87
+ manifest?: ManifestFields | undefined;
88
+ }
89
+ /**
90
+ * Build an archive from a base directory and a list of files, optionally
91
+ * signing it and optionally prepending a launcher or binary.
92
+ */
93
+ export declare function createBundle(options: CreateOptions): Promise<BuildResult>;
94
+ /**
95
+ * Sign an existing archive into a new file. The input is never modified: its
96
+ * members are read out, laid down again behind whatever prefix was asked for,
97
+ * and the finished bytes are hashed and signed as a whole. One unsigned archive
98
+ * therefore yields every shape — a `#!` launcher, a self-contained binary, or a
99
+ * plain mountable archive — each correctly offset and each signed over itself.
100
+ */
101
+ export declare function signBundle(options: SignOptions): Promise<BuildResult>;
102
+ /** A signer backed by a private key and certificate chain read from disk. */
103
+ export declare function fileSigner({ key, chain, signAlg }: {
104
+ key: string;
105
+ chain: string;
106
+ signAlg?: string | undefined;
107
+ }): Signer;
108
+ /**
109
+ * Verify an archive: recompute the whole-file hash, check the signature over it
110
+ * against the leaf certificate, check every member's own digest, and decide
111
+ * whether the certificate chain means anything to us.
112
+ */
113
+ export declare function verifyBundle(source: ArchiveSource, options?: VerifyBundleOptions): Promise<VerificationResult>;
114
+ /** The synchronous form, for callers on a path that cannot await — a mount. */
115
+ export declare function verifyBundleSync(source: ArchiveSource, options?: VerifyBundleOptions): VerificationResult;
116
+ /**
117
+ * What an archive claims about itself — its members, whether it is signed at
118
+ * all, and what its manifest declares. This is the cheap "what am I looking at"
119
+ * call; `verifyBundle` is the expensive one that answers whether any of it is
120
+ * true, and nothing here should be believed until it has run.
121
+ */
122
+ export declare function inspectBundle(source: string): Inspection;
123
+ /**
124
+ * Mount a signed archive and run the application inside it, in a child process
125
+ * with the verifying provider preloaded — so the archive is checked and mounted
126
+ * by the child's own bootstrap, and what runs is what was verified.
127
+ *
128
+ * Verification happens here too, before the child is spawned. That buys nothing
129
+ * the child does not already enforce; it buys a legible refusal instead of an
130
+ * uncaught error thrown from inside node's startup.
131
+ */
132
+ export declare function runBundle(archive: string, options?: RunOptions): RunResult;
133
+ /**
134
+ * The node arguments that mount `archive` as the filesystem and run the program
135
+ * inside it, with this package's verifying provider preloaded. Anything after
136
+ * these is the application's own argv — the trailing `--` is what makes that
137
+ * true, since without it node claims any argument that looks like one of its
138
+ * own flags and the application never sees it.
139
+ */
140
+ export declare function mountArgv(archive: string): string[];
141
+ /**
142
+ * The absolute path of the preload that registers the verifying provider, as a
143
+ * real path `node -r` can resolve.
144
+ */
145
+ export declare function registerPath(): string;
146
+ export {};
147
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js ADDED
@@ -0,0 +1,174 @@
1
+ import * as FS from 'node:fs';
2
+ import * as PATH from 'node:path';
3
+ import * as ZLIB from 'node:zlib';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { bundle, rebundle, keySigner, members } from './archive.js';
6
+ import { verify, verifySync, signatureOf, parseManifest, AUTHORITY, } from './manifest.js';
7
+ /**
8
+ * Build an archive from a base directory and a list of files, optionally
9
+ * signing it and optionally prepending a launcher or binary.
10
+ */
11
+ export async function createBundle(options) {
12
+ const { base = '.', files, prefix, hashAlg, signAlg, key, chain, signer } = options;
13
+ if (!files.length)
14
+ throw new Error('create: the file list is empty');
15
+ if (!signer && Boolean(key) !== Boolean(chain))
16
+ throw new Error('create: key and chain must be given together');
17
+ return await produce(options, Boolean(prefix), (out) => bundle({
18
+ base, files, prefix, hashAlg, signAlg, key, chain, signer, out,
19
+ }));
20
+ }
21
+ /**
22
+ * Sign an existing archive into a new file. The input is never modified: its
23
+ * members are read out, laid down again behind whatever prefix was asked for,
24
+ * and the finished bytes are hashed and signed as a whole. One unsigned archive
25
+ * therefore yields every shape — a `#!` launcher, a self-contained binary, or a
26
+ * plain mountable archive — each correctly offset and each signed over itself.
27
+ */
28
+ export async function signBundle(options) {
29
+ const { source, output, prefix, executable, hashAlg, signAlg, key, chain, signer } = options;
30
+ if (!source)
31
+ throw new Error('sign: an archive path is required');
32
+ if (!signer && Boolean(key) !== Boolean(chain))
33
+ throw new Error('sign: key and chain must be given together');
34
+ if (output && PATH.resolve(output) === PATH.resolve(source)) {
35
+ throw new Error('sign: the output must differ from the input archive');
36
+ }
37
+ return await produce(options, Boolean(prefix || executable), (out) => rebundle({
38
+ source, prefix, hashAlg, signAlg, key, chain, signer, out,
39
+ }));
40
+ }
41
+ /** A signer backed by a private key and certificate chain read from disk. */
42
+ export function fileSigner({ key, chain, signAlg = 'sha256' }) {
43
+ return keySigner({ key: FS.readFileSync(key), chain: FS.readFileSync(chain, 'utf-8'), signAlg });
44
+ }
45
+ /**
46
+ * Verify an archive: recompute the whole-file hash, check the signature over it
47
+ * against the leaf certificate, check every member's own digest, and decide
48
+ * whether the certificate chain means anything to us.
49
+ */
50
+ export async function verifyBundle(source, options) {
51
+ return verify(source, withRoots(options));
52
+ }
53
+ /** The synchronous form, for callers on a path that cannot await — a mount. */
54
+ export function verifyBundleSync(source, options) {
55
+ return verifySync(source, withRoots(options));
56
+ }
57
+ /**
58
+ * What an archive claims about itself — its members, whether it is signed at
59
+ * all, and what its manifest declares. This is the cheap "what am I looking at"
60
+ * call; `verifyBundle` is the expensive one that answers whether any of it is
61
+ * true, and nothing here should be believed until it has run.
62
+ */
63
+ export function inspectBundle(source) {
64
+ const marker = signatureOf(source);
65
+ const names = members(source);
66
+ let manifest;
67
+ const zip = ZLIB.ZipFile.openSync(PATH.resolve(source));
68
+ try {
69
+ if (zip.has(AUTHORITY))
70
+ manifest = parseManifest(zip.getSync(AUTHORITY).contentSync());
71
+ }
72
+ finally {
73
+ zip.closeSync();
74
+ }
75
+ return {
76
+ members: names,
77
+ signed: marker !== null,
78
+ hash: marker?.hash,
79
+ fields: marker ? [...marker.fields.keys()] : [],
80
+ manifest,
81
+ };
82
+ }
83
+ /**
84
+ * Mount a signed archive and run the application inside it, in a child process
85
+ * with the verifying provider preloaded — so the archive is checked and mounted
86
+ * by the child's own bootstrap, and what runs is what was verified.
87
+ *
88
+ * Verification happens here too, before the child is spawned. That buys nothing
89
+ * the child does not already enforce; it buys a legible refusal instead of an
90
+ * uncaught error thrown from inside node's startup.
91
+ */
92
+ export function runBundle(archive, options = {}) {
93
+ const roots = options.roots ?? [];
94
+ const res = verifyBundleSync(archive, {
95
+ roots, deep: false, identity: options.identity, issuer: options.issuer,
96
+ });
97
+ const acceptable = res.state === 'valid' || (Boolean(options.allowUntrusted) && res.state === 'valid-untrusted');
98
+ if (!acceptable) {
99
+ throw Object.assign(new Error(`refusing to run '${archive}': ${res.state} — ${res.reason}`), { code: 'ERR_BUNDLE_UNTRUSTED', state: res.state });
100
+ }
101
+ const env = { ...process.env, ...options.env };
102
+ if (roots.length)
103
+ env['BUNDLE_ROOTS'] = roots.join(PATH.delimiter);
104
+ if (options.identity)
105
+ env['BUNDLE_IDENTITY'] = options.identity;
106
+ if (options.issuer)
107
+ env['BUNDLE_ISSUER'] = options.issuer;
108
+ if (options.allowUntrusted)
109
+ env['BUNDLE_ALLOW_UNTRUSTED'] = '1';
110
+ const child = spawnSync(process.execPath, [...mountArgv(archive), ...(options.args ?? [])], { stdio: options.stdio ?? 'inherit', env, encoding: 'utf-8' });
111
+ if (child.error)
112
+ throw child.error;
113
+ return { status: child.status, signal: child.signal, stdout: child.stdout, stderr: child.stderr };
114
+ }
115
+ /**
116
+ * The node arguments that mount `archive` as the filesystem and run the program
117
+ * inside it, with this package's verifying provider preloaded. Anything after
118
+ * these is the application's own argv — the trailing `--` is what makes that
119
+ * true, since without it node claims any argument that looks like one of its
120
+ * own flags and the application never sees it.
121
+ */
122
+ export function mountArgv(archive) {
123
+ return [
124
+ '--no-warnings', '--experimental-vfs',
125
+ '-r', registerPath(),
126
+ '--vfs-load', archive,
127
+ '--',
128
+ ];
129
+ }
130
+ /**
131
+ * The absolute path of the preload that registers the verifying provider, as a
132
+ * real path `node -r` can resolve.
133
+ */
134
+ export function registerPath() {
135
+ const ext = import.meta.filename.endsWith('.ts') ? '.ts' : '.js';
136
+ return PATH.join(PATH.dirname(import.meta.filename), `register${ext}`);
137
+ }
138
+ // `roots` may name PEM files or carry PEM text; `verifySync` wants text.
139
+ function withRoots(options) {
140
+ if (!options)
141
+ return {};
142
+ const { roots, ...rest } = options;
143
+ if (!roots?.length)
144
+ return rest;
145
+ const loaded = roots.map((root) => (root.includes('-----BEGIN') ? root : FS.readFileSync(root, 'utf-8')));
146
+ return { ...rest, extraRoots: [...(rest.extraRoots ?? []), ...loaded] };
147
+ }
148
+ // Open the destination, run the build into it, wait for the bytes to land, and
149
+ // report what was written. A stream the caller supplied is left open — its
150
+ // lifetime is theirs — while one opened here is closed and waited on. stdout is
151
+ // ended (so a redirect sees EOF) but not awaited for 'finish', which never
152
+ // fires for a TTY or a pipe.
153
+ async function produce({ output, stream }, executable, build) {
154
+ const out = stream ?? (output ? FS.createWriteStream(output) : process.stdout);
155
+ const res = await build(out);
156
+ if (!stream)
157
+ await close(out);
158
+ if (output && executable)
159
+ FS.chmodSync(output, 0o755);
160
+ return {
161
+ ...res,
162
+ output: output ?? null,
163
+ members: output ? members(output) : [],
164
+ size: output ? FS.statSync(output).size : undefined,
165
+ };
166
+ }
167
+ function close(out) {
168
+ return new Promise((resolve, reject) => {
169
+ if (out === process.stdout)
170
+ return void out.end(() => resolve());
171
+ out.on('error', reject).on('finish', () => resolve()).end();
172
+ });
173
+ }
174
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ import * as ZLIB from 'node:zlib';
2
+ import * as CRYPTO from 'node:crypto';
3
+ import { type Writable } from 'node:stream';
4
+ /** One file about to become an archive member. */
5
+ export interface Member {
6
+ /** The member's name inside the archive; a `/`-separated relative path. */
7
+ name: string;
8
+ data: Buffer;
9
+ mode?: number | undefined;
10
+ }
11
+ /** What a signer hands back once the finished hash exists. */
12
+ export interface Signature {
13
+ signature: Buffer | Uint8Array;
14
+ /**
15
+ * Unsigned attributes to record beside the signature in the EOCD comment —
16
+ * anything obtained *after* signing, which therefore cannot be inside what
17
+ * the signature covers.
18
+ */
19
+ fields?: Record<string, string | undefined> | undefined;
20
+ }
21
+ /**
22
+ * The two-phase signing interface. `chain` is known up front (it goes into
23
+ * `AUTHORITY.PEM`, inside the hashed region); `sign()` is called afterwards
24
+ * with the hash of the finished bytes.
25
+ */
26
+ export interface Signer {
27
+ kind?: string | undefined;
28
+ /** Full PEM certificate chain, leaf first. */
29
+ chain: string;
30
+ /** Digest the signature over the whole-file hash uses. */
31
+ signAlg: string;
32
+ sign(digest: Buffer): Promise<Signature>;
33
+ }
34
+ /** What `emit()` reports once the file has been fully written. */
35
+ export interface EmitResult {
36
+ /** The whole-file hash, hex — null for an unsigned archive. */
37
+ hash: string | null;
38
+ signed: boolean;
39
+ }
40
+ export interface BundleOptions {
41
+ /** Base directory the file list is relative to. */
42
+ base: string;
43
+ /** Member names, relative to `base`. */
44
+ files: string[];
45
+ /** A launcher or binary to prepend, making the result self-running. */
46
+ prefix?: string | undefined;
47
+ /** Digest for the whole-file hash and member digests (default: 'sha256'). */
48
+ hashAlg?: string | undefined;
49
+ /** Digest the signature uses (default: 'sha256'). */
50
+ signAlg?: string | undefined;
51
+ /** Shorthand for `signer: keySigner({ key, chain, signAlg })`. */
52
+ key?: Buffer | string | CRYPTO.KeyObject | undefined;
53
+ chain?: string | undefined;
54
+ signer?: Signer | undefined;
55
+ out: Writable;
56
+ }
57
+ export interface RebundleOptions {
58
+ /** Path to the archive whose members are re-emitted. */
59
+ source: string;
60
+ prefix?: string | undefined;
61
+ hashAlg?: string | undefined;
62
+ signAlg?: string | undefined;
63
+ key?: Buffer | string | CRYPTO.KeyObject | undefined;
64
+ chain?: string | undefined;
65
+ signer?: Signer | undefined;
66
+ out: Writable;
67
+ }
68
+ export declare function fromDirectory(base: string, files: string[]): AsyncGenerator<Member>;
69
+ export declare function fromArchive(zip: ZLIB.ZipFile): AsyncGenerator<Member>;
70
+ /**
71
+ * A Readable of the ZIP archive over `members`. Its members carry per-file
72
+ * digests and its AUTHORITY.PEM manifest declares the algorithms and chain, but
73
+ * the archive itself is left with an empty EOCD comment: the whole-file hash
74
+ * and its signature are a property of the finished file and are applied by
75
+ * `emit()`. `baseOffset` seeds the archive's internal offsets for when it is
76
+ * appended after a prefix.
77
+ */
78
+ export declare function createArchive({ members, base, files, hashAlg, signAlg, chain, baseOffset }: {
79
+ members?: AsyncIterable<Member> | Iterable<Member> | undefined;
80
+ base?: string | undefined;
81
+ files?: string[] | undefined;
82
+ hashAlg?: string | undefined;
83
+ signAlg?: string | undefined;
84
+ chain?: string | undefined;
85
+ baseOffset?: number | undefined;
86
+ }): import("node:stream").Readable;
87
+ /**
88
+ * A signer backed by a private key and a certificate chain already on disk —
89
+ * the offline-CA path, and the shape `sigstore.ts` implements too.
90
+ */
91
+ export declare function keySigner({ key, chain, signAlg }: {
92
+ key: Buffer | string | CRYPTO.KeyObject;
93
+ chain: Buffer | string;
94
+ signAlg?: string | undefined;
95
+ }): Signer;
96
+ /**
97
+ * Build an archive from files on disk. `key`/`chain` are accepted as a
98
+ * shorthand for `signer: keySigner({ key, chain, signAlg })`, so the
99
+ * create-and-sign-in-one-step path stays available.
100
+ */
101
+ export declare function bundle({ base, files, prefix, hashAlg, signAlg, key, chain, signer, out }: BundleOptions): Promise<EmitResult>;
102
+ /**
103
+ * Re-emit an existing archive: same members, new prefix, new signature. This is
104
+ * what `bundle sign` runs. `source` is a path to an archive (signed or not,
105
+ * prefixed or not) — its members are read out, its old AUTHORITY.PEM is
106
+ * dropped, and everything is laid down again at the offsets the new prefix
107
+ * implies before the result is hashed and signed as a whole.
108
+ */
109
+ export declare function rebundle({ source, prefix, hashAlg, signAlg, key, chain, signer, out }: RebundleOptions): Promise<EmitResult>;
110
+ /**
111
+ * The member names an archive holds, in order, excluding AUTHORITY.PEM — for
112
+ * reporting what is about to be re-signed without reading every member's bytes.
113
+ */
114
+ export declare function members(source: string): string[];
115
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,188 @@
1
+ import * as ZLIB from 'node:zlib';
2
+ import * as PATH from 'node:path';
3
+ import * as FS from 'node:fs';
4
+ import * as CRYPTO from 'node:crypto';
5
+ import { Transform } from 'node:stream';
6
+ import { buildManifest, formatSignature, AUTHORITY } from './manifest.js';
7
+ // Members, as `{ name, data, mode }`. Two sources: a directory plus a file
8
+ // list, or an existing archive.
9
+ //
10
+ // `AUTHORITY.PEM` is never carried across from a source archive — it describes
11
+ // the signing of the archive it came from, and a re-emitted archive gets a
12
+ // fresh one.
13
+ export async function* fromDirectory(base, files) {
14
+ for (const name of files) {
15
+ yield { name, data: FS.readFileSync(PATH.resolve(base, name)), mode: 0o444 };
16
+ }
17
+ }
18
+ export async function* fromArchive(zip) {
19
+ for (const [name, entry] of zip.entriesSync()) {
20
+ if (name === AUTHORITY || entry.isDirectory)
21
+ continue;
22
+ yield { name, data: entry.contentSync(), mode: entry.mode || 0o444 };
23
+ }
24
+ }
25
+ // Yields a ZipEntry per member — each stamped, in its entry comment, with the
26
+ // hex digest of its own content — then a final `AUTHORITY.PEM` manifest entry
27
+ // declaring the algorithms and (when signing) carrying the certificate chain.
28
+ // Members are small (an application's own files; the heavy runtime is the
29
+ // prepended prefix, not an archive member), so each is held whole to hash it
30
+ // before the entry, whose comment must be fixed at creation time, is built.
31
+ async function* entries(members, { hashAlg, signAlg, chain }) {
32
+ for await (const { name, data, mode } of members) {
33
+ const digest = CRYPTO.createHash(hashAlg).update(data).digest('hex');
34
+ yield await ZLIB.ZipEntry.create(name, data, { mode: mode ?? 0o444, comment: digest });
35
+ }
36
+ yield await ZLIB.ZipEntry.create(AUTHORITY, buildManifest({ hashAlg, signAlg, chain }), { mode: 0o444 });
37
+ }
38
+ /**
39
+ * A Readable of the ZIP archive over `members`. Its members carry per-file
40
+ * digests and its AUTHORITY.PEM manifest declares the algorithms and chain, but
41
+ * the archive itself is left with an empty EOCD comment: the whole-file hash
42
+ * and its signature are a property of the finished file and are applied by
43
+ * `emit()`. `baseOffset` seeds the archive's internal offsets for when it is
44
+ * appended after a prefix.
45
+ */
46
+ export function createArchive({ members, base, files, hashAlg = 'sha256', signAlg, chain, baseOffset = 0 }) {
47
+ const source = members ?? fromDirectory(base ?? '.', files ?? []);
48
+ return ZLIB.createZipArchive(entries(source, { hashAlg, signAlg, chain }), { baseOffset });
49
+ }
50
+ /**
51
+ * A signer backed by a private key and a certificate chain already on disk —
52
+ * the offline-CA path, and the shape `sigstore.ts` implements too.
53
+ */
54
+ export function keySigner({ key, chain, signAlg = 'sha256' }) {
55
+ return {
56
+ kind: 'key',
57
+ chain: String(chain),
58
+ signAlg,
59
+ async sign(digest) {
60
+ return { signature: Buffer.from(CRYPTO.sign(signAlg, digest, key)) };
61
+ },
62
+ };
63
+ }
64
+ // Writes `prefix` (when given) then the archive to `out`, without closing
65
+ // `out`. With no prefix the result is a plain archive — a `.bundle` meant to be
66
+ // run through `--vfs-load`; with one it is a self-running container (a shebang
67
+ // launcher or a SEA binary) that carries the same archive in its tail. When a
68
+ // `signer` is given, the whole file is signed. The hash runs over the prefix
69
+ // and then over the archive up to (but not including) the EOCD comment; that
70
+ // hash is what the signer signs. The EOCD comment records both, so a verifier
71
+ // can validate the hash on its own (a cheap pre-mount integrity gate) and only
72
+ // then check the signature over that hash against the certificate:
73
+ //
74
+ // SIGNED:<hash-of-region-hex>:<signature-hex>[:<NAME>=<value>]*
75
+ async function emit({ members, prefix, hashAlg = 'sha256', signer, out }) {
76
+ const hasher = signer ? CRYPTO.createHash(hashAlg) : null;
77
+ // 1. Stream the prefix straight to `out`, feeding the whole-file hash.
78
+ if (prefix)
79
+ await prepend(prefix, out, hasher);
80
+ // 2. Build the archive (small) with an empty EOCD comment, in memory. The
81
+ // chain has to be embedded here, before anything is hashed — which is
82
+ // why a signer hands over its certificate up front and signs later.
83
+ const archive = await collect(createArchive({
84
+ members, hashAlg,
85
+ signAlg: signer?.signAlg,
86
+ chain: signer?.chain,
87
+ baseOffset: prefix ? FS.statSync(prefix).size : 0,
88
+ }));
89
+ if (!signer || !hasher) {
90
+ await write(out, archive);
91
+ return { hash: null, signed: false };
92
+ }
93
+ // 3. The signed region is the prefix plus the archive minus its trailing
94
+ // 2-byte (empty) comment-length field. Hash it, sign the hash, and
95
+ // re-emit the archive with the marker as the EOCD comment. Anything the
96
+ // signer produced *after* signing — a transparency-log entry, a
97
+ // timestamp — comes back as fields and rides in the same comment, since
98
+ // it could not have been inside the hash it postdates.
99
+ const region = archive.subarray(0, archive.length - 2);
100
+ hasher.update(region);
101
+ const digest = hasher.digest();
102
+ const { signature, fields } = await signer.sign(digest);
103
+ const marker = formatSignature({
104
+ hash: digest.toString('hex'),
105
+ sig: Buffer.from(signature).toString('hex'),
106
+ fields,
107
+ });
108
+ const comment = Buffer.from(marker, 'ascii');
109
+ if (comment.length > 0xffff) {
110
+ throw new Error(`signature marker is ${comment.length} bytes; a ZIP comment holds at most 65535`);
111
+ }
112
+ const length = Buffer.alloc(2);
113
+ length.writeUInt16LE(comment.length, 0);
114
+ await write(out, Buffer.concat([region, length, comment]));
115
+ return { hash: digest.toString('hex'), signed: true };
116
+ }
117
+ /**
118
+ * Build an archive from files on disk. `key`/`chain` are accepted as a
119
+ * shorthand for `signer: keySigner({ key, chain, signAlg })`, so the
120
+ * create-and-sign-in-one-step path stays available.
121
+ */
122
+ export async function bundle({ base, files, prefix, hashAlg = 'sha256', signAlg = 'sha256', key, chain, signer, out }) {
123
+ const active = signer ?? (key && chain ? keySigner({ key, chain, signAlg }) : undefined);
124
+ return emit({ members: fromDirectory(base, files), prefix, hashAlg, signer: active, out });
125
+ }
126
+ /**
127
+ * Re-emit an existing archive: same members, new prefix, new signature. This is
128
+ * what `bundle sign` runs. `source` is a path to an archive (signed or not,
129
+ * prefixed or not) — its members are read out, its old AUTHORITY.PEM is
130
+ * dropped, and everything is laid down again at the offsets the new prefix
131
+ * implies before the result is hashed and signed as a whole.
132
+ */
133
+ export async function rebundle({ source, prefix, hashAlg = 'sha256', signAlg = 'sha256', key, chain, signer, out }) {
134
+ const active = signer ?? (key && chain ? keySigner({ key, chain, signAlg }) : undefined);
135
+ const zip = ZLIB.ZipFile.openSync(PATH.resolve(source));
136
+ try {
137
+ // The member list is drained into memory before writing starts: the
138
+ // source archive may be the file being overwritten, and in any case the
139
+ // entries have to outlive the ZipFile handle closed below.
140
+ const members = [];
141
+ for await (const member of fromArchive(zip))
142
+ members.push(member);
143
+ if (!members.length)
144
+ throw new Error(`'${source}' contains no members to sign`);
145
+ return await emit({ members, prefix, hashAlg, signer: active, out });
146
+ }
147
+ finally {
148
+ zip.closeSync();
149
+ }
150
+ }
151
+ /**
152
+ * The member names an archive holds, in order, excluding AUTHORITY.PEM — for
153
+ * reporting what is about to be re-signed without reading every member's bytes.
154
+ */
155
+ export function members(source) {
156
+ const zip = ZLIB.ZipFile.openSync(PATH.resolve(source));
157
+ try {
158
+ return [...zip.entriesSync()]
159
+ .filter(([name, entry]) => name !== AUTHORITY && !entry.isDirectory)
160
+ .map(([name]) => name);
161
+ }
162
+ finally {
163
+ zip.closeSync();
164
+ }
165
+ }
166
+ function prepend(file, out, sink) {
167
+ return new Promise((resolve, reject) => {
168
+ const tap = new Transform({
169
+ transform(chunk, _enc, cb) { if (sink)
170
+ sink.update(chunk); cb(null, chunk); },
171
+ });
172
+ FS.createReadStream(file).on('error', reject)
173
+ .pipe(tap).on('error', reject).on('end', () => resolve())
174
+ .pipe(out, { end: false });
175
+ });
176
+ }
177
+ async function collect(readable) {
178
+ const chunks = [];
179
+ for await (const chunk of readable)
180
+ chunks.push(chunk);
181
+ return Buffer.concat(chunks);
182
+ }
183
+ function write(out, buffer) {
184
+ return new Promise((resolve, reject) => {
185
+ out.write(buffer, (err) => (err ? reject(err) : resolve()));
186
+ });
187
+ }
188
+ //# sourceMappingURL=archive.js.map