Skip to content

Commit 4e4cac8

Browse files
security: use constant-time equality for secret-bearing types
Secret-bearing types in the public API used derived (non-constant-time) `PartialEq`, allowing timing side-channels when comparing secrets. Replace the derived `PartialEq` with manual implementations backed by `subtle::ConstantTimeEq`: - `SharedSecret` in `src/kem/mod.rs` - `SecretKey` and `UserSecretKey` in `boyen_waters`, `waters` and `waters_naccache` Each type now implements `ConstantTimeEq` (delegating to the field(s)' `ct_eq`) and `PartialEq` via `self.ct_eq(other).into()`, matching the pattern already used for `CipherText` in the `cgw` module. Refs GHSA-whr9-835c-9m5j, closes #43 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1a60b08 commit 4e4cac8

4 files changed

Lines changed: 99 additions & 10 deletions

File tree

src/ibe/boyen_waters.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{ibe::IBE, Compress};
1212
use arrayref::{array_refs, mut_array_refs};
1313
use pg_curve::{multi_miller_loop, pairing, G1Affine, G2Affine, G2Prepared, Scalar};
1414
use rand::{CryptoRng, Rng};
15-
use subtle::CtOption;
15+
use subtle::{Choice, ConstantTimeEq, CtOption};
1616

1717
#[allow(unused_imports)]
1818
use group::Group;
@@ -49,7 +49,7 @@ pub struct PublicKey {
4949
}
5050

5151
/// Secret key parameter generated by the PKG used to extract user secret keys.
52-
#[derive(Debug, Clone, Copy, PartialEq)]
52+
#[derive(Debug, Clone, Copy)]
5353
pub struct SecretKey {
5454
alpha: Scalar,
5555
t1: Scalar,
@@ -58,12 +58,40 @@ pub struct SecretKey {
5858
t4: Scalar,
5959
}
6060

61+
impl ConstantTimeEq for SecretKey {
62+
fn ct_eq(&self, other: &Self) -> Choice {
63+
self.alpha.ct_eq(&other.alpha)
64+
& self.t1.ct_eq(&other.t1)
65+
& self.t2.ct_eq(&other.t2)
66+
& self.t3.ct_eq(&other.t3)
67+
& self.t4.ct_eq(&other.t4)
68+
}
69+
}
70+
71+
impl PartialEq for SecretKey {
72+
fn eq(&self, other: &Self) -> bool {
73+
self.ct_eq(other).into()
74+
}
75+
}
76+
6177
/// Points on the paired curves that form the user secret key.
62-
#[derive(Debug, Clone, Copy, PartialEq)]
78+
#[derive(Debug, Clone, Copy)]
6379
pub struct UserSecretKey {
6480
d: [G2Affine; 5],
6581
}
6682

83+
impl ConstantTimeEq for UserSecretKey {
84+
fn ct_eq(&self, other: &Self) -> Choice {
85+
self.d[..].ct_eq(&other.d[..])
86+
}
87+
}
88+
89+
impl PartialEq for UserSecretKey {
90+
fn eq(&self, other: &Self) -> bool {
91+
self.ct_eq(other).into()
92+
}
93+
}
94+
6795
/// Encrypted message. Can only be decrypted with an user secret key.
6896
#[derive(Debug, Clone, Copy, PartialEq)]
6997
pub struct CipherText {

src/ibe/waters.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{ibe::IBE, Compress, Derive};
77
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
88
use pg_curve::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Gt, Scalar};
99
use rand::{CryptoRng, Rng};
10-
use subtle::{Choice, ConditionallySelectable, CtOption};
10+
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
1111

1212
#[allow(unused_imports)]
1313
use group::Group;
@@ -48,18 +48,42 @@ pub struct PublicKey {
4848
}
4949

5050
/// Secret key parameter generated by the PKG used to extract user secret keys.
51-
#[derive(Debug, Clone, Copy, PartialEq)]
51+
#[derive(Debug, Clone, Copy)]
5252
pub struct SecretKey {
5353
g1prime: G1Affine,
5454
}
5555

56+
impl ConstantTimeEq for SecretKey {
57+
fn ct_eq(&self, other: &Self) -> Choice {
58+
self.g1prime.ct_eq(&other.g1prime)
59+
}
60+
}
61+
62+
impl PartialEq for SecretKey {
63+
fn eq(&self, other: &Self) -> bool {
64+
self.ct_eq(other).into()
65+
}
66+
}
67+
5668
/// Points on the paired curves that form the user secret key.
57-
#[derive(Debug, Clone, Copy, PartialEq)]
69+
#[derive(Debug, Clone, Copy)]
5870
pub struct UserSecretKey {
5971
d1: G1Affine,
6072
d2: G2Affine,
6173
}
6274

75+
impl ConstantTimeEq for UserSecretKey {
76+
fn ct_eq(&self, other: &Self) -> Choice {
77+
self.d1.ct_eq(&other.d1) & self.d2.ct_eq(&other.d2)
78+
}
79+
}
80+
81+
impl PartialEq for UserSecretKey {
82+
fn eq(&self, other: &Self) -> bool {
83+
self.ct_eq(other).into()
84+
}
85+
}
86+
6387
/// Field parameters for an identity.
6488
///
6589
/// Effectively a hash of an identity, mapped to the curve field.

src/ibe/waters_naccache.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{ibe::IBE, Compress, Derive};
88
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
99
use pg_curve::{multi_miller_loop, G1Affine, G2Affine, G2Prepared, G2Projective, Gt, Scalar};
1010
use rand::{CryptoRng, Rng};
11-
use subtle::{Choice, ConditionallySelectable, CtOption};
11+
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
1212

1313
#[allow(unused_imports)]
1414
use group::Group;
@@ -58,18 +58,42 @@ pub struct PublicKey {
5858
pub struct Identity([Scalar; CHUNKS]);
5959

6060
/// Secret key parameter generated by the PKG used to extract user secret keys.
61-
#[derive(Clone, Copy, Debug, PartialEq)]
61+
#[derive(Clone, Copy, Debug)]
6262
pub struct SecretKey {
6363
g2prime: G2Affine,
6464
}
6565

66+
impl ConstantTimeEq for SecretKey {
67+
fn ct_eq(&self, other: &Self) -> Choice {
68+
self.g2prime.ct_eq(&other.g2prime)
69+
}
70+
}
71+
72+
impl PartialEq for SecretKey {
73+
fn eq(&self, other: &Self) -> bool {
74+
self.ct_eq(other).into()
75+
}
76+
}
77+
6678
/// Points on the paired curves that form the user secret key.
67-
#[derive(Clone, Copy, Debug, PartialEq)]
79+
#[derive(Clone, Copy, Debug)]
6880
pub struct UserSecretKey {
6981
d1: G2Affine,
7082
d2: G1Affine,
7183
}
7284

85+
impl ConstantTimeEq for UserSecretKey {
86+
fn ct_eq(&self, other: &Self) -> Choice {
87+
self.d1.ct_eq(&other.d1) & self.d2.ct_eq(&other.d2)
88+
}
89+
}
90+
91+
impl PartialEq for UserSecretKey {
92+
fn eq(&self, other: &Self) -> bool {
93+
self.ct_eq(other).into()
94+
}
95+
}
96+
7397
/// Encrypted message. Can only be decrypted with an user secret key.
7498
#[derive(Clone, Copy, Debug, PartialEq)]
7599
pub struct CipherText {

src/kem/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::{Compress, Derive};
2626
use core::ops::BitXorAssign;
2727
use pg_curve::Gt;
2828
use rand::{CryptoRng, Rng};
29+
use subtle::{Choice, ConstantTimeEq};
2930

3031
/// Size of the shared secret in bytes.
3132
pub const SS_BYTES: usize = 32;
@@ -34,9 +35,21 @@ pub const SS_BYTES: usize = 32;
3435
///
3536
/// This shared secret has roughly a 127 bits of security.
3637
/// This is due to the fact that BLS12-381 targets this security level (optimistically).
37-
#[derive(Clone, Copy, Debug, PartialEq)]
38+
#[derive(Clone, Copy, Debug)]
3839
pub struct SharedSecret(pub [u8; SS_BYTES]);
3940

41+
impl ConstantTimeEq for SharedSecret {
42+
fn ct_eq(&self, other: &Self) -> Choice {
43+
self.0.ct_eq(&other.0)
44+
}
45+
}
46+
47+
impl PartialEq for SharedSecret {
48+
fn eq(&self, other: &Self) -> bool {
49+
self.ct_eq(other).into()
50+
}
51+
}
52+
4053
/// Uses SHAKE256 to derive a 32-byte shared secret from a target group element.
4154
///
4255
/// Internally compresses the target group element to byte representation.

0 commit comments

Comments
 (0)