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
12 changed files with 108 additions and 56 deletions

7
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,14 +22,11 @@ pub use batch::BatchVerifier;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
// Feature gated due to MSRV requirements // Use black_box when possible
#[cfg(feature = "black_box")] #[rustversion::since(1.66)]
pub(crate) fn black_box<T>(val: T) -> T { use core::hint::black_box;
core::hint::black_box(val) #[rustversion::before(1.66)]
} fn black_box<T>(val: T) -> T {
#[cfg(not(feature = "black_box"))]
pub(crate) fn black_box<T>(val: T) -> T {
val 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. /// A signature aggregator capable of consuming signatures in order to produce an aggregate.
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Zeroize)]
pub struct SchnorrAggregator<C: Ciphersuite> { pub struct SchnorrAggregator<C: Ciphersuite> {
digest: DigestTranscript<C::H>, digest: DigestTranscript<C::H>,
sigs: Vec<SchnorrSignature<C>>, sigs: Vec<SchnorrSignature<C>>,
} }
impl<C: Ciphersuite> Zeroize for SchnorrAggregator<C> {
fn zeroize(&mut self) {
self.sigs.zeroize();
}
}
impl<C: Ciphersuite> SchnorrAggregator<C> { impl<C: Ciphersuite> SchnorrAggregator<C> {
/// Create a new aggregator. /// Create a new aggregator.
/// ///

View File

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

View File

@@ -2,6 +2,15 @@
#![no_std] #![no_std]
///! A transcript trait valid over a variety of transcript formats. ///! 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")] #[cfg(feature = "merlin")]
mod merlin; mod merlin;
@@ -12,13 +21,6 @@ pub use crate::merlin::MerlinTranscript;
#[cfg(any(test, feature = "tests"))] #[cfg(any(test, feature = "tests"))]
pub mod 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. /// A transcript trait valid over a variety of transcript formats.
pub trait Transcript: Send + Clone { pub trait Transcript: Send + Clone {
type Challenge: Send + Sync + Clone + AsRef<[u8]>; 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. /// The recommended transcript, guaranteed to be secure against length-extension attacks.
#[cfg(feature = "recommended")] #[cfg(feature = "recommended")]
pub type RecommendedTranscript = DigestTranscript<blake2::Blake2b512>; pub type RecommendedTranscript = DigestTranscript<blake2::Blake2b512>;