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:
Luke Parker 2023-03-28 04:43:10 -04:00
parent 79aff5d4c8
commit 47be373eb0
No known key found for this signature in database
12 changed files with 108 additions and 56 deletions

7
Cargo.lock generated
View file

@ -1658,6 +1658,7 @@ dependencies = [
"ff-group-tests",
"group 0.13.0",
"rand_core 0.6.4",
"rustversion",
"sha2 0.9.9",
"subtle",
"zeroize",
@ -1995,6 +1996,7 @@ dependencies = [
"k256",
"multiexp",
"rand_core 0.6.4",
"rustversion",
"thiserror",
"zeroize",
]
@ -2798,7 +2800,10 @@ dependencies = [
"blake2",
"digest 0.10.6",
"merlin 3.0.0",
"rustversion",
"sha2 0.10.6",
"subtle",
"zeroize",
]
[[package]]
@ -5046,6 +5051,7 @@ dependencies = [
"hex",
"lazy_static",
"rand_core 0.6.4",
"rustversion",
"subtle",
"zeroize",
]
@ -5278,6 +5284,7 @@ dependencies = [
"group 0.13.0",
"k256",
"rand_core 0.6.4",
"rustversion",
"zeroize",
]

View file

@ -13,12 +13,15 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rand_core = "0.6"
digest = "0.10"
rustversion = "1"
zeroize = { version = "^1.5", features = ["zeroize_derive"] }
subtle = "^2.4"
rand_core = "0.6"
digest = "0.10"
ff = "0.13"
group = "0.13"
@ -29,6 +32,3 @@ curve25519-dalek = "^3.2"
[dev-dependencies]
ff-group-tests = { path = "../ff-group-tests" }
[features]
black_box = []

View file

@ -38,14 +38,11 @@ use group::{
mod field;
pub use field::FieldElement;
// Feature gated due to MSRV requirements
#[cfg(feature = "black_box")]
pub(crate) fn black_box<T>(val: T) -> T {
core::hint::black_box(val)
}
#[cfg(not(feature = "black_box"))]
pub(crate) fn black_box<T>(val: T) -> T {
// Use black_box when possible
#[rustversion::since(1.66)]
use core::hint::black_box;
#[rustversion::before(1.66)]
fn black_box<T>(val: T) -> T {
val
}

View file

@ -12,6 +12,8 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rustversion = "1"
thiserror = { version = "1", optional = true }
rand_core = "0.6"
@ -41,7 +43,6 @@ std = []
serialize = ["std"]
# Needed for cross-group DLEqs
black_box = []
secure_capacity_difference = []
experimental = ["std", "thiserror", "multiexp"]

View file

@ -30,14 +30,11 @@ pub(crate) mod aos;
mod bits;
use bits::{BitSignature, Bits};
// Feature gated due to MSRV requirements
#[cfg(feature = "black_box")]
pub(crate) fn black_box<T>(val: T) -> T {
core::hint::black_box(val)
}
#[cfg(not(feature = "black_box"))]
pub(crate) fn black_box<T>(val: T) -> T {
// Use black_box when possible
#[rustversion::since(1.66)]
use core::hint::black_box;
#[rustversion::before(1.66)]
fn black_box<T>(val: T) -> T {
val
}

View file

@ -13,6 +13,8 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rustversion = "1"
lazy_static = "1"
rand_core = "0.6"
@ -30,6 +32,3 @@ crypto-bigint = { version = "0.5", features = ["zeroize"] }
hex = "0.4"
ff-group-tests = { path = "../ff-group-tests" }
[features]
black_box = []

View file

@ -1,13 +1,10 @@
use zeroize::Zeroize;
// Feature gated due to MSRV requirements
#[cfg(feature = "black_box")]
pub(crate) fn black_box<T>(val: T) -> T {
core::hint::black_box(val)
}
#[cfg(not(feature = "black_box"))]
pub(crate) fn black_box<T>(val: T) -> T {
// Use black_box when possible
#[rustversion::since(1.66)]
use core::hint::black_box;
#[rustversion::before(1.66)]
fn black_box<T>(val: T) -> T {
val
}

View file

@ -13,6 +13,8 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rustversion = "1"
zeroize = { version = "^1.5", features = ["zeroize_derive"] }
ff = "0.13"
@ -27,5 +29,4 @@ k256 = { version = "0.13", features = ["bits"] }
dalek-ff-group = { path = "../dalek-ff-group" }
[features]
black_box = []
batch = ["rand_core"]

View file

@ -22,14 +22,11 @@ pub use batch::BatchVerifier;
#[cfg(test)]
mod tests;
// Feature gated due to MSRV requirements
#[cfg(feature = "black_box")]
pub(crate) fn black_box<T>(val: T) -> T {
core::hint::black_box(val)
}
#[cfg(not(feature = "black_box"))]
pub(crate) fn black_box<T>(val: T) -> T {
// Use black_box when possible
#[rustversion::since(1.66)]
use core::hint::black_box;
#[rustversion::before(1.66)]
fn black_box<T>(val: T) -> T {
val
}

View file

@ -139,18 +139,12 @@ impl<C: Ciphersuite> SchnorrAggregate<C> {
/// A signature aggregator capable of consuming signatures in order to produce an aggregate.
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Zeroize)]
pub struct SchnorrAggregator<C: Ciphersuite> {
digest: DigestTranscript<C::H>,
sigs: Vec<SchnorrSignature<C>>,
}
impl<C: Ciphersuite> Zeroize for SchnorrAggregator<C> {
fn zeroize(&mut self) {
self.sigs.zeroize();
}
}
impl<C: Ciphersuite> SchnorrAggregator<C> {
/// Create a new aggregator.
///

View file

@ -13,6 +13,11 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rustversion = "1"
subtle = "^2.4"
zeroize = "^1.5"
digest = "0.10"
blake2 = { version = "0.10", optional = true }

View file

@ -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>;