diff --git a/Cargo.toml b/Cargo.toml index 0946958..b91dbe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ subtle = { version = "2.6.1", default-features = false } tiny-keccak = { version = "2.0.2", features = ["sha3", "shake"] } # aes-gcm >= 0.10.3 fixes RUSTSEC-2023-0096 / CVE-2023-42811. aes-gcm = { version = "0.10.3", optional = true } +zeroize = { version = "1.7", default-features = false, features = ["derive"], optional = true } [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2", features = ["js"] } @@ -43,6 +44,7 @@ kv1 = [] waters = [] waters_naccache = [] mkem = ["aes-gcm"] +zeroize = ["dep:zeroize", "pg-curve/zeroize"] [lib] bench = false diff --git a/src/ibe/boyen_waters.rs b/src/ibe/boyen_waters.rs index f93b8fd..4a69a35 100644 --- a/src/ibe/boyen_waters.rs +++ b/src/ibe/boyen_waters.rs @@ -49,7 +49,15 @@ pub struct PublicKey { } /// Secret key parameter generated by the PKG used to extract user secret keys. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SecretKey { alpha: Scalar, t1: Scalar, @@ -75,7 +83,15 @@ impl PartialEq for SecretKey { } /// Points on the paired curves that form the user secret key. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct UserSecretKey { d: [G2Affine; 5], } @@ -102,7 +118,15 @@ pub struct CipherText { /// A point on the paired curve that can be encrypted and decrypted. /// /// You can use the byte representation to derive an AES key. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SharedSecret(Gt); /// A message that can be encrypted using the PKE. @@ -227,7 +251,7 @@ impl IBE for BoyenWaters { } } - /// Decrypt ciphertext to a SharedSecret using a user secret key. + /// Decrypt ciphertext to a message using a user secret key. fn decrypt(usk: &UserSecretKey, ct: &CipherText) -> Msg { ct.cprime + multi_miller_loop(&[ @@ -466,4 +490,23 @@ impl Compress for CipherText { #[cfg(test)] mod tests { test_ibe!(BoyenWaters); + + #[cfg(feature = "zeroize")] + #[test] + fn shared_secret_zeroize() { + use group::Group; + use zeroize::Zeroize; + + let mut rng = rand::thread_rng(); + let mut ss = SharedSecret(Gt::random(&mut rng)); + let before = ss; + + ss.zeroize(); + assert_ne!(ss, before, "SharedSecret did not zeroize"); + + // Zeroization is deterministic: two independently-wiped values match. + let mut ss2 = SharedSecret(Gt::random(&mut rng)); + ss2.zeroize(); + assert_eq!(ss, ss2, "zeroized SharedSecrets should be identical"); + } } diff --git a/src/ibe/cgw.rs b/src/ibe/cgw.rs index 3ed899e..0a1547c 100644 --- a/src/ibe/cgw.rs +++ b/src/ibe/cgw.rs @@ -48,7 +48,15 @@ pub struct PublicKey { /// Secret key parameter generated by the PKG used to extract user secret keys. /// Also known as MSK. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SecretKey { b: [Scalar; 2], k: [Scalar; 2], @@ -58,7 +66,15 @@ pub struct SecretKey { /// User secret key. Can be used to decrypt the corresponding ciphertext. /// Also known as USK_{id}. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct UserSecretKey { d0: [G2Affine; 2], d1: [G2Affine; 2], diff --git a/src/ibe/waters.rs b/src/ibe/waters.rs index b1f9844..b159961 100644 --- a/src/ibe/waters.rs +++ b/src/ibe/waters.rs @@ -48,7 +48,15 @@ pub struct PublicKey { } /// Secret key parameter generated by the PKG used to extract user secret keys. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SecretKey { g1prime: G1Affine, } @@ -66,7 +74,15 @@ impl PartialEq for SecretKey { } /// Points on the paired curves that form the user secret key. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct UserSecretKey { d1: G1Affine, d2: G2Affine, diff --git a/src/ibe/waters_naccache.rs b/src/ibe/waters_naccache.rs index fbacb4b..9e4fa08 100644 --- a/src/ibe/waters_naccache.rs +++ b/src/ibe/waters_naccache.rs @@ -58,7 +58,15 @@ pub struct PublicKey { pub struct Identity([Scalar; CHUNKS]); /// Secret key parameter generated by the PKG used to extract user secret keys. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SecretKey { g2prime: G2Affine, } @@ -76,7 +84,15 @@ impl PartialEq for SecretKey { } /// Points on the paired curves that form the user secret key. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct UserSecretKey { d1: G2Affine, d2: G1Affine, diff --git a/src/kem/cgw_fo.rs b/src/kem/cgw_fo.rs index 6876ce6..b34f25a 100644 --- a/src/kem/cgw_fo.rs +++ b/src/kem/cgw_fo.rs @@ -29,7 +29,15 @@ pub const USK_BYTES: usize = CPA_USK_BYTES + ID_BYTES; /// User secret key. Can be used to decaps the corresponding ciphertext. /// Also known as USK_{id}. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct UserSecretKey { usk: crate::ibe::cgw::UserSecretKey, id: Identity, diff --git a/src/kem/cgw_kv.rs b/src/kem/cgw_kv.rs index 87a75dc..3a1b880 100644 --- a/src/kem/cgw_kv.rs +++ b/src/kem/cgw_kv.rs @@ -47,7 +47,15 @@ pub struct PublicKey { /// Secret key parameter generated by the PKG used to extract user secret keys. /// Also known as MSK. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SecretKey { b: [Scalar; 2], k: [Scalar; 2], @@ -58,7 +66,15 @@ pub struct SecretKey { /// User secret key. Can be used to decaps the corresponding ciphertext. /// Also known as USK_{id}. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct UserSecretKey { d0: [G2Affine; 2], d1: [G2Affine; 2], @@ -516,4 +532,37 @@ mod tests { #[cfg(feature = "mkem")] test_multi_kem!(CGWKV); + + #[cfg(feature = "zeroize")] + #[test] + fn secret_types_zeroize() { + use zeroize::Zeroize; + let mut rng = rand::thread_rng(); + let id = ::Id::derive_str("alice@example.com"); + let (pk, mut sk) = CGWKV::setup(&mut rng); + let mut usk = CGWKV::extract_usk(Some(&pk), &sk, &id, &mut rng); + let (_ct, mut ss) = CGWKV::encaps(&pk, &id, &mut rng); + + // Snapshot serialized form so we can assert zeroization actually blanks it. + let sk_bytes_before = sk.to_bytes(); + let usk_bytes_before = usk.to_bytes(); + let ss_bytes_before = ss.0; + + sk.zeroize(); + usk.zeroize(); + ss.zeroize(); + + assert_ne!(sk.to_bytes(), sk_bytes_before, "SecretKey did not zeroize"); + assert_ne!( + usk.to_bytes(), + usk_bytes_before, + "UserSecretKey did not zeroize" + ); + assert_ne!(ss.0, ss_bytes_before, "SharedSecret did not zeroize"); + assert_eq!( + ss.0, + [0u8; crate::kem::SS_BYTES], + "SharedSecret bytes should be zero" + ); + } } diff --git a/src/kem/kiltz_vahlis_one.rs b/src/kem/kiltz_vahlis_one.rs index aa6cef4..3f267bb 100644 --- a/src/kem/kiltz_vahlis_one.rs +++ b/src/kem/kiltz_vahlis_one.rs @@ -44,13 +44,29 @@ pub struct PublicKey { } /// Secret key parameter generated by the PKG used to extract user secret keys. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SecretKey { alpha: G1Affine, } /// Points on the paired curves that form the user secret key. +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct UserSecretKey { d1: G1Affine, d2: G2Affine, diff --git a/src/kem/mod.rs b/src/kem/mod.rs index 2069cf5..038e358 100644 --- a/src/kem/mod.rs +++ b/src/kem/mod.rs @@ -35,7 +35,15 @@ pub const SS_BYTES: usize = 32; /// /// This shared secret has roughly a 127 bits of security. /// This is due to the fact that BLS12-381 targets this security level (optimistically). +/// +/// # Zeroization +/// +/// With the `zeroize` feature enabled this type derives `Zeroize` but **not** +/// `ZeroizeOnDrop` (it is `Copy`). Secret material is **not** cleared on drop — +/// you **MUST** call `.zeroize()` explicitly once done. See the +/// [crate-level docs](crate#zeroizing-secret-material). #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct SharedSecret(pub [u8; SS_BYTES]); impl ConstantTimeEq for SharedSecret { diff --git a/src/lib.rs b/src/lib.rs index 1a34908..850dd85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,43 @@ //! //! assert_eq!(k, k2); //! ``` +//! +//! # Zeroizing secret material +//! +//! When the `zeroize` feature is enabled, the secret types in this crate — the +//! [`SharedSecret`](crate::kem::SharedSecret) produced by the KEMs, and the +//! `SecretKey` and `UserSecretKey` of every scheme — derive +//! [`Zeroize`](https://docs.rs/zeroize), but **not** `ZeroizeOnDrop`. +//! +//! These types are `Copy`, and a `Copy` type cannot implement `Drop` (Rust +//! forbids `Copy` and `Drop` on the same type), so `ZeroizeOnDrop` cannot be +//! derived for them. As a consequence **secret key material is not wiped from +//! memory automatically when a value goes out of scope**. If you care about +//! clearing secrets, you **MUST** call `.zeroize()` explicitly once you are +//! done with each secret value: +//! +//! ```ignore +//! use ibe::kem::{IBKEM, cgw_kv::CGWKV}; +//! use ibe::Derive; +//! use zeroize::Zeroize; +//! +//! let mut rng = rand::thread_rng(); +//! let id = ::Id::derive_str("alice@example.com"); +//! let (pk, mut sk) = CGWKV::setup(&mut rng); +//! let mut usk = CGWKV::extract_usk(Some(&pk), &sk, &id, &mut rng); +//! let (_ct, mut ss) = CGWKV::encaps(&pk, &id, &mut rng); +//! +//! // ... use sk / usk / ss ... +//! +//! // Wipe the secret material once you are done with it. +//! sk.zeroize(); +//! usk.zeroize(); +//! ss.zeroize(); +//! ``` +//! +//! Making these types `!Copy` so that `ZeroizeOnDrop` can be derived (and the +//! wiping happens automatically) is a breaking API change; it is deferred to a +//! future major release. #![no_std] #![deny(missing_debug_implementations, rust_2018_idioms, missing_docs)] diff --git a/src/util.rs b/src/util.rs index 0f315f4..ed3cc26 100644 --- a/src/util.rs +++ b/src/util.rs @@ -128,6 +128,7 @@ pub fn rpc(k: &[u8; 32], gs: &[Gr]) -> Scalar { /// /// This identity is obtained by hashing using sha3_512. #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct Identity(pub [u8; ID_BYTES]); impl Default for Identity {