diff options
author | Neal H. Walfield <neal@pep.foundation> | 2020-02-10 10:57:12 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2020-02-10 11:01:01 +0100 |
commit | d4497d1aa85eaa6fc5bb056900e372c7e067d686 (patch) | |
tree | c84c8b6b764addb6f0eae0254d133be8d36770b2 /openpgp/src/policy | |
parent | 24dd586cd415ced18aaec2e7c3595fecaa847019 (diff) |
openpgp: Extend StandardPolicy to consider hash algorithms.
- Extend `StandardPolicy` to consider hash functions when evaluating
signatures.
- Use defaults that are based on published attacks.
- Provide an interface to modify the policy.
Diffstat (limited to 'openpgp/src/policy')
-rw-r--r-- | openpgp/src/policy/cutofflist.rs | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/openpgp/src/policy/cutofflist.rs b/openpgp/src/policy/cutofflist.rs new file mode 100644 index 00000000..6c1933ea --- /dev/null +++ b/openpgp/src/policy/cutofflist.rs @@ -0,0 +1,248 @@ +use std::fmt; +use std::mem; +use std::ops::{Index, IndexMut}; + +use crate::{ + Error, + Result, + types::Timestamp, +}; + +// A `const fn` function can only use a subset of Rust's +// functionality. The subset is growing, but we restrict ourselves to +// only use `const fn` functionality that is available in Debian +// stable, which, as of 2020, includes rustc version 1.34.2. This +// requires a bit of creativity. +#[derive(Debug, Clone)] +pub(super) enum VecOrSlice<'a, T> { + Vec(Vec<T>), + Slice(&'a [T]), + Empty(), +} + +// Make a `VecOrSlice` act like a `Vec`. +impl<'a, T> VecOrSlice<'a, T> { + // Returns an empty `VecOrSlice`. + const fn empty() -> Self { + VecOrSlice::Empty() + } + + // Like `Vec::get`. + fn get(&self, i: usize) -> Option<&T> { + match self { + VecOrSlice::Vec(v) => v.get(i), + VecOrSlice::Slice(s) => s.get(i), + VecOrSlice::Empty() => None, + } + } + + // Like `Vec::len`. + fn len(&self) -> usize { + match self { + VecOrSlice::Vec(v) => v.len(), + VecOrSlice::Slice(s) => s.len(), + VecOrSlice::Empty() => 0, + } + } + + // Like `Vec::resize`. + fn resize(&mut self, size: usize, value: T) + where T: Clone + { + let mut v : Vec<T> = match self { + VecOrSlice::Vec(ref mut v) => mem::replace(v, Vec::new()), + VecOrSlice::Slice(s) => s.to_vec(), + VecOrSlice::Empty() => Vec::with_capacity(size), + }; + + v.resize(size, value); + + *self = VecOrSlice::Vec(v); + } +} + +impl<'a, T> Index<usize> for VecOrSlice<'a, T> { + type Output = T; + + fn index(&self, i: usize) -> &T { + match self { + VecOrSlice::Vec(v) => &v[i], + VecOrSlice::Slice(s) => &s[i], + VecOrSlice::Empty() => &[][i], + } + } +} + +impl<'a, T> IndexMut<usize> for VecOrSlice<'a, T> + where T: Clone +{ + fn index_mut(&mut self, i: usize) -> &mut T { + if let VecOrSlice::Slice(s) = self { + *self = VecOrSlice::Vec(s.to_vec()); + }; + + match self { + VecOrSlice::Vec(v) => &mut v[i], + VecOrSlice::Slice(_) => unreachable!(), + VecOrSlice::Empty() => + panic!("index out of bounds: the len is 0 but the index is {}", + i), + } + } +} + +/// A given algorithm may be considered: completely broken, safe, or +/// too weak to be used after a certain time. +#[derive(Debug, Clone)] +pub(super) struct CutoffList<A> { + // Indexed by `A as u8`. + // + // A value of `None` means that no vulnerabilities are known. + // + // Note: we use `u64` and not `SystemTime`, because there is no + // way to construct a `SystemTime` in a `const fn`. + pub(super) cutoffs: VecOrSlice<'static, Option<Timestamp>>, + + pub(super) _a: std::marker::PhantomData<A>, +} + +pub(super) const REJECT : Option<Timestamp> = Some(Timestamp::UNIX_EPOCH); +pub(super) const ACCEPT : Option<Timestamp> = None; + +pub(super) const DEFAULT_POLICY : Option<Timestamp> = REJECT; + +impl<A> Default for CutoffList<A> { + fn default() -> Self { + Self::reject_all() + } +} + +impl<A> CutoffList<A> { + // Rejects all algorithms. + const fn reject_all() -> Self { + Self { + cutoffs: VecOrSlice::empty(), + _a: std::marker::PhantomData, + } + } +} + +impl<A> CutoffList<A> + where u8: From<A>, + A: fmt::Display, + A: std::clone::Clone +{ + // Sets a cutoff time. + pub(super) fn set(&mut self, a: A, cutoff: Option<Timestamp>) { + let i : u8 = a.into(); + let i : usize = i.into(); + + if i >= self.cutoffs.len() { + // We reject by default. + self.cutoffs.resize(i + 1, DEFAULT_POLICY) + } + self.cutoffs[i] = cutoff; + } + + // Returns the cutoff time for algorithm `a`. + #[inline] + pub(super) fn cutoff(&self, a: A) -> Option<Timestamp> { + let i : u8 = a.into(); + *self.cutoffs.get(i as usize).unwrap_or(&DEFAULT_POLICY) + } + + // Checks whether the `a` is safe to use at time `time`. + #[inline] + pub(super) fn check(&self, a: A, time: Timestamp) -> Result<()> { + if let Some(cutoff) = self.cutoff(a.clone()) { + if time >= cutoff { + Err(Error::PolicyViolation( + a.to_string(), Some(cutoff.into())).into()) + } else { + Ok(()) + } + } else { + // None => always secure. + Ok(()) + } + } +} + +macro_rules! a_cutoff_list { + ($name:ident, $algo:ty, $values_count:expr, $values:expr) => { + // It would be nicer to just have a `CutoffList` and store the + // default as a `VecOrSlice::Slice`. Unfortunately, we can't + // create a slice in a `const fn`, so that doesn't work. + // + // To work around that issue, we store the array in the + // wrapper type, and remember if we are using it or a custom + // version. + #[derive(Debug, Clone)] + enum $name { + Default(), + Custom(CutoffList<$algo>), + } + + impl $name { + const DEFAULTS : [ Option<Timestamp>; $values_count ] = $values; + + // Turn the `Foo::Default` into a `Foo::Custom`, if + // necessary. + fn force(&mut self) -> &mut CutoffList<$algo> { + use crate::policy::cutofflist::VecOrSlice; + + if let $name::Default() = self { + *self = $name::Custom(CutoffList { + cutoffs: VecOrSlice::Vec(Self::DEFAULTS.to_vec()), + _a: std::marker::PhantomData, + }); + } + + match self { + $name::Custom(ref mut l) => l, + _ => unreachable!(), + } + } + + fn set(&mut self, a: $algo, cutoff: Option<Timestamp>) { + self.force().set(a, cutoff) + } + + fn cutoff(&self, a: $algo) -> Option<Timestamp> { + use crate::policy::cutofflist::DEFAULT_POLICY; + + match self { + $name::Default() => { + let i : u8 = a.into(); + let i : usize = i.into(); + + if i >= Self::DEFAULTS.len() { + DEFAULT_POLICY + } else { + Self::DEFAULTS[i] + } + } + $name::Custom(ref l) => l.cutoff(a), + } + } + + fn check(&self, a: $algo, time: Timestamp) -> Result<()> { + use crate::policy::cutofflist::VecOrSlice; + + match self { + $name::Default() => { + // Convert the default to a `CutoffList` on + // the fly to avoid duplicating + // `CutoffList::check`. + CutoffList { + cutoffs: VecOrSlice::Slice(&Self::DEFAULTS[..]), + _a: std::marker::PhantomData, + }.check(a, time) + } + + $name::Custom(ref l) => l.check(a, time), + } + } + } + } +} |