p2panda_encryption/crypto/
secret.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3#[cfg(not(test))]
4use std::fmt;
5
6use serde::{Deserialize, Serialize};
7use subtle::ConstantTimeEq;
8use zeroize::ZeroizeOnDrop;
9
10/// Generic container for sensitive bytes with best-effort security measures.
11///
12/// In particular this implementation provides:
13/// 1. Zeroise memory on drop.
14/// 2. Private API methods to retrieve bytes, preventing misuse.
15/// 3. Hide bytes value when printing debug info.
16/// 4. Constant-time comparison implementation to prevent timing attacks.
17///
18/// This represents a "best-effort" attempt, since side-channels are ultimately a property of a
19/// deployed cryptographic system including the hardware it runs on, not just of software.
20#[derive(Clone, Eq, Serialize, Deserialize, ZeroizeOnDrop)]
21#[cfg_attr(test, derive(Debug))]
22pub struct Secret<const N: usize>(#[serde(with = "serde_bytes")] [u8; N]);
23
24impl<const N: usize> Secret<N> {
25    pub(crate) fn from_bytes(bytes: [u8; N]) -> Self {
26        Self(bytes)
27    }
28
29    pub(crate) fn as_bytes(&self) -> &[u8; N] {
30        &self.0
31    }
32}
33
34impl<const N: usize> PartialEq for Secret<N> {
35    fn eq(&self, other: &Self) -> bool {
36        // Constant-time comparison.
37        bool::from(self.0.ct_eq(&other.0))
38    }
39}
40
41#[cfg(not(test))]
42impl<const N: usize> fmt::Debug for Secret<N> {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        // Do not reveal secret values when printing debug info.
45        f.debug_struct("Secret").field("value", &"***").finish()
46    }
47}