mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Resolve #268 by adding a Zeroize to DigestTranscript which writes a full block
This is a 'better-than-nothing' attempt to invalidate its state. Also replaces black_box features with usage of the rustversion crate.
This commit is contained in:
@@ -2,6 +2,15 @@
|
||||
#![no_std]
|
||||
|
||||
///! A transcript trait valid over a variety of transcript formats.
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use digest::{
|
||||
typenum::{
|
||||
consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq,
|
||||
},
|
||||
core_api::BlockSizeUser,
|
||||
Digest, Output, HashMarker,
|
||||
};
|
||||
|
||||
#[cfg(feature = "merlin")]
|
||||
mod merlin;
|
||||
@@ -12,13 +21,6 @@ pub use crate::merlin::MerlinTranscript;
|
||||
#[cfg(any(test, feature = "tests"))]
|
||||
pub mod tests;
|
||||
|
||||
use digest::{
|
||||
typenum::{
|
||||
consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq,
|
||||
},
|
||||
Digest, Output, HashMarker,
|
||||
};
|
||||
|
||||
/// A transcript trait valid over a variety of transcript formats.
|
||||
pub trait Transcript: Send + Clone {
|
||||
type Challenge: Send + Sync + Clone + AsRef<[u8]>;
|
||||
@@ -134,6 +136,61 @@ impl<D: Send + Clone + SecureDigest> Transcript for DigestTranscript<D> {
|
||||
}
|
||||
}
|
||||
|
||||
// Digest doesn't implement Zeroize
|
||||
// Implement Zeroize for DigestTranscript by writing twice the block size to the digest in an
|
||||
// attempt to overwrite the internal hash state/any leftover bytes
|
||||
impl<D: Send + Clone + SecureDigest> Zeroize for DigestTranscript<D>
|
||||
where
|
||||
D: BlockSizeUser,
|
||||
{
|
||||
fn zeroize(&mut self) {
|
||||
// Update in 4-byte chunks to reduce call quantity and enable word-level update optimizations
|
||||
const WORD_SIZE: usize = 4;
|
||||
|
||||
// block_size returns the block_size in bytes
|
||||
// Use a ceil div in case the block size isn't evenly divisible by our word size
|
||||
let words = (D::block_size() + (WORD_SIZE - 1)) / WORD_SIZE;
|
||||
for _ in 0 .. (2 * words) {
|
||||
self.0.update([255; WORD_SIZE]);
|
||||
}
|
||||
|
||||
// Hopefully, the hash state is now overwritten to the point no data is recoverable
|
||||
// These writes may be optimized out if they're never read
|
||||
// Attempt to get them marked as read
|
||||
|
||||
#[rustversion::since(1.66)]
|
||||
fn mark_read<D: Send + Clone + SecureDigest>(transcript: &mut DigestTranscript<D>) {
|
||||
// Just get a challenge from the state
|
||||
let mut challenge = core::hint::black_box(transcript.0.clone().finalize());
|
||||
challenge.as_mut().zeroize();
|
||||
}
|
||||
|
||||
#[rustversion::before(1.66)]
|
||||
fn mark_read<D: Send + Clone + SecureDigest>(transcript: &mut DigestTranscript<D>) {
|
||||
// Get a challenge
|
||||
let challenge = transcript.0.clone().finalize();
|
||||
|
||||
// Attempt to use subtle's, non-exposed black_box function, by creating a Choice from this
|
||||
// challenge
|
||||
|
||||
let mut read = 0;
|
||||
for byte in challenge.as_ref() {
|
||||
read ^= byte;
|
||||
}
|
||||
challenge.as_mut().zeroize();
|
||||
|
||||
// Since this Choice isn't further read, its creation may be optimized out, including its
|
||||
// internal black_box
|
||||
// This remains our best attempt
|
||||
let mut choice = bool::from(subtle::Choice::from(read >> 7));
|
||||
read.zeroize();
|
||||
choice.zeroize();
|
||||
}
|
||||
|
||||
mark_read(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The recommended transcript, guaranteed to be secure against length-extension attacks.
|
||||
#[cfg(feature = "recommended")]
|
||||
pub type RecommendedTranscript = DigestTranscript<blake2::Blake2b512>;
|
||||
|
||||
Reference in New Issue
Block a user