-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove non-constant-time comparisons of secret values. #734
Changes from 1 commit
fe2e421
0b624de
1d54c6d
676609b
7618ecf
00fef14
c1d5494
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,7 +110,9 @@ impl<P, const SEED_SIZE: usize> ParameterizedDecode<Poplar1<P, SEED_SIZE>> for P | |
/// | ||
/// This is comprised of an IDPF key share and the correlated randomness used to compute the sketch | ||
/// during preparation. | ||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
#[derive(Debug, Clone)] | ||
// Only derive equality checks in test code, as the content of this type is a secret. | ||
#[cfg_attr(feature = "test-util", derive(PartialEq, Eq))] | ||
pub struct Poplar1InputShare<const SEED_SIZE: usize> { | ||
/// IDPF key share. | ||
idpf_key: Seed<16>, | ||
|
@@ -128,6 +130,24 @@ pub struct Poplar1InputShare<const SEED_SIZE: usize> { | |
corr_leaf: [Field255; 2], | ||
} | ||
|
||
impl<const SEED_SIZE: usize> ConstantTimeEq for Poplar1InputShare<SEED_SIZE> { | ||
fn ct_eq(&self, other: &Self) -> Choice { | ||
// We short-circuit on the length of corr_inner being different. Only the content is | ||
// protected. | ||
if self.corr_inner.len() != other.corr_inner.len() { | ||
return Choice::from(0); | ||
} | ||
|
||
let mut rslt = self.idpf_key.ct_eq(&other.idpf_key) | ||
branlwyd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
& self.corr_seed.ct_eq(&other.corr_seed) | ||
& self.corr_leaf.ct_eq(&other.corr_leaf); | ||
for (x, y) in self.corr_inner.iter().zip(other.corr_inner.iter()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can avoid manually checking lengths and iterating by using the blanket There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried this, unfortunately it doesn't work with an error like:
I think the reason There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I filed dalek-cryptography/subtle#114, we'll see if it lands. The last release was 6 months ago so we may be waiting awhile in any case. |
||
rslt &= x.ct_eq(y); | ||
} | ||
rslt | ||
} | ||
} | ||
|
||
impl<const SEED_SIZE: usize> Encode for Poplar1InputShare<SEED_SIZE> { | ||
fn encode(&self, bytes: &mut Vec<u8>) { | ||
self.idpf_key.encode(bytes); | ||
|
@@ -174,7 +194,9 @@ impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1<P, SEED_SIZ | |
} | ||
|
||
/// Poplar1 preparation state. | ||
#[derive(Clone, Debug, PartialEq, Eq)] | ||
#[derive(Clone, Debug)] | ||
// Only derive equality checks in test code, as the content of this type is a secret. | ||
#[cfg_attr(feature = "test-util", derive(PartialEq, Eq))] | ||
pub struct Poplar1PrepareState(PrepareStateVariant); | ||
|
||
impl Encode for Poplar1PrepareState { | ||
|
@@ -201,7 +223,9 @@ impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1<P, SEED_SIZ | |
} | ||
} | ||
|
||
#[derive(Clone, Debug, PartialEq, Eq)] | ||
#[derive(Clone, Debug)] | ||
// Only derive equality checks in test code, as the content of this type is a secret. | ||
#[cfg_attr(feature = "test-util", derive(PartialEq, Eq))] | ||
enum PrepareStateVariant { | ||
Inner(PrepareState<Field64>), | ||
Leaf(PrepareState<Field255>), | ||
|
@@ -252,7 +276,9 @@ impl<'a, P, const SEED_SIZE: usize> ParameterizedDecode<(&'a Poplar1<P, SEED_SIZ | |
} | ||
} | ||
|
||
#[derive(Clone, Debug, PartialEq, Eq)] | ||
#[derive(Clone, Debug)] | ||
// Only derive equality checks in test code, as the content of this type is a secret. | ||
#[cfg_attr(feature = "test-util", derive(PartialEq, Eq))] | ||
struct PrepareState<F> { | ||
sketch: SketchState<F>, | ||
output_share: Vec<F>, | ||
|
@@ -450,7 +476,9 @@ impl ParameterizedDecode<Poplar1PrepareState> for Poplar1PrepareMessage { | |
} | ||
|
||
/// A vector of field elements transmitted while evaluating Poplar1. | ||
#[derive(Clone, Debug, PartialEq, Eq)] | ||
#[derive(Clone, Debug)] | ||
// Only derive equality checks in test code, as the content of this type is a secret. | ||
#[cfg_attr(feature = "test-util", derive(PartialEq, Eq))] | ||
pub enum Poplar1FieldVec { | ||
/// Field type for inner nodes of the IDPF tree. | ||
Inner(Vec<Field64>), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned that this API might be too permissive. The intent is to fence the unsafe comparison to testing, but IIUC, the user can still shoot themself in the foot if they're not careful. Let's say I enable "test-util" in my crate that I also deploy to production; then I get to use
Eq
in my production code, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One way we could resolve this is to write
ConstantTimeEq
implementations for the relevant structs/enums, and writePartialEq
implementations that dispatch to them. (for an example of this pattern, seeIdpfPublicShare
) This would provide safe APIs for both downstream dependents and for test code, avoid the need for thetest-util
Cargo feature, and still give us the convenience benefit of being able to useassert_eq!()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds reasonable to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concern that led to this implementation was (from #722):
That is, if we expose
PartialEq + Eq
by default (and expect users of the library to use these traits for comparison), we risk future VDAFs implementing variable-time versions of these traits. This would lead to any DAP implementations built on these VDAFs silently becoming insecure, in a way that would be particularly hard to notice.OTOH, I like the idea of safe-by-default implementations and especially not requiring callers to know about
subtle
at all to use the library. I have moved everything relevant over to a constant-time implmementation ofPartialEq + Eq
.This required implementing a few more constant-time equality checks than I otherwise would have had to, since a few types had test-only equality derivations before.
Correct; if you enable a feature called "test-util" in your production binaries, IMO you are responsible for what comes next. :) This is a somewhat-common pattern for libraries -- for example,
subtle
's documentation contains the following: "Note: the subtle crate contains debug_asserts to check invariants during debug builds. These invariant checks involve secret-dependent branches, and are not present when compiled in release mode. This crate is intended to be used in release mode."