use core::fmt;
use rand_core::CryptoRng;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub const SECRET_LEN: usize = 32;
macro_rules! secret_type {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct $name([u8; SECRET_LEN]);
impl $name {
pub fn from_bytes(bytes: [u8; SECRET_LEN]) -> Self {
Self(bytes)
}
pub fn random<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
let mut bytes = [0u8; SECRET_LEN];
rng.fill_bytes(&mut bytes);
let secret = Self(bytes);
bytes.zeroize();
secret
}
pub fn expose(&self) -> &[u8; SECRET_LEN] {
&self.0
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, concat!(stringify!($name), "(<секрет скрыт>)"))
}
}
};
}
secret_type! {
Cek
}
secret_type! {
Kek
}
secret_type! {
SecretA
}
secret_type! {
SecretB
}
secret_type! {
PayloadKey
}
secret_type! {
MacKey
}
secret_type! {
SessionMacKey
}
secret_type! {
MetaKey
}
secret_type! {
ClaimSecret
}
secret_type! {
X25519Secret
}
pub struct SecretBuf {
bytes: Vec<u8>,
len: usize,
}
impl SecretBuf {
pub fn with_capacity(capacity: usize) -> Self {
Self { bytes: vec![0u8; capacity], len: 0 }
}
pub fn capacity(&self) -> usize {
self.bytes.len()
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_slice(&self) -> &[u8] {
self.bytes.get(..self.len).unwrap_or_default()
}
pub fn as_capacity_mut(&mut self) -> &mut [u8] {
self.wipe();
&mut self.bytes
}
pub fn declare_len(&mut self, len: usize) -> Result<(), crate::CryptoError> {
if len > self.capacity() {
return Err(crate::CryptoError::BadLength);
}
self.len = len;
Ok(())
}
pub fn as_declared_mut(&mut self) -> &mut [u8] {
let len = self.len;
self.bytes.get_mut(..len).unwrap_or_default()
}
pub fn fill_from(&mut self, src: &[u8]) -> Result<(), crate::CryptoError> {
self.wipe();
let room = self.bytes.get_mut(..src.len()).ok_or(crate::CryptoError::BadLength)?;
room.copy_from_slice(src);
self.len = src.len();
Ok(())
}
pub fn wipe(&mut self) {
self.bytes.zeroize();
self.bytes.resize(self.bytes.capacity(), 0);
self.len = 0;
}
}
impl Drop for SecretBuf {
fn drop(&mut self) {
self.bytes.zeroize();
}
}
impl fmt::Debug for SecretBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SecretBuf({} байт, содержимое скрыто)", self.len)
}
}
pub const STACK_WIPE_BYTES: usize = 64 * 1024;
#[inline(never)]
pub fn wipe_stack_below() {
let mut pad = [0u8; STACK_WIPE_BYTES];
pad.zeroize();
core::hint::black_box(&pad);
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
struct SeqRng(u8);
impl rand_core::TryRng for SeqRng {
type Error = core::convert::Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
Ok(u32::from(self.0))
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
Ok(u64::from(self.0))
}
fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
for b in dst.iter_mut() {
self.0 = self.0.wrapping_add(1);
*b = self.0;
}
Ok(())
}
}
impl rand_core::TryCryptoRng for SeqRng {}
#[test]
fn debug_never_leaks_the_bytes() {
let cek = Cek::from_bytes([0xab; SECRET_LEN]);
let rendered = format!("{cek:?}");
assert!(!rendered.contains("ab"), "Debug выдал байты секрета: {rendered}");
assert!(rendered.contains("скрыт"));
}
#[test]
fn random_uses_the_supplied_generator() {
let mut rng = SeqRng(0);
let a = Cek::random(&mut rng);
assert_eq!(a.expose()[0], 1, "генератор должен использоваться, а не подменяться");
let b = Cek::random(&mut rng);
assert_ne!(a.expose(), b.expose());
}
#[test]
fn distinct_secret_types_do_not_interchange() {
let a = SecretA::from_bytes([1; SECRET_LEN]);
let b = SecretB::from_bytes([1; SECRET_LEN]);
assert_eq!(a.expose(), b.expose(), "байты совпадают");
}
}