diff --git a/sigstore-verify-ts/hello.txt b/sigstore-verify-ts/hello.txt new file mode 100644 index 0000000..a05db9d --- /dev/null +++ b/sigstore-verify-ts/hello.txt @@ -0,0 +1 @@ +hello hatter 2025 diff --git a/sigstore-verify-ts/main.ts b/sigstore-verify-ts/main.ts new file mode 100755 index 0000000..b0a810a --- /dev/null +++ b/sigstore-verify-ts/main.ts @@ -0,0 +1,145 @@ +#!/usr/bin/env -S deno run --allow-env --allow-run --allow-read + +import {parseArgs} from "jsr:@std/cli/parse-args"; + +// Reference: +// * https://docs.deno.com/api/deno/~/Deno.Command +// * https://docs.deno.com/examples/command_line_arguments/ + +// { +// "format": "rekor-attest-v1", +// "files": [ +// { +// "filename": "example.rs", +// "rekorId": "xxxxxxxxxxxxxxxxx" +// } +// ] +// } + +// get from url or rekor-cli +// const REKOR_ENTRY_URL = "https://rekor.sigstore.dev/api/v1/log/entries/${UUID}"; + +// ----- CONFIG ----- +interface RekorAttest { + format: string; + files: Array; +} + +interface RekorAttestFile { + filename: string; + rekorId: string; +} + +// ----- REKOR RECORD ----- +interface RekorRecord { + Attestation: string; + AttestationType: string; + Body: RekorRecordBody; + LogIndex: number; + IntegratedTime: number; + UUID: number; + LogID: number; +} + +interface RekorRecordBody { + RekordObj: RekorRecordBodyRekordObj; +} + +interface RekorRecordBodyRekordObj { + data: RekorRecordBodyRekordObjData; + signature: RekorRecordBodyRekordObjSignature; +} + +interface RekorRecordBodyRekordObjData { + hash: RekorRecordHash; +} + +interface RekorRecordHash { + algorithm: string; + value: string; +} + +interface RekorRecordBodyRekordObjSignature { + content: string; + format: string; + publicKey: RekorRecordPublicKey; +} + +interface RekorRecordPublicKey { + content: string; +} + +async function loadAttest(filename: string): Promise { + const attest = await Deno.readTextFile(filename); + return JSON.parse(attest); +} + +function fetchRekorViaCli(uuid: string): RekorRecord { + if (isStringEmpty(uuid)) { + throw "Rekor UUID is empty." + } + const command = new Deno.Command("rekor-cli", { + args: [ + "get", + "--format", + "json", + "--uuid", + uuid + ], + }); + const {code, stdout, stderr} = command.outputSync(); + if (code !== 0) { + console.error(`Execute command rekor-cli failed: +code: ${code} +stdout: ${new TextDecoder().decode(stdout)} +stderr: ${new TextDecoder().decode(stderr)}`); + throw `Execute command rekor-cli failed, code: ${code}` + } + const rekorBody = new TextDecoder().decode(stdout); + return JSON.parse(rekorBody); +} + +function isStringEmpty(str: string): boolean { + return str === null || str === undefined || str == ""; +} + +interface Args { + help: boolean; + attest?: string; +} + +function parseToArgs(): Args { + return parseArgs(Deno.args, { + boolean: ["help"], + string: ["attest"], + }); +} + +async function verifyFile(attestFile: RekorAttestFile) { + const rekorRecord = fetchRekorViaCli(attestFile.rekorId); + console.log(rekorRecord); + // TODO ... +} + +async function main() { + const args = parseToArgs(); + if (args.help) { + console.log(`sigstore-verify.ts - sigstore verify cli + +sigstore-verify.ts [--attest sigstore-attest.json] +`); + return; + } + let attestFileName = args.attest || "sigstore-attest.json"; + let attest = await loadAttest(attestFileName); + if (attest.format !== "rekor-attest-v1") { + throw `Bad rekor attest file format: ${attest.format}`; + } + for (let i = 0; i < attest.files.length; i++) { + const attestFile = attest.files[i]; + console.log(`Process file: ${attestFile.filename} [${i + 1} / ${attest.files.length}]`); + await verifyFile(attestFile); + } +} + +await main(); diff --git a/sigstore-verify-ts/sigstore-attest.json b/sigstore-verify-ts/sigstore-attest.json new file mode 100644 index 0000000..f1426f1 --- /dev/null +++ b/sigstore-verify-ts/sigstore-attest.json @@ -0,0 +1,9 @@ +{ + "format": "rekor-attest-v1", + "files": [ + { + "filename": "hello.txt", + "rekorId": "108e9186e8c5677ab315931b32e40e6af1a5e3267f2a1e2660e15b6604406d59f6a0d8009e6839c1" + } + ] +}