From d2f423521ec76406944ad83098ec33afe20c692b Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Mon, 9 Jan 2023 13:18:33 +0100 Subject: This is it Squashed commit of all the exploration history. Development starts here. Signed-off-by: Kim Altintop --- src/cmd/drop/show.rs | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 src/cmd/drop/show.rs (limited to 'src/cmd/drop/show.rs') diff --git a/src/cmd/drop/show.rs b/src/cmd/drop/show.rs new file mode 100644 index 0000000..e3fdcfc --- /dev/null +++ b/src/cmd/drop/show.rs @@ -0,0 +1,208 @@ +// Copyright © 2022 Kim Altintop +// SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception + +use std::{ + collections::BTreeMap, + io, + path::PathBuf, +}; + +use anyhow::Context; + +use super::{ + Common, + META_FILE_ALTERNATES, + META_FILE_MIRRORS, +}; +use crate::{ + cmd::{ + self, + util::args::Refname, + FromGit as _, + GitAlternates, + GitDrop, + GitMirrors, + }, + git, + metadata::{ + self, + ContentHash, + IdentityId, + KeySet, + }, + patches::REF_IT_PATCHES, +}; + +#[derive(Debug, clap::Args)] +pub struct Show { + #[clap(flatten)] + common: Common, + /// Name of the git ref holding the drop metadata history + #[clap( + long = "drop", + value_parser, + value_name = "REF", + default_value_t = REF_IT_PATCHES.parse().unwrap(), + )] + drop_ref: Refname, +} + +#[derive(serde::Serialize)] +pub struct Output { + repo: PathBuf, + refname: Refname, + drop: Data, + #[serde(skip_serializing_if = "Option::is_none")] + mirrors: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + alternates: Option>, +} + +#[derive(serde::Serialize)] +pub struct Data { + hash: ContentHash, + status: Status, + json: T, +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum Status { + Verified, + #[serde(with = "crate::serde::display")] + Invalid(metadata::error::Verification), +} + +impl From> for Status { + fn from(r: Result<(), metadata::error::Verification>) -> Self { + r.map(|()| Self::Verified).unwrap_or_else(Self::Invalid) + } +} + +pub fn show(args: Show) -> cmd::Result { + let Common { git_dir, .. } = args.common; + let drop_ref = args.drop_ref; + + let repo = git::repo::open(git_dir)?; + + let GitDrop { + hash, + signed: metadata::Signed { + signed: drop, + signatures, + }, + } = metadata::Drop::from_tip(&repo, &drop_ref)?; + + let mut signer_cache = SignerCache::new(&repo, &drop_ref)?; + let status = drop + .verify( + &signatures, + cmd::find_parent(&repo), + find_signer(&mut signer_cache), + ) + .into(); + + let mut mirrors = None; + let mut alternates = None; + + let tree = repo.find_reference(&drop_ref)?.peel_to_commit()?.tree()?; + if let Some(entry) = tree.get_name(META_FILE_MIRRORS) { + let blob = entry.to_object(&repo)?.peel_to_blob()?; + let GitMirrors { hash, signed } = metadata::Mirrors::from_blob(&blob)?; + let status = drop + .verify_mirrors(&signed, find_signer(&mut signer_cache)) + .into(); + + mirrors = Some(Data { + hash, + status, + json: signed.signed, + }); + } + + if let Some(entry) = tree.get_name(META_FILE_ALTERNATES) { + let blob = entry.to_object(&repo)?.peel_to_blob()?; + let GitAlternates { hash, signed } = metadata::Alternates::from_blob(&blob)?; + let status = drop + .verify_alternates(&signed, find_signer(&mut signer_cache)) + .into(); + + alternates = Some(Data { + hash, + status, + json: signed.signed, + }); + } + + Ok(Output { + repo: repo.path().to_owned(), + refname: drop_ref, + drop: Data { + hash, + status, + json: drop, + }, + mirrors, + alternates, + }) +} + +struct SignerCache<'a> { + repo: &'a git2::Repository, + root: git2::Tree<'a>, + keys: BTreeMap>, +} + +impl<'a> SignerCache<'a> { + pub(self) fn new(repo: &'a git2::Repository, refname: &Refname) -> git::Result { + let root = { + let id = repo + .find_reference(refname)? + .peel_to_tree()? + .get_name("ids") + .ok_or_else(|| { + git2::Error::new( + git2::ErrorCode::NotFound, + git2::ErrorClass::Tree, + "'ids' tree not found", + ) + })? + .id(); + repo.find_tree(id)? + }; + let keys = BTreeMap::new(); + + Ok(Self { repo, root, keys }) + } +} + +fn find_signer<'a>( + cache: &'a mut SignerCache, +) -> impl FnMut(&IdentityId) -> io::Result> + 'a { + fn go( + repo: &git2::Repository, + root: &git2::Tree, + keys: &mut BTreeMap>, + id: &IdentityId, + ) -> cmd::Result> { + match keys.get(id) { + Some(keys) => Ok(keys.clone()), + None => { + let (id, verified) = metadata::identity::find_in_tree(repo, root, id) + .with_context(|| format!("identity {id} failed to verify"))? + .into_parts(); + keys.insert(id, verified.keys.clone()); + Ok(verified.keys) + }, + } + } + + |id| go(cache.repo, &cache.root, &mut cache.keys, id).map_err(as_io) +} + +fn as_io(e: E) -> io::Error +where + E: Into>, +{ + io::Error::new(io::ErrorKind::Other, e) +} -- cgit v1.2.3