summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmd/id.rs16
-rw-r--r--src/cmd/id/init.rs3
-rw-r--r--src/metadata/identity.rs92
3 files changed, 93 insertions, 18 deletions
diff --git a/src/cmd/id.rs b/src/cmd/id.rs
index 57e79b0..9f1de7e 100644
--- a/src/cmd/id.rs
+++ b/src/cmd/id.rs
@@ -3,7 +3,6 @@
use std::{
collections::BTreeSet,
- num::NonZeroUsize,
path::PathBuf,
};
@@ -134,7 +133,8 @@ pub fn identity_ref(id: Either<&IdentityId, &git2::Config>) -> cmd::Result<Refna
#[derive(serde::Serialize, serde::Deserialize)]
struct Editable {
keys: metadata::KeySet<'static>,
- threshold: NonZeroUsize,
+ #[serde(flatten)]
+ roles: metadata::identity::Roles,
mirrors: BTreeSet<Url>,
expires: Option<metadata::DateTime>,
custom: metadata::Custom,
@@ -144,7 +144,7 @@ impl From<metadata::Identity> for Editable {
fn from(
metadata::Identity {
keys,
- threshold,
+ roles,
mirrors,
expires,
custom,
@@ -153,7 +153,7 @@ impl From<metadata::Identity> for Editable {
) -> Self {
Self {
keys,
- threshold,
+ roles,
mirrors,
expires,
custom,
@@ -167,19 +167,23 @@ impl TryFrom<Editable> for metadata::Identity {
fn try_from(
Editable {
keys,
- threshold,
+ roles,
mirrors,
expires,
custom,
}: Editable,
) -> Result<Self, Self::Error> {
ensure!(!keys.is_empty(), "keys cannot be empty");
+ ensure!(
+ !roles.is_threshold(),
+ "flat threshold is deprecated, please specify the root keys explicity"
+ );
Ok(Self {
fmt_version: Default::default(),
prev: None,
keys,
- threshold,
+ roles,
mirrors,
expires,
custom,
diff --git a/src/cmd/id/init.rs b/src/cmd/id/init.rs
index 35d3bb8..f481f48 100644
--- a/src/cmd/id/init.rs
+++ b/src/cmd/id/init.rs
@@ -145,13 +145,14 @@ pub fn init(args: Init) -> cmd::Result<Output> {
.map(metadata::Key::from)
.chain(args.public)
.collect::<KeySet>();
+ let roles = metadata::identity::Roles::root(keys.keys().cloned().collect(), threshold);
let meta = {
let id = metadata::Identity {
fmt_version: Default::default(),
prev: None,
keys,
- threshold,
+ roles,
mirrors: args.mirrors.into_iter().collect(),
expires: args.expires,
custom,
diff --git a/src/metadata/identity.rs b/src/metadata/identity.rs
index 15967c4..9e17213 100644
--- a/src/metadata/identity.rs
+++ b/src/metadata/identity.rs
@@ -56,7 +56,7 @@ use crate::{
metadata::git::find_parent,
};
-pub const FMT_VERSION: FmtVersion = FmtVersion(super::FmtVersion::new(0, 2, 0));
+pub const FMT_VERSION: FmtVersion = FmtVersion(super::FmtVersion::new(1, 0, 0));
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
pub struct FmtVersion(super::FmtVersion);
@@ -154,13 +154,42 @@ impl AsRef<Identity> for Verified {
}
}
+#[derive(Clone, serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum Roles {
+ /// Legacy
+ Threshold(NonZeroUsize),
+ Roles {
+ root: Role,
+ },
+}
+
+impl Roles {
+ pub fn root(keys: BTreeSet<KeyId>, threshold: NonZeroUsize) -> Self {
+ Self::Roles {
+ root: Role { keys, threshold },
+ }
+ }
+
+ pub fn is_threshold(&self) -> bool {
+ matches!(self, Self::Threshold(_))
+ }
+}
+
+#[derive(Clone, serde::Serialize, serde::Deserialize)]
+pub struct Role {
+ pub keys: BTreeSet<KeyId>,
+ pub threshold: NonZeroUsize,
+}
+
#[derive(Clone, serde::Deserialize)]
pub struct Identity {
#[serde(alias = "spec_version")]
pub fmt_version: FmtVersion,
pub prev: Option<ContentHash>,
pub keys: KeySet<'static>,
- pub threshold: NonZeroUsize,
+ #[serde(flatten)]
+ pub roles: Roles,
pub mirrors: BTreeSet<Url>,
pub expires: Option<DateTime>,
#[serde(default)]
@@ -214,14 +243,9 @@ impl Identity {
let canonical = self.canonicalise()?;
let signed = Sha512::digest(&canonical);
- verify_signatures(&signed, self.threshold, signatures.iter(), &self.keys)?;
+ self.verify_signatures(signatures.iter(), &signed)?;
if let Some(prev) = self.prev.as_ref().map(&mut find_prev).transpose()? {
- verify_signatures(
- &signed,
- prev.signed.threshold,
- signatures.iter(),
- &prev.signed.keys,
- )?;
+ prev.signed.verify_signatures(signatures.iter(), &signed)?;
return prev
.signed
.verify_tail(Cow::Owned(prev.signatures), find_prev);
@@ -230,6 +254,39 @@ impl Identity {
Ok(IdentityId(Sha256::digest(canonical).into()))
}
+ fn verify_signatures<'a, I>(
+ &self,
+ signatures: I,
+ payload: &[u8],
+ ) -> Result<(), error::Verification>
+ where
+ I: IntoIterator<Item = (&'a KeyId, &'a Signature)>,
+ {
+ match &self.roles {
+ Roles::Threshold(threshold) => {
+ verify_signatures(payload, *threshold, signatures, &self.keys)?;
+ },
+ Roles::Roles {
+ root: Role { keys, threshold },
+ } => {
+ let root_keys = self
+ .keys
+ .iter()
+ .filter_map(|(id, key)| {
+ if keys.contains(id) {
+ Some((id.clone(), key.clone()))
+ } else {
+ None
+ }
+ })
+ .collect();
+ verify_signatures(payload, *threshold, signatures, &root_keys)?;
+ },
+ }
+
+ Ok(())
+ }
+
pub fn canonicalise(&self) -> Result<Vec<u8>, canonical::error::Canonicalise> {
canonical::to_vec(Metadata::identity(self))
}
@@ -286,19 +343,32 @@ impl serde::Serialize for Identity {
{
use serde::ser::SerializeStruct;
+ const HAVE_FMT_VERSION: FmtVersion = FmtVersion(super::FmtVersion::new(0, 2, 0));
+
let mut s = serializer.serialize_struct("Identity", 7)?;
- let version_field = if self.fmt_version < FMT_VERSION {
+ let version_field = if self.fmt_version < HAVE_FMT_VERSION {
"spec_version"
} else {
"fmt_version"
};
+
s.serialize_field(version_field, &self.fmt_version)?;
s.serialize_field("prev", &self.prev)?;
s.serialize_field("keys", &self.keys)?;
- s.serialize_field("threshold", &self.threshold)?;
+ match &self.roles {
+ Roles::Threshold(t) => s.serialize_field("threshold", t)?,
+ Roles::Roles { root } => {
+ #[derive(serde::Serialize)]
+ struct Roles<'a> {
+ root: &'a Role,
+ }
+ s.serialize_field("roles", &Roles { root })?
+ },
+ }
s.serialize_field("mirrors", &self.mirrors)?;
s.serialize_field("expires", &self.expires)?;
s.serialize_field("custom", &self.custom)?;
+
s.end()
}
}