use crate::id::TrustAnchorId;
use crate::tree::HASH_SIZE;
use crate::{Error, Result, SignatureAlgorithm};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CosignerInfo {
pub id: TrustAnchorId,
pub algorithm: SignatureAlgorithm,
pub spki_der: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CosignerGroup {
members: Vec<CosignerInfo>,
threshold: usize,
}
impl CosignerGroup {
pub fn new(members: Vec<CosignerInfo>, threshold: usize) -> Result<Self> {
if threshold == 0 || threshold > members.len() {
return Err(Error::Configuration("cosigner group threshold"));
}
for (i, member) in members.iter().enumerate() {
if members[..i].iter().any(|m| m.id == member.id) {
return Err(Error::Configuration("duplicate cosigner in group"));
}
}
Ok(Self { members, threshold })
}
pub fn single(member: CosignerInfo) -> Self {
Self {
members: vec![member],
threshold: 1,
}
}
pub fn members(&self) -> &[CosignerInfo] {
&self.members
}
pub fn threshold(&self) -> usize {
self.threshold
}
pub(crate) fn satisfied_by(&self, satisfied: &[TrustAnchorId]) -> bool {
if self.threshold == 0 || self.threshold > self.members.len() {
return false;
}
satisfied
.iter()
.filter(|id| self.members.iter().any(|m| m.id == **id))
.count()
>= self.threshold
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrustedSubtree {
pub log_number: u16,
pub start: u64,
pub end: u64,
pub hash: [u8; HASH_SIZE],
}
#[derive(Clone, Debug)]
pub struct CaTrust {
pub ca_id: TrustAnchorId,
pub ca_cosigner: CosignerInfo,
pub required_groups: Vec<CosignerGroup>,
pub revoked_ranges: Vec<core::ops::RangeInclusive<u64>>,
pub trusted_subtrees: Vec<TrustedSubtree>,
}
impl CaTrust {
pub fn new(ca_id: TrustAnchorId, ca_cosigner: CosignerInfo) -> Self {
Self {
ca_id,
ca_cosigner,
required_groups: Vec::new(),
revoked_ranges: Vec::new(),
trusted_subtrees: Vec::new(),
}
}
pub fn require_cosigner(&mut self, cosigner: CosignerInfo) -> Result<()> {
self.require_group(CosignerGroup::single(cosigner))
}
pub fn require_group(&mut self, group: CosignerGroup) -> Result<()> {
if group.members().iter().any(|m| m.id == self.ca_cosigner.id) {
return Err(Error::Configuration("CA cosigner in transparency group"));
}
self.required_groups.push(group);
Ok(())
}
pub fn revoke_range(&mut self, first: u64, last: u64) {
self.revoked_ranges.push(first..=last);
}
pub fn trust_subtree(&mut self, subtree: TrustedSubtree) {
self.trusted_subtrees.push(subtree);
}
pub(crate) fn cosigner(&self, id: &TrustAnchorId) -> Option<&CosignerInfo> {
if self.ca_cosigner.id == *id {
return Some(&self.ca_cosigner);
}
self.required_groups
.iter()
.flat_map(|group| group.members().iter())
.find(|c| c.id == *id)
}
}
#[derive(Clone, Debug, Default)]
pub struct TrustStore {
pub cas: Vec<CaTrust>,
pub required_for_all: Vec<CosignerGroup>,
}
impl TrustStore {
pub fn new() -> Self {
Self::default()
}
pub fn add_ca(&mut self, ca: CaTrust) {
self.cas.push(ca);
}
pub fn require_group_for_all(&mut self, group: CosignerGroup) {
self.required_for_all.push(group);
}
pub fn ca(&self, ca_id: &TrustAnchorId) -> Option<&CaTrust> {
self.cas.iter().find(|ca| ca.ca_id == *ca_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn member(dotted: &str) -> CosignerInfo {
CosignerInfo {
id: TrustAnchorId::from_dotted(dotted).unwrap(),
algorithm: SignatureAlgorithm::Ed25519,
spki_der: Vec::new(),
}
}
#[test]
fn group_construction_rejects_bad_thresholds_and_duplicates() {
let a = member("32473.2.1");
let b = member("32473.2.2");
assert!(CosignerGroup::new(vec![a.clone()], 0).is_err());
assert!(CosignerGroup::new(vec![a.clone()], 2).is_err());
assert!(CosignerGroup::new(vec![], 1).is_err());
assert!(CosignerGroup::new(vec![a.clone(), a.clone()], 2).is_err());
assert!(CosignerGroup::new(vec![a.clone(), b.clone()], 2).is_ok());
assert!(CosignerGroup::new(vec![a, b], 1).is_ok());
}
#[test]
fn satisfied_by_counts_distinct_members_and_rejects_bad_shapes() {
let a = member("32473.2.1");
let b = member("32473.2.2");
let group = CosignerGroup::new(vec![a.clone(), b.clone()], 2).unwrap();
let only_a = vec![a.id.clone()];
let both = vec![a.id.clone(), b.id.clone()];
assert!(!group.satisfied_by(&only_a));
assert!(group.satisfied_by(&both));
let degenerate = CosignerGroup {
members: vec![a.clone()],
threshold: 0,
};
assert!(!degenerate.satisfied_by(&both));
let overdrawn = CosignerGroup {
members: vec![a.clone()],
threshold: 2,
};
assert!(!overdrawn.satisfied_by(&both));
let duplicated = CosignerGroup {
members: vec![a.clone(), a.clone()],
threshold: 2,
};
assert!(!duplicated.satisfied_by(&both));
}
}