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/metadata.rs | 749 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 749 insertions(+) create mode 100644 src/metadata.rs (limited to 'src/metadata.rs') diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..9caee96 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,749 @@ +// Copyright © 2022 Kim Altintop +// SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception + +use core::{ + convert::TryFrom, + fmt, + ops::Deref, + str::FromStr, +}; +use std::{ + borrow::Cow, + collections::BTreeMap, + io, + marker::PhantomData, + ops::DerefMut, +}; + +use serde::ser::SerializeSeq; +use sha2::{ + Digest, + Sha512, +}; +use time::{ + Duration, + OffsetDateTime, + UtcOffset, +}; +use versions::SemVer; + +use crate::{ + git::blob_hash_sha2, + json::canonical, + keys::{ + Signer, + VerificationKey, + }, + ssh, +}; + +pub mod drop; +pub use drop::Drop; + +pub mod error; +pub mod git; + +mod mirrors; +pub use mirrors::{ + Alternates, + Mirrors, +}; + +pub mod identity; +pub use identity::{ + Identity, + IdentityId, +}; + +#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] +pub struct SpecVersion(SemVer); + +impl SpecVersion { + pub const fn current() -> Self { + Self::new(0, 1, 0) + } + + const fn new(major: u32, minor: u32, patch: u32) -> Self { + Self(SemVer { + major, + minor, + patch, + pre_rel: None, + meta: None, + }) + } + + /// This spec version is compatible if its major version is greater than or + /// equal to `other`'s + pub fn is_compatible(&self, other: &Self) -> bool { + self.0.major >= other.major() + } + + pub fn major(&self) -> u32 { + self.0.major + } + + pub fn minor(&self) -> u32 { + self.0.minor + } + + pub fn patch(&self) -> u32 { + self.0.patch + } +} + +impl Default for SpecVersion { + fn default() -> Self { + Self::current() + } +} + +impl fmt::Display for SpecVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for SpecVersion { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + SemVer::from_str(s).map(Self) + } +} + +impl<'a> TryFrom<&'a str> for SpecVersion { + type Error = >::Error; + + fn try_from(value: &str) -> Result { + SemVer::try_from(value).map(Self) + } +} + +impl AsRef for SpecVersion { + fn as_ref(&self) -> &SemVer { + &self.0 + } +} + +impl serde::Serialize for SpecVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> serde::Deserialize<'de> for SpecVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: &str = serde::Deserialize::deserialize(deserializer)?; + Self::try_from(s).map_err(|_| serde::de::Error::custom("invalid version string")) + } +} + +pub type Custom = serde_json::Map; + +#[derive( + Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash, serde::Serialize, serde::Deserialize, +)] +pub struct KeyId(#[serde(with = "hex::serde")] [u8; 32]); + +impl KeyId { + pub fn as_bytes(&self) -> &[u8] { + self.as_ref() + } +} + +impl AsRef<[u8]> for KeyId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From<&Key<'_>> for KeyId { + fn from(key: &Key<'_>) -> Self { + Self::from(&key.0) + } +} + +impl From> for KeyId { + fn from(key: Key<'_>) -> Self { + Self::from(key.0) + } +} + +impl From<&VerificationKey<'_>> for KeyId { + fn from(key: &VerificationKey<'_>) -> Self { + Self(key.sha256()) + } +} + +impl From> for KeyId { + fn from(key: VerificationKey<'_>) -> Self { + Self(key.sha256()) + } +} + +impl fmt::Display for KeyId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&hex::encode(self.0)) + } +} + +impl fmt::Debug for KeyId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("KeyId").field(&hex::encode(self.0)).finish() + } +} + +#[derive(Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ContentHash { + #[serde(with = "hex::serde")] + pub sha1: [u8; 20], + #[serde(with = "hex::serde")] + pub sha2: [u8; 32], +} + +impl ContentHash { + pub fn as_oid(&self) -> git2::Oid { + self.into() + } +} + +impl From<&git2::Blob<'_>> for ContentHash { + fn from(blob: &git2::Blob) -> Self { + let sha1 = blob + .id() + .as_bytes() + .try_into() + .expect("libgit2 to support only sha1 oids"); + let sha2 = blob_hash_sha2(blob.content()); + + Self { sha1, sha2 } + } +} + +impl From<&ContentHash> for git2::Oid { + fn from(ContentHash { sha1, .. }: &ContentHash) -> Self { + Self::from_bytes(sha1).expect("20 bytes are a valid git2::Oid") + } +} + +impl PartialEq for ContentHash { + fn eq(&self, other: &git2::Oid) -> bool { + self.sha1.as_slice() == other.as_bytes() + } +} + +impl fmt::Debug for ContentHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ContentHash") + .field("sha1", &hex::encode(self.sha1)) + .field("sha2", &hex::encode(self.sha2)) + .finish() + } +} + +impl fmt::Display for ContentHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&hex::encode(self.sha1)) + } +} + +#[derive( + Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, +)] +pub struct DateTime(#[serde(with = "time::serde::rfc3339")] OffsetDateTime); + +impl DateTime { + pub fn now() -> Self { + Self(time::OffsetDateTime::now_utc()) + } + + pub const fn checked_add(self, duration: Duration) -> Option { + // `map` is not const yet + match self.0.checked_add(duration) { + None => None, + Some(x) => Some(Self(x)), + } + } +} + +impl FromStr for DateTime { + type Err = time::error::Parse; + + fn from_str(s: &str) -> Result { + OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339) + .map(|dt| dt.to_offset(UtcOffset::UTC)) + .map(Self) + } +} + +impl Deref for DateTime { + type Target = time::OffsetDateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(tag = "_type")] +pub enum Metadata<'a> { + #[serde(rename = "eagain.io/it/identity")] + Identity(Cow<'a, Identity>), + #[serde(rename = "eagain.io/it/drop")] + Drop(Cow<'a, Drop>), + #[serde(rename = "eagain.io/it/mirrors")] + Mirrors(Cow<'a, Mirrors>), + #[serde(rename = "eagain.io/it/alternates")] + Alternates(Cow<'a, Alternates>), +} + +impl<'a> Metadata<'a> { + pub fn identity(s: T) -> Self + where + T: Into>, + { + Self::Identity(s.into()) + } + + pub fn drop(d: T) -> Self + where + T: Into>, + { + Self::Drop(d.into()) + } + + pub fn mirrors(a: T) -> Self + where + T: Into>, + { + Self::Mirrors(a.into()) + } + + pub fn alternates(a: T) -> Self + where + T: Into>, + { + Self::Alternates(a.into()) + } + + pub fn sign<'b, I, S>(self, keys: I) -> crate::Result> + where + I: IntoIterator, + S: Signer + ?Sized + 'b, + { + let payload = Sha512::digest(canonical::to_vec(&self)?); + let signatures = keys + .into_iter() + .map(|signer| { + let keyid = KeyId::from(signer.ident()); + let sig = signer.sign(&payload)?; + Ok::<_, crate::Error>((keyid, Signature::from(sig))) + }) + .collect::>()?; + + Ok(Signed { + signed: self, + signatures, + }) + } +} + +impl From for Metadata<'static> { + fn from(s: Identity) -> Self { + Self::identity(s) + } +} + +impl<'a> From<&'a Identity> for Metadata<'a> { + fn from(s: &'a Identity) -> Self { + Self::identity(s) + } +} + +impl From for Metadata<'static> { + fn from(d: Drop) -> Self { + Self::drop(d) + } +} + +impl<'a> From<&'a Drop> for Metadata<'a> { + fn from(d: &'a Drop) -> Self { + Self::drop(d) + } +} + +impl From for Metadata<'static> { + fn from(m: Mirrors) -> Self { + Self::mirrors(m) + } +} + +impl<'a> From<&'a Mirrors> for Metadata<'a> { + fn from(m: &'a Mirrors) -> Self { + Self::mirrors(m) + } +} + +impl From for Metadata<'static> { + fn from(a: Alternates) -> Self { + Self::alternates(a) + } +} + +impl<'a> From<&'a Alternates> for Metadata<'a> { + fn from(a: &'a Alternates) -> Self { + Self::alternates(a) + } +} + +impl<'a> TryFrom> for Cow<'a, Identity> { + type Error = Metadata<'a>; + + fn try_from(value: Metadata<'a>) -> Result { + match value { + Metadata::Identity(inner) => Ok(inner), + _ => Err(value), + } + } +} + +impl<'a> TryFrom> for Cow<'a, Drop> { + type Error = Metadata<'a>; + + fn try_from(value: Metadata<'a>) -> Result { + match value { + Metadata::Drop(inner) => Ok(inner), + _ => Err(value), + } + } +} + +impl<'a> TryFrom> for Cow<'a, Mirrors> { + type Error = Metadata<'a>; + + fn try_from(value: Metadata<'a>) -> Result { + match value { + Metadata::Mirrors(inner) => Ok(inner), + _ => Err(value), + } + } +} + +impl<'a> TryFrom> for Cow<'a, Alternates> { + type Error = Metadata<'a>; + + fn try_from(value: Metadata<'a>) -> Result { + match value { + Metadata::Alternates(inner) => Ok(inner), + _ => Err(value), + } + } +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct Signed { + pub signed: T, + pub signatures: BTreeMap, +} + +impl Signed { + pub fn fmap(self, f: F) -> Signed + where + F: FnOnce(T) -> U, + { + Signed { + signed: f(self.signed), + signatures: self.signatures, + } + } +} + +impl Signed> { + pub fn transpose(self) -> Result, E> { + let Self { signed, signatures } = self; + signed.map(|signed| Signed { signed, signatures }) + } +} + +impl Signed { + pub fn ancestors(&self, find_prev: F) -> impl Iterator> + where + F: FnMut(&ContentHash) -> io::Result, + { + Ancestors { + prev: self.signed.prev().cloned(), + find_prev, + _marker: PhantomData, + } + } + + pub fn has_ancestor(&self, ancestor: &ContentHash, find_prev: F) -> io::Result + where + F: FnMut(&ContentHash) -> io::Result, + { + match self.signed.prev() { + None => Ok(false), + Some(parent) if parent == ancestor => Ok(true), + Some(_) => { + for prev in self.ancestors(find_prev) { + match prev?.signed.prev() { + None => return Ok(false), + Some(parent) if parent == ancestor => return Ok(true), + _ => continue, + } + } + + Ok(false) + }, + } + } +} + +impl Signed { + pub fn verified<'a, F, G>( + self, + find_prev: F, + find_signer: G, + ) -> Result + where + F: FnMut(&ContentHash) -> io::Result, + G: FnMut(&IdentityId) -> io::Result>, + { + self.signed + .verified(&self.signatures, find_prev, find_signer) + } +} + +impl Signed { + pub fn verified(self, find_prev: F) -> Result + where + F: FnMut(&ContentHash) -> io::Result, + { + self.signed.verified(&self.signatures, find_prev) + } + + pub fn verify(&self, find_prev: F) -> Result + where + F: FnMut(&ContentHash) -> io::Result, + { + self.signed.verify(&self.signatures, find_prev) + } +} + +impl AsRef for Signed { + fn as_ref(&self) -> &T { + &self.signed + } +} + +struct Ancestors { + prev: Option, + find_prev: F, + _marker: PhantomData, +} + +impl Iterator for Ancestors +where + T: HasPrev, + F: FnMut(&ContentHash) -> io::Result>, +{ + type Item = io::Result>; + + fn next(&mut self) -> Option { + let prev = self.prev.take()?; + (self.find_prev)(&prev) + .map(|parent| { + self.prev = parent.signed.prev().cloned(); + Some(parent) + }) + .transpose() + } +} + +pub trait HasPrev { + fn prev(&self) -> Option<&ContentHash>; +} + +impl HasPrev for Identity { + fn prev(&self) -> Option<&ContentHash> { + self.prev.as_ref() + } +} + +impl HasPrev for Drop { + fn prev(&self) -> Option<&ContentHash> { + self.prev.as_ref() + } +} + +#[derive(Clone)] +pub struct Key<'a>(VerificationKey<'a>); + +impl Key<'_> { + pub fn id(&self) -> KeyId { + self.into() + } +} + +impl fmt::Debug for Key<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Key").field(&self.0.to_string()).finish() + } +} + +impl<'a> From> for Key<'a> { + fn from(vk: VerificationKey<'a>) -> Self { + Self(vk.without_comment()) + } +} + +impl signature::Verifier for Key<'_> { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + let ssh = ssh::Signature::new(self.0.algorithm(), signature.as_ref())?; + self.0.verify(msg, &ssh) + } +} + +impl serde::Serialize for Key<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.0.to_openssh().map_err(serde::ser::Error::custom)?) + } +} + +impl<'de> serde::Deserialize<'de> for Key<'_> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: &str = serde::Deserialize::deserialize(deserializer)?; + VerificationKey::from_openssh(s) + .map(Self) + .map_err(serde::de::Error::custom) + } +} + +impl FromStr for Key<'_> { + type Err = ssh_key::Error; + + fn from_str(s: &str) -> Result { + VerificationKey::from_openssh(s).map(Self) + } +} + +#[derive(Clone, Default)] +pub struct KeySet<'a>(BTreeMap>); + +impl<'a> Deref for KeySet<'a> { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for KeySet<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> FromIterator> for KeySet<'a> { + fn from_iter>>(iter: T) -> Self { + let mut kv = BTreeMap::new(); + for key in iter { + kv.insert(KeyId::from(&key), key); + } + Self(kv) + } +} + +impl serde::Serialize for KeySet<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for key in self.0.values() { + seq.serialize_element(key)?; + } + seq.end() + } +} + +impl<'de> serde::Deserialize<'de> for KeySet<'static> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = KeySet<'static>; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a sequence of keys") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut kv = BTreeMap::new(); + while let Some(key) = seq.next_element()? { + kv.insert(KeyId::from(&key), key); + } + + Ok(KeySet(kv)) + } + } + + deserializer.deserialize_seq(Visitor) + } +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Signature(#[serde(with = "hex::serde")] Vec); + +impl From for Signature { + fn from(sig: ssh::Signature) -> Self { + Self(sig.as_bytes().to_vec()) + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl signature::Signature for Signature { + fn from_bytes(bytes: &[u8]) -> Result { + Ok(Self(bytes.to_vec())) + } +} + +pub struct Verified(T); + +impl Verified { + pub fn into_inner(self) -> T { + self.0 + } +} + +impl Deref for Verified { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} -- cgit v1.2.3