Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions node-data/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ bs58 = { workspace = true }
tracing = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true }
zeroize = { workspace = true }

# faker feature dependencies
fake = { workspace = true, features = ['derive'], optional = true }
Expand Down
23 changes: 13 additions & 10 deletions node-data/src/bls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize};
use serde_with::base64::Base64;
use serde_with::serde_as;
use sha2::{Digest, Sha256};
use zeroize::Zeroize;

pub const PUBLIC_BLS_SIZE: usize = BlsPublicKey::SIZE;

Expand Down Expand Up @@ -165,7 +166,7 @@ fn read_from_file(
)
})?;

let aes_key = hash_sha256(pwd);
let aes_key = hash_sha256(pwd.as_bytes());

let bytes = decrypt(&ciphertext[..], &aes_key)
.map_err(|e| anyhow::anyhow!("Invalid consensus keys password {e}"))?;
Expand All @@ -187,30 +188,32 @@ pub fn save_consensus_keys(
filename: &str,
pk: &BlsPublicKey,
sk: &BlsSecretKey,
pwd: &str,
pwd: &[u8],
) -> Result<(PathBuf, PathBuf), ConsensusKeysError> {
let path = path.join(filename);
let bytes = pk.to_bytes();
fs::write(path.with_extension("cpk"), bytes)?;

let bls = BlsKeyPair {
let mut bls = BlsKeyPair {
public_key_bls: pk.to_bytes().to_vec(),
secret_key_bls: sk.to_bytes().to_vec(),
};
let json = serde_json::to_string(&bls)?;

let mut bytes = json.as_bytes().to_vec();
let mut bytes = serde_json::to_vec(&bls)?;
let aes_key = hash_sha256(pwd);
bytes = encrypt(&bytes, &aes_key)?;
let encrypted_bytes = encrypt(&bytes, &aes_key)?;

fs::write(path.with_extension("keys"), encrypted_bytes)?;

fs::write(path.with_extension("keys"), bytes)?;
bls.secret_key_bls.zeroize();
bytes.zeroize();

Ok((path.with_extension("keys"), path.with_extension("cpk")))
}

fn hash_sha256(pwd: &str) -> Vec<u8> {
fn hash_sha256(pwd: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(pwd.as_bytes());
hasher.update(pwd);
hasher.finalize().to_vec()
}

Expand Down Expand Up @@ -277,7 +280,7 @@ mod tests {
let pk = BlsPublicKey::from(&sk);
let pwd = "password";

save_consensus_keys(dir.path(), "consensus", &pk, &sk, pwd)?;
save_consensus_keys(dir.path(), "consensus", &pk, &sk, pwd.as_bytes())?;
let keys_path = dir.path().join("consensus.keys");
let (loaded_sk, loaded_pk) = load_keys(
keys_path
Expand Down
2 changes: 2 additions & 0 deletions rusk-wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add deploy contract output (display the new contractId)
- Add optional deposit to ContractCall [#3650]
- Add pagination for transaction history to not pollute the stdout [#3292]
- Add zeroization for passwords and AES keys to prevent data leaks [#3687]

### Changed

Expand Down Expand Up @@ -99,6 +100,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#3704]: https://github.com/dusk-network/rusk/issues/3704
[#3702]: https://github.com/dusk-network/rusk/issues/3702
[#3700]: https://github.com/dusk-network/rusk/issues/3700
[#3687]: https://github.com/dusk-network/rusk/issues/3687
[#3650]: https://github.com/dusk-network/rusk/issues/3650
[#3623]: https://github.com/dusk-network/rusk/issues/3623
[#3602]: https://github.com/dusk-network/rusk/issues/3602
Expand Down
12 changes: 7 additions & 5 deletions rusk-wallet/src/bin/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub use history::TransactionHistory;
#[cfg(all(test, feature = "e2e-test"))]
mod tests;

use std::borrow::Borrow;
use std::fmt;
use std::fs::File;
use std::io::Write;
Expand Down Expand Up @@ -41,6 +42,7 @@ use wallet_core::BalanceInfo;
use crate::io::prompt;
use crate::prompt::Prompt;
use crate::settings::Settings;
use crate::zeroizing_bytes::ZeroizingBytes;
use crate::{WalletFile, WalletPath};

/// Commands that can be run against the Dusk wallet
Expand Down Expand Up @@ -336,8 +338,8 @@ pub(crate) enum Command {
name: Option<String>,

/// Password for the exported keys [default: env(RUSK_WALLET_PWD)]
#[arg(short, long, env = "RUSK_WALLET_EXPORT_PWD")]
export_pwd: Option<String>,
#[arg(short, long, env = "RUSK_WALLET_EXPORT_PWD", value_parser = clap::value_parser!(ZeroizingBytes))]
export_pwd: Option<ZeroizingBytes>,
},

/// Show current settings
Expand Down Expand Up @@ -542,7 +544,7 @@ impl Command {
let pwd = match export_pwd {
Some(pwd) => pwd,
None => match settings.password.as_ref() {
Some(p) => p.to_string(),
Some(p) => p.clone(),
None => prompt::ask_pwd(
"Provide a password for your provisioner keys",
)?,
Expand All @@ -555,7 +557,7 @@ impl Command {
profile_idx,
&dir,
name,
&pwd,
pwd.borrow(),
)?;

Ok(RunResult::ExportedKeys(pub_key, key_pair))
Expand Down Expand Up @@ -786,7 +788,7 @@ impl Command {
pub(crate) fn run_create(
skip_recovery: bool,
seed_file: &Option<PathBuf>,
password: &Option<String>,
password: &Option<ZeroizingBytes>,
wallet_path: &WalletPath,
prompter: &dyn Prompt,
) -> anyhow::Result<Wallet<WalletFile>> {
Expand Down
5 changes: 3 additions & 2 deletions rusk-wallet/src/bin/command/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use url::Url;
use super::*;
use crate::command::history::TransactionDirection;
use crate::settings::{LogLevel, Logging};
use crate::zeroizing_bytes::ZeroizingBytes;
use crate::{connect, status, LogFormat};

#[derive(Default)]
Expand All @@ -29,8 +30,8 @@ struct FakePrompter {
impl Prompt for FakePrompter {
fn create_new_password(
&self,
) -> anyhow::Result<String, inquire::InquireError> {
Ok("password".to_string())
) -> anyhow::Result<ZeroizingBytes, inquire::InquireError> {
Ok(ZeroizingBytes::from("password".to_string()))
}

fn prompt_text(&self, _msg: &str) -> inquire::error::InquireResult<String> {
Expand Down
5 changes: 3 additions & 2 deletions rusk-wallet/src/bin/io/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::path::PathBuf;
use clap::{arg, Parser};

use crate::settings::{LogFormat, LogLevel};
use crate::zeroizing_bytes::ZeroizingBytes;
use crate::Command;

#[derive(Parser, Debug)]
Expand All @@ -27,8 +28,8 @@ pub(crate) struct WalletArgs {
pub network: Option<String>,

/// Set the password for wallet's creation
#[arg(long, env = "RUSK_WALLET_PWD")]
pub password: Option<String>,
#[arg(long, env = "RUSK_WALLET_PWD", value_parser = clap::value_parser!(ZeroizingBytes))]
pub password: Option<ZeroizingBytes>,

/// The state server fully qualified URL
#[arg(long)]
Expand Down
36 changes: 19 additions & 17 deletions rusk-wallet/src/bin/io/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//
// Copyright (c) DUSK NETWORK. All rights reserved.

use std::borrow::Borrow;
use std::fmt::Display;
use std::path::PathBuf;
use std::str::FromStr;
Expand Down Expand Up @@ -35,10 +36,11 @@ use rusk_wallet::{PBKDF2_ROUNDS, SALT_SIZE};
use sha2::{Digest, Sha256};

use crate::command::TransactionHistory;
use crate::zeroizing_bytes::ZeroizingBytes;

pub(crate) trait Prompt {
/// Prompt the user to enter a password
fn create_new_password(&self) -> InquireResult<String> {
fn create_new_password(&self) -> InquireResult<ZeroizingBytes> {
create_new_password()
}

Expand All @@ -52,37 +54,36 @@ pub(crate) struct Prompter;

impl Prompt for Prompter {}

pub(crate) fn ask_pwd(msg: &str) -> Result<String, InquireError> {
pub(crate) fn ask_pwd(msg: &str) -> Result<ZeroizingBytes, InquireError> {
let pwd = Password::new(msg)
.with_display_toggle_enabled()
.without_confirmation()
.with_display_mode(PasswordDisplayMode::Masked)
.prompt();

pwd
pwd.map(ZeroizingBytes::from)
}

pub(crate) fn create_new_password() -> Result<String, InquireError> {
pub(crate) fn create_new_password() -> Result<ZeroizingBytes, InquireError> {
let pwd = Password::new("Password:")
.with_display_toggle_enabled()
.with_display_mode(PasswordDisplayMode::Hidden)
.with_custom_confirmation_message("Confirm password: ")
.with_custom_confirmation_error_message("The passwords doesn't match")
.prompt();

pwd
pwd.map(ZeroizingBytes::from)
}

/// Request the user to authenticate with a password and return the derived key
pub(crate) fn derive_key_from_password(
msg: &str,
password: &Option<String>,
password: &Option<ZeroizingBytes>,
salt: Option<&[u8; SALT_SIZE]>,
file_version: DatFileVersion,
) -> anyhow::Result<Vec<u8>> {
) -> anyhow::Result<ZeroizingBytes> {
let pwd = match password.as_ref() {
Some(p) => p.to_string(),

Some(p) => p.clone(),
None => ask_pwd(msg)?,
};

Expand All @@ -91,13 +92,13 @@ pub(crate) fn derive_key_from_password(

/// Request the user to create a wallet password and return the derived key
pub(crate) fn derive_key_from_new_password(
password: &Option<String>,
password: &Option<ZeroizingBytes>,
salt: Option<&[u8; SALT_SIZE]>,
file_version: DatFileVersion,
prompter: &dyn Prompt,
) -> anyhow::Result<Vec<u8>> {
) -> anyhow::Result<ZeroizingBytes> {
let pwd = match password.as_ref() {
Some(p) => p.to_string(),
Some(p) => p.clone(),
None => prompter.create_new_password()?,
};

Expand Down Expand Up @@ -155,28 +156,29 @@ pub(crate) fn request_mnemonic_phrase(

pub(crate) fn derive_key(
file_version: DatFileVersion,
pwd: &str,
pwd: &ZeroizingBytes,
salt: Option<&[u8; SALT_SIZE]>,
) -> anyhow::Result<Vec<u8>> {
) -> anyhow::Result<ZeroizingBytes> {
match file_version {
DatFileVersion::RuskBinaryFileFormat(version) => {
if version_without_pre_higher(version) >= (0, 0, 2, 0) {
let salt = salt
.ok_or_else(|| anyhow::anyhow!("Couldn't find the salt"))?;
Ok(pbkdf2::pbkdf2_hmac_array::<Sha256, SALT_SIZE>(
pwd.as_bytes(),
pwd.borrow(),
salt,
PBKDF2_ROUNDS,
)
.to_vec())
} else {
let mut hasher = Sha256::new();
hasher.update(pwd.as_bytes());
hasher.update(Borrow::<[u8]>::borrow(pwd));
Ok(hasher.finalize().to_vec())
}
}
_ => Ok(blake3::hash(pwd.as_bytes()).as_bytes().to_vec()),
_ => Ok(blake3::hash(pwd.borrow()).as_bytes().to_vec()),
}
.map(ZeroizingBytes::from)
}

/// Request a directory
Expand Down
11 changes: 6 additions & 5 deletions rusk-wallet/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ mod config;
mod interactive;
mod io;
mod settings;
mod zeroizing_bytes;

use command::{gen_iv, gen_salt};
pub(crate) use command::{Command, RunResult};
use io::prompt::{ask_pwd, derive_key, Prompter};
use zeroizing_bytes::ZeroizingBytes;

use std::borrow::Borrow;
use std::fs;
use std::path::PathBuf;

Expand All @@ -36,7 +39,7 @@ use io::{prompt, status, WalletArgs};
#[derive(Debug, Clone)]
pub(crate) struct WalletFile {
path: WalletPath,
aes_key: Vec<u8>,
aes_key: ZeroizingBytes,
salt: Option<[u8; SALT_SIZE]>,
iv: Option<[u8; IV_SIZE]>,
}
Expand All @@ -47,7 +50,7 @@ impl SecureWalletFile for WalletFile {
}

fn aes_key(&self) -> &[u8] {
&self.aes_key
self.aes_key.borrow()
}

fn salt(&self) -> Option<&[u8; SALT_SIZE]> {
Expand Down Expand Up @@ -295,13 +298,11 @@ async fn exec() -> anyhow::Result<()> {

let file_version = wallet.get_file_version()?;

let password = &settings.password;

if file_version.is_old() {
let salt = gen_salt();
let iv = gen_iv();
let pwd = match password.as_ref() {
Some(p) => p.to_string(),
Some(p) => p.clone(),
None => ask_pwd("Updating your wallet data file, please enter your wallet password ")?,
};

Expand Down
3 changes: 2 additions & 1 deletion rusk-wallet/src/bin/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use url::Url;

use crate::config::Network;
use crate::io::WalletArgs;
use crate::zeroizing_bytes::ZeroizingBytes;

#[derive(clap::ValueEnum, Debug, Clone)]
pub(crate) enum LogFormat {
Expand Down Expand Up @@ -55,7 +56,7 @@ pub(crate) struct Settings {
pub(crate) logging: Logging,

pub(crate) wallet_dir: PathBuf,
pub(crate) password: Option<String>,
pub(crate) password: Option<ZeroizingBytes>,
}

pub(crate) struct SettingsBuilder {
Expand Down
Loading
Loading