@ternent/seal 0.3.10

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.
@@ -0,0 +1,82 @@
1
+ import { c as canonicalStringify } from "./chunks/utils.es-ad8f1dc4.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(value: Record<string, unknown>, allowed: string[]): 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,YAAY,OAAgC,SAA4B;AACxE,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;AAEO,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;AAEJ;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,237 @@
1
+ import { c as canonicalStringify, h as hashBytes } from "./chunks/utils.es-ad8f1dc4.js";
2
+ import { resolveSealSigner, signSealUtf8, verifyPublicKeyKeyId, verifySealUtf8 } from "./crypto.js";
3
+ import "@ternent/identity";
4
+ const SEAL_PROOF_VERSION = "2";
5
+ const SEAL_PROOF_TYPE = "seal-proof";
6
+ const SEAL_PUBLIC_KEY_TYPE = "seal-public-key";
7
+ const SEAL_SIGNATURE_ALGORITHM = "Ed25519";
8
+ function isRecord(value) {
9
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10
+ }
11
+ function hasOnlyKeys(value, allowed) {
12
+ return Object.keys(value).every((key) => allowed.includes(key));
13
+ }
14
+ function isSealHash(value) {
15
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
16
+ }
17
+ function isIsoDate(value) {
18
+ return !Number.isNaN(Date.parse(value));
19
+ }
20
+ function getSealProofSignableFields(proof) {
21
+ return {
22
+ version: proof.version,
23
+ type: proof.type,
24
+ algorithm: proof.algorithm,
25
+ createdAt: proof.createdAt,
26
+ subject: proof.subject,
27
+ signer: proof.signer
28
+ };
29
+ }
30
+ function getSealProofSigningPayload(proof) {
31
+ return canonicalStringify(getSealProofSignableFields(proof));
32
+ }
33
+ async function createSealHash(bytes) {
34
+ const hash = await hashBytes(bytes);
35
+ return `sha256:${hash}`;
36
+ }
37
+ async function createSealProof(input) {
38
+ const signer = await resolveSealSigner(input.signer);
39
+ const fields = {
40
+ version: SEAL_PROOF_VERSION,
41
+ type: SEAL_PROOF_TYPE,
42
+ algorithm: SEAL_SIGNATURE_ALGORITHM,
43
+ createdAt: input.createdAt ?? new Date().toISOString(),
44
+ subject: input.subject,
45
+ signer: {
46
+ publicKey: signer.publicKey,
47
+ keyId: signer.keyId
48
+ }
49
+ };
50
+ const signature = await signSealUtf8(signer.identity, getSealProofSigningPayload(fields));
51
+ return {
52
+ ...fields,
53
+ signature
54
+ };
55
+ }
56
+ async function createSealPublicKeyArtifact(signer) {
57
+ const resolved = await resolveSealSigner(signer);
58
+ return {
59
+ version: SEAL_PROOF_VERSION,
60
+ type: SEAL_PUBLIC_KEY_TYPE,
61
+ algorithm: SEAL_SIGNATURE_ALGORITHM,
62
+ publicKey: resolved.publicKey,
63
+ keyId: resolved.keyId
64
+ };
65
+ }
66
+ function validateSealProofShape(value) {
67
+ if (!isRecord(value)) {
68
+ return { ok: false, errors: ["Proof must be a JSON object."], proof: null };
69
+ }
70
+ const errors = [];
71
+ if (!hasOnlyKeys(value, [
72
+ "version",
73
+ "type",
74
+ "algorithm",
75
+ "createdAt",
76
+ "subject",
77
+ "signer",
78
+ "signature"
79
+ ])) {
80
+ errors.push("Proof contains unsupported fields.");
81
+ }
82
+ if (value.version !== SEAL_PROOF_VERSION) {
83
+ errors.push(`Proof version must be ${SEAL_PROOF_VERSION}.`);
84
+ }
85
+ if (value.type !== SEAL_PROOF_TYPE) {
86
+ errors.push(`Proof type must be ${SEAL_PROOF_TYPE}.`);
87
+ }
88
+ if (value.algorithm !== SEAL_SIGNATURE_ALGORITHM) {
89
+ errors.push(`Proof algorithm must be ${SEAL_SIGNATURE_ALGORITHM}.`);
90
+ }
91
+ if (typeof value.createdAt !== "string" || !isIsoDate(value.createdAt)) {
92
+ errors.push("Proof createdAt must be an ISO timestamp.");
93
+ }
94
+ if (typeof value.signature !== "string" || value.signature.length === 0) {
95
+ errors.push("Proof signature must be a non-empty base64url string.");
96
+ }
97
+ if (!isRecord(value.subject)) {
98
+ errors.push("Proof subject must be an object.");
99
+ }
100
+ if (!isRecord(value.signer)) {
101
+ errors.push("Proof signer must be an object.");
102
+ }
103
+ if (errors.length > 0 || !isRecord(value.subject) || !isRecord(value.signer)) {
104
+ return { ok: false, errors, proof: null };
105
+ }
106
+ if (!hasOnlyKeys(value.subject, ["kind", "path", "hash"])) {
107
+ errors.push("Proof subject contains unsupported fields.");
108
+ }
109
+ if (!hasOnlyKeys(value.signer, ["publicKey", "keyId"])) {
110
+ errors.push("Proof signer contains unsupported fields.");
111
+ }
112
+ if (value.subject.kind !== "file" && value.subject.kind !== "manifest" && value.subject.kind !== "artifact") {
113
+ errors.push("Proof subject kind must be file, manifest, or artifact.");
114
+ }
115
+ if (typeof value.subject.path !== "string" || value.subject.path.length === 0) {
116
+ errors.push("Proof subject path must be a non-empty string.");
117
+ }
118
+ if (!isSealHash(value.subject.hash)) {
119
+ errors.push("Proof subject hash must be a sha256 hash.");
120
+ }
121
+ if (typeof value.signer.publicKey !== "string" || value.signer.publicKey.length === 0) {
122
+ errors.push("Proof signer publicKey must be a non-empty base64url string.");
123
+ }
124
+ if (typeof value.signer.keyId !== "string" || value.signer.keyId.length === 0) {
125
+ errors.push("Proof signer keyId must be a non-empty string.");
126
+ }
127
+ if (errors.length > 0) {
128
+ return { ok: false, errors, proof: null };
129
+ }
130
+ return {
131
+ ok: true,
132
+ errors: [],
133
+ proof: value
134
+ };
135
+ }
136
+ function parseSealProofJson(raw) {
137
+ try {
138
+ return validateSealProofShape(JSON.parse(raw));
139
+ } catch {
140
+ return {
141
+ ok: false,
142
+ errors: ["Proof JSON is not valid JSON."],
143
+ proof: null
144
+ };
145
+ }
146
+ }
147
+ function validateSealPublicKeyShape(value) {
148
+ if (!isRecord(value)) {
149
+ return {
150
+ ok: false,
151
+ errors: ["Public key artifact must be a JSON object."],
152
+ artifact: null
153
+ };
154
+ }
155
+ const errors = [];
156
+ if (!hasOnlyKeys(value, ["version", "type", "algorithm", "publicKey", "keyId"])) {
157
+ errors.push("Public key artifact contains unsupported fields.");
158
+ }
159
+ if (value.version !== SEAL_PROOF_VERSION) {
160
+ errors.push(`Public key artifact version must be ${SEAL_PROOF_VERSION}.`);
161
+ }
162
+ if (value.type !== SEAL_PUBLIC_KEY_TYPE) {
163
+ errors.push(`Public key artifact type must be ${SEAL_PUBLIC_KEY_TYPE}.`);
164
+ }
165
+ if (value.algorithm !== SEAL_SIGNATURE_ALGORITHM) {
166
+ errors.push(`Public key artifact algorithm must be ${SEAL_SIGNATURE_ALGORITHM}.`);
167
+ }
168
+ if (typeof value.publicKey !== "string" || value.publicKey.length === 0) {
169
+ errors.push("Public key artifact publicKey must be a non-empty base64url string.");
170
+ }
171
+ if (typeof value.keyId !== "string" || value.keyId.length === 0) {
172
+ errors.push("Public key artifact keyId must be a non-empty string.");
173
+ }
174
+ if (errors.length > 0) {
175
+ return { ok: false, errors, artifact: null };
176
+ }
177
+ return {
178
+ ok: true,
179
+ errors: [],
180
+ artifact: value
181
+ };
182
+ }
183
+ function parseSealPublicKeyJson(raw) {
184
+ try {
185
+ return validateSealPublicKeyShape(JSON.parse(raw));
186
+ } catch {
187
+ return {
188
+ ok: false,
189
+ errors: ["Public key JSON is not valid JSON."],
190
+ artifact: null
191
+ };
192
+ }
193
+ }
194
+ async function verifySealProofSignature(proof) {
195
+ const validation = validateSealProofShape(proof);
196
+ if (!validation.ok || !validation.proof) {
197
+ return { ok: false, errors: validation.errors };
198
+ }
199
+ if (!await verifyPublicKeyKeyId(proof.signer.publicKey, proof.signer.keyId)) {
200
+ return {
201
+ ok: false,
202
+ errors: ["Proof signer keyId does not match signer public key."]
203
+ };
204
+ }
205
+ try {
206
+ const valid = await verifySealUtf8(
207
+ proof.signature,
208
+ getSealProofSigningPayload(proof),
209
+ proof.signer.publicKey
210
+ );
211
+ if (!valid) {
212
+ return { ok: false, errors: ["Invalid signature."] };
213
+ }
214
+ return { ok: true, errors: [] };
215
+ } catch (caught) {
216
+ return {
217
+ ok: false,
218
+ errors: ["Invalid signature."]
219
+ };
220
+ }
221
+ }
222
+ async function verifySealProofAgainstBytes(proof, bytes) {
223
+ const subjectHash = await createSealHash(bytes);
224
+ const signatureCheck = await verifySealProofSignature(proof);
225
+ const hashMatch = subjectHash === proof.subject.hash;
226
+ const signatureValid = signatureCheck.ok;
227
+ return {
228
+ valid: hashMatch && signatureValid,
229
+ hashMatch,
230
+ signatureValid,
231
+ keyId: proof.signer.keyId,
232
+ algorithm: proof.algorithm,
233
+ subjectHash
234
+ };
235
+ }
236
+ export { SEAL_PROOF_TYPE, SEAL_PROOF_VERSION, SEAL_PUBLIC_KEY_TYPE, SEAL_SIGNATURE_ALGORITHM, createSealHash, createSealProof, createSealPublicKeyArtifact, getSealProofSignableFields, getSealProofSigningPayload, parseSealProofJson, parseSealPublicKeyJson, validateSealProofShape, validateSealPublicKeyShape, verifySealProofAgainstBytes, verifySealProofSignature };
237
+ //# 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 resolveSealSigner,\n signSealUtf8,\n verifyPublicKeyKeyId,\n verifySealUtf8,\n type SealSignerInput,\n} from \"./crypto\";\n\nexport const SEAL_PROOF_VERSION = \"2\" as const;\nexport const SEAL_PROOF_TYPE = \"seal-proof\" as const;\nexport const SEAL_PUBLIC_KEY_TYPE = \"seal-public-key\" as const;\nexport const SEAL_SIGNATURE_ALGORITHM = \"Ed25519\" as const;\n\nexport type SealSubjectKind = \"file\" | \"manifest\" | \"artifact\";\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 version: typeof SEAL_PROOF_VERSION;\n type: typeof SEAL_PUBLIC_KEY_TYPE;\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(value: Record<string, unknown>, allowed: string[]): 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(proof: SealProofV1 | SealProofSignableFields): string {\n return canonicalStringify(getSealProofSignableFields(proof));\n}\n\nexport async function createSealHash(bytes: Uint8Array | ArrayBuffer): 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.publicKey,\n keyId: signer.keyId,\n },\n };\n\n const signature = await signSealUtf8(signer.identity, getSealProofSigningPayload(fields));\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 version: SEAL_PROOF_VERSION,\n type: SEAL_PUBLIC_KEY_TYPE,\n algorithm: SEAL_SIGNATURE_ALGORITHM,\n publicKey: resolved.publicKey,\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 base64url 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 if (\n value.subject.kind !== \"file\" &&\n value.subject.kind !== \"manifest\" &&\n value.subject.kind !== \"artifact\"\n ) {\n errors.push(\"Proof subject kind must be file, manifest, or artifact.\");\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 (typeof value.signer.publicKey !== \"string\" || value.signer.publicKey.length === 0) {\n errors.push(\"Proof signer publicKey must be a non-empty base64url 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, [\"version\", \"type\", \"algorithm\", \"publicKey\", \"keyId\"])) {\n errors.push(\"Public key artifact contains unsupported fields.\");\n }\n if (value.version !== SEAL_PROOF_VERSION) {\n errors.push(`Public key artifact version must be ${SEAL_PROOF_VERSION}.`);\n }\n if (value.type !== SEAL_PUBLIC_KEY_TYPE) {\n errors.push(`Public key artifact type must be ${SEAL_PUBLIC_KEY_TYPE}.`);\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 base64url 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 valid = await verifySealUtf8(\n proof.signature,\n getSealProofSigningPayload(proof),\n proof.signer.publicKey,\n );\n if (!valid) {\n return { ok: false, errors: [\"Invalid signature.\"] };\n }\n return { ok: true, errors: [] };\n } catch (caught) {\n return {\n ok: false,\n errors: [\"Invalid signature.\"],\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,59 @@
1
+ {
2
+ "name": "@ternent/seal",
3
+ "version": "0.3.10",
4
+ "description": "Deterministic artifact signing for Seal",
5
+ "keywords": [
6
+ "cli",
7
+ "integrity",
8
+ "manifest",
9
+ "seal",
10
+ "signature"
11
+ ],
12
+ "homepage": "https://github.com/ternent/core/tree/main/packages/seal",
13
+ "bugs": {
14
+ "url": "https://github.com/ternent/core/issues"
15
+ },
16
+ "license": "ISC",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/ternent/core.git",
20
+ "directory": "packages/seal"
21
+ },
22
+ "bin": {
23
+ "seal": "bin/seal"
24
+ },
25
+ "files": [
26
+ "bin",
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "type": "module",
31
+ "sideEffects": false,
32
+ "main": "dist/index.js",
33
+ "module": "dist/index.js",
34
+ "exports": {
35
+ ".": "./dist/index.js",
36
+ "./proof": "./dist/proof.js",
37
+ "./artifact": "./dist/artifact.js",
38
+ "./manifest": "./dist/manifest.js",
39
+ "./crypto": "./dist/crypto.js",
40
+ "./errors": "./dist/errors.js"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build:proof": "vite build --mode proof-only",
47
+ "build": "vite build",
48
+ "test": "vitest run"
49
+ },
50
+ "dependencies": {
51
+ "@ternent/armour": "workspace:*",
52
+ "@ternent/identity": "workspace:*",
53
+ "@ternent/utils": "workspace:*"
54
+ },
55
+ "devDependencies": {
56
+ "vite": "^2.9.18",
57
+ "vitest": "^1.6.0"
58
+ }
59
+ }