@ternent/seal-cli 0.1.6

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/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # @ternent/seal-cli
2
+
3
+ Seal signs files and manifests. It verifies artifacts offline. It emits portable proof JSON that `apps/proof` can verify in the browser without duplicate crypto logic.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add -D @ternent/seal-cli
9
+ ```
10
+
11
+ ## Environment
12
+
13
+ `@ternent/seal-cli` reads signer material from environment variables:
14
+
15
+ ```bash
16
+ export SEAL_PRIVATE_KEY="$(cat private-key.pem)"
17
+ export SEAL_PUBLIC_KEY="$(cat public-key.pem)"
18
+ ```
19
+
20
+ `SEAL_PUBLIC_KEY` is optional. If it is present, Seal verifies that it matches `SEAL_PRIVATE_KEY`.
21
+
22
+ ## Commands
23
+
24
+ ```bash
25
+ seal manifest create --input apps/proof/dist --out apps/proof/dist/dist-manifest.json
26
+ seal sign --input apps/proof/dist/dist-manifest.json --out apps/proof/dist/proof.json
27
+ seal verify --proof apps/proof/dist/proof.json --input apps/proof/dist/dist-manifest.json --json
28
+ seal public-key --json
29
+ ```
30
+
31
+ ## GitHub Actions
32
+
33
+ Use the published GitHub Action:
34
+
35
+ ```yaml
36
+ - name: Generate Seal artifacts
37
+ uses: samternent/seal-action@1.0.0
38
+ env:
39
+ SEAL_PRIVATE_KEY: ${{ secrets.SEAL_PRIVATE_KEY }}
40
+ SEAL_PUBLIC_KEY: ${{ secrets.SEAL_PUBLIC_KEY }}
41
+ with:
42
+ assets-directory: dist
43
+ package-name: @ternent/seal-cli
44
+ package-version: latest
45
+ ```
46
+
47
+ The action is intentionally narrow. Your workflow still needs to:
48
+
49
+ - check out the repo
50
+ - set up Node
51
+ - build the static directory you want to sign
52
+
53
+ Inputs:
54
+
55
+ - `assets-directory`: built static directory to sign
56
+ - `working-directory`: base directory for path resolution
57
+ - `manifest-name`: manifest output filename
58
+ - `proof-name`: proof output filename
59
+ - `public-key-name`: public key output filename
60
+ - `package-name`: npm package name to execute when `cli-command` is omitted
61
+ - `package-version`: npm version or dist-tag to execute when `cli-command` is omitted
62
+ - `cli-command`: command prefix used to invoke Seal
63
+
64
+ Outputs:
65
+
66
+ - `manifest-path`
67
+ - `proof-path`
68
+ - `public-key-path`
69
+
70
+ The default path is npm-backed: if `cli-command` is empty, the action runs `npm exec --yes --package=<package-name>@<package-version> seal`.
71
+
72
+ ## Schemas
73
+
74
+ Manifest:
75
+
76
+ ```json
77
+ {
78
+ "version": "1",
79
+ "type": "seal-manifest",
80
+ "root": "dist",
81
+ "files": {
82
+ "assets/index.js": "sha256:..."
83
+ }
84
+ }
85
+ ```
86
+
87
+ Proof:
88
+
89
+ ```json
90
+ {
91
+ "version": "1",
92
+ "type": "seal-proof",
93
+ "algorithm": "ECDSA-P256-SHA256",
94
+ "createdAt": "2026-03-13T00:00:00.000Z",
95
+ "subject": {
96
+ "kind": "manifest",
97
+ "path": "dist-manifest.json",
98
+ "hash": "sha256:..."
99
+ },
100
+ "signer": {
101
+ "publicKey": "BASE64-SPKI",
102
+ "keyId": "..."
103
+ },
104
+ "signature": "..."
105
+ }
106
+ ```
107
+
108
+ Public key:
109
+
110
+ ```json
111
+ {
112
+ "algorithm": "ECDSA-P256-SHA256",
113
+ "publicKey": "BASE64-SPKI",
114
+ "keyId": "..."
115
+ }
116
+ ```
117
+
118
+ ## Frontend Contract
119
+
120
+ `apps/proof` verifies published artifacts by fetching:
121
+
122
+ - `/dist-manifest.json`
123
+ - `/proof.json`
124
+ - `/public-key.json` (optional)
125
+
126
+ Browser verification reuses `@ternent/seal-cli/proof`, `@ternent/seal-cli/crypto`, `ternent-identity`, and `ternent-utils`.
127
+
128
+ Validation rules:
129
+
130
+ - parse `seal-proof`
131
+ - recompute the fetched manifest hash from raw bytes
132
+ - verify the embedded signature
133
+ - verify `signer.keyId`
134
+ - if `/public-key.json` exists, require its `publicKey` and `keyId` to match the proof signer
135
+
136
+ ## Exit Codes
137
+
138
+ - `0` success
139
+ - `1` general failure
140
+ - `2` subject hash mismatch
141
+ - `3` signature invalid
142
+ - `4` invalid proof
143
+ - `5` key or config error
package/bin/seal ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../dist/cli.js";
3
+
4
+ runCli(process.argv.slice(2)).then((exitCode) => {
5
+ process.exitCode = exitCode;
6
+ });
@@ -0,0 +1,56 @@
1
+ function formatIdentityKey(key) {
2
+ return `-----BEGIN PUBLIC KEY-----
3
+ ${key}
4
+ -----END PUBLIC KEY-----`;
5
+ }
6
+ function getHashArray(hash) {
7
+ return Array.from(new Uint8Array(hash));
8
+ }
9
+ function getHashHex(hash) {
10
+ return hash.map((buf) => buf.toString(16).padStart(2, "0")).join("");
11
+ }
12
+ function canonicalize(value, seen) {
13
+ if (value === void 0) {
14
+ throw new TypeError("Cannot hash undefined");
15
+ }
16
+ const valueType = typeof value;
17
+ if (valueType === "function" || valueType === "symbol") {
18
+ throw new TypeError(`Cannot hash ${valueType} values`);
19
+ }
20
+ if (value === null || valueType === "string" || valueType === "number" || valueType === "boolean") {
21
+ return value;
22
+ }
23
+ if (valueType === "bigint") {
24
+ throw new TypeError("Cannot hash bigint values");
25
+ }
26
+ if (typeof value.toJSON === "function") {
27
+ return canonicalize(value.toJSON(), seen);
28
+ }
29
+ if (Array.isArray(value)) {
30
+ return value.map((item) => canonicalize(item, seen));
31
+ }
32
+ if (typeof value === "object") {
33
+ if (seen.has(value)) {
34
+ throw new TypeError("Cannot hash circular references");
35
+ }
36
+ seen.add(value);
37
+ const entries = Object.keys(value).sort();
38
+ const result = {};
39
+ for (const key of entries) {
40
+ result[key] = canonicalize(value[key], seen);
41
+ }
42
+ seen.delete(value);
43
+ return result;
44
+ }
45
+ throw new TypeError(`Cannot hash unsupported value type: ${valueType}`);
46
+ }
47
+ function canonicalStringify(data) {
48
+ return JSON.stringify(canonicalize(data, /* @__PURE__ */ new WeakSet()));
49
+ }
50
+ async function hashBytes(input) {
51
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
52
+ const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
53
+ return getHashHex(getHashArray(hashBuffer));
54
+ }
55
+ export { canonicalStringify as c, formatIdentityKey as f, hashBytes as h };
56
+ //# sourceMappingURL=utils.es-586f669f.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.es-586f669f.js","sources":["../../../utils/dist/utils.es.js"],"sourcesContent":["function getBufferCtor() {\n const maybeBuffer = globalThis.Buffer;\n return maybeBuffer != null ? maybeBuffer : null;\n}\nfunction addNewLines(str) {\n let finalString = \"\";\n while (str.length > 0) {\n finalString += str.substring(0, 64) + \"\\n\";\n str = str.substring(64);\n }\n return finalString;\n}\nfunction removeLines(str) {\n return str.replaceAll(\"\\n\", \"\");\n}\nfunction stripIdentityKey(key) {\n return key.replace(\"-----BEGIN PUBLIC KEY-----\\n\", \"\").replace(\"\\n-----END PUBLIC KEY-----\", \"\");\n}\nfunction formatIdentityKey(key) {\n return `-----BEGIN PUBLIC KEY-----\n${key}\n-----END PUBLIC KEY-----`;\n}\nfunction stripEncryptionFile(file) {\n return file.replace(\"-----BEGIN AGE ENCRYPTED FILE-----\\n\", \"\").replace(\"\\n-----END AGE ENCRYPTED FILE-----\\n\", \"\");\n}\nfunction formatEncryptionFile(file) {\n return `-----BEGIN AGE ENCRYPTED FILE-----\n${file}\n-----END AGE ENCRYPTED FILE-----\n`;\n}\nfunction generateId() {\n const uint32 = crypto.getRandomValues(new Uint32Array(1))[0];\n return uint32.toString(16);\n}\nfunction arrayBufferToBase64(arrayBuffer) {\n const byteArray = new Uint8Array(arrayBuffer);\n const bufferCtor = getBufferCtor();\n if (bufferCtor) {\n return bufferCtor.from(byteArray).toString(\"base64\");\n }\n let byteString = \"\";\n for (let i = 0; i < byteArray.byteLength; i++) {\n byteString += String.fromCharCode(byteArray[i]);\n }\n return btoa(byteString);\n}\nfunction base64ToArrayBuffer(b64) {\n const bufferCtor = getBufferCtor();\n if (bufferCtor) {\n const bytes = Uint8Array.from(\n bufferCtor.from(b64, \"base64\").toString(\"binary\"),\n (char) => char.charCodeAt(0)\n );\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength\n );\n }\n const byteString = atob(b64);\n const byteArray = new Uint8Array(byteString.length);\n for (let i = 0; i < byteString.length; i++) {\n byteArray[i] = byteString.charCodeAt(i);\n }\n return byteArray.buffer;\n}\nfunction b64encode(buf) {\n return arrayBufferToBase64(buf);\n}\nfunction b64decode(str) {\n return base64ToArrayBuffer(str);\n}\nfunction encode(data) {\n const payload = typeof data === \"string\" ? data : JSON.stringify(data);\n return new TextEncoder().encode(payload);\n}\nfunction decode(data) {\n return new TextDecoder(\"utf-8\").decode(new Uint8Array(data));\n}\nfunction getHashBuffer(data) {\n return crypto.subtle.digest(\"SHA-256\", encode(data));\n}\nfunction getHashArray(hash) {\n return Array.from(new Uint8Array(hash));\n}\nfunction getHashHex(hash) {\n return hash.map((buf) => buf.toString(16).padStart(2, \"0\")).join(\"\");\n}\nfunction canonicalize(value, seen) {\n if (value === void 0) {\n throw new TypeError(\"Cannot hash undefined\");\n }\n const valueType = typeof value;\n if (valueType === \"function\" || valueType === \"symbol\") {\n throw new TypeError(`Cannot hash ${valueType} values`);\n }\n if (value === null || valueType === \"string\" || valueType === \"number\" || valueType === \"boolean\") {\n return value;\n }\n if (valueType === \"bigint\") {\n throw new TypeError(\"Cannot hash bigint values\");\n }\n if (typeof value.toJSON === \"function\") {\n return canonicalize(value.toJSON(), seen);\n }\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item, seen));\n }\n if (typeof value === \"object\") {\n if (seen.has(value)) {\n throw new TypeError(\"Cannot hash circular references\");\n }\n seen.add(value);\n const entries = Object.keys(value).sort();\n const result = {};\n for (const key of entries) {\n result[key] = canonicalize(value[key], seen);\n }\n seen.delete(value);\n return result;\n }\n throw new TypeError(`Cannot hash unsupported value type: ${valueType}`);\n}\nfunction canonicalStringify(data) {\n return JSON.stringify(canonicalize(data, /* @__PURE__ */ new WeakSet()));\n}\nasync function hashData(data) {\n const hash_buffer = await getHashBuffer(canonicalStringify(data));\n const hash_array = getHashArray(hash_buffer);\n return getHashHex(hash_array);\n}\nasync function hashBytes(input) {\n const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", bytes);\n return getHashHex(getHashArray(hashBuffer));\n}\nfunction generateColorStops(primaryColor, secondaryColor, steps) {\n const colors = [];\n for (let i = 0; i <= steps; i++) {\n const ratio = i / steps;\n const color = lerpColor(primaryColor, secondaryColor, ratio);\n colors.push(color);\n }\n return colors;\n}\nfunction lerpColor(color1, color2, ratio) {\n const r1 = parseInt(color1.substring(1, 3), 16);\n const g1 = parseInt(color1.substring(3, 5), 16);\n const b1 = parseInt(color1.substring(5, 7), 16);\n const r2 = parseInt(color2.substring(1, 3), 16);\n const g2 = parseInt(color2.substring(3, 5), 16);\n const b2 = parseInt(color2.substring(5, 7), 16);\n const r = Math.round(r1 + (r2 - r1) * ratio);\n const g = Math.round(g1 + (g2 - g1) * ratio);\n const b = Math.round(b1 + (b2 - b1) * ratio);\n return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1).toUpperCase()}`;\n}\nexport { addNewLines, arrayBufferToBase64, b64decode, b64encode, base64ToArrayBuffer, canonicalStringify, decode, encode, formatEncryptionFile, formatIdentityKey, generateColorStops, generateId, getHashArray, getHashBuffer, getHashHex, hashBytes, hashData, removeLines, stripEncryptionFile, stripIdentityKey };\n"],"names":[],"mappings":"AAkBA,SAAS,kBAAkB,KAAK;AAC9B,SAAO;AAAA,EACP;AAAA;AAEF;AA6DA,SAAS,aAAa,MAAM;AAC1B,SAAO,MAAM,KAAK,IAAI,WAAW,IAAI,CAAC;AACxC;AACA,SAAS,WAAW,MAAM;AACxB,SAAO,KAAK,IAAI,CAAC,QAAQ,IAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACrE;AACA,SAAS,aAAa,OAAO,MAAM;AACjC,MAAI,UAAU,QAAQ;AACpB,UAAM,IAAI,UAAU,uBAAuB;AAAA,EAC5C;AACD,QAAM,YAAY,OAAO;AACzB,MAAI,cAAc,cAAc,cAAc,UAAU;AACtD,UAAM,IAAI,UAAU,eAAe,kBAAkB;AAAA,EACtD;AACD,MAAI,UAAU,QAAQ,cAAc,YAAY,cAAc,YAAY,cAAc,WAAW;AACjG,WAAO;AAAA,EACR;AACD,MAAI,cAAc,UAAU;AAC1B,UAAM,IAAI,UAAU,2BAA2B;AAAA,EAChD;AACD,MAAI,OAAO,MAAM,WAAW,YAAY;AACtC,WAAO,aAAa,MAAM,OAAQ,GAAE,IAAI;AAAA,EACzC;AACD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,SAAS,aAAa,MAAM,IAAI,CAAC;AAAA,EACpD;AACD,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,KAAK,IAAI,KAAK,GAAG;AACnB,YAAM,IAAI,UAAU,iCAAiC;AAAA,IACtD;AACD,SAAK,IAAI,KAAK;AACd,UAAM,UAAU,OAAO,KAAK,KAAK,EAAE,KAAI;AACvC,UAAM,SAAS,CAAA;AACf,eAAW,OAAO,SAAS;AACzB,aAAO,OAAO,aAAa,MAAM,MAAM,IAAI;AAAA,IAC5C;AACD,SAAK,OAAO,KAAK;AACjB,WAAO;AAAA,EACR;AACD,QAAM,IAAI,UAAU,uCAAuC,WAAW;AACxE;AACA,SAAS,mBAAmB,MAAM;AAChC,SAAO,KAAK,UAAU,aAAa,MAAsB,oBAAI,QAAS,CAAA,CAAC;AACzE;AAMA,eAAe,UAAU,OAAO;AAC9B,QAAM,QAAQ,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;AACxE,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK;AAC9D,SAAO,WAAW,aAAa,UAAU,CAAC;AAC5C;;"}
package/dist/cli.js ADDED
@@ -0,0 +1,296 @@
1
+ import { stat, readFile, readdir, mkdir, writeFile } from "node:fs/promises";
2
+ import { resolve, basename, join, relative, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { createSealHash, createSealProof, createSealPublicKeyArtifact, parseSealProofJson, verifySealProofAgainstBytes } from "./proof.js";
5
+ import { SEAL_MANIFEST_VERSION, SEAL_MANIFEST_TYPE, stringifySealManifest, parseSealManifestJson } from "./manifest.js";
6
+ import { EXIT_SUCCESS, EXIT_HASH_MISMATCH, EXIT_SIGNATURE_INVALID, SealCliError, EXIT_FAILURE, EXIT_INVALID_PROOF, EXIT_KEY_CONFIG, getExitCode } from "./errors.js";
7
+ import "./chunks/utils.es-586f669f.js";
8
+ import "./crypto.js";
9
+ function normalizeRelativePath(value) {
10
+ return value.split("\\").join("/");
11
+ }
12
+ async function collectFiles(rootPath, currentPath, files) {
13
+ const entries = await readdir(currentPath, { withFileTypes: true });
14
+ const sorted = entries.filter((entry) => entry.name !== ".DS_Store").sort((a, b) => a.name.localeCompare(b.name));
15
+ for (const entry of sorted) {
16
+ const entryPath = join(currentPath, entry.name);
17
+ if (entry.isDirectory()) {
18
+ await collectFiles(rootPath, entryPath, files);
19
+ continue;
20
+ }
21
+ if (!entry.isFile()) {
22
+ continue;
23
+ }
24
+ const bytes = await readFile(entryPath);
25
+ const relativePath = normalizeRelativePath(relative(rootPath, entryPath));
26
+ files[relativePath] = await createSealHash(bytes);
27
+ }
28
+ }
29
+ async function createManifestArtifact(inputPath) {
30
+ const resolvedInput = resolve(inputPath);
31
+ const inputStat = await stat(resolvedInput);
32
+ const root = basename(resolvedInput);
33
+ const files = {};
34
+ if (inputStat.isDirectory()) {
35
+ await collectFiles(resolvedInput, resolvedInput, files);
36
+ } else if (inputStat.isFile()) {
37
+ const bytes = await readFile(resolvedInput);
38
+ files[root] = await createSealHash(bytes);
39
+ } else {
40
+ throw new Error("Manifest input must be a file or directory.");
41
+ }
42
+ const manifest = {
43
+ version: SEAL_MANIFEST_VERSION,
44
+ type: SEAL_MANIFEST_TYPE,
45
+ root,
46
+ files: Object.fromEntries(
47
+ Object.entries(files).sort(([left], [right]) => left.localeCompare(right))
48
+ )
49
+ };
50
+ return {
51
+ manifest,
52
+ content: `${stringifySealManifest(manifest)}
53
+ `
54
+ };
55
+ }
56
+ async function createProofArtifact(params) {
57
+ const bytes = await readFile(params.inputPath);
58
+ const raw = new TextDecoder().decode(bytes);
59
+ const parsedManifest = parseSealManifestJson(raw);
60
+ const proof = await createSealProof({
61
+ signer: {
62
+ privateKeyPem: params.privateKeyPem,
63
+ publicKeyPem: params.publicKeyPem
64
+ },
65
+ subject: {
66
+ kind: parsedManifest.ok ? "manifest" : "file",
67
+ path: basename(params.inputPath),
68
+ hash: await createSealHash(bytes)
69
+ }
70
+ });
71
+ return {
72
+ proof,
73
+ content: `${JSON.stringify(proof, null, 2)}
74
+ `
75
+ };
76
+ }
77
+ async function createPublicKeyArtifact(params) {
78
+ return createSealPublicKeyArtifact({
79
+ privateKeyPem: params.privateKeyPem,
80
+ publicKeyPem: params.publicKeyPem
81
+ });
82
+ }
83
+ async function verifyProofArtifact(params) {
84
+ const rawProof = await readFile(params.proofPath, "utf8");
85
+ const parsed = parseSealProofJson(rawProof);
86
+ if (!parsed.ok || !parsed.proof) {
87
+ throw new Error(parsed.errors.join(" "));
88
+ }
89
+ const subjectBytes = await readFile(params.inputPath);
90
+ const result = await verifySealProofAgainstBytes(parsed.proof, subjectBytes);
91
+ return {
92
+ proof: parsed.proof,
93
+ result
94
+ };
95
+ }
96
+ function parseArgs(argv) {
97
+ const result = { _: [], flags: {} };
98
+ for (let index = 0; index < argv.length; index += 1) {
99
+ const arg = argv[index];
100
+ if (!arg.startsWith("--")) {
101
+ result._.push(arg);
102
+ continue;
103
+ }
104
+ const [rawKey, inlineValue] = arg.slice(2).split("=");
105
+ const key = rawKey.trim();
106
+ const next = argv[index + 1];
107
+ const hasNextValue = inlineValue === void 0 && next && !next.startsWith("--");
108
+ const value = inlineValue ?? (hasNextValue ? next : void 0);
109
+ if (hasNextValue) {
110
+ index += 1;
111
+ }
112
+ if (value === void 0) {
113
+ result.flags[key] = true;
114
+ continue;
115
+ }
116
+ const existing = result.flags[key];
117
+ if (existing === void 0) {
118
+ result.flags[key] = value;
119
+ continue;
120
+ }
121
+ if (Array.isArray(existing)) {
122
+ existing.push(value);
123
+ result.flags[key] = existing;
124
+ continue;
125
+ }
126
+ result.flags[key] = [String(existing), value];
127
+ }
128
+ return result;
129
+ }
130
+ function getFlag(flags, key) {
131
+ const value = flags[key];
132
+ if (Array.isArray(value)) {
133
+ return value[value.length - 1];
134
+ }
135
+ return typeof value === "string" ? value : void 0;
136
+ }
137
+ function hasFlag(flags, key) {
138
+ return flags[key] === true;
139
+ }
140
+ function requireFlag(flags, key) {
141
+ const value = getFlag(flags, key);
142
+ if (!value) {
143
+ throw new SealCliError(`Missing --${key}`, EXIT_FAILURE);
144
+ }
145
+ return value;
146
+ }
147
+ function getEnvVar(env, name) {
148
+ const value = env[name];
149
+ return value && value.trim().length > 0 ? value : void 0;
150
+ }
151
+ async function writeOutputFile(filePath, content) {
152
+ const resolvedPath = resolve(filePath);
153
+ await mkdir(dirname(resolvedPath), { recursive: true });
154
+ await writeFile(resolvedPath, content, "utf8");
155
+ }
156
+ function outputResult(writer, json, quiet, value) {
157
+ if (quiet) {
158
+ return;
159
+ }
160
+ if (typeof value === "string") {
161
+ writer.stdout(`${value}
162
+ `);
163
+ return;
164
+ }
165
+ if (json) {
166
+ writer.stdout(`${JSON.stringify(value, null, 2)}
167
+ `);
168
+ return;
169
+ }
170
+ writer.stdout(`${JSON.stringify(value, null, 2)}
171
+ `);
172
+ }
173
+ function outputError(writer, json, error) {
174
+ const exitCode = getExitCode(error);
175
+ const message = error instanceof Error ? error.message : String(error);
176
+ if (json) {
177
+ writer.stderr(
178
+ `${JSON.stringify({ error: message, exitCode }, null, 2)}
179
+ `
180
+ );
181
+ } else {
182
+ writer.stderr(`${message}
183
+ `);
184
+ }
185
+ return exitCode;
186
+ }
187
+ function readSigningEnv(env) {
188
+ const privateKeyPem = getEnvVar(env, "SEAL_PRIVATE_KEY");
189
+ if (!privateKeyPem) {
190
+ throw new SealCliError(
191
+ "Missing SEAL_PRIVATE_KEY environment variable.",
192
+ EXIT_KEY_CONFIG
193
+ );
194
+ }
195
+ return {
196
+ privateKeyPem,
197
+ publicKeyPem: getEnvVar(env, "SEAL_PUBLIC_KEY")
198
+ };
199
+ }
200
+ async function runCli(argv, params = {}) {
201
+ const parsed = parseArgs(argv);
202
+ const env = params.env ?? process.env;
203
+ const writer = params.writer ?? {
204
+ stdout: (value) => process.stdout.write(value),
205
+ stderr: (value) => process.stderr.write(value)
206
+ };
207
+ const json = hasFlag(parsed.flags, "json");
208
+ const quiet = hasFlag(parsed.flags, "quiet");
209
+ try {
210
+ const [command, subcommand] = parsed._;
211
+ if (command === "manifest" && subcommand === "create") {
212
+ const artifact = await createManifestArtifact(requireFlag(parsed.flags, "input"));
213
+ const outPath = getFlag(parsed.flags, "out");
214
+ if (outPath) {
215
+ await writeOutputFile(outPath, artifact.content);
216
+ outputResult(writer, json, quiet, json ? artifact.manifest : outPath);
217
+ } else {
218
+ outputResult(writer, true, quiet, artifact.manifest);
219
+ }
220
+ return EXIT_SUCCESS;
221
+ }
222
+ if (command === "sign") {
223
+ const signingEnv = readSigningEnv(env);
224
+ const artifact = await createProofArtifact({
225
+ inputPath: requireFlag(parsed.flags, "input"),
226
+ ...signingEnv
227
+ });
228
+ const outPath = getFlag(parsed.flags, "out");
229
+ if (outPath) {
230
+ await writeOutputFile(outPath, artifact.content);
231
+ outputResult(writer, json, quiet, json ? artifact.proof : outPath);
232
+ } else {
233
+ outputResult(writer, true, quiet, artifact.proof);
234
+ }
235
+ return EXIT_SUCCESS;
236
+ }
237
+ if (command === "verify") {
238
+ const proofPath = requireFlag(parsed.flags, "proof");
239
+ const inputPath = requireFlag(parsed.flags, "input");
240
+ const { result } = await verifyProofArtifact({ proofPath, inputPath });
241
+ outputResult(
242
+ writer,
243
+ json,
244
+ quiet,
245
+ json ? result : [
246
+ `valid=${result.valid}`,
247
+ `hashMatch=${result.hashMatch}`,
248
+ `signatureValid=${result.signatureValid}`,
249
+ `keyId=${result.keyId}`,
250
+ `algorithm=${result.algorithm}`,
251
+ `subjectHash=${result.subjectHash}`
252
+ ].join("\n")
253
+ );
254
+ if (!result.hashMatch) {
255
+ return EXIT_HASH_MISMATCH;
256
+ }
257
+ if (!result.signatureValid) {
258
+ return EXIT_SIGNATURE_INVALID;
259
+ }
260
+ return EXIT_SUCCESS;
261
+ }
262
+ if (command === "public-key") {
263
+ const artifact = await createPublicKeyArtifact(readSigningEnv(env));
264
+ outputResult(writer, true, quiet, artifact);
265
+ return EXIT_SUCCESS;
266
+ }
267
+ throw new SealCliError(
268
+ "Usage: seal manifest create --input <path> [--out <path>] [--json] [--quiet]\n seal sign --input <path> [--out <path>] [--json] [--quiet]\n seal verify --proof <proof.json> --input <path> [--json] [--quiet]\n seal public-key [--json] [--quiet]",
269
+ EXIT_FAILURE
270
+ );
271
+ } catch (error) {
272
+ if (error instanceof Error && error.message.includes("Proof")) {
273
+ return outputError(
274
+ writer,
275
+ json,
276
+ new SealCliError(error.message, EXIT_INVALID_PROOF)
277
+ );
278
+ }
279
+ if (error instanceof Error && (error.message.includes("public key") || error.message.includes("SEAL_PRIVATE_KEY") || error.message.includes("private key"))) {
280
+ return outputError(
281
+ writer,
282
+ json,
283
+ new SealCliError(error.message, EXIT_KEY_CONFIG)
284
+ );
285
+ }
286
+ return outputError(writer, json, error);
287
+ }
288
+ }
289
+ const isDirectRun = typeof process !== "undefined" && process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1]);
290
+ if (isDirectRun) {
291
+ runCli(process.argv.slice(2)).then((exitCode) => {
292
+ process.exitCode = exitCode;
293
+ });
294
+ }
295
+ export { runCli };
296
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sources":["../src/commands/manifest.ts","../src/commands/sign.ts","../src/commands/publicKey.ts","../src/commands/verify.ts","../src/cli.ts"],"sourcesContent":["import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { basename, join, relative, resolve } from \"node:path\";\nimport { createSealHash } from \"../proof\";\nimport {\n SEAL_MANIFEST_TYPE,\n SEAL_MANIFEST_VERSION,\n stringifySealManifest,\n type SealManifestV1,\n} from \"../manifest\";\n\nfunction normalizeRelativePath(value: string): string {\n return value.split(\"\\\\\").join(\"/\");\n}\n\nasync function collectFiles(\n rootPath: string,\n currentPath: string,\n files: Record<string, SealManifestV1[\"files\"][string]>\n): Promise<void> {\n const entries = await readdir(currentPath, { withFileTypes: true });\n const sorted = entries\n .filter((entry) => entry.name !== \".DS_Store\")\n .sort((a, b) => a.name.localeCompare(b.name));\n\n for (const entry of sorted) {\n const entryPath = join(currentPath, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(rootPath, entryPath, files);\n continue;\n }\n if (!entry.isFile()) {\n continue;\n }\n const bytes = await readFile(entryPath);\n const relativePath = normalizeRelativePath(relative(rootPath, entryPath));\n files[relativePath] = await createSealHash(bytes);\n }\n}\n\nexport async function createManifestArtifact(inputPath: string): Promise<{\n manifest: SealManifestV1;\n content: string;\n}> {\n const resolvedInput = resolve(inputPath);\n const inputStat = await stat(resolvedInput);\n const root = basename(resolvedInput);\n const files: SealManifestV1[\"files\"] = {};\n\n if (inputStat.isDirectory()) {\n await collectFiles(resolvedInput, resolvedInput, files);\n } else if (inputStat.isFile()) {\n const bytes = await readFile(resolvedInput);\n files[root] = await createSealHash(bytes);\n } else {\n throw new Error(\"Manifest input must be a file or directory.\");\n }\n\n const manifest: SealManifestV1 = {\n version: SEAL_MANIFEST_VERSION,\n type: SEAL_MANIFEST_TYPE,\n root,\n files: Object.fromEntries(\n Object.entries(files).sort(([left], [right]) => left.localeCompare(right))\n ),\n };\n\n return {\n manifest,\n content: `${stringifySealManifest(manifest)}\\n`,\n };\n}\n","import { basename } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { parseSealManifestJson } from \"../manifest\";\nimport { createSealProof, createSealHash, type SealProofV1 } from \"../proof\";\n\nexport async function createProofArtifact(params: {\n inputPath: string;\n privateKeyPem: string;\n publicKeyPem?: string;\n}): Promise<{\n proof: SealProofV1;\n content: string;\n}> {\n const bytes = await readFile(params.inputPath);\n const raw = new TextDecoder().decode(bytes);\n const parsedManifest = parseSealManifestJson(raw);\n const proof = await createSealProof({\n signer: {\n privateKeyPem: params.privateKeyPem,\n publicKeyPem: params.publicKeyPem,\n },\n subject: {\n kind: parsedManifest.ok ? \"manifest\" : \"file\",\n path: basename(params.inputPath),\n hash: await createSealHash(bytes),\n },\n });\n\n return {\n proof,\n content: `${JSON.stringify(proof, null, 2)}\\n`,\n };\n}\n","import { createSealPublicKeyArtifact } from \"../proof\";\n\nexport async function createPublicKeyArtifact(params: {\n privateKeyPem: string;\n publicKeyPem?: string;\n}) {\n return createSealPublicKeyArtifact({\n privateKeyPem: params.privateKeyPem,\n publicKeyPem: params.publicKeyPem,\n });\n}\n","import { readFile } from \"node:fs/promises\";\nimport {\n parseSealProofJson,\n verifySealProofAgainstBytes,\n type SealProofV1,\n} from \"../proof\";\n\nexport type VerifyArtifactResult = {\n valid: boolean;\n hashMatch: boolean;\n signatureValid: boolean;\n keyId: string;\n algorithm: \"ECDSA-P256-SHA256\";\n subjectHash: `sha256:${string}`;\n};\n\nexport async function verifyProofArtifact(params: {\n proofPath: string;\n inputPath: string;\n}): Promise<{\n proof: SealProofV1;\n result: VerifyArtifactResult;\n}> {\n const rawProof = await readFile(params.proofPath, \"utf8\");\n const parsed = parseSealProofJson(rawProof);\n if (!parsed.ok || !parsed.proof) {\n throw new Error(parsed.errors.join(\" \"));\n }\n\n const subjectBytes = await readFile(params.inputPath);\n const result = await verifySealProofAgainstBytes(parsed.proof, subjectBytes);\n return {\n proof: parsed.proof,\n result,\n };\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createManifestArtifact } from \"./commands/manifest\";\nimport { createProofArtifact } from \"./commands/sign\";\nimport { createPublicKeyArtifact } from \"./commands/publicKey\";\nimport { verifyProofArtifact } from \"./commands/verify\";\nimport {\n EXIT_FAILURE,\n EXIT_HASH_MISMATCH,\n EXIT_INVALID_PROOF,\n EXIT_KEY_CONFIG,\n EXIT_SIGNATURE_INVALID,\n EXIT_SUCCESS,\n SealCliError,\n getExitCode,\n} from \"./errors\";\n\ntype ProcessEnvLike = Record<string, string | undefined>;\n\ndeclare const process: {\n argv: string[];\n env: ProcessEnvLike;\n stdout: { write: (value: string) => void };\n stderr: { write: (value: string) => void };\n exitCode?: number;\n};\n\ntype ParsedArgs = {\n _: string[];\n flags: Record<string, string | boolean | string[]>;\n};\n\ntype OutputWriter = {\n stdout: (value: string) => void;\n stderr: (value: string) => void;\n};\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const result: ParsedArgs = { _: [], flags: {} };\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n if (!arg.startsWith(\"--\")) {\n result._.push(arg);\n continue;\n }\n\n const [rawKey, inlineValue] = arg.slice(2).split(\"=\");\n const key = rawKey.trim();\n const next = argv[index + 1];\n const hasNextValue = inlineValue === undefined && next && !next.startsWith(\"--\");\n const value = inlineValue ?? (hasNextValue ? next : undefined);\n\n if (hasNextValue) {\n index += 1;\n }\n\n if (value === undefined) {\n result.flags[key] = true;\n continue;\n }\n\n const existing = result.flags[key];\n if (existing === undefined) {\n result.flags[key] = value;\n continue;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n result.flags[key] = existing;\n continue;\n }\n\n result.flags[key] = [String(existing), value];\n }\n\n return result;\n}\n\nfunction getFlag(flags: ParsedArgs[\"flags\"], key: string): string | undefined {\n const value = flags[key];\n if (Array.isArray(value)) {\n return value[value.length - 1];\n }\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction hasFlag(flags: ParsedArgs[\"flags\"], key: string): boolean {\n return flags[key] === true;\n}\n\nfunction requireFlag(flags: ParsedArgs[\"flags\"], key: string): string {\n const value = getFlag(flags, key);\n if (!value) {\n throw new SealCliError(`Missing --${key}`, EXIT_FAILURE);\n }\n return value;\n}\n\nfunction getEnvVar(env: ProcessEnvLike, name: string): string | undefined {\n const value = env[name];\n return value && value.trim().length > 0 ? value : undefined;\n}\n\nasync function writeOutputFile(filePath: string, content: string): Promise<void> {\n const resolvedPath = resolve(filePath);\n await mkdir(dirname(resolvedPath), { recursive: true });\n await writeFile(resolvedPath, content, \"utf8\");\n}\n\nfunction outputResult(\n writer: OutputWriter,\n json: boolean,\n quiet: boolean,\n value: unknown\n): void {\n if (quiet) {\n return;\n }\n\n if (typeof value === \"string\") {\n writer.stdout(`${value}\\n`);\n return;\n }\n\n if (json) {\n writer.stdout(`${JSON.stringify(value, null, 2)}\\n`);\n return;\n }\n\n writer.stdout(`${JSON.stringify(value, null, 2)}\\n`);\n}\n\nfunction outputError(\n writer: OutputWriter,\n json: boolean,\n error: unknown\n): number {\n const exitCode = getExitCode(error);\n const message = error instanceof Error ? error.message : String(error);\n if (json) {\n writer.stderr(\n `${JSON.stringify({ error: message, exitCode }, null, 2)}\\n`\n );\n } else {\n writer.stderr(`${message}\\n`);\n }\n return exitCode;\n}\n\nfunction readSigningEnv(env: ProcessEnvLike): {\n privateKeyPem: string;\n publicKeyPem?: string;\n} {\n const privateKeyPem = getEnvVar(env, \"SEAL_PRIVATE_KEY\");\n if (!privateKeyPem) {\n throw new SealCliError(\n \"Missing SEAL_PRIVATE_KEY environment variable.\",\n EXIT_KEY_CONFIG\n );\n }\n\n return {\n privateKeyPem,\n publicKeyPem: getEnvVar(env, \"SEAL_PUBLIC_KEY\"),\n };\n}\n\nexport async function runCli(\n argv: string[],\n params: {\n env?: ProcessEnvLike;\n writer?: OutputWriter;\n } = {}\n): Promise<number> {\n const parsed = parseArgs(argv);\n const env = params.env ?? process.env;\n const writer: OutputWriter = params.writer ?? {\n stdout: (value) => process.stdout.write(value),\n stderr: (value) => process.stderr.write(value),\n };\n const json = hasFlag(parsed.flags, \"json\");\n const quiet = hasFlag(parsed.flags, \"quiet\");\n\n try {\n const [command, subcommand] = parsed._;\n\n if (command === \"manifest\" && subcommand === \"create\") {\n const artifact = await createManifestArtifact(requireFlag(parsed.flags, \"input\"));\n const outPath = getFlag(parsed.flags, \"out\");\n if (outPath) {\n await writeOutputFile(outPath, artifact.content);\n outputResult(writer, json, quiet, json ? artifact.manifest : outPath);\n } else {\n outputResult(writer, true, quiet, artifact.manifest);\n }\n return EXIT_SUCCESS;\n }\n\n if (command === \"sign\") {\n const signingEnv = readSigningEnv(env);\n const artifact = await createProofArtifact({\n inputPath: requireFlag(parsed.flags, \"input\"),\n ...signingEnv,\n });\n const outPath = getFlag(parsed.flags, \"out\");\n if (outPath) {\n await writeOutputFile(outPath, artifact.content);\n outputResult(writer, json, quiet, json ? artifact.proof : outPath);\n } else {\n outputResult(writer, true, quiet, artifact.proof);\n }\n return EXIT_SUCCESS;\n }\n\n if (command === \"verify\") {\n const proofPath = requireFlag(parsed.flags, \"proof\");\n const inputPath = requireFlag(parsed.flags, \"input\");\n const { result } = await verifyProofArtifact({ proofPath, inputPath });\n outputResult(\n writer,\n json,\n quiet,\n json\n ? result\n : [\n `valid=${result.valid}`,\n `hashMatch=${result.hashMatch}`,\n `signatureValid=${result.signatureValid}`,\n `keyId=${result.keyId}`,\n `algorithm=${result.algorithm}`,\n `subjectHash=${result.subjectHash}`,\n ].join(\"\\n\")\n );\n\n if (!result.hashMatch) {\n return EXIT_HASH_MISMATCH;\n }\n if (!result.signatureValid) {\n return EXIT_SIGNATURE_INVALID;\n }\n return EXIT_SUCCESS;\n }\n\n if (command === \"public-key\") {\n const artifact = await createPublicKeyArtifact(readSigningEnv(env));\n outputResult(writer, true, quiet, artifact);\n return EXIT_SUCCESS;\n }\n\n throw new SealCliError(\n \"Usage: seal manifest create --input <path> [--out <path>] [--json] [--quiet]\\n\" +\n \" seal sign --input <path> [--out <path>] [--json] [--quiet]\\n\" +\n \" seal verify --proof <proof.json> --input <path> [--json] [--quiet]\\n\" +\n \" seal public-key [--json] [--quiet]\",\n EXIT_FAILURE\n );\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Proof\")) {\n return outputError(\n writer,\n json,\n new SealCliError(error.message, EXIT_INVALID_PROOF)\n );\n }\n if (\n error instanceof Error &&\n (error.message.includes(\"public key\") ||\n error.message.includes(\"SEAL_PRIVATE_KEY\") ||\n error.message.includes(\"private key\"))\n ) {\n return outputError(\n writer,\n json,\n new SealCliError(error.message, EXIT_KEY_CONFIG)\n );\n }\n return outputError(writer, json, error);\n }\n}\n\nconst isDirectRun =\n typeof process !== \"undefined\" &&\n process.argv[1] &&\n fileURLToPath(import.meta.url) === resolve(process.argv[1]);\n\nif (isDirectRun) {\n runCli(process.argv.slice(2)).then((exitCode) => {\n process.exitCode = exitCode;\n });\n}\n"],"names":[],"mappings":"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"}
package/dist/crypto.js ADDED
@@ -0,0 +1,249 @@
1
+ import { f as formatIdentityKey } from "./chunks/utils.es-586f669f.js";
2
+ function getBufferCtor() {
3
+ const maybeBuffer = globalThis.Buffer;
4
+ return maybeBuffer != null ? maybeBuffer : null;
5
+ }
6
+ function addNewLines(str) {
7
+ let finalString = "";
8
+ while (str.length > 0) {
9
+ finalString += str.substring(0, 64) + "\n";
10
+ str = str.substring(64);
11
+ }
12
+ return finalString;
13
+ }
14
+ function removeLines(str) {
15
+ return str.replaceAll("\n", "");
16
+ }
17
+ function arrayBufferToBase64(arrayBuffer) {
18
+ const byteArray = new Uint8Array(arrayBuffer);
19
+ const bufferCtor = getBufferCtor();
20
+ if (bufferCtor) {
21
+ return bufferCtor.from(byteArray).toString("base64");
22
+ }
23
+ let byteString = "";
24
+ for (let i = 0; i < byteArray.byteLength; i++) {
25
+ byteString += String.fromCharCode(byteArray[i]);
26
+ }
27
+ return btoa(byteString);
28
+ }
29
+ function base64ToArrayBuffer(b64) {
30
+ const bufferCtor = getBufferCtor();
31
+ if (bufferCtor) {
32
+ const bytes = Uint8Array.from(
33
+ bufferCtor.from(b64, "base64").toString("binary"),
34
+ (char) => char.charCodeAt(0)
35
+ );
36
+ return bytes.buffer.slice(
37
+ bytes.byteOffset,
38
+ bytes.byteOffset + bytes.byteLength
39
+ );
40
+ }
41
+ const byteString = atob(b64);
42
+ const byteArray = new Uint8Array(byteString.length);
43
+ for (let i = 0; i < byteString.length; i++) {
44
+ byteArray[i] = byteString.charCodeAt(i);
45
+ }
46
+ return byteArray.buffer;
47
+ }
48
+ function b64encode(buf) {
49
+ return arrayBufferToBase64(buf);
50
+ }
51
+ function b64decode(str) {
52
+ return base64ToArrayBuffer(str);
53
+ }
54
+ function encode(data) {
55
+ const payload = typeof data === "string" ? data : JSON.stringify(data);
56
+ return new TextEncoder().encode(payload);
57
+ }
58
+ function getHashBuffer(data) {
59
+ return crypto.subtle.digest("SHA-256", encode(data));
60
+ }
61
+ function getHashArray(hash) {
62
+ return Array.from(new Uint8Array(hash));
63
+ }
64
+ function getHashHex(hash) {
65
+ return hash.map((buf) => buf.toString(16).padStart(2, "0")).join("");
66
+ }
67
+ function canonicalize(value, seen) {
68
+ if (value === void 0) {
69
+ throw new TypeError("Cannot hash undefined");
70
+ }
71
+ const valueType = typeof value;
72
+ if (valueType === "function" || valueType === "symbol") {
73
+ throw new TypeError(`Cannot hash ${valueType} values`);
74
+ }
75
+ if (value === null || valueType === "string" || valueType === "number" || valueType === "boolean") {
76
+ return value;
77
+ }
78
+ if (valueType === "bigint") {
79
+ throw new TypeError("Cannot hash bigint values");
80
+ }
81
+ if (typeof value.toJSON === "function") {
82
+ return canonicalize(value.toJSON(), seen);
83
+ }
84
+ if (Array.isArray(value)) {
85
+ return value.map((item) => canonicalize(item, seen));
86
+ }
87
+ if (typeof value === "object") {
88
+ if (seen.has(value)) {
89
+ throw new TypeError("Cannot hash circular references");
90
+ }
91
+ seen.add(value);
92
+ const entries = Object.keys(value).sort();
93
+ const result = {};
94
+ for (const key of entries) {
95
+ result[key] = canonicalize(value[key], seen);
96
+ }
97
+ seen.delete(value);
98
+ return result;
99
+ }
100
+ throw new TypeError(`Cannot hash unsupported value type: ${valueType}`);
101
+ }
102
+ function canonicalStringify(data) {
103
+ return JSON.stringify(canonicalize(data, /* @__PURE__ */ new WeakSet()));
104
+ }
105
+ async function hashData(data) {
106
+ const hash_buffer = await getHashBuffer(canonicalStringify(data));
107
+ const hash_array = getHashArray(hash_buffer);
108
+ return getHashHex(hash_array);
109
+ }
110
+ async function importPrivateKeyFromPem(key) {
111
+ const b64key = key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");
112
+ return crypto.subtle.importKey(
113
+ "pkcs8",
114
+ base64ToArrayBuffer(removeLines(b64key)),
115
+ { name: "ECDSA", namedCurve: "P-256" },
116
+ true,
117
+ ["sign"]
118
+ );
119
+ }
120
+ async function exportPrivateKey(privateKey) {
121
+ return crypto.subtle.exportKey("jwk", privateKey);
122
+ }
123
+ function importPublicKey(points) {
124
+ return crypto.subtle.importKey(
125
+ "jwk",
126
+ {
127
+ crv: "P-256",
128
+ ext: true,
129
+ kty: "EC",
130
+ ...points
131
+ },
132
+ {
133
+ name: "ECDSA",
134
+ namedCurve: "P-256"
135
+ },
136
+ true,
137
+ ["verify"]
138
+ );
139
+ }
140
+ async function importPublicKeyFromPem(key) {
141
+ const b64key = key.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
142
+ return crypto.subtle.importKey(
143
+ "spki",
144
+ base64ToArrayBuffer(removeLines(b64key)),
145
+ { name: "ECDSA", namedCurve: "P-256" },
146
+ true,
147
+ ["verify"]
148
+ );
149
+ }
150
+ async function exportPublicKey(key) {
151
+ const exported = await crypto.subtle.exportKey("spki", key);
152
+ return b64encode(exported);
153
+ }
154
+ async function exportPublicKeyAsPem(key) {
155
+ const exportedPublicKey = await crypto.subtle.exportKey("spki", key);
156
+ return `-----BEGIN PUBLIC KEY-----
157
+ ${addNewLines(arrayBufferToBase64(exportedPublicKey))}-----END PUBLIC KEY-----`;
158
+ }
159
+ async function derivePublicFromPrivatePEM(privateKeyPEM) {
160
+ const key = await importPrivateKeyFromPem(privateKeyPEM);
161
+ const priv = await exportPrivateKey(key);
162
+ const pub = await importPublicKey({ x: priv.x, y: priv.y });
163
+ return exportPublicKeyAsPem(pub);
164
+ }
165
+ async function signBytes(signingKey, data) {
166
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
167
+ const signatureBuffer = await crypto.subtle.sign(
168
+ {
169
+ name: "ECDSA",
170
+ hash: {
171
+ name: "SHA-256"
172
+ }
173
+ },
174
+ signingKey,
175
+ bytes
176
+ );
177
+ return b64encode(signatureBuffer);
178
+ }
179
+ async function verifyBytes(signature, data, publicKey) {
180
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
181
+ return crypto.subtle.verify(
182
+ {
183
+ name: "ECDSA",
184
+ hash: { name: "SHA-256" }
185
+ },
186
+ publicKey,
187
+ b64decode(signature),
188
+ bytes
189
+ );
190
+ }
191
+ function normalizePublicKeyBody(publicKeyBase64) {
192
+ return addNewLines(publicKeyBase64).trimEnd();
193
+ }
194
+ async function deriveKeyIdFromPublicKeyBase64(publicKeyBase64) {
195
+ return hashData(normalizePublicKeyBody(publicKeyBase64));
196
+ }
197
+ async function deriveKeyIdFromPublicKey(publicKey) {
198
+ const publicKeyBase64 = await exportPublicKey(publicKey);
199
+ return deriveKeyIdFromPublicKeyBase64(publicKeyBase64);
200
+ }
201
+ function utf8Bytes(value) {
202
+ return new TextEncoder().encode(value);
203
+ }
204
+ async function importPublicKeyFromBase64(publicKeyBase64) {
205
+ return importPublicKeyFromPem(formatIdentityKey(publicKeyBase64));
206
+ }
207
+ async function exportPublicKeyBase64(key) {
208
+ return exportPublicKey(key);
209
+ }
210
+ async function derivePublicKeyBase64FromPrivateKeyPem(privateKeyPem) {
211
+ const publicKeyPem = await derivePublicFromPrivatePEM(privateKeyPem);
212
+ const publicKey = await importPublicKeyFromPem(publicKeyPem);
213
+ return exportPublicKeyBase64(publicKey);
214
+ }
215
+ async function resolveSealSigner(input) {
216
+ const privateKey = await importPrivateKeyFromPem(input.privateKeyPem);
217
+ const publicKeyPem = input.publicKeyPem ? input.publicKeyPem : await derivePublicFromPrivatePEM(input.privateKeyPem);
218
+ const publicKey = await importPublicKeyFromPem(publicKeyPem);
219
+ const publicKeyBase64 = await exportPublicKeyBase64(publicKey);
220
+ const derivedPublicKeyBase64 = await derivePublicKeyBase64FromPrivateKeyPem(
221
+ input.privateKeyPem
222
+ );
223
+ if (publicKeyBase64 !== derivedPublicKeyBase64) {
224
+ throw new Error("Provided public key does not match the private key.");
225
+ }
226
+ const derivedKeyId = await deriveKeyIdFromPublicKey(publicKey);
227
+ if (input.keyId && input.keyId !== derivedKeyId) {
228
+ throw new Error("Provided keyId does not match the signer public key.");
229
+ }
230
+ const keyId = input.keyId ?? derivedKeyId;
231
+ return {
232
+ privateKey,
233
+ publicKey,
234
+ publicKeyPem,
235
+ publicKeyBase64,
236
+ keyId
237
+ };
238
+ }
239
+ async function signUtf8(signingKey, value) {
240
+ return signBytes(signingKey, utf8Bytes(value));
241
+ }
242
+ async function verifyUtf8(signature, value, publicKey) {
243
+ return verifyBytes(signature, utf8Bytes(value), publicKey);
244
+ }
245
+ async function verifyPublicKeyKeyId(publicKeyBase64, keyId) {
246
+ return await deriveKeyIdFromPublicKeyBase64(publicKeyBase64) === keyId;
247
+ }
248
+ export { derivePublicKeyBase64FromPrivateKeyPem, exportPublicKeyBase64, importPublicKeyFromBase64, resolveSealSigner, signUtf8, verifyPublicKeyKeyId, verifyUtf8 };
249
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sources":["../../identity/dist/identity.es.js","../src/crypto.ts"],"sourcesContent":["async function createIdentity() {\n return crypto.subtle.generateKey(\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\", \"verify\"]\n );\n}\nfunction getBufferCtor() {\n const maybeBuffer = globalThis.Buffer;\n return maybeBuffer != null ? maybeBuffer : null;\n}\nfunction addNewLines(str) {\n let finalString = \"\";\n while (str.length > 0) {\n finalString += str.substring(0, 64) + \"\\n\";\n str = str.substring(64);\n }\n return finalString;\n}\nfunction removeLines(str) {\n return str.replaceAll(\"\\n\", \"\");\n}\nfunction stripIdentityKey(key) {\n return key.replace(\"-----BEGIN PUBLIC KEY-----\\n\", \"\").replace(\"\\n-----END PUBLIC KEY-----\", \"\");\n}\nfunction arrayBufferToBase64(arrayBuffer) {\n const byteArray = new Uint8Array(arrayBuffer);\n const bufferCtor = getBufferCtor();\n if (bufferCtor) {\n return bufferCtor.from(byteArray).toString(\"base64\");\n }\n let byteString = \"\";\n for (let i = 0; i < byteArray.byteLength; i++) {\n byteString += String.fromCharCode(byteArray[i]);\n }\n return btoa(byteString);\n}\nfunction base64ToArrayBuffer(b64) {\n const bufferCtor = getBufferCtor();\n if (bufferCtor) {\n const bytes = Uint8Array.from(\n bufferCtor.from(b64, \"base64\").toString(\"binary\"),\n (char) => char.charCodeAt(0)\n );\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength\n );\n }\n const byteString = atob(b64);\n const byteArray = new Uint8Array(byteString.length);\n for (let i = 0; i < byteString.length; i++) {\n byteArray[i] = byteString.charCodeAt(i);\n }\n return byteArray.buffer;\n}\nfunction b64encode(buf) {\n return arrayBufferToBase64(buf);\n}\nfunction b64decode(str) {\n return base64ToArrayBuffer(str);\n}\nfunction encode(data) {\n const payload = typeof data === \"string\" ? data : JSON.stringify(data);\n return new TextEncoder().encode(payload);\n}\nfunction getHashBuffer(data) {\n return crypto.subtle.digest(\"SHA-256\", encode(data));\n}\nfunction getHashArray(hash) {\n return Array.from(new Uint8Array(hash));\n}\nfunction getHashHex(hash) {\n return hash.map((buf) => buf.toString(16).padStart(2, \"0\")).join(\"\");\n}\nfunction canonicalize(value, seen) {\n if (value === void 0) {\n throw new TypeError(\"Cannot hash undefined\");\n }\n const valueType = typeof value;\n if (valueType === \"function\" || valueType === \"symbol\") {\n throw new TypeError(`Cannot hash ${valueType} values`);\n }\n if (value === null || valueType === \"string\" || valueType === \"number\" || valueType === \"boolean\") {\n return value;\n }\n if (valueType === \"bigint\") {\n throw new TypeError(\"Cannot hash bigint values\");\n }\n if (typeof value.toJSON === \"function\") {\n return canonicalize(value.toJSON(), seen);\n }\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item, seen));\n }\n if (typeof value === \"object\") {\n if (seen.has(value)) {\n throw new TypeError(\"Cannot hash circular references\");\n }\n seen.add(value);\n const entries = Object.keys(value).sort();\n const result = {};\n for (const key of entries) {\n result[key] = canonicalize(value[key], seen);\n }\n seen.delete(value);\n return result;\n }\n throw new TypeError(`Cannot hash unsupported value type: ${valueType}`);\n}\nfunction canonicalStringify(data) {\n return JSON.stringify(canonicalize(data, /* @__PURE__ */ new WeakSet()));\n}\nasync function hashData(data) {\n const hash_buffer = await getHashBuffer(canonicalStringify(data));\n const hash_array = getHashArray(hash_buffer);\n return getHashHex(hash_array);\n}\nfunction importPrivateKey(points, secret) {\n return crypto.subtle.importKey(\n \"jwk\",\n {\n crv: \"P-256\",\n ext: true,\n kty: \"EC\",\n ...points,\n d: secret\n },\n {\n name: \"ECDSA\",\n namedCurve: \"P-256\"\n },\n true,\n [\"sign\"]\n );\n}\nasync function importPrivateKeyFromPem(key) {\n const b64key = key.replace(\"-----BEGIN PRIVATE KEY-----\", \"\").replace(\"-----END PRIVATE KEY-----\", \"\");\n return crypto.subtle.importKey(\n \"pkcs8\",\n base64ToArrayBuffer(removeLines(b64key)),\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"]\n );\n}\nasync function exportPrivateKey(privateKey) {\n return crypto.subtle.exportKey(\"jwk\", privateKey);\n}\nasync function exportPrivateKeyAsPem(privateKey) {\n const exportedPrivateKey = await crypto.subtle.exportKey(\"pkcs8\", privateKey);\n return `-----BEGIN PRIVATE KEY-----\n${addNewLines(\n arrayBufferToBase64(exportedPrivateKey)\n )}-----END PRIVATE KEY-----`;\n}\nfunction importPublicKey(points) {\n return crypto.subtle.importKey(\n \"jwk\",\n {\n crv: \"P-256\",\n ext: true,\n kty: \"EC\",\n ...points\n },\n {\n name: \"ECDSA\",\n namedCurve: \"P-256\"\n },\n true,\n [\"verify\"]\n );\n}\nasync function importPublicKeyFromPem(key) {\n const b64key = key.replace(\"-----BEGIN PUBLIC KEY-----\", \"\").replace(\"-----END PUBLIC KEY-----\", \"\");\n return crypto.subtle.importKey(\n \"spki\",\n base64ToArrayBuffer(removeLines(b64key)),\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"]\n );\n}\nasync function exportPublicKey(key) {\n const exported = await crypto.subtle.exportKey(\"spki\", key);\n return b64encode(exported);\n}\nasync function exportPublicKeyAsPem(key) {\n const exportedPublicKey = await crypto.subtle.exportKey(\"spki\", key);\n return `-----BEGIN PUBLIC KEY-----\n${addNewLines(arrayBufferToBase64(exportedPublicKey))}-----END PUBLIC KEY-----`;\n}\nasync function derivePublicFromPrivatePEM(privateKeyPEM) {\n const key = await importPrivateKeyFromPem(privateKeyPEM);\n const priv = await exportPrivateKey(key);\n const pub = await importPublicKey({ x: priv.x, y: priv.y });\n return exportPublicKeyAsPem(pub);\n}\nasync function signBytes(signingKey, data) {\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n const signatureBuffer = await crypto.subtle.sign(\n {\n name: \"ECDSA\",\n hash: {\n name: \"SHA-256\"\n }\n },\n signingKey,\n bytes\n );\n return b64encode(signatureBuffer);\n}\nasync function sign(signingKey, data) {\n const payload = typeof data === \"string\" ? data : JSON.stringify(data);\n const dataBuffer = new TextEncoder().encode(payload);\n return signBytes(signingKey, dataBuffer);\n}\nasync function verifyBytes(signature, data, publicKey) {\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n return crypto.subtle.verify(\n {\n name: \"ECDSA\",\n hash: { name: \"SHA-256\" }\n },\n publicKey,\n b64decode(signature),\n bytes\n );\n}\nasync function verify(signature, data, publicKey) {\n const dataBuffer = new TextEncoder().encode(data);\n return verifyBytes(signature, dataBuffer, publicKey);\n}\nasync function verifyJson(signature, data, publicKey) {\n return verify(signature, JSON.stringify(data), publicKey);\n}\nfunction normalizePublicKeyBody(publicKeyBase64) {\n return addNewLines(publicKeyBase64).trimEnd();\n}\nasync function deriveKeyIdFromPublicKeyBase64(publicKeyBase64) {\n return hashData(normalizePublicKeyBody(publicKeyBase64));\n}\nasync function deriveKeyIdFromPublicKeyPem(publicKeyPem) {\n return hashData(stripIdentityKey(publicKeyPem));\n}\nasync function deriveKeyIdFromPublicKey(publicKey) {\n const publicKeyBase64 = await exportPublicKey(publicKey);\n return deriveKeyIdFromPublicKeyBase64(publicKeyBase64);\n}\nexport { createIdentity, deriveKeyIdFromPublicKey, deriveKeyIdFromPublicKeyBase64, deriveKeyIdFromPublicKeyPem, derivePublicFromPrivatePEM, exportPrivateKey, exportPrivateKeyAsPem, exportPublicKey, exportPublicKeyAsPem, importPrivateKey, importPrivateKeyFromPem, importPublicKey, importPublicKeyFromPem, sign, signBytes, verify, verifyBytes, verifyJson };\n","import {\n deriveKeyIdFromPublicKey,\n deriveKeyIdFromPublicKeyBase64,\n derivePublicFromPrivatePEM,\n exportPublicKey,\n importPrivateKeyFromPem,\n importPublicKeyFromPem,\n signBytes,\n verifyBytes,\n} from \"ternent-identity\";\nimport { formatIdentityKey } from \"ternent-utils\";\n\nexport type SealSignerInput = {\n privateKeyPem: string;\n publicKeyPem?: string;\n keyId?: string;\n};\n\nexport type ResolvedSealSigner = {\n privateKey: CryptoKey;\n publicKey: CryptoKey;\n publicKeyPem: string;\n publicKeyBase64: string;\n keyId: string;\n};\n\nfunction utf8Bytes(value: string): Uint8Array {\n return new TextEncoder().encode(value);\n}\n\nexport async function importPublicKeyFromBase64(\n publicKeyBase64: string\n): Promise<CryptoKey> {\n return importPublicKeyFromPem(formatIdentityKey(publicKeyBase64));\n}\n\nexport async function exportPublicKeyBase64(key: CryptoKey): Promise<string> {\n return exportPublicKey(key);\n}\n\nexport async function derivePublicKeyBase64FromPrivateKeyPem(\n privateKeyPem: string\n): Promise<string> {\n const publicKeyPem = await derivePublicFromPrivatePEM(privateKeyPem);\n const publicKey = await importPublicKeyFromPem(publicKeyPem);\n return exportPublicKeyBase64(publicKey);\n}\n\nexport async function resolveSealSigner(\n input: SealSignerInput\n): Promise<ResolvedSealSigner> {\n const privateKey = await importPrivateKeyFromPem(input.privateKeyPem);\n const publicKeyPem = input.publicKeyPem\n ? input.publicKeyPem\n : await derivePublicFromPrivatePEM(input.privateKeyPem);\n const publicKey = await importPublicKeyFromPem(publicKeyPem);\n const publicKeyBase64 = await exportPublicKeyBase64(publicKey);\n const derivedPublicKeyBase64 = await derivePublicKeyBase64FromPrivateKeyPem(\n input.privateKeyPem\n );\n\n if (publicKeyBase64 !== derivedPublicKeyBase64) {\n throw new Error(\"Provided public key does not match the private key.\");\n }\n\n const derivedKeyId = await deriveKeyIdFromPublicKey(publicKey);\n if (input.keyId && input.keyId !== derivedKeyId) {\n throw new Error(\"Provided keyId does not match the signer public key.\");\n }\n const keyId = input.keyId ?? derivedKeyId;\n\n return {\n privateKey,\n publicKey,\n publicKeyPem,\n publicKeyBase64,\n keyId,\n };\n}\n\nexport async function signUtf8(\n signingKey: CryptoKey,\n value: string\n): Promise<string> {\n return signBytes(signingKey, utf8Bytes(value));\n}\n\nexport async function verifyUtf8(\n signature: string,\n value: string,\n publicKey: CryptoKey\n): Promise<boolean> {\n return verifyBytes(signature, utf8Bytes(value), publicKey);\n}\n\nexport async function verifyPublicKeyKeyId(\n publicKeyBase64: string,\n keyId: string\n): Promise<boolean> {\n return (await deriveKeyIdFromPublicKeyBase64(publicKeyBase64)) === keyId;\n}\n"],"names":[],"mappings":"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"}
package/dist/errors.js ADDED
@@ -0,0 +1,22 @@
1
+ const EXIT_SUCCESS = 0;
2
+ const EXIT_FAILURE = 1;
3
+ const EXIT_HASH_MISMATCH = 2;
4
+ const EXIT_SIGNATURE_INVALID = 3;
5
+ const EXIT_INVALID_PROOF = 4;
6
+ const EXIT_KEY_CONFIG = 5;
7
+ class SealCliError extends Error {
8
+ exitCode;
9
+ constructor(message, exitCode = EXIT_FAILURE) {
10
+ super(message);
11
+ this.name = "SealCliError";
12
+ this.exitCode = exitCode;
13
+ }
14
+ }
15
+ function getExitCode(error) {
16
+ if (error instanceof SealCliError) {
17
+ return error.exitCode;
18
+ }
19
+ return EXIT_FAILURE;
20
+ }
21
+ export { EXIT_FAILURE, EXIT_HASH_MISMATCH, EXIT_INVALID_PROOF, EXIT_KEY_CONFIG, EXIT_SIGNATURE_INVALID, EXIT_SUCCESS, SealCliError, getExitCode };
22
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sources":["../src/errors.ts"],"sourcesContent":["export const EXIT_SUCCESS = 0;\nexport const EXIT_FAILURE = 1;\nexport const EXIT_HASH_MISMATCH = 2;\nexport const EXIT_SIGNATURE_INVALID = 3;\nexport const EXIT_INVALID_PROOF = 4;\nexport const EXIT_KEY_CONFIG = 5;\n\nexport class SealCliError extends Error {\n exitCode: number;\n\n constructor(message: string, exitCode = EXIT_FAILURE) {\n super(message);\n this.name = \"SealCliError\";\n this.exitCode = exitCode;\n }\n}\n\nexport function getExitCode(error: unknown): number {\n if (error instanceof SealCliError) {\n return error.exitCode;\n }\n return EXIT_FAILURE;\n}\n"],"names":[],"mappings":"AAAO,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,qBAAqB;AAC3B,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB;AAExB,MAAM,qBAAqB,MAAM;AAAA,EACtC;AAAA,EAEA,YAAY,SAAiB,WAAW,cAAc;AACpD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAEO,SAAS,YAAY,OAAwB;AAClD,MAAI,iBAAiB,cAAc;AACjC,WAAO,MAAM;AAAA,EACf;AACO,SAAA;AACT;;"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { SEAL_PROOF_TYPE, SEAL_PROOF_VERSION, SEAL_SIGNATURE_ALGORITHM, createSealHash, createSealProof, createSealPublicKeyArtifact, getSealProofSignableFields, getSealProofSigningPayload, parseSealProofJson, parseSealPublicKeyJson, validateSealProofShape, validateSealPublicKeyShape, verifySealProofAgainstBytes, verifySealProofSignature } from "./proof.js";
2
+ export { SEAL_MANIFEST_TYPE, SEAL_MANIFEST_VERSION, parseSealManifestJson, stringifySealManifest, validateSealManifestShape } from "./manifest.js";
3
+ export { derivePublicKeyBase64FromPrivateKeyPem, exportPublicKeyBase64, importPublicKeyFromBase64, resolveSealSigner, signUtf8, verifyPublicKeyKeyId, verifyUtf8 } from "./crypto.js";
4
+ export { EXIT_FAILURE, EXIT_HASH_MISMATCH, EXIT_INVALID_PROOF, EXIT_KEY_CONFIG, EXIT_SIGNATURE_INVALID, EXIT_SUCCESS, SealCliError, getExitCode } from "./errors.js";
5
+ import "./chunks/utils.es-586f669f.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
@@ -0,0 +1,82 @@
1
+ import { c as canonicalStringify } from "./chunks/utils.es-586f669f.js";
2
+ const SEAL_MANIFEST_VERSION = "1";
3
+ const SEAL_MANIFEST_TYPE = "seal-manifest";
4
+ function isRecord(value) {
5
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ function hasOnlyKeys(value, allowed) {
8
+ return Object.keys(value).every((key) => allowed.includes(key));
9
+ }
10
+ function isSealHash(value) {
11
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
12
+ }
13
+ function validateSealManifestShape(value) {
14
+ if (!isRecord(value)) {
15
+ return {
16
+ ok: false,
17
+ errors: ["Manifest must be a JSON object."],
18
+ manifest: null
19
+ };
20
+ }
21
+ const errors = [];
22
+ if (!hasOnlyKeys(value, ["version", "type", "root", "files"])) {
23
+ errors.push("Manifest contains unsupported fields.");
24
+ }
25
+ if (value.version !== SEAL_MANIFEST_VERSION) {
26
+ errors.push(`Manifest version must be ${SEAL_MANIFEST_VERSION}.`);
27
+ }
28
+ if (value.type !== SEAL_MANIFEST_TYPE) {
29
+ errors.push(`Manifest type must be ${SEAL_MANIFEST_TYPE}.`);
30
+ }
31
+ if (typeof value.root !== "string" || value.root.length === 0) {
32
+ errors.push("Manifest root must be a non-empty string.");
33
+ }
34
+ if (!isRecord(value.files)) {
35
+ errors.push("Manifest files must be an object.");
36
+ }
37
+ if (errors.length > 0 || !isRecord(value.files)) {
38
+ return { ok: false, errors, manifest: null };
39
+ }
40
+ const filesEntries = Object.entries(value.files);
41
+ const files = {};
42
+ for (const [path, hash] of filesEntries) {
43
+ if (!path || path.includes("\\")) {
44
+ errors.push(`Manifest file path is invalid: ${path || "<empty>"}.`);
45
+ continue;
46
+ }
47
+ if (!isSealHash(hash)) {
48
+ errors.push(`Manifest file hash is invalid for ${path}.`);
49
+ continue;
50
+ }
51
+ files[path] = hash;
52
+ }
53
+ if (errors.length > 0) {
54
+ return { ok: false, errors, manifest: null };
55
+ }
56
+ return {
57
+ ok: true,
58
+ errors: [],
59
+ manifest: {
60
+ version: SEAL_MANIFEST_VERSION,
61
+ type: SEAL_MANIFEST_TYPE,
62
+ root: value.root,
63
+ files
64
+ }
65
+ };
66
+ }
67
+ function parseSealManifestJson(raw) {
68
+ try {
69
+ return validateSealManifestShape(JSON.parse(raw));
70
+ } catch {
71
+ return {
72
+ ok: false,
73
+ errors: ["Manifest JSON is not valid JSON."],
74
+ manifest: null
75
+ };
76
+ }
77
+ }
78
+ function stringifySealManifest(manifest) {
79
+ return canonicalStringify(manifest);
80
+ }
81
+ export { SEAL_MANIFEST_TYPE, SEAL_MANIFEST_VERSION, parseSealManifestJson, stringifySealManifest, validateSealManifestShape };
82
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sources":["../src/manifest.ts"],"sourcesContent":["import { canonicalStringify } from \"ternent-utils\";\n\nexport const SEAL_MANIFEST_VERSION = \"1\" as const;\nexport const SEAL_MANIFEST_TYPE = \"seal-manifest\" as const;\n\nexport type SealHash = `sha256:${string}`;\n\nexport type SealManifestV1 = {\n version: typeof SEAL_MANIFEST_VERSION;\n type: typeof SEAL_MANIFEST_TYPE;\n root: string;\n files: Record<string, SealHash>;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(\n value: Record<string, unknown>,\n allowed: string[]\n): boolean {\n return Object.keys(value).every((key) => allowed.includes(key));\n}\n\nfunction isSealHash(value: unknown): value is SealHash {\n return typeof value === \"string\" && /^sha256:[0-9a-f]{64}$/.test(value);\n}\n\nexport function validateSealManifestShape(value: unknown): {\n ok: boolean;\n errors: string[];\n manifest: SealManifestV1 | null;\n} {\n if (!isRecord(value)) {\n return {\n ok: false,\n errors: [\"Manifest must be a JSON object.\"],\n manifest: null,\n };\n }\n\n const errors: string[] = [];\n\n if (!hasOnlyKeys(value, [\"version\", \"type\", \"root\", \"files\"])) {\n errors.push(\"Manifest contains unsupported fields.\");\n }\n\n if (value.version !== SEAL_MANIFEST_VERSION) {\n errors.push(`Manifest version must be ${SEAL_MANIFEST_VERSION}.`);\n }\n\n if (value.type !== SEAL_MANIFEST_TYPE) {\n errors.push(`Manifest type must be ${SEAL_MANIFEST_TYPE}.`);\n }\n\n if (typeof value.root !== \"string\" || value.root.length === 0) {\n errors.push(\"Manifest root must be a non-empty string.\");\n }\n\n if (!isRecord(value.files)) {\n errors.push(\"Manifest files must be an object.\");\n }\n\n if (errors.length > 0 || !isRecord(value.files)) {\n return { ok: false, errors, manifest: null };\n }\n\n const filesEntries = Object.entries(value.files);\n const files: Record<string, SealHash> = {};\n for (const [path, hash] of filesEntries) {\n if (!path || path.includes(\"\\\\\")) {\n errors.push(`Manifest file path is invalid: ${path || \"<empty>\"}.`);\n continue;\n }\n if (!isSealHash(hash)) {\n errors.push(`Manifest file hash is invalid for ${path}.`);\n continue;\n }\n files[path] = hash;\n }\n\n if (errors.length > 0) {\n return { ok: false, errors, manifest: null };\n }\n\n return {\n ok: true,\n errors: [],\n manifest: {\n version: SEAL_MANIFEST_VERSION,\n type: SEAL_MANIFEST_TYPE,\n root: value.root as string,\n files,\n },\n };\n}\n\nexport function parseSealManifestJson(raw: string): {\n ok: boolean;\n errors: string[];\n manifest: SealManifestV1 | null;\n} {\n try {\n return validateSealManifestShape(JSON.parse(raw));\n } catch {\n return {\n ok: false,\n errors: [\"Manifest JSON is not valid JSON.\"],\n manifest: null,\n };\n }\n}\n\nexport function stringifySealManifest(manifest: SealManifestV1): string {\n return canonicalStringify(manifest);\n}\n"],"names":[],"mappings":";AAEO,MAAM,wBAAwB;AAC9B,MAAM,qBAAqB;AAWlC,SAAS,SAAS,OAAkD;AAC3D,SAAA,QAAQ,KAAK,KAAK,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,YACP,OACA,SACS;AACF,SAAA,OAAO,KAAK,KAAK,EAAE,MAAM,CAAC,QAAQ,QAAQ,SAAS,GAAG,CAAC;AAChE;AAEA,SAAS,WAAW,OAAmC;AACrD,SAAO,OAAO,UAAU,YAAY,wBAAwB,KAAK,KAAK;AACxE;AAEO,SAAS,0BAA0B,OAIxC;AACI,MAAA,CAAC,SAAS,KAAK,GAAG;AACb,WAAA;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ,CAAC,iCAAiC;AAAA,MAC1C,UAAU;AAAA,IAAA;AAAA,EAEd;AAEA,QAAM,SAAmB,CAAA;AAErB,MAAA,CAAC,YAAY,OAAO,CAAC,WAAW,QAAQ,QAAQ,OAAO,CAAC,GAAG;AAC7D,WAAO,KAAK,uCAAuC;AAAA,EACrD;AAEI,MAAA,MAAM,YAAY,uBAAuB;AACpC,WAAA,KAAK,4BAA4B,wBAAwB;AAAA,EAClE;AAEI,MAAA,MAAM,SAAS,oBAAoB;AAC9B,WAAA,KAAK,yBAAyB,qBAAqB;AAAA,EAC5D;AAEA,MAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,WAAW,GAAG;AAC7D,WAAO,KAAK,2CAA2C;AAAA,EACzD;AAEA,MAAI,CAAC,SAAS,MAAM,KAAK,GAAG;AAC1B,WAAO,KAAK,mCAAmC;AAAA,EACjD;AAEA,MAAI,OAAO,SAAS,KAAK,CAAC,SAAS,MAAM,KAAK,GAAG;AAC/C,WAAO,EAAE,IAAI,OAAO,QAAQ,UAAU,KAAK;AAAA,EAC7C;AAEA,QAAM,eAAe,OAAO,QAAQ,MAAM,KAAK;AAC/C,QAAM,QAAkC,CAAA;AACxC,aAAW,CAAC,MAAM,IAAI,KAAK,cAAc;AACvC,QAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG;AACzB,aAAA,KAAK,kCAAkC,QAAQ,YAAY;AAClE;AAAA,IACF;AACI,QAAA,CAAC,WAAW,IAAI,GAAG;AACd,aAAA,KAAK,qCAAqC,OAAO;AACxD;AAAA,IACF;AACA,UAAM,QAAQ;AAAA,EAChB;AAEI,MAAA,OAAO,SAAS,GAAG;AACrB,WAAO,EAAE,IAAI,OAAO,QAAQ,UAAU,KAAK;AAAA,EAC7C;AAES,SAAA;AAAA,IACL,IAAI;AAAA,IACJ,QAAQ,CAAC;AAAA,IACT,UAAU;AAAA,MACR,SAAS;AAAA,MACT,MAAM;AAAA,MACN,MAAM,MAAM;AAAA,MACZ;AAAA,IACF;AAAA,EAAA;AAEN;AAEO,SAAS,sBAAsB,KAIpC;AACI,MAAA;AACF,WAAO,0BAA0B,KAAK,MAAM,GAAG,CAAC;AAAA,EAAA,QAChD;AACO,WAAA;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ,CAAC,kCAAkC;AAAA,MAC3C,UAAU;AAAA,IAAA;AAAA,EAEd;AACF;AAEO,SAAS,sBAAsB,UAAkC;AACtE,SAAO,mBAAmB,QAAQ;AACpC;;"}
package/dist/proof.js ADDED
@@ -0,0 +1,232 @@
1
+ import { c as canonicalStringify, h as hashBytes } from "./chunks/utils.es-586f669f.js";
2
+ import { resolveSealSigner, signUtf8, verifyPublicKeyKeyId, importPublicKeyFromBase64, verifyUtf8 } from "./crypto.js";
3
+ const SEAL_PROOF_VERSION = "1";
4
+ const SEAL_PROOF_TYPE = "seal-proof";
5
+ const SEAL_SIGNATURE_ALGORITHM = "ECDSA-P256-SHA256";
6
+ function isRecord(value) {
7
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+ function hasOnlyKeys(value, allowed) {
10
+ return Object.keys(value).every((key) => allowed.includes(key));
11
+ }
12
+ function isSealHash(value) {
13
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
14
+ }
15
+ function isIsoDate(value) {
16
+ return !Number.isNaN(Date.parse(value));
17
+ }
18
+ function getSealProofSignableFields(proof) {
19
+ return {
20
+ version: proof.version,
21
+ type: proof.type,
22
+ algorithm: proof.algorithm,
23
+ createdAt: proof.createdAt,
24
+ subject: proof.subject,
25
+ signer: proof.signer
26
+ };
27
+ }
28
+ function getSealProofSigningPayload(proof) {
29
+ return canonicalStringify(getSealProofSignableFields(proof));
30
+ }
31
+ async function createSealHash(bytes) {
32
+ const hash = await hashBytes(bytes);
33
+ return `sha256:${hash}`;
34
+ }
35
+ async function createSealProof(input) {
36
+ const signer = await resolveSealSigner(input.signer);
37
+ const fields = {
38
+ version: SEAL_PROOF_VERSION,
39
+ type: SEAL_PROOF_TYPE,
40
+ algorithm: SEAL_SIGNATURE_ALGORITHM,
41
+ createdAt: input.createdAt ?? new Date().toISOString(),
42
+ subject: input.subject,
43
+ signer: {
44
+ publicKey: signer.publicKeyBase64,
45
+ keyId: signer.keyId
46
+ }
47
+ };
48
+ const signature = await signUtf8(
49
+ signer.privateKey,
50
+ getSealProofSigningPayload(fields)
51
+ );
52
+ return {
53
+ ...fields,
54
+ signature
55
+ };
56
+ }
57
+ async function createSealPublicKeyArtifact(signer) {
58
+ const resolved = await resolveSealSigner(signer);
59
+ return {
60
+ algorithm: SEAL_SIGNATURE_ALGORITHM,
61
+ publicKey: resolved.publicKeyBase64,
62
+ keyId: resolved.keyId
63
+ };
64
+ }
65
+ function validateSealProofShape(value) {
66
+ if (!isRecord(value)) {
67
+ return { ok: false, errors: ["Proof must be a JSON object."], proof: null };
68
+ }
69
+ const errors = [];
70
+ if (!hasOnlyKeys(value, [
71
+ "version",
72
+ "type",
73
+ "algorithm",
74
+ "createdAt",
75
+ "subject",
76
+ "signer",
77
+ "signature"
78
+ ])) {
79
+ errors.push("Proof contains unsupported fields.");
80
+ }
81
+ if (value.version !== SEAL_PROOF_VERSION) {
82
+ errors.push(`Proof version must be ${SEAL_PROOF_VERSION}.`);
83
+ }
84
+ if (value.type !== SEAL_PROOF_TYPE) {
85
+ errors.push(`Proof type must be ${SEAL_PROOF_TYPE}.`);
86
+ }
87
+ if (value.algorithm !== SEAL_SIGNATURE_ALGORITHM) {
88
+ errors.push(`Proof algorithm must be ${SEAL_SIGNATURE_ALGORITHM}.`);
89
+ }
90
+ if (typeof value.createdAt !== "string" || !isIsoDate(value.createdAt)) {
91
+ errors.push("Proof createdAt must be an ISO timestamp.");
92
+ }
93
+ if (typeof value.signature !== "string" || value.signature.length === 0) {
94
+ errors.push("Proof signature must be a non-empty base64 string.");
95
+ }
96
+ if (!isRecord(value.subject)) {
97
+ errors.push("Proof subject must be an object.");
98
+ }
99
+ if (!isRecord(value.signer)) {
100
+ errors.push("Proof signer must be an object.");
101
+ }
102
+ if (errors.length > 0 || !isRecord(value.subject) || !isRecord(value.signer)) {
103
+ return { ok: false, errors, proof: null };
104
+ }
105
+ if (!hasOnlyKeys(value.subject, ["kind", "path", "hash"])) {
106
+ errors.push("Proof subject contains unsupported fields.");
107
+ }
108
+ if (!hasOnlyKeys(value.signer, ["publicKey", "keyId"])) {
109
+ errors.push("Proof signer contains unsupported fields.");
110
+ }
111
+ if (value.subject.kind !== "file" && value.subject.kind !== "manifest") {
112
+ errors.push("Proof subject kind must be file or manifest.");
113
+ }
114
+ if (typeof value.subject.path !== "string" || value.subject.path.length === 0) {
115
+ errors.push("Proof subject path must be a non-empty string.");
116
+ }
117
+ if (!isSealHash(value.subject.hash)) {
118
+ errors.push("Proof subject hash must be a sha256 hash.");
119
+ }
120
+ if (typeof value.signer.publicKey !== "string" || value.signer.publicKey.length === 0) {
121
+ errors.push("Proof signer publicKey must be a non-empty base64 string.");
122
+ }
123
+ if (typeof value.signer.keyId !== "string" || value.signer.keyId.length === 0) {
124
+ errors.push("Proof signer keyId must be a non-empty string.");
125
+ }
126
+ if (errors.length > 0) {
127
+ return { ok: false, errors, proof: null };
128
+ }
129
+ return {
130
+ ok: true,
131
+ errors: [],
132
+ proof: value
133
+ };
134
+ }
135
+ function parseSealProofJson(raw) {
136
+ try {
137
+ return validateSealProofShape(JSON.parse(raw));
138
+ } catch {
139
+ return {
140
+ ok: false,
141
+ errors: ["Proof JSON is not valid JSON."],
142
+ proof: null
143
+ };
144
+ }
145
+ }
146
+ function validateSealPublicKeyShape(value) {
147
+ if (!isRecord(value)) {
148
+ return {
149
+ ok: false,
150
+ errors: ["Public key artifact must be a JSON object."],
151
+ artifact: null
152
+ };
153
+ }
154
+ const errors = [];
155
+ if (!hasOnlyKeys(value, ["algorithm", "publicKey", "keyId"])) {
156
+ errors.push("Public key artifact contains unsupported fields.");
157
+ }
158
+ if (value.algorithm !== SEAL_SIGNATURE_ALGORITHM) {
159
+ errors.push(`Public key artifact algorithm must be ${SEAL_SIGNATURE_ALGORITHM}.`);
160
+ }
161
+ if (typeof value.publicKey !== "string" || value.publicKey.length === 0) {
162
+ errors.push("Public key artifact publicKey must be a non-empty base64 string.");
163
+ }
164
+ if (typeof value.keyId !== "string" || value.keyId.length === 0) {
165
+ errors.push("Public key artifact keyId must be a non-empty string.");
166
+ }
167
+ if (errors.length > 0) {
168
+ return { ok: false, errors, artifact: null };
169
+ }
170
+ return {
171
+ ok: true,
172
+ errors: [],
173
+ artifact: value
174
+ };
175
+ }
176
+ function parseSealPublicKeyJson(raw) {
177
+ try {
178
+ return validateSealPublicKeyShape(JSON.parse(raw));
179
+ } catch {
180
+ return {
181
+ ok: false,
182
+ errors: ["Public key JSON is not valid JSON."],
183
+ artifact: null
184
+ };
185
+ }
186
+ }
187
+ async function verifySealProofSignature(proof) {
188
+ const validation = validateSealProofShape(proof);
189
+ if (!validation.ok || !validation.proof) {
190
+ return { ok: false, errors: validation.errors };
191
+ }
192
+ if (!await verifyPublicKeyKeyId(proof.signer.publicKey, proof.signer.keyId)) {
193
+ return {
194
+ ok: false,
195
+ errors: ["Proof signer keyId does not match signer public key."]
196
+ };
197
+ }
198
+ try {
199
+ const publicKey = await importPublicKeyFromBase64(proof.signer.publicKey);
200
+ const valid = await verifyUtf8(
201
+ proof.signature,
202
+ getSealProofSigningPayload(proof),
203
+ publicKey
204
+ );
205
+ if (!valid) {
206
+ return { ok: false, errors: ["Invalid signature."] };
207
+ }
208
+ return { ok: true, errors: [] };
209
+ } catch (caught) {
210
+ const message = caught instanceof Error ? caught.message : String(caught);
211
+ return {
212
+ ok: false,
213
+ errors: [`Failed to verify signature: ${message}`]
214
+ };
215
+ }
216
+ }
217
+ async function verifySealProofAgainstBytes(proof, bytes) {
218
+ const subjectHash = await createSealHash(bytes);
219
+ const signatureCheck = await verifySealProofSignature(proof);
220
+ const hashMatch = subjectHash === proof.subject.hash;
221
+ const signatureValid = signatureCheck.ok;
222
+ return {
223
+ valid: hashMatch && signatureValid,
224
+ hashMatch,
225
+ signatureValid,
226
+ keyId: proof.signer.keyId,
227
+ algorithm: proof.algorithm,
228
+ subjectHash
229
+ };
230
+ }
231
+ export { SEAL_PROOF_TYPE, SEAL_PROOF_VERSION, SEAL_SIGNATURE_ALGORITHM, createSealHash, createSealProof, createSealPublicKeyArtifact, getSealProofSignableFields, getSealProofSigningPayload, parseSealProofJson, parseSealPublicKeyJson, validateSealProofShape, validateSealPublicKeyShape, verifySealProofAgainstBytes, verifySealProofSignature };
232
+ //# sourceMappingURL=proof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof.js","sources":["../src/proof.ts"],"sourcesContent":["import { canonicalStringify, hashBytes } from \"ternent-utils\";\nimport {\n importPublicKeyFromBase64,\n resolveSealSigner,\n signUtf8,\n verifyPublicKeyKeyId,\n verifyUtf8,\n type SealSignerInput,\n} from \"./crypto\";\n\nexport const SEAL_PROOF_VERSION = \"1\" as const;\nexport const SEAL_PROOF_TYPE = \"seal-proof\" as const;\nexport const SEAL_SIGNATURE_ALGORITHM = \"ECDSA-P256-SHA256\" as const;\n\nexport type SealSubjectKind = \"file\" | \"manifest\";\n\nexport type SealProofV1 = {\n version: typeof SEAL_PROOF_VERSION;\n type: typeof SEAL_PROOF_TYPE;\n algorithm: typeof SEAL_SIGNATURE_ALGORITHM;\n createdAt: string;\n subject: {\n kind: SealSubjectKind;\n path: string;\n hash: `sha256:${string}`;\n };\n signer: {\n publicKey: string;\n keyId: string;\n };\n signature: string;\n};\n\nexport type SealPublicKeyArtifact = {\n algorithm: typeof SEAL_SIGNATURE_ALGORITHM;\n publicKey: string;\n keyId: string;\n};\n\ntype SealProofSignableFields = Omit<SealProofV1, \"signature\">;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(\n value: Record<string, unknown>,\n allowed: string[]\n): boolean {\n return Object.keys(value).every((key) => allowed.includes(key));\n}\n\nfunction isSealHash(value: unknown): value is `sha256:${string}` {\n return typeof value === \"string\" && /^sha256:[0-9a-f]{64}$/.test(value);\n}\n\nfunction isIsoDate(value: string): boolean {\n return !Number.isNaN(Date.parse(value));\n}\n\nexport function getSealProofSignableFields(\n proof: SealProofV1 | SealProofSignableFields\n): SealProofSignableFields {\n return {\n version: proof.version,\n type: proof.type,\n algorithm: proof.algorithm,\n createdAt: proof.createdAt,\n subject: proof.subject,\n signer: proof.signer,\n };\n}\n\nexport function getSealProofSigningPayload(\n proof: SealProofV1 | SealProofSignableFields\n): string {\n return canonicalStringify(getSealProofSignableFields(proof));\n}\n\nexport async function createSealHash(\n bytes: Uint8Array | ArrayBuffer\n): Promise<`sha256:${string}`> {\n const hash = await hashBytes(bytes);\n return `sha256:${hash}`;\n}\n\nexport async function createSealProof(input: {\n createdAt?: string;\n signer: SealSignerInput;\n subject: SealProofV1[\"subject\"];\n}): Promise<SealProofV1> {\n const signer = await resolveSealSigner(input.signer);\n const fields: SealProofSignableFields = {\n version: SEAL_PROOF_VERSION,\n type: SEAL_PROOF_TYPE,\n algorithm: SEAL_SIGNATURE_ALGORITHM,\n createdAt: input.createdAt ?? new Date().toISOString(),\n subject: input.subject,\n signer: {\n publicKey: signer.publicKeyBase64,\n keyId: signer.keyId,\n },\n };\n\n const signature = await signUtf8(\n signer.privateKey,\n getSealProofSigningPayload(fields)\n );\n\n return {\n ...fields,\n signature,\n };\n}\n\nexport async function createSealPublicKeyArtifact(\n signer: SealSignerInput\n): Promise<SealPublicKeyArtifact> {\n const resolved = await resolveSealSigner(signer);\n return {\n algorithm: SEAL_SIGNATURE_ALGORITHM,\n publicKey: resolved.publicKeyBase64,\n keyId: resolved.keyId,\n };\n}\n\nexport function validateSealProofShape(value: unknown): {\n ok: boolean;\n errors: string[];\n proof: SealProofV1 | null;\n} {\n if (!isRecord(value)) {\n return { ok: false, errors: [\"Proof must be a JSON object.\"], proof: null };\n }\n\n const errors: string[] = [];\n\n if (\n !hasOnlyKeys(value, [\n \"version\",\n \"type\",\n \"algorithm\",\n \"createdAt\",\n \"subject\",\n \"signer\",\n \"signature\",\n ])\n ) {\n errors.push(\"Proof contains unsupported fields.\");\n }\n\n if (value.version !== SEAL_PROOF_VERSION) {\n errors.push(`Proof version must be ${SEAL_PROOF_VERSION}.`);\n }\n if (value.type !== SEAL_PROOF_TYPE) {\n errors.push(`Proof type must be ${SEAL_PROOF_TYPE}.`);\n }\n if (value.algorithm !== SEAL_SIGNATURE_ALGORITHM) {\n errors.push(`Proof algorithm must be ${SEAL_SIGNATURE_ALGORITHM}.`);\n }\n if (typeof value.createdAt !== \"string\" || !isIsoDate(value.createdAt)) {\n errors.push(\"Proof createdAt must be an ISO timestamp.\");\n }\n if (typeof value.signature !== \"string\" || value.signature.length === 0) {\n errors.push(\"Proof signature must be a non-empty base64 string.\");\n }\n if (!isRecord(value.subject)) {\n errors.push(\"Proof subject must be an object.\");\n }\n if (!isRecord(value.signer)) {\n errors.push(\"Proof signer must be an object.\");\n }\n\n if (errors.length > 0 || !isRecord(value.subject) || !isRecord(value.signer)) {\n return { ok: false, errors, proof: null };\n }\n\n if (!hasOnlyKeys(value.subject, [\"kind\", \"path\", \"hash\"])) {\n errors.push(\"Proof subject contains unsupported fields.\");\n }\n if (!hasOnlyKeys(value.signer, [\"publicKey\", \"keyId\"])) {\n errors.push(\"Proof signer contains unsupported fields.\");\n }\n\n if (value.subject.kind !== \"file\" && value.subject.kind !== \"manifest\") {\n errors.push(\"Proof subject kind must be file or manifest.\");\n }\n if (typeof value.subject.path !== \"string\" || value.subject.path.length === 0) {\n errors.push(\"Proof subject path must be a non-empty string.\");\n }\n if (!isSealHash(value.subject.hash)) {\n errors.push(\"Proof subject hash must be a sha256 hash.\");\n }\n if (\n typeof value.signer.publicKey !== \"string\" ||\n value.signer.publicKey.length === 0\n ) {\n errors.push(\"Proof signer publicKey must be a non-empty base64 string.\");\n }\n if (typeof value.signer.keyId !== \"string\" || value.signer.keyId.length === 0) {\n errors.push(\"Proof signer keyId must be a non-empty string.\");\n }\n\n if (errors.length > 0) {\n return { ok: false, errors, proof: null };\n }\n\n return {\n ok: true,\n errors: [],\n proof: value as SealProofV1,\n };\n}\n\nexport function parseSealProofJson(raw: string): {\n ok: boolean;\n errors: string[];\n proof: SealProofV1 | null;\n} {\n try {\n return validateSealProofShape(JSON.parse(raw));\n } catch {\n return {\n ok: false,\n errors: [\"Proof JSON is not valid JSON.\"],\n proof: null,\n };\n }\n}\n\nexport function validateSealPublicKeyShape(value: unknown): {\n ok: boolean;\n errors: string[];\n artifact: SealPublicKeyArtifact | null;\n} {\n if (!isRecord(value)) {\n return {\n ok: false,\n errors: [\"Public key artifact must be a JSON object.\"],\n artifact: null,\n };\n }\n\n const errors: string[] = [];\n\n if (!hasOnlyKeys(value, [\"algorithm\", \"publicKey\", \"keyId\"])) {\n errors.push(\"Public key artifact contains unsupported fields.\");\n }\n if (value.algorithm !== SEAL_SIGNATURE_ALGORITHM) {\n errors.push(`Public key artifact algorithm must be ${SEAL_SIGNATURE_ALGORITHM}.`);\n }\n if (typeof value.publicKey !== \"string\" || value.publicKey.length === 0) {\n errors.push(\"Public key artifact publicKey must be a non-empty base64 string.\");\n }\n if (typeof value.keyId !== \"string\" || value.keyId.length === 0) {\n errors.push(\"Public key artifact keyId must be a non-empty string.\");\n }\n\n if (errors.length > 0) {\n return { ok: false, errors, artifact: null };\n }\n\n return {\n ok: true,\n errors: [],\n artifact: value as SealPublicKeyArtifact,\n };\n}\n\nexport function parseSealPublicKeyJson(raw: string): {\n ok: boolean;\n errors: string[];\n artifact: SealPublicKeyArtifact | null;\n} {\n try {\n return validateSealPublicKeyShape(JSON.parse(raw));\n } catch {\n return {\n ok: false,\n errors: [\"Public key JSON is not valid JSON.\"],\n artifact: null,\n };\n }\n}\n\nexport async function verifySealProofSignature(proof: SealProofV1): Promise<{\n ok: boolean;\n errors: string[];\n}> {\n const validation = validateSealProofShape(proof);\n if (!validation.ok || !validation.proof) {\n return { ok: false, errors: validation.errors };\n }\n\n if (!(await verifyPublicKeyKeyId(proof.signer.publicKey, proof.signer.keyId))) {\n return {\n ok: false,\n errors: [\"Proof signer keyId does not match signer public key.\"],\n };\n }\n\n try {\n const publicKey = await importPublicKeyFromBase64(proof.signer.publicKey);\n const valid = await verifyUtf8(\n proof.signature,\n getSealProofSigningPayload(proof),\n publicKey\n );\n if (!valid) {\n return { ok: false, errors: [\"Invalid signature.\"] };\n }\n return { ok: true, errors: [] };\n } catch (caught) {\n const message = caught instanceof Error ? caught.message : String(caught);\n return {\n ok: false,\n errors: [`Failed to verify signature: ${message}`],\n };\n }\n}\n\nexport async function verifySealProofAgainstBytes(\n proof: SealProofV1,\n bytes: Uint8Array | ArrayBuffer\n): Promise<{\n valid: boolean;\n hashMatch: boolean;\n signatureValid: boolean;\n keyId: string;\n algorithm: typeof SEAL_SIGNATURE_ALGORITHM;\n subjectHash: `sha256:${string}`;\n}> {\n const subjectHash = await createSealHash(bytes);\n const signatureCheck = await verifySealProofSignature(proof);\n const hashMatch = subjectHash === proof.subject.hash;\n const signatureValid = signatureCheck.ok;\n\n return {\n valid: hashMatch && signatureValid,\n hashMatch,\n signatureValid,\n keyId: proof.signer.keyId,\n algorithm: proof.algorithm,\n subjectHash,\n };\n}\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@ternent/seal-cli",
3
+ "version": "0.1.6",
4
+ "description": "Deterministic artifact signing for Seal",
5
+ "license": "ISC",
6
+ "type": "module",
7
+ "files": [
8
+ "bin",
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/samternent/home.git",
15
+ "directory": "packages/seal-cli"
16
+ },
17
+ "homepage": "https://github.com/samternent/home/tree/main/packages/seal-cli",
18
+ "bugs": {
19
+ "url": "https://github.com/samternent/home/issues"
20
+ },
21
+ "keywords": [
22
+ "seal",
23
+ "integrity",
24
+ "signature",
25
+ "manifest",
26
+ "cli"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "bin": {
32
+ "seal": "bin/seal"
33
+ },
34
+ "main": "dist/index.js",
35
+ "module": "dist/index.js",
36
+ "exports": {
37
+ ".": "./dist/index.js",
38
+ "./proof": "./dist/proof.js",
39
+ "./manifest": "./dist/manifest.js",
40
+ "./crypto": "./dist/crypto.js",
41
+ "./errors": "./dist/errors.js"
42
+ },
43
+ "dependencies": {},
44
+ "devDependencies": {
45
+ "vite": "^2.9.18",
46
+ "vitest": "^1.6.0",
47
+ "ternent-identity": "0.0.13",
48
+ "ternent-utils": "1.1.1"
49
+ },
50
+ "scripts": {
51
+ "build": "vite build",
52
+ "test": "vitest run"
53
+ }
54
+ }