Skip to content

Commit 2ca5810

Browse files
committed
rusk-wallet: zeroize passwords and AES keys
1 parent 9687bdd commit 2ca5810

9 files changed

Lines changed: 74 additions & 34 deletions

File tree

rusk-wallet/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- Add deploy contract output (display the new contractId)
2828
- Add optional deposit to ContractCall [#3650]
2929
- Add pagination for transaction history to not pollute the stdout [#3292]
30+
- Add zeroization for passwords and AES keys to prevent data leaks [#3687]
3031

3132
### Changed
3233

@@ -86,6 +87,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8687

8788

8889
<!-- Issues -->
90+
[#3687]: https://github.com/dusk-network/rusk/issues/3687
8991
[#3734]: https://github.com/dusk-network/rusk/issues/3734
9092
[#3713]: https://github.com/dusk-network/rusk/issues/3713
9193
[#3712]: https://github.com/dusk-network/rusk/issues/3712

rusk-wallet/src/bin/command.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub use history::TransactionHistory;
1111
#[cfg(all(test, feature = "e2e-test"))]
1212
mod tests;
1313

14+
use std::borrow::Borrow;
1415
use std::fmt;
1516
use std::fs::File;
1617
use std::io::Write;
@@ -40,6 +41,7 @@ use wallet_core::BalanceInfo;
4041
use crate::io::prompt;
4142
use crate::prompt::Prompt;
4243
use crate::settings::Settings;
44+
use crate::zeroizing_bytes::ZeroizingBytes;
4345
use crate::{WalletFile, WalletPath};
4446

4547
/// Commands that can be run against the Dusk wallet
@@ -316,8 +318,8 @@ pub(crate) enum Command {
316318
name: Option<String>,
317319

318320
/// Password for the exported keys [default: env(RUSK_WALLET_PWD)]
319-
#[arg(short, long, env = "RUSK_WALLET_EXPORT_PWD")]
320-
export_pwd: Option<String>,
321+
#[arg(short, long, env = "RUSK_WALLET_EXPORT_PWD", value_parser = clap::value_parser!(ZeroizingBytes))]
322+
export_pwd: Option<ZeroizingBytes>,
321323
},
322324

323325
/// Show current settings
@@ -522,7 +524,7 @@ impl Command {
522524
let pwd = match export_pwd {
523525
Some(pwd) => pwd,
524526
None => match settings.password.as_ref() {
525-
Some(p) => p.to_string(),
527+
Some(p) => p.clone(),
526528
None => prompt::ask_pwd(
527529
"Provide a password for your provisioner keys",
528530
)?,
@@ -535,7 +537,7 @@ impl Command {
535537
profile_idx,
536538
&dir,
537539
name,
538-
&pwd,
540+
pwd.borrow(),
539541
)?;
540542

541543
Ok(RunResult::ExportedKeys(pub_key, key_pair))
@@ -720,7 +722,7 @@ impl Command {
720722
pub(crate) fn run_create(
721723
skip_recovery: bool,
722724
seed_file: &Option<PathBuf>,
723-
password: &Option<String>,
725+
password: &Option<ZeroizingBytes>,
724726
wallet_path: &WalletPath,
725727
prompter: &dyn Prompt,
726728
) -> anyhow::Result<Wallet<WalletFile>> {

rusk-wallet/src/bin/command/tests/utils.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use url::Url;
1919
use super::*;
2020
use crate::command::history::TransactionDirection;
2121
use crate::settings::{LogLevel, Logging};
22+
use crate::zeroizing_bytes::ZeroizingBytes;
2223
use crate::{connect, status, LogFormat};
2324

2425
#[derive(Default)]
@@ -29,8 +30,8 @@ struct FakePrompter {
2930
impl Prompt for FakePrompter {
3031
fn create_new_password(
3132
&self,
32-
) -> anyhow::Result<String, inquire::InquireError> {
33-
Ok("password".to_string())
33+
) -> anyhow::Result<ZeroizingBytes, inquire::InquireError> {
34+
Ok(ZeroizingBytes::from("password".to_string()))
3435
}
3536

3637
fn prompt_text(&self, _msg: &str) -> inquire::error::InquireResult<String> {

rusk-wallet/src/bin/io/args.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::path::PathBuf;
99
use clap::{arg, Parser};
1010

1111
use crate::settings::{LogFormat, LogLevel};
12+
use crate::zeroizing_bytes::ZeroizingBytes;
1213
use crate::Command;
1314

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

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

3334
/// The state server fully qualified URL
3435
#[arg(long)]

rusk-wallet/src/bin/io/prompt.rs

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55
// Copyright (c) DUSK NETWORK. All rights reserved.
66

7+
use std::borrow::Borrow;
78
use std::fmt::Display;
89
use std::path::PathBuf;
910
use std::str::FromStr;
@@ -35,10 +36,11 @@ use rusk_wallet::{PBKDF2_ROUNDS, SALT_SIZE};
3536
use sha2::{Digest, Sha256};
3637

3738
use crate::command::TransactionHistory;
39+
use crate::zeroizing_bytes::ZeroizingBytes;
3840

3941
pub(crate) trait Prompt {
4042
/// Prompt the user to enter a password
41-
fn create_new_password(&self) -> InquireResult<String> {
43+
fn create_new_password(&self) -> InquireResult<ZeroizingBytes> {
4244
create_new_password()
4345
}
4446

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

5355
impl Prompt for Prompter {}
5456

55-
pub(crate) fn ask_pwd(msg: &str) -> Result<String, InquireError> {
57+
pub(crate) fn ask_pwd(msg: &str) -> Result<ZeroizingBytes, InquireError> {
5658
let pwd = Password::new(msg)
5759
.with_display_toggle_enabled()
5860
.without_confirmation()
5961
.with_display_mode(PasswordDisplayMode::Masked)
6062
.prompt();
6163

62-
pwd
64+
pwd.map(ZeroizingBytes::from)
6365
}
6466

65-
pub(crate) fn create_new_password() -> Result<String, InquireError> {
67+
pub(crate) fn create_new_password() -> Result<ZeroizingBytes, InquireError> {
6668
let pwd = Password::new("Password:")
6769
.with_display_toggle_enabled()
6870
.with_display_mode(PasswordDisplayMode::Hidden)
6971
.with_custom_confirmation_message("Confirm password: ")
7072
.with_custom_confirmation_error_message("The passwords doesn't match")
7173
.prompt();
7274

73-
pwd
75+
pwd.map(ZeroizingBytes::from)
7476
}
7577

7678
/// Request the user to authenticate with a password and return the derived key
7779
pub(crate) fn derive_key_from_password(
7880
msg: &str,
79-
password: &Option<String>,
81+
password: &Option<ZeroizingBytes>,
8082
salt: Option<&[u8; SALT_SIZE]>,
8183
file_version: DatFileVersion,
82-
) -> anyhow::Result<Vec<u8>> {
84+
) -> anyhow::Result<ZeroizingBytes> {
8385
let pwd = match password.as_ref() {
84-
Some(p) => p.to_string(),
85-
86+
Some(p) => p.clone(),
8687
None => ask_pwd(msg)?,
8788
};
8889

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

9293
/// Request the user to create a wallet password and return the derived key
9394
pub(crate) fn derive_key_from_new_password(
94-
password: &Option<String>,
95+
password: &Option<ZeroizingBytes>,
9596
salt: Option<&[u8; SALT_SIZE]>,
9697
file_version: DatFileVersion,
9798
prompter: &dyn Prompt,
98-
) -> anyhow::Result<Vec<u8>> {
99+
) -> anyhow::Result<ZeroizingBytes> {
99100
let pwd = match password.as_ref() {
100-
Some(p) => p.to_string(),
101+
Some(p) => p.clone(),
101102
None => prompter.create_new_password()?,
102103
};
103104

@@ -155,28 +156,29 @@ pub(crate) fn request_mnemonic_phrase(
155156

156157
pub(crate) fn derive_key(
157158
file_version: DatFileVersion,
158-
pwd: &str,
159+
pwd: &ZeroizingBytes,
159160
salt: Option<&[u8; SALT_SIZE]>,
160-
) -> anyhow::Result<Vec<u8>> {
161+
) -> anyhow::Result<ZeroizingBytes> {
161162
match file_version {
162163
DatFileVersion::RuskBinaryFileFormat(version) => {
163164
if version_without_pre_higher(version) >= (0, 0, 2, 0) {
164165
let salt = salt
165166
.ok_or_else(|| anyhow::anyhow!("Couldn't find the salt"))?;
166167
Ok(pbkdf2::pbkdf2_hmac_array::<Sha256, SALT_SIZE>(
167-
pwd.as_bytes(),
168+
pwd.borrow(),
168169
salt,
169170
PBKDF2_ROUNDS,
170171
)
171172
.to_vec())
172173
} else {
173174
let mut hasher = Sha256::new();
174-
hasher.update(pwd.as_bytes());
175+
hasher.update(Borrow::<[u8]>::borrow(pwd));
175176
Ok(hasher.finalize().to_vec())
176177
}
177178
}
178-
_ => Ok(blake3::hash(pwd.as_bytes()).as_bytes().to_vec()),
179+
_ => Ok(blake3::hash(pwd.borrow()).as_bytes().to_vec()),
179180
}
181+
.map(ZeroizingBytes::from)
180182
}
181183

182184
/// Request a directory

rusk-wallet/src/bin/main.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ mod config;
99
mod interactive;
1010
mod io;
1111
mod settings;
12+
mod zeroizing_bytes;
1213

1314
use command::{gen_iv, gen_salt};
1415
pub(crate) use command::{Command, RunResult};
1516
use io::prompt::{ask_pwd, derive_key, Prompter};
17+
use zeroizing_bytes::ZeroizingBytes;
1618

19+
use std::borrow::Borrow;
1720
use std::fs;
1821
use std::path::PathBuf;
1922

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

4952
fn aes_key(&self) -> &[u8] {
50-
&self.aes_key
53+
self.aes_key.borrow()
5154
}
5255

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

296299
let file_version = wallet.get_file_version()?;
297300

298-
let password = &settings.password;
299-
300301
if file_version.is_old() {
301302
let salt = gen_salt();
302303
let iv = gen_iv();
303304
let pwd = match password.as_ref() {
304-
Some(p) => p.to_string(),
305+
Some(p) => p.clone(),
305306
None => ask_pwd("Updating your wallet data file, please enter your wallet password ")?,
306307
};
307308

rusk-wallet/src/bin/settings.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use url::Url;
1313

1414
use crate::config::Network;
1515
use crate::io::WalletArgs;
16+
use crate::zeroizing_bytes::ZeroizingBytes;
1617

1718
#[derive(clap::ValueEnum, Debug, Clone)]
1819
pub(crate) enum LogFormat {
@@ -54,7 +55,7 @@ pub(crate) struct Settings {
5455
pub(crate) logging: Logging,
5556

5657
pub(crate) wallet_dir: PathBuf,
57-
pub(crate) password: Option<String>,
58+
pub(crate) password: Option<ZeroizingBytes>,
5859
}
5960

6061
pub(crate) struct SettingsBuilder {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
//
5+
// Copyright (c) DUSK NETWORK. All rights reserved.
6+
7+
use std::borrow::Borrow;
8+
9+
use zeroize::{Zeroize, ZeroizeOnDrop};
10+
11+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Zeroize, ZeroizeOnDrop)]
12+
pub struct ZeroizingBytes(Vec<u8>);
13+
14+
impl From<String> for ZeroizingBytes {
15+
fn from(s: String) -> Self {
16+
Self(s.into_bytes())
17+
}
18+
}
19+
20+
impl From<Vec<u8>> for ZeroizingBytes {
21+
fn from(v: Vec<u8>) -> Self {
22+
Self(v)
23+
}
24+
}
25+
26+
impl Borrow<[u8]> for ZeroizingBytes {
27+
fn borrow(&self) -> &[u8] {
28+
&self.0
29+
}
30+
}

rusk-wallet/src/wallet.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ impl<F: SecureWalletFile + Debug> Wallet<F> {
580580
profile_idx: u8,
581581
dir: &Path,
582582
filename: Option<String>,
583-
pwd: &str,
583+
pwd: &[u8],
584584
) -> Result<(PathBuf, PathBuf), Error> {
585585
// we're expecting a directory here
586586
if !dir.is_dir() {
@@ -596,7 +596,7 @@ impl<F: SecureWalletFile + Debug> Wallet<F> {
596596
)?;
597597

598598
sk.zeroize();
599-
599+
600600
Ok(keys_paths)
601601
}
602602

0 commit comments

Comments
 (0)