1#![forbid(unsafe_code)]
2
3use std::fs;
4use std::path::PathBuf;
5
6use anyhow::{Context, Result};
7use clap::Parser;
8use ed25519_dalek::VerifyingKey;
9use ed25519_dalek::pkcs8::DecodePublicKey;
10use greentic_types::{PackManifest, SignatureAlgorithm, encode_pack_manifest};
11
12#[derive(Debug, Parser)]
13pub struct VerifyArgs {
14 #[arg(long = "pack", value_name = "DIR")]
16 pub pack: PathBuf,
17
18 #[arg(long = "manifest", value_name = "FILE")]
20 pub manifest: Option<PathBuf>,
21
22 #[arg(long = "key", value_name = "FILE")]
24 pub key: PathBuf,
25}
26
27pub fn handle(args: VerifyArgs, json: bool) -> Result<()> {
28 let pack_dir = args
29 .pack
30 .canonicalize()
31 .with_context(|| format!("failed to resolve pack dir {}", args.pack.display()))?;
32 let manifest_path = args
33 .manifest
34 .map(|p| if p.is_relative() { pack_dir.join(p) } else { p })
35 .unwrap_or_else(|| pack_dir.join("dist").join("manifest.cbor"));
36
37 let manifest_bytes = fs::read(&manifest_path)
38 .with_context(|| format!("failed to read {}", manifest_path.display()))?;
39 let manifest: PackManifest = greentic_types::decode_pack_manifest(&manifest_bytes)
40 .context("manifest.cbor is not a valid PackManifest")?;
41
42 if manifest.signatures.signatures.is_empty() {
43 anyhow::bail!("no signatures present in manifest");
44 }
45
46 let public_pem = fs::read_to_string(&args.key)
47 .with_context(|| format!("failed to read public key {}", args.key.display()))?;
48 let verifying_key =
49 VerifyingKey::from_public_key_pem(&public_pem).context("failed to parse public key")?;
50
51 let unsigned_bytes = encode_unsigned(&manifest)?;
52
53 let mut verified = false;
54 let mut errors = Vec::new();
55 for sig in &manifest.signatures.signatures {
56 if sig.algorithm != SignatureAlgorithm::Ed25519 {
57 errors.push(format!("unsupported algorithm {:?}", sig.algorithm));
58 continue;
59 }
60 let Ok(signature) = ed25519_dalek::Signature::try_from(sig.signature.as_slice()) else {
61 errors.push("invalid signature bytes".to_string());
62 continue;
63 };
64 if verifying_key
65 .verify_strict(&unsigned_bytes, &signature)
66 .is_ok()
67 {
68 verified = true;
69 break;
70 } else {
71 errors.push("signature verification failed".to_string());
72 }
73 }
74
75 if !verified {
76 anyhow::bail!("no signatures verified: {}", errors.join(", "));
77 }
78
79 if json {
80 println!(
81 "{}",
82 serde_json::to_string_pretty(&serde_json::json!({
83 "status": "verified",
84 "manifest": manifest_path,
85 "signatures": manifest.signatures.signatures.len(),
86 }))?
87 );
88 } else {
89 println!(
90 "verified manifest\n manifest: {}\n signatures checked: {}",
91 manifest_path.display(),
92 manifest.signatures.signatures.len()
93 );
94 }
95
96 Ok(())
97}
98
99fn encode_unsigned(manifest: &PackManifest) -> Result<Vec<u8>> {
100 let mut unsigned = manifest.clone();
101 unsigned.signatures.signatures.clear();
102 encode_pack_manifest(&unsigned).context("failed to encode unsigned manifest")
103}