146 lines
3.4 KiB
TypeScript
Executable File
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();
|