diff --git a/Cargo.lock b/Cargo.lock index df2702d..30945da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1104,6 +1104,7 @@ dependencies = [ "thiserror 2.0.11", "tokio", "xeddsa", + "zeroize", ] [[package]] @@ -1304,6 +1305,7 @@ dependencies = [ "toml", "trusted-dealer", "xeddsa", + "zeroize", ] [[package]] @@ -2598,6 +2600,7 @@ dependencies = [ "snow", "tokio", "xeddsa", + "zeroize", ] [[package]] @@ -5232,6 +5235,7 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ + "serde", "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 947233a..37c0d56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ zcash_keys = "0.6.0" zcash_primitives = "0.21.0" zcash_proofs = "0.21.0" zcash_protocol = "0.4.3" +zeroize = "1.8.1" [patch.crates-io] # TODO: remove this when https://github.com/zcash/orchard/issues/430 is fully diff --git a/dkg/Cargo.toml b/dkg/Cargo.toml index 7b8167b..92fe2dd 100644 --- a/dkg/Cargo.toml +++ b/dkg/Cargo.toml @@ -22,6 +22,7 @@ xeddsa = { workspace = true } reqwest = { workspace = true, features = ["json", "rustls-tls-native-roots"] } tokio = { workspace = true, features = ["full"] } snow = { workspace = true } +zeroize = { workspace = true, features = ["serde", "zeroize_derive"] } [features] default = [] diff --git a/dkg/src/args.rs b/dkg/src/args.rs index 42e7911..c132e88 100644 --- a/dkg/src/args.rs +++ b/dkg/src/args.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use clap::Parser; use frost_core::{Ciphersuite, Identifier}; +use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Parser, Debug, Default)] #[command(author, version, about, long_about = None)] @@ -10,7 +11,7 @@ pub struct Args { pub ciphersuite: String, } -#[derive(Clone)] +#[derive(Clone, Zeroize)] pub struct ProcessedArgs { /// CLI mode. If enabled, it will prompt for inputs from stdin /// and print values to stdout, ignoring other flags. @@ -38,6 +39,7 @@ pub struct ProcessedArgs { // using `fn()` would preclude using closures and using generics would // require a lot of code change for something simple. #[allow(clippy::type_complexity)] + #[zeroize(skip)] pub comm_participant_pubkey_getter: Option) -> Option>>>, /// The threshold to use for the shares @@ -51,9 +53,12 @@ pub struct ProcessedArgs { pub participants: Vec>, /// Identifier to use for the participant. Only needed for CLI mode. + #[zeroize(skip)] pub identifier: Option>, } +impl ZeroizeOnDrop for ProcessedArgs where C: Ciphersuite {} + impl ProcessedArgs where C: Ciphersuite, diff --git a/dkg/src/cli.rs b/dkg/src/cli.rs index aaef953..9efb939 100644 --- a/dkg/src/cli.rs +++ b/dkg/src/cli.rs @@ -7,6 +7,7 @@ use reddsa::frost::redpallas::keys::EvenY; use std::collections::HashMap; use std::error::Error; use std::io::{BufRead, Write}; +use zeroize::Zeroizing; use crate::args::ProcessedArgs; use crate::comms::cli::CLIComms; @@ -102,6 +103,7 @@ pub async fn cli_for_processed_args( let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(round1_secret_package, &received_round1_packages)?; + let round2_secret_package = Zeroizing::new(round2_secret_package); let received_round2_packages = comms .get_round2_packages(input, logger, round2_packages) diff --git a/frost-client/Cargo.toml b/frost-client/Cargo.toml index 5c77862..2113e54 100644 --- a/frost-client/Cargo.toml +++ b/frost-client/Cargo.toml @@ -32,3 +32,4 @@ rand = { workspace = true } stable-eyre = { workspace = true } itertools = { workspace = true } xeddsa = { workspace = true } +zeroize = { workspace = true, features = ["serde", "zeroize_derive"] } \ No newline at end of file diff --git a/frost-client/src/config.rs b/frost-client/src/config.rs index cb379fc..7dbe297 100644 --- a/frost-client/src/config.rs +++ b/frost-client/src/config.rs @@ -9,6 +9,7 @@ use std::{ use eyre::{eyre, OptionExt}; use frost_core::{Ciphersuite, Identifier}; use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; use crate::{ciphersuite_helper::ciphersuite_helper, contact::Contact, write_atomic}; @@ -29,6 +30,14 @@ pub struct Config { pub group: BTreeMap, } +impl Zeroize for Config { + fn zeroize(&mut self) { + self.group.iter_mut().for_each(|(_, g)| g.zeroize()); + } +} + +impl ZeroizeOnDrop for Config {} + impl Config { pub fn contact_by_pubkey(&self, pubkey: &[u8]) -> Result> { if Some(pubkey) == self.communication_key.as_ref().map(|c| c.pubkey.as_slice()) { @@ -48,7 +57,7 @@ impl Config { } /// The communication key pair for the user. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, ZeroizeOnDrop)] pub struct CommunicationKey { /// The private key. #[serde( @@ -65,7 +74,7 @@ pub struct CommunicationKey { } /// A FROST group the user belongs to. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Zeroize)] pub struct Group { /// A human-readable description of the group to make it easier to select /// groups @@ -87,9 +96,12 @@ pub struct Group { /// The default server the participants are using, if any. pub server_url: Option, /// The group participants, keyed by hex-encoded identifier + #[zeroize(skip)] pub participant: BTreeMap, } +impl ZeroizeOnDrop for Group {} + impl Group { /// Returns a human-readable summary of the contact; used when it is /// printed to the terminal. @@ -176,7 +188,7 @@ impl Config { ..Default::default() }); } - let bytes = std::fs::read(&path)?; + let bytes = Zeroizing::new(std::fs::read(&path)?); let s = str::from_utf8(&bytes)?; let mut config: Config = toml::from_str(s)?; config.path = Some(path); @@ -185,7 +197,7 @@ impl Config { /// Write the config to path it was loaded from. pub fn write(&self) -> Result<(), Box> { - let s = toml::to_string_pretty(self)?; + let s = Zeroizing::new(toml::to_string_pretty(self)?); let bytes = s.as_bytes(); Ok(write_atomic::write_file( self.path diff --git a/frost-client/src/contact.rs b/frost-client/src/contact.rs index 1565822..d7e0caf 100644 --- a/frost-client/src/contact.rs +++ b/frost-client/src/contact.rs @@ -94,7 +94,8 @@ pub(crate) fn export(args: &Command) -> Result<(), Box> { pubkey: config .communication_key .ok_or(eyre!("pubkey not generated yet"))? - .pubkey, + .pubkey + .clone(), }; eprintln!("Exporting this information:"); diff --git a/frost-client/src/dkg.rs b/frost-client/src/dkg.rs index 190043a..f84d187 100644 --- a/frost-client/src/dkg.rs +++ b/frost-client/src/dkg.rs @@ -10,6 +10,7 @@ use dkg::cli::MaybeIntoEvenY; use frost_core::Ciphersuite; use frost_ed25519::Ed25519Sha512; use reqwest::Url; +use zeroize::Zeroizing; use crate::{ args::Command, @@ -57,7 +58,8 @@ pub(crate) async fn dkg_for_ciphersuite(dkg_config, &mut input, &mut output).await?; + let key_package = Zeroizing::new(key_package); // Reverse pubkey_map let pubkey_map = pubkey_map diff --git a/frost-client/src/trusted_dealer.rs b/frost-client/src/trusted_dealer.rs index 7484a5e..dd99880 100644 --- a/frost-client/src/trusted_dealer.rs +++ b/frost-client/src/trusted_dealer.rs @@ -72,7 +72,8 @@ pub(crate) fn trusted_dealer_for_ciphersuite { /// CLI mode. If enabled, it will prompt for inputs from stdin /// and print values to stdout, ignoring other flags. @@ -82,9 +83,12 @@ pub struct ProcessedArgs { // using `fn()` would preclude using closures and using generics would // require a lot of code change for something simple. #[allow(clippy::type_complexity)] + #[zeroize(skip)] pub comm_coordinator_pubkey_getter: Option) -> Option>>>, } +impl ZeroizeOnDrop for ProcessedArgs where C: Ciphersuite {} + impl ProcessedArgs { /// Create a ProcessedArgs from a Args. /// diff --git a/participant/src/cli.rs b/participant/src/cli.rs index 8e11dce..34c46a0 100644 --- a/participant/src/cli.rs +++ b/participant/src/cli.rs @@ -15,6 +15,7 @@ use frost_rerandomized::RandomizedCiphersuite; use rand::thread_rng; use reddsa::frost::redpallas::PallasBlake2b512; use std::io::{BufRead, Write}; +use zeroize::Zeroizing; pub async fn cli( args: &Args, @@ -40,10 +41,11 @@ pub async fn cli_for_processed_args( // Round 1 - let key_package = pargs.key_package; + let key_package = &pargs.key_package; let mut rng = thread_rng(); - let (nonces, commitments) = generate_nonces_and_commitments(&key_package, &mut rng); + let (nonces, commitments) = generate_nonces_and_commitments(key_package, &mut rng); + let nonces = Zeroizing::new(nonces); if pargs.cli { print_values(commitments, logger)?; @@ -80,7 +82,7 @@ pub async fn cli_for_processed_args( return Err(eyre!("signing cancelled").into()); } - let signature = generate_signature(round_2_config, &key_package, &nonces)?; + let signature = generate_signature(round_2_config, key_package, &nonces)?; comms .send_signature_share(*key_package.identifier(), signature)