veriskit 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +24 -2
- package/dist/index.js +293 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.1 — 2026-07-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Signed evidence. `veris evidence keygen` creates an Ed25519 keypair (via Node's built-in crypto, no new dependency); `veris evidence sign <evidence.json>` writes a detached signature; `veris evidence verify` checks a sibling signature automatically and can assert the signer with `--pubkey` or `--key-id`. Bundles carry the signature. Signing is opt-in; unsigned evidence still verifies for integrity. `VERISKIT_SIGNING_KEY` supplies the key in CI.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- `veris init` now gitignores `keys/`.
|
|
10
|
+
|
|
3
11
|
## 0.4.0 — 2026-07-10
|
|
4
12
|
|
|
5
13
|
### Added
|
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ veris evidence verify .veris/runs/<run-id>/evidence.json
|
|
|
190
190
|
This recomputes the digest and reports whether the record was edited or
|
|
191
191
|
corrupted since it was written. An integrity digest is not forgery-proof on its
|
|
192
192
|
own. To prove authorship, publish the digest separately (a CI log or PR) or sign
|
|
193
|
-
|
|
193
|
+
the record with an Ed25519 key (see Signing below).
|
|
194
194
|
|
|
195
195
|
Package a run as a single portable file (the record, its report, and its logs,
|
|
196
196
|
each with a digest, plus a bundle digest over everything):
|
|
@@ -202,6 +202,28 @@ veris evidence verify <bundle> # checks the record, every log, and the report
|
|
|
202
202
|
|
|
203
203
|
`veris evidence show` prints the latest record's key facts.
|
|
204
204
|
|
|
205
|
+
### Signing (optional)
|
|
206
|
+
|
|
207
|
+
An integrity digest proves a record was not edited. A signature proves a
|
|
208
|
+
specific key vouched for it. VerisKit signs with Ed25519 from Node's built-in
|
|
209
|
+
crypto, so signing adds no dependency and works offline.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
veris evidence keygen # writes .veris/keys/veriskit-signing.key(.pub)
|
|
213
|
+
veris evidence sign .veris/runs/<id>/evidence.json --key .veris/keys/veriskit-signing.key
|
|
214
|
+
veris evidence verify .veris/runs/<id>/evidence.json --pubkey .veris/keys/veriskit-signing.key.pub
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`sign` writes a detached `<evidence.json>.sig` next to the record. `verify`
|
|
218
|
+
picks it up automatically and checks it. In CI, pass the key through the
|
|
219
|
+
`VERISKIT_SIGNING_KEY` environment variable instead of a file.
|
|
220
|
+
|
|
221
|
+
A signature proves that whoever holds the private key signed the record. It
|
|
222
|
+
does not prove who that is. VerisKit prints the key fingerprint; to bind it to
|
|
223
|
+
a person or system, compare that fingerprint to one you already trust, or
|
|
224
|
+
assert it with `--pubkey` or `--key-id`. Keep the private key secret; `keygen`
|
|
225
|
+
writes it into `.veris/keys/`, which `veris init` gitignores.
|
|
226
|
+
|
|
205
227
|
Commit `.veris/config.json` and `.veris/.gitignore`. `veris init` keeps `runs/`,
|
|
206
228
|
`reports/`, `cache/`, `graph.json`, and `evidence/` out of your history.
|
|
207
229
|
|
|
@@ -213,7 +235,7 @@ VerisKit says what it cannot do as plainly as what it can:
|
|
|
213
235
|
- **No test generation.** `plan` tells you what to test. Writing the tests is a later release.
|
|
214
236
|
- **One project root.** A monorepo with several `tsconfig.json` files is not modeled yet. Resolution runs against the root project.
|
|
215
237
|
- **Scanner fallback on plain-JS or TS 7.x-native projects.** The accurate resolver needs the classic TypeScript compiler API. Without it you get the labeled, relative-imports-only graph described above, and no dependency is added to paper over the gap.
|
|
216
|
-
- **No
|
|
238
|
+
- **No keyless or identity-bound signing.** Evidence can be signed with a local Ed25519 key (see Signing), but sigstore-style keyless signing that ties a signature to an identity is not built yet.
|
|
217
239
|
|
|
218
240
|
## Part of Baseframe Labs
|
|
219
241
|
|
package/dist/index.js
CHANGED
|
@@ -849,6 +849,7 @@ var init_init = __esm({
|
|
|
849
849
|
"cache/",
|
|
850
850
|
"graph.json",
|
|
851
851
|
"evidence/",
|
|
852
|
+
"keys/",
|
|
852
853
|
""
|
|
853
854
|
].join("\n");
|
|
854
855
|
}
|
|
@@ -1897,7 +1898,84 @@ var init_plan = __esm({
|
|
|
1897
1898
|
}
|
|
1898
1899
|
});
|
|
1899
1900
|
|
|
1901
|
+
// src/evidence/signing.ts
|
|
1902
|
+
import {
|
|
1903
|
+
createHash as createHash2,
|
|
1904
|
+
createPrivateKey,
|
|
1905
|
+
createPublicKey,
|
|
1906
|
+
sign as cryptoSign,
|
|
1907
|
+
verify as cryptoVerify,
|
|
1908
|
+
generateKeyPairSync
|
|
1909
|
+
} from "crypto";
|
|
1910
|
+
function derOf(publicKey) {
|
|
1911
|
+
return publicKey.export({ type: "spki", format: "der" });
|
|
1912
|
+
}
|
|
1913
|
+
function keyId(publicKey) {
|
|
1914
|
+
const key = typeof publicKey === "string" ? createPublicKey(publicKey) : publicKey;
|
|
1915
|
+
return createHash2("sha256").update(derOf(key)).digest("hex").slice(0, 8);
|
|
1916
|
+
}
|
|
1917
|
+
function signatureKeyId(sig) {
|
|
1918
|
+
const key = createPublicKey({
|
|
1919
|
+
key: Buffer.from(sig.publicKey, "base64"),
|
|
1920
|
+
format: "der",
|
|
1921
|
+
type: "spki"
|
|
1922
|
+
});
|
|
1923
|
+
return keyId(key);
|
|
1924
|
+
}
|
|
1925
|
+
function generateKeyPair() {
|
|
1926
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
1927
|
+
return {
|
|
1928
|
+
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }),
|
|
1929
|
+
privateKeyPem: privateKey.export({
|
|
1930
|
+
type: "pkcs8",
|
|
1931
|
+
format: "pem"
|
|
1932
|
+
}),
|
|
1933
|
+
keyId: keyId(publicKey)
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
function signDigest(digest, privateKeyPem) {
|
|
1937
|
+
const privateKey = createPrivateKey(privateKeyPem);
|
|
1938
|
+
const publicKey = createPublicKey(
|
|
1939
|
+
privateKey
|
|
1940
|
+
);
|
|
1941
|
+
const sig = cryptoSign(null, Buffer.from(digest, "utf8"), privateKey);
|
|
1942
|
+
return {
|
|
1943
|
+
schema: SIGNATURE_SCHEMA,
|
|
1944
|
+
alg: "ed25519",
|
|
1945
|
+
keyId: keyId(publicKey),
|
|
1946
|
+
publicKey: derOf(publicKey).toString("base64"),
|
|
1947
|
+
digest,
|
|
1948
|
+
signature: sig.toString("base64"),
|
|
1949
|
+
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
function verifySignature(sig) {
|
|
1953
|
+
try {
|
|
1954
|
+
const publicKey = createPublicKey({
|
|
1955
|
+
key: Buffer.from(sig.publicKey, "base64"),
|
|
1956
|
+
format: "der",
|
|
1957
|
+
type: "spki"
|
|
1958
|
+
});
|
|
1959
|
+
return cryptoVerify(
|
|
1960
|
+
null,
|
|
1961
|
+
Buffer.from(sig.digest, "utf8"),
|
|
1962
|
+
publicKey,
|
|
1963
|
+
Buffer.from(sig.signature, "base64")
|
|
1964
|
+
);
|
|
1965
|
+
} catch {
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
var SIGNATURE_SCHEMA;
|
|
1970
|
+
var init_signing = __esm({
|
|
1971
|
+
"src/evidence/signing.ts"() {
|
|
1972
|
+
"use strict";
|
|
1973
|
+
SIGNATURE_SCHEMA = "veriskit/signature@1";
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1900
1977
|
// src/evidence/verify-evidence.ts
|
|
1978
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1901
1979
|
import { readFile as readFile3 } from "fs/promises";
|
|
1902
1980
|
function verifyRecord(record) {
|
|
1903
1981
|
const recomputed = computeDigest(
|
|
@@ -1911,20 +1989,71 @@ function verifyRecord(record) {
|
|
|
1911
1989
|
detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
|
|
1912
1990
|
}
|
|
1913
1991
|
];
|
|
1914
|
-
return { ok, kind: "record", record, checks };
|
|
1992
|
+
return { ok, kind: "record", record, checks, signed: false };
|
|
1993
|
+
}
|
|
1994
|
+
function signatureChecks(recordDigest, sig, opts = {}) {
|
|
1995
|
+
const checks = [];
|
|
1996
|
+
const kid = signatureKeyId(sig);
|
|
1997
|
+
const validSig = verifySignature(sig) && sig.digest === recordDigest;
|
|
1998
|
+
checks.push({
|
|
1999
|
+
name: "signature",
|
|
2000
|
+
ok: validSig,
|
|
2001
|
+
detail: validSig ? `valid ed25519 signature by key ${kid}` : "signature does not match this record"
|
|
2002
|
+
});
|
|
2003
|
+
if (opts.expectedKeyId || opts.expectedPubKeyPem) {
|
|
2004
|
+
let matches = false;
|
|
2005
|
+
if (opts.expectedPubKeyPem) {
|
|
2006
|
+
try {
|
|
2007
|
+
matches = keyId(opts.expectedPubKeyPem) === kid;
|
|
2008
|
+
} catch {
|
|
2009
|
+
matches = false;
|
|
2010
|
+
}
|
|
2011
|
+
} else if (opts.expectedKeyId) {
|
|
2012
|
+
matches = opts.expectedKeyId.toLowerCase() === kid.toLowerCase();
|
|
2013
|
+
}
|
|
2014
|
+
checks.push({
|
|
2015
|
+
name: "signer",
|
|
2016
|
+
ok: matches,
|
|
2017
|
+
detail: matches ? `signer matches the expected key ${kid}` : `signer ${kid} does not match the expected key`
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
return checks;
|
|
1915
2021
|
}
|
|
1916
|
-
async function verifyEvidenceFile(path) {
|
|
2022
|
+
async function verifyEvidenceFile(path, opts = {}) {
|
|
1917
2023
|
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
1918
2024
|
if (parsed?.schema === "veriskit/bundle@1") {
|
|
1919
2025
|
const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
1920
|
-
return verifyBundle2(parsed);
|
|
2026
|
+
return verifyBundle2(parsed, opts);
|
|
2027
|
+
}
|
|
2028
|
+
const result = verifyRecord(parsed);
|
|
2029
|
+
const assertedSigner = Boolean(
|
|
2030
|
+
opts.expectedKeyId || opts.expectedPubKeyPem || opts.sigPath
|
|
2031
|
+
);
|
|
2032
|
+
const sigPath = opts.sigPath ?? `${path}.sig`;
|
|
2033
|
+
if (existsSync5(sigPath)) {
|
|
2034
|
+
const sig = JSON.parse(await readFile3(sigPath, "utf8"));
|
|
2035
|
+
result.checks.push(
|
|
2036
|
+
...signatureChecks(result.record.digest, sig, {
|
|
2037
|
+
expectedKeyId: opts.expectedKeyId,
|
|
2038
|
+
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
2039
|
+
})
|
|
2040
|
+
);
|
|
2041
|
+
result.signed = true;
|
|
2042
|
+
} else if (assertedSigner) {
|
|
2043
|
+
result.checks.push({
|
|
2044
|
+
name: "signature",
|
|
2045
|
+
ok: false,
|
|
2046
|
+
detail: "a signature was required (--pubkey/--key-id/--sig) but none was found"
|
|
2047
|
+
});
|
|
1921
2048
|
}
|
|
1922
|
-
|
|
2049
|
+
result.ok = result.checks.every((c) => c.ok);
|
|
2050
|
+
return result;
|
|
1923
2051
|
}
|
|
1924
2052
|
var init_verify_evidence = __esm({
|
|
1925
2053
|
"src/evidence/verify-evidence.ts"() {
|
|
1926
2054
|
"use strict";
|
|
1927
2055
|
init_record();
|
|
2056
|
+
init_signing();
|
|
1928
2057
|
}
|
|
1929
2058
|
});
|
|
1930
2059
|
|
|
@@ -1935,7 +2064,7 @@ __export(bundle_exports, {
|
|
|
1935
2064
|
buildBundle: () => buildBundle,
|
|
1936
2065
|
verifyBundle: () => verifyBundle
|
|
1937
2066
|
});
|
|
1938
|
-
function buildBundle(record, report, logs) {
|
|
2067
|
+
function buildBundle(record, report, logs, signature) {
|
|
1939
2068
|
const manifest = {
|
|
1940
2069
|
record: record.digest,
|
|
1941
2070
|
report: sha256(report),
|
|
@@ -1943,10 +2072,10 @@ function buildBundle(record, report, logs) {
|
|
|
1943
2072
|
Object.entries(logs).map(([k, v]) => [k, sha256(v)])
|
|
1944
2073
|
)
|
|
1945
2074
|
};
|
|
1946
|
-
const base = { schema: BUNDLE_SCHEMA, record, report, logs, manifest };
|
|
2075
|
+
const base = signature ? { schema: BUNDLE_SCHEMA, record, report, logs, manifest, signature } : { schema: BUNDLE_SCHEMA, record, report, logs, manifest };
|
|
1947
2076
|
return { ...base, bundleDigest: sha256(canonicalize(base)) };
|
|
1948
2077
|
}
|
|
1949
|
-
function verifyBundle(bundle) {
|
|
2078
|
+
function verifyBundle(bundle, opts = {}) {
|
|
1950
2079
|
const checks = [];
|
|
1951
2080
|
const recordResult = verifyRecord(bundle.record);
|
|
1952
2081
|
checks.push(...recordResult.checks);
|
|
@@ -1971,11 +2100,28 @@ function verifyBundle(bundle) {
|
|
|
1971
2100
|
ok: recomputed === bundle.bundleDigest,
|
|
1972
2101
|
detail: recomputed === bundle.bundleDigest ? "matches" : "mismatch"
|
|
1973
2102
|
});
|
|
2103
|
+
let signed = false;
|
|
2104
|
+
if (bundle.signature) {
|
|
2105
|
+
signed = true;
|
|
2106
|
+
checks.push(
|
|
2107
|
+
...signatureChecks(bundle.record.digest, bundle.signature, {
|
|
2108
|
+
expectedKeyId: opts.expectedKeyId,
|
|
2109
|
+
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
2110
|
+
})
|
|
2111
|
+
);
|
|
2112
|
+
} else if (opts.expectedKeyId || opts.expectedPubKeyPem) {
|
|
2113
|
+
checks.push({
|
|
2114
|
+
name: "signature",
|
|
2115
|
+
ok: false,
|
|
2116
|
+
detail: "a signature was required (--pubkey/--key-id) but the bundle is unsigned"
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
1974
2119
|
return {
|
|
1975
2120
|
ok: checks.every((c) => c.ok),
|
|
1976
2121
|
kind: "bundle",
|
|
1977
2122
|
record: bundle.record,
|
|
1978
|
-
checks
|
|
2123
|
+
checks,
|
|
2124
|
+
signed
|
|
1979
2125
|
};
|
|
1980
2126
|
}
|
|
1981
2127
|
var BUNDLE_SCHEMA;
|
|
@@ -1992,17 +2138,33 @@ var init_bundle = __esm({
|
|
|
1992
2138
|
var evidence_exports = {};
|
|
1993
2139
|
__export(evidence_exports, {
|
|
1994
2140
|
runEvidenceBundle: () => runEvidenceBundle,
|
|
2141
|
+
runEvidenceKeygen: () => runEvidenceKeygen,
|
|
1995
2142
|
runEvidenceShow: () => runEvidenceShow,
|
|
2143
|
+
runEvidenceSign: () => runEvidenceSign,
|
|
1996
2144
|
runEvidenceVerify: () => runEvidenceVerify
|
|
1997
2145
|
});
|
|
1998
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
2146
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync } from "fs";
|
|
1999
2147
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
2000
|
-
import { basename as basename3, join as join12 } from "path";
|
|
2148
|
+
import { basename as basename3, dirname as dirname2, join as join12 } from "path";
|
|
2001
2149
|
import pc5 from "picocolors";
|
|
2002
|
-
async function runEvidenceVerify(path) {
|
|
2150
|
+
async function runEvidenceVerify(path, opts = {}) {
|
|
2151
|
+
let expectedPubKeyPem;
|
|
2152
|
+
if (opts.pubkey) {
|
|
2153
|
+
try {
|
|
2154
|
+
expectedPubKeyPem = readFileSync5(opts.pubkey, "utf8");
|
|
2155
|
+
} catch {
|
|
2156
|
+
process.stderr.write(`veris: cannot read public key at ${opts.pubkey}
|
|
2157
|
+
`);
|
|
2158
|
+
return 1;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2003
2161
|
let result;
|
|
2004
2162
|
try {
|
|
2005
|
-
result = await verifyEvidenceFile(path
|
|
2163
|
+
result = await verifyEvidenceFile(path, {
|
|
2164
|
+
sigPath: opts.sig,
|
|
2165
|
+
expectedKeyId: opts.keyId,
|
|
2166
|
+
expectedPubKeyPem
|
|
2167
|
+
});
|
|
2006
2168
|
} catch (err) {
|
|
2007
2169
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2008
2170
|
process.stderr.write(`veris: cannot read evidence at ${path}: ${msg}
|
|
@@ -2027,8 +2189,93 @@ ${result.ok ? "digest OK" : "TAMPERED"} \xB7 verdict ${result.record.verdict.sta
|
|
|
2027
2189
|
process.stdout.write(`
|
|
2028
2190
|
${HONESTY}
|
|
2029
2191
|
`);
|
|
2192
|
+
if (result.signed && !opts.pubkey && !opts.keyId) {
|
|
2193
|
+
process.stdout.write(
|
|
2194
|
+
"\nThis record is signed. Confirm you trust the signing key by comparing its\nkey id above to one you already trust, or re-run with --pubkey / --key-id.\n"
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2030
2197
|
return result.ok ? 0 : 1;
|
|
2031
2198
|
}
|
|
2199
|
+
async function runEvidenceKeygen(root, opts = {}) {
|
|
2200
|
+
const out = opts.out ?? join12(root, ".veris", "keys", "veriskit-signing.key");
|
|
2201
|
+
const pub = `${out}.pub`;
|
|
2202
|
+
if (existsSync6(out) || existsSync6(pub)) {
|
|
2203
|
+
process.stderr.write(
|
|
2204
|
+
`veris: a key already exists at ${out}. Refusing to overwrite it.
|
|
2205
|
+
`
|
|
2206
|
+
);
|
|
2207
|
+
return 1;
|
|
2208
|
+
}
|
|
2209
|
+
const { mkdir: mkdir2 } = await import("fs/promises");
|
|
2210
|
+
await mkdir2(dirname2(out), { recursive: true });
|
|
2211
|
+
const kp = generateKeyPair();
|
|
2212
|
+
writeFileSync(out, kp.privateKeyPem, { mode: 384 });
|
|
2213
|
+
chmodSync(out, 384);
|
|
2214
|
+
writeFileSync(pub, kp.publicKeyPem, "utf8");
|
|
2215
|
+
process.stdout.write(
|
|
2216
|
+
[
|
|
2217
|
+
`Wrote signing key ${out} (keep this secret; do not commit it)`,
|
|
2218
|
+
`Wrote public key ${pub}`,
|
|
2219
|
+
`Key id ${kp.keyId}`,
|
|
2220
|
+
""
|
|
2221
|
+
].join("\n")
|
|
2222
|
+
);
|
|
2223
|
+
return 0;
|
|
2224
|
+
}
|
|
2225
|
+
async function runEvidenceSign(evidencePath, opts = {}) {
|
|
2226
|
+
let privateKeyPem;
|
|
2227
|
+
if (process.env.VERISKIT_SIGNING_KEY) {
|
|
2228
|
+
privateKeyPem = process.env.VERISKIT_SIGNING_KEY;
|
|
2229
|
+
} else if (opts.key) {
|
|
2230
|
+
try {
|
|
2231
|
+
privateKeyPem = readFileSync5(opts.key, "utf8");
|
|
2232
|
+
} catch {
|
|
2233
|
+
process.stderr.write(`veris: cannot read signing key at ${opts.key}
|
|
2234
|
+
`);
|
|
2235
|
+
return 1;
|
|
2236
|
+
}
|
|
2237
|
+
} else {
|
|
2238
|
+
process.stderr.write(
|
|
2239
|
+
"veris: no signing key. Pass --key <path> or set VERISKIT_SIGNING_KEY.\n"
|
|
2240
|
+
);
|
|
2241
|
+
return 1;
|
|
2242
|
+
}
|
|
2243
|
+
let record;
|
|
2244
|
+
try {
|
|
2245
|
+
record = JSON.parse(readFileSync5(evidencePath, "utf8"));
|
|
2246
|
+
} catch {
|
|
2247
|
+
process.stderr.write(`veris: cannot read evidence at ${evidencePath}
|
|
2248
|
+
`);
|
|
2249
|
+
return 1;
|
|
2250
|
+
}
|
|
2251
|
+
const recomputed = computeDigest(
|
|
2252
|
+
record
|
|
2253
|
+
);
|
|
2254
|
+
if (recomputed !== record.digest) {
|
|
2255
|
+
process.stderr.write(
|
|
2256
|
+
"veris: refusing to sign, the record digest does not match its contents.\n"
|
|
2257
|
+
);
|
|
2258
|
+
return 1;
|
|
2259
|
+
}
|
|
2260
|
+
let sig;
|
|
2261
|
+
try {
|
|
2262
|
+
sig = signDigest(record.digest, privateKeyPem);
|
|
2263
|
+
} catch (err) {
|
|
2264
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2265
|
+
process.stderr.write(`veris: signing failed: ${msg}
|
|
2266
|
+
`);
|
|
2267
|
+
return 1;
|
|
2268
|
+
}
|
|
2269
|
+
const out = opts.out ?? `${evidencePath}.sig`;
|
|
2270
|
+
writeFileSync(out, `${JSON.stringify(sig, null, 2)}
|
|
2271
|
+
`, "utf8");
|
|
2272
|
+
process.stdout.write(
|
|
2273
|
+
`Signed ${evidencePath}
|
|
2274
|
+
Wrote ${out} (key ${sig.keyId})
|
|
2275
|
+
`
|
|
2276
|
+
);
|
|
2277
|
+
return 0;
|
|
2278
|
+
}
|
|
2032
2279
|
async function runEvidenceBundle(root, opts = {}) {
|
|
2033
2280
|
const runDir = latestRunDir(root);
|
|
2034
2281
|
if (!runDir) {
|
|
@@ -2055,7 +2302,15 @@ async function runEvidenceBundle(root, opts = {}) {
|
|
|
2055
2302
|
}
|
|
2056
2303
|
const logIds = record.checks.filter((c) => c.logDigest).map((c) => c.id);
|
|
2057
2304
|
const logs = await readRunLogs(runDir, logIds);
|
|
2058
|
-
|
|
2305
|
+
let signature;
|
|
2306
|
+
const sigPath = join12(runDir, "evidence.json.sig");
|
|
2307
|
+
if (existsSync6(sigPath)) {
|
|
2308
|
+
try {
|
|
2309
|
+
signature = JSON.parse(readFileSync5(sigPath, "utf8"));
|
|
2310
|
+
} catch {
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
const bundle = buildBundle(record, report, logs, signature);
|
|
2059
2314
|
const outDir = await ensureEvidenceDir(root);
|
|
2060
2315
|
const out = opts.out ?? join12(outDir, `${record.id}.bundle.json`);
|
|
2061
2316
|
await writeFile4(out, `${JSON.stringify(bundle, null, 2)}
|
|
@@ -2101,10 +2356,12 @@ var init_evidence = __esm({
|
|
|
2101
2356
|
"src/cli/commands/evidence.ts"() {
|
|
2102
2357
|
"use strict";
|
|
2103
2358
|
init_bundle();
|
|
2359
|
+
init_record();
|
|
2360
|
+
init_signing();
|
|
2104
2361
|
init_store();
|
|
2105
2362
|
init_verify_evidence();
|
|
2106
2363
|
init_tty();
|
|
2107
|
-
HONESTY = "An integrity digest confirms the record was not edited or corrupted since it was written.\nIt is not forgery-proof on its own: publish the digest separately (CI log, PR) or sign it (
|
|
2364
|
+
HONESTY = "An integrity digest confirms the record was not edited or corrupted since it was written.\nIt is not forgery-proof on its own: publish the digest separately (CI log, PR) or sign it (veris evidence sign) to prove authorship.";
|
|
2108
2365
|
}
|
|
2109
2366
|
});
|
|
2110
2367
|
|
|
@@ -2164,8 +2421,8 @@ function buildCli() {
|
|
|
2164
2421
|
});
|
|
2165
2422
|
cli.command(
|
|
2166
2423
|
"evidence <action> [file]",
|
|
2167
|
-
"Evidence
|
|
2168
|
-
).option("--out <file>", "For bundle:
|
|
2424
|
+
"Evidence: verify <file> | bundle | show [file] | keygen | sign <file>"
|
|
2425
|
+
).option("--out <file>", "For bundle/keygen/sign: output path").option("--key <file>", "For sign: the private signing key (PEM)").option("--pubkey <file>", "For verify: assert the signer's public key").option("--key-id <id>", "For verify: assert the signer's key id").option("--sig <file>", "For verify: signature path (default <file>.sig)").action(
|
|
2169
2426
|
async (action, file, opts) => {
|
|
2170
2427
|
const mod = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
2171
2428
|
if (action === "verify") {
|
|
@@ -2174,16 +2431,34 @@ function buildCli() {
|
|
|
2174
2431
|
process.exitCode = 1;
|
|
2175
2432
|
return;
|
|
2176
2433
|
}
|
|
2177
|
-
process.exitCode = await mod.runEvidenceVerify(file
|
|
2434
|
+
process.exitCode = await mod.runEvidenceVerify(file, {
|
|
2435
|
+
pubkey: opts.pubkey,
|
|
2436
|
+
keyId: opts.keyId,
|
|
2437
|
+
sig: opts.sig
|
|
2438
|
+
});
|
|
2178
2439
|
} else if (action === "bundle") {
|
|
2179
2440
|
process.exitCode = await mod.runEvidenceBundle(process.cwd(), {
|
|
2180
2441
|
out: opts.out
|
|
2181
2442
|
});
|
|
2182
2443
|
} else if (action === "show") {
|
|
2183
2444
|
process.exitCode = await mod.runEvidenceShow(process.cwd(), file);
|
|
2445
|
+
} else if (action === "keygen") {
|
|
2446
|
+
process.exitCode = await mod.runEvidenceKeygen(process.cwd(), {
|
|
2447
|
+
out: opts.out
|
|
2448
|
+
});
|
|
2449
|
+
} else if (action === "sign") {
|
|
2450
|
+
if (!file) {
|
|
2451
|
+
process.stderr.write("veris: evidence sign needs a <file>\n");
|
|
2452
|
+
process.exitCode = 1;
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
process.exitCode = await mod.runEvidenceSign(file, {
|
|
2456
|
+
key: opts.key,
|
|
2457
|
+
out: opts.out
|
|
2458
|
+
});
|
|
2184
2459
|
} else {
|
|
2185
2460
|
process.stderr.write(
|
|
2186
|
-
`veris: unknown evidence action '${action}' (use verify | bundle | show)
|
|
2461
|
+
`veris: unknown evidence action '${action}' (use verify | bundle | show | keygen | sign)
|
|
2187
2462
|
`
|
|
2188
2463
|
);
|
|
2189
2464
|
process.exitCode = 1;
|