db: add update manifest data structures
This commit is contained in:
parent
90356ddb28
commit
adc00fa469
|
@ -42,6 +42,7 @@ use types::{Email, Fingerprint, KeyID};
|
|||
|
||||
pub mod wkd;
|
||||
pub mod sync;
|
||||
pub mod updates;
|
||||
|
||||
mod fs;
|
||||
pub use self::fs::Filesystem as KeyDatabase;
|
||||
|
|
|
@ -135,6 +135,13 @@ impl FromStr for Fingerprint {
|
|||
}
|
||||
}
|
||||
|
||||
impl Fingerprint {
|
||||
/// Returns the fingerprint as slice.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct KeyID([u8; 8]);
|
||||
|
||||
|
|
|
@ -0,0 +1,401 @@
|
|||
use std::{
|
||||
collections::BTreeSet,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
io,
|
||||
ops,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
types::{
|
||||
Fingerprint,
|
||||
},
|
||||
};
|
||||
|
||||
/// The number of seconds in one epoch.
|
||||
pub const SECONDS_PER_EPOCH: u64 = 1 << 15;
|
||||
|
||||
/// Set of certificate updates.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Manifest {
|
||||
start: Epoch,
|
||||
end: Epoch,
|
||||
prefixes: BTreeSet<u32>,
|
||||
}
|
||||
|
||||
impl Manifest {
|
||||
/// Magic string for Update Manifests.
|
||||
pub const MAGIC: &'static [u8] = b"\xE4\x2B\xAF\xBD\xD5\x75\x77\x0A";
|
||||
|
||||
/// Creates a new Update Manifest
|
||||
pub fn new<S, E>(start: S, end: E)
|
||||
-> anyhow::Result<Manifest>
|
||||
where
|
||||
S: Into<Epoch>,
|
||||
E: Into<Epoch>,
|
||||
{
|
||||
let start = start.into();
|
||||
let end = end.into();
|
||||
if start <= end {
|
||||
Ok(Manifest {
|
||||
start,
|
||||
end,
|
||||
prefixes: Default::default(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow::anyhow!("End epoch predates start epoch"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the prefix of the given Fingerprint.
|
||||
fn prefix(fingerprint: &Fingerprint) -> u32 {
|
||||
let mut prefix = [0u8; 4];
|
||||
prefix.copy_from_slice(&fingerprint.as_bytes()[..4]);
|
||||
u32::from_be_bytes(prefix)
|
||||
}
|
||||
|
||||
/// Tests whether a cert is included in the Update Manifest.
|
||||
///
|
||||
/// Note: Due to the privacy-preserving nature of Update Manifests
|
||||
/// that store only fingerprint prefixes, this may return false
|
||||
/// positives.
|
||||
pub fn contains(&self, fingerprint: &Fingerprint) -> bool {
|
||||
self.prefixes.contains(&Self::prefix(fingerprint))
|
||||
}
|
||||
|
||||
/// Tests whether an epoch is included in the Update Manifest.
|
||||
pub fn contains_epoch(&self, epoch: Epoch) -> bool {
|
||||
// Both start and end are inclusive, therefore:
|
||||
self.start <= epoch && epoch <= self.end
|
||||
}
|
||||
|
||||
/// Iterates over all epochs contained in this Update Manifest.
|
||||
pub fn epochs(&self) -> impl Iterator<Item = Epoch> {
|
||||
(self.start.0..self.end.0 + 1).into_iter()
|
||||
.map(|n| Epoch(n))
|
||||
}
|
||||
|
||||
/// Returns the start epoch.
|
||||
pub fn start(&self) -> Epoch {
|
||||
self.start
|
||||
}
|
||||
|
||||
/// Returns the end epoch.
|
||||
pub fn end(&self) -> Epoch {
|
||||
self.end
|
||||
}
|
||||
|
||||
/// Returns the number of fingerprint prefixes in this Update
|
||||
/// Manifest.
|
||||
pub fn len(&self) -> usize {
|
||||
self.prefixes.len()
|
||||
}
|
||||
|
||||
/// Inserts a fingerprint into the Update Manifest.
|
||||
pub fn insert(&mut self, fingerprint: &Fingerprint) {
|
||||
self.prefixes.insert(Self::prefix(fingerprint));
|
||||
}
|
||||
|
||||
/// Merges two Update Manifests into one.
|
||||
pub fn merge(&self, other: &Self) -> Manifest {
|
||||
Manifest {
|
||||
start: self.start.min(other.start),
|
||||
end: self.end.max(other.end),
|
||||
prefixes: self.prefixes.iter().cloned()
|
||||
.chain(other.prefixes.iter().cloned())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the Update Manifest to the given `io::Write`r.
|
||||
pub fn serialize(&self, sink: &mut dyn io::Write) -> io::Result<()> {
|
||||
sink.write_all(Self::MAGIC)?;
|
||||
|
||||
self.start.serialize(sink)?;
|
||||
self.end.serialize(sink)?;
|
||||
|
||||
for prefix in &self.prefixes {
|
||||
sink.write_all(&prefix.to_be_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads the Epoch from the given `io::Read`er.
|
||||
pub fn parse(source: &mut dyn io::Read) -> io::Result<Self> {
|
||||
let mut magic = [0; 8];
|
||||
source.read_exact(&mut magic)?;
|
||||
if &magic[..] != Self::MAGIC {
|
||||
return Err(io::Error::new(io::ErrorKind::Other,
|
||||
anyhow::anyhow!("Bad magic string")));
|
||||
}
|
||||
|
||||
let start = Epoch::parse(source)?;
|
||||
let end = Epoch::parse(source)?;
|
||||
if start > end {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
anyhow::anyhow!("End epoch predates start epoch")));
|
||||
}
|
||||
|
||||
let mut prefixes = BTreeSet::default();
|
||||
let mut prefix = [0; 4];
|
||||
'parse: loop {
|
||||
let mut read = 0;
|
||||
loop {
|
||||
let n = source.read(&mut prefix[read..])?;
|
||||
if n == 0 {
|
||||
match read {
|
||||
0 => break 'parse,
|
||||
_ => return Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"Truncated fingerprint prefix")),
|
||||
}
|
||||
}
|
||||
|
||||
read += n;
|
||||
if read == 4 {
|
||||
prefixes.insert(u32::from_be_bytes(prefix.clone()));
|
||||
continue 'parse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Manifest {
|
||||
start,
|
||||
end,
|
||||
prefixes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Epoch(u32);
|
||||
|
||||
impl fmt::Display for Epoch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Epoch {
|
||||
fn from(e: u32) -> Self {
|
||||
Epoch(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<std::time::SystemTime> for Epoch {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(t: std::time::SystemTime) -> anyhow::Result<Self> {
|
||||
use std::time::*;
|
||||
let unix_epoch = t.duration_since(UNIX_EPOCH)?;
|
||||
Self::try_from_unix(unix_epoch.as_secs())
|
||||
}
|
||||
}
|
||||
|
||||
impl Epoch {
|
||||
/// Returns the currently active Epoch.
|
||||
pub fn current() -> anyhow::Result<Epoch> {
|
||||
std::time::SystemTime::now().try_into()
|
||||
}
|
||||
|
||||
/// Returns the epoch for the given UNIX epoch.
|
||||
pub fn try_from_unix(t: u64) -> anyhow::Result<Self> {
|
||||
Ok(Epoch((t / SECONDS_PER_EPOCH).try_into()?))
|
||||
}
|
||||
|
||||
/// Returns the previous Epoch, if any.
|
||||
pub fn pred(&self) -> Option<Epoch> {
|
||||
self.0.checked_sub(1).map(|e| Epoch(e))
|
||||
}
|
||||
|
||||
/// Returns the next Epoch, if any.
|
||||
pub fn succ(&self) -> Option<Epoch> {
|
||||
self.0.checked_add(1).map(|e| Epoch(e))
|
||||
}
|
||||
|
||||
/// Returns an iterator over all epochs starting from this one to
|
||||
/// `other`, in descending order, excluding `other`.
|
||||
pub fn since(&self, other: Epoch)
|
||||
-> anyhow::Result<impl Iterator<Item = Epoch>> {
|
||||
if other <= *self {
|
||||
Ok((other.0 + 1..self.0).into_iter().rev().map(|e| Epoch(e)))
|
||||
} else {
|
||||
Err(anyhow::anyhow!("other is later than self"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the Epoch to the given `io::Write`r.
|
||||
pub fn serialize(&self, sink: &mut dyn io::Write) -> io::Result<()> {
|
||||
sink.write_all(&self.0.to_be_bytes())
|
||||
}
|
||||
|
||||
/// Reads the Epoch from the given `io::Read`er.
|
||||
pub fn parse(source: &mut dyn io::Read) -> io::Result<Self> {
|
||||
let mut be_bytes = [0; 4];
|
||||
source.read_exact(&mut be_bytes)?;
|
||||
Ok(Self(u32::from_be_bytes(be_bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Epoch {
|
||||
type Err = anyhow::Error;
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
if s == "current" {
|
||||
Ok(Epoch::current()?)
|
||||
} else {
|
||||
Ok(Epoch(s.parse()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Epoch> for Epoch {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Epoch) -> Self {
|
||||
Self(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<Epoch> for Epoch {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Epoch) -> Self {
|
||||
Self(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<u32> for Epoch {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: u32) -> Self {
|
||||
Self(self.0 + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<u32> for Epoch {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: u32) -> Self {
|
||||
Self(self.0 - other)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn current_epoch() -> crate::Result<()> {
|
||||
let _ = Epoch::current()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() -> crate::Result<()> {
|
||||
let c = Epoch::current()?;
|
||||
|
||||
// End predates start.
|
||||
assert!(Manifest::new(c - 1, c - 2).is_err());
|
||||
|
||||
let mut u_0 = Manifest::new(c - 1, c)?;
|
||||
let mut u_1 = Manifest::new(c - 2, c - 1)?;
|
||||
|
||||
let neal: Fingerprint =
|
||||
"8F17777118A33DDA9BA48E62AACB3243630052D9".parse()?;
|
||||
let justus: Fingerprint =
|
||||
"CBCD8F030588653EEDD7E2659B7DD433F254904A".parse()?;
|
||||
let vincent: Fingerprint =
|
||||
"D4AB192964F76A7F8F8A9B357BD18320DEADFA11".parse()?;
|
||||
|
||||
u_0.insert(&neal);
|
||||
u_1.insert(&neal);
|
||||
u_0.insert(&justus);
|
||||
u_1.insert(&vincent);
|
||||
|
||||
let u_01 = u_0.merge(&u_1);
|
||||
|
||||
assert_eq!(u_01.start, c - 2);
|
||||
assert_eq!(u_01.end, c);
|
||||
assert_eq!(u_01.len(), 3);
|
||||
assert!(u_01.contains(&neal));
|
||||
assert!(u_01.contains(&justus));
|
||||
assert!(u_01.contains(&vincent));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks serialization using the sample Update Manifest.
|
||||
///
|
||||
/// ```text
|
||||
/// Below is a hexdump of an update manifest that covers epochs from 15296
|
||||
/// to 15322 (inclusive). This corresponds to the time range from
|
||||
/// 1985-11-18T22:35:28 through 1985-11-29T04:21:03 (inclusive). The
|
||||
/// observed updated certificates had five primary key fingerprint
|
||||
/// prefixes:
|
||||
///
|
||||
/// 32144D9D, 65FB1218, 7E91F402, 9ED85A5E, EA71546A
|
||||
///
|
||||
/// 00000000 e4 2b af bd d5 75 77 0a 00 00 3b c0 00 00 3b da
|
||||
/// 00000010 32 14 4d 9d 65 fb 12 18 7e 91 f4 02 9e d8 5a 5e
|
||||
/// 00000020 ea 71 54 6a
|
||||
/// ```
|
||||
fn sample_manifest() -> crate::Result<(Epoch, Epoch,
|
||||
Vec<Fingerprint>,
|
||||
&'static[u8])> {
|
||||
let start = Epoch(15296);
|
||||
let end = Epoch(15322);
|
||||
let fp0: Fingerprint =
|
||||
"32144D9DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse()?;
|
||||
let fp1: Fingerprint =
|
||||
"65FB1218AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse()?;
|
||||
let fp2: Fingerprint =
|
||||
"7E91F402AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse()?;
|
||||
let fp3: Fingerprint =
|
||||
"9ED85A5EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse()?;
|
||||
let fp4: Fingerprint =
|
||||
"EA71546AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse()?;
|
||||
let bytes = b"\
|
||||
\xe4\x2b\xaf\xbd\xd5\x75\x77\x0a\x00\x00\x3b\xc0\x00\x00\x3b\xda\
|
||||
\x32\x14\x4d\x9d\x65\xfb\x12\x18\x7e\x91\xf4\x02\x9e\xd8\x5a\x5e\
|
||||
\xea\x71\x54\x6a";
|
||||
|
||||
Ok((start, end, vec![fp0, fp1, fp2, fp3, fp4], bytes))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() -> crate::Result<()> {
|
||||
let (start, end, fingerprints, bytes) = sample_manifest()?;
|
||||
|
||||
let mut manifest = Manifest::new(start, end)?;
|
||||
manifest.insert(&fingerprints[3]);
|
||||
manifest.insert(&fingerprints[1]);
|
||||
manifest.insert(&fingerprints[0]);
|
||||
manifest.insert(&fingerprints[4]);
|
||||
manifest.insert(&fingerprints[2]);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
manifest.serialize(&mut buf)?;
|
||||
assert_eq!(&buf[..], bytes);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() -> crate::Result<()> {
|
||||
let (start, end, fingerprints, bytes) = sample_manifest()?;
|
||||
|
||||
let manifest = Manifest::parse(&mut io::Cursor::new(bytes))?;
|
||||
assert_eq!(manifest.start, start);
|
||||
assert_eq!(manifest.end, end);
|
||||
assert!(manifest.contains(&fingerprints[3]));
|
||||
assert!(manifest.contains(&fingerprints[1]));
|
||||
assert!(manifest.contains(&fingerprints[0]));
|
||||
assert!(manifest.contains(&fingerprints[4]));
|
||||
assert!(manifest.contains(&fingerprints[2]));
|
||||
assert_eq!(manifest.len(), 5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue