From d3f495672835c87366f17c20ea2741b971852eda Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 21 Nov 2022 13:23:09 -0500 Subject: [PATCH] safelog: Define a "Redactable" trait A "redactable" object is one that can be _partially_ scrubbed in sensitive contexts. This can be very helpful for UX, but is not risk-free: see comments. --- crates/safelog/semver.md | 2 + crates/safelog/src/lib.rs | 91 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 crates/safelog/semver.md diff --git a/crates/safelog/semver.md b/crates/safelog/semver.md new file mode 100644 index 000000000..b0e2f7cd8 --- /dev/null +++ b/crates/safelog/semver.md @@ -0,0 +1,2 @@ +MODIFIED: added Redacted, Redactable. + diff --git a/crates/safelog/src/lib.rs b/crates/safelog/src/lib.rs index 365569eb2..34018edde 100644 --- a/crates/safelog/src/lib.rs +++ b/crates/safelog/src/lib.rs @@ -186,6 +186,97 @@ impl_display_traits! { Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer } +/// A `redactable` object is one where we know a way to display _part_ of it +/// when we are running with safe logging enabled. +/// +/// For example, instead of referring to a user as `So-and-So` or `[scrubbed]`, +/// this trait would allow referring to the user as `S[...]`. +/// +/// # Privacy notes +/// +/// Displaying some information about an object is always less safe than +/// displaying no information about it! +/// +/// For example, in an environment with only a small number of users, the first +/// letter of a user's name might be plenty of information to identify them +/// uniquely. +/// +/// Even if a piece of redacted information is safe on its own, several pieces +/// of redacted information, when taken together, can be enough for an adversary +/// to infer more than you want. For example, if you log somebody's first +/// initial, month of birth, and last-two-digits of ID number, you have just +/// discarded 99.9% of potential individuals from the attacker's consideration. +pub trait Redactable: std::fmt::Display + std::fmt::Debug { + /// As `Display::fmt`, but produce a redacted representation. + fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; + /// As `Debug::fmt`, but produce a redacted representation. + fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; + /// Return a smart pointer that will display or debug this object as its + /// redacted form. + fn redacted(&self) -> Redacted<&Self> { + Redacted(self) + } +} + +impl<'a, T: Redactable + ?Sized> Redactable for &'a T { + fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (*self).display_redacted(f) + } + + fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (*self).debug_redacted(f) + } +} + +/// A wrapper around a `Redactable` that displays it in redacted format. +#[derive(Educe)] +#[educe( + Clone(bound), + Default(bound), + Deref, + DerefMut, + Eq(bound), + Hash(bound), + Ord(bound), + PartialEq(bound), + PartialOrd(bound) +)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct Redacted(T); + +impl Redacted { + /// Create a new `Redacted`. + pub fn new(value: T) -> Self { + Self(value) + } + + /// Consume this wrapper and return its inner value. + pub fn unwrap(self) -> T { + self.0 + } +} + +impl std::fmt::Display for Redacted { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if flags::unsafe_logging_enabled() { + self.0.display_redacted(f) + } else { + std::fmt::Display::fmt(&self.0, f) + } + } +} + +impl std::fmt::Debug for Redacted { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if flags::unsafe_logging_enabled() { + self.0.debug_redacted(f) + } else { + std::fmt::Debug::fmt(&self.0, f) + } + } +} + #[cfg(test)] mod test { #![allow(clippy::unwrap_used)]