Files
ts-scripts/sigstore-verify-ts/main.ts

146 lines
3.4 KiB
TypeScript
Executable File

#!/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<RekorAttestFile>;
}
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<RekorAttest> {
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();