Harness the power of signify(1) to sign arbitrary git objects
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

implement sign

+110 -12
+4
Cargo.toml
··· 4 4 edition = "2021" 5 5 6 6 [dependencies] 7 + anyhow = "1.0.72" 7 8 clap = { version = "4.3.17", features = ["derive"] } 9 + git2 = "0.17.2" 8 10 libsignify = "0.5.3" 11 + rpassword = "7.2.0" 12 + zeroize = "1.6.0"
+106 -12
src/main.rs
··· 1 - use clap::Parser; 1 + use std::error; 2 + use std::fmt; 3 + use std::path::PathBuf; 4 + 5 + use anyhow::{Context, Result}; 6 + use clap::{Parser, Subcommand}; 7 + use git2::{Oid, Repository}; 8 + use libsignify::{Codeable, PrivateKey}; 9 + use zeroize::Zeroizing; 2 10 3 - /// Simple program to greet a person 4 - #[derive(Parser, Debug)] 11 + #[derive(Debug)] 12 + pub struct Error<E> { 13 + inner: E, 14 + } 15 + 16 + /// A git sub-command to sign arbitrary objects 17 + #[derive(Parser)] 5 18 #[command(author, version, about, long_about = None)] 6 19 struct Args { 7 - /// Name of the person to greet 8 - #[arg(short, long)] 9 - name: String, 20 + /// The action to execute 21 + #[command(subcommand)] 22 + action: Action, 23 + } 24 + 25 + #[derive(Subcommand)] 26 + enum Action { 27 + /// Sign an arbitrary git object 28 + Sign { 29 + /// The path to the base64 encoded secret key to sign with 30 + #[arg(short = 'k', long)] 31 + secret_key: PathBuf, 10 32 11 - /// Number of times to greet 12 - #[arg(short, long, default_value_t = 1)] 13 - count: u8, 33 + /// The object id to sign 34 + git_object_id: String, 35 + }, 14 36 } 15 37 16 - fn main() { 38 + fn main() -> Result<()> { 17 39 let args = Args::parse(); 18 40 19 - for _ in 0..args.count { 20 - println!("Hello {}!", args.name) 41 + match args.action { 42 + Action::Sign { 43 + secret_key, 44 + git_object_id: oid, 45 + } => sign(secret_key, oid), 46 + } 47 + } 48 + 49 + fn sign(key_path: PathBuf, oid: String) -> Result<()> { 50 + let repo = Repository::open(".").context("Failed to open git repository")?; 51 + 52 + let oid = Oid::from_str(&oid).context("Failed to parse git object id")?; 53 + repo.find_object(oid, None) 54 + .context("Failed to look-up object in the repository")?; 55 + 56 + let secret_key = get_secret_key(key_path)?; 57 + let signature = secret_key.sign(oid.as_bytes()).as_bytes(); 58 + let signature_blob = repo 59 + .blob(&signature) 60 + .context("Failed to write signature to the object store")?; 61 + 62 + let mut tree_builder = repo 63 + .treebuilder(None) 64 + .context("Failed to get a git tree object builder")?; 65 + 66 + tree_builder 67 + .insert("object", oid, 0o100644) 68 + .context("Failed to write object to the tree")?; 69 + tree_builder 70 + .insert("signature", signature_blob, 0o100644) 71 + .context("Failed to write signature to the tree")?; 72 + 73 + let tree_oid = tree_builder 74 + .write() 75 + .context("Failed to write tree to the object store")?; 76 + 77 + println!("{tree_oid}"); 78 + Ok(()) 79 + } 80 + 81 + fn get_secret_key(path: PathBuf) -> Result<PrivateKey> { 82 + let key_data = std::fs::read_to_string(path) 83 + .map(Zeroizing::new) 84 + .context("Failed to read secret key")?; 85 + 86 + let (mut secret_key, _) = PrivateKey::from_base64(&key_data[..]) 87 + .map_err(Error::new) 88 + .context("Failed to decode secret key")?; 89 + 90 + if secret_key.is_encrypted() { 91 + let passphrase = rpassword::read_password() 92 + .map(Zeroizing::new) 93 + .context("Failed to read secret key password")?; 94 + 95 + secret_key 96 + .decrypt_with_password(&passphrase) 97 + .map_err(Error::new) 98 + .context("Failed to decrypt secret key")?; 99 + } 100 + 101 + Ok(secret_key) 102 + } 103 + 104 + impl<E: fmt::Display> fmt::Display for Error<E> { 105 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 106 + write!(f, "{}", self.inner) 107 + } 108 + } 109 + 110 + impl<E: fmt::Display + fmt::Debug> error::Error for Error<E> {} 111 + 112 + impl<E> Error<E> { 113 + fn new(inner: E) -> Self { 114 + Self { inner } 21 115 } 22 116 }