mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 11:41:51 +00:00
Remove ZeroizeString in favour of Zeroizing<String> (#6661)
* Remove ZeroizeString in favour of Zeroizing<String> * cargo fmt * remove unrelated line that slipped in * Update beacon_node/store/Cargo.toml thanks michael! Co-authored-by: Michael Sproul <micsproul@gmail.com> * Merge branch 'unstable' into remove-zeroizedstring
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -36,6 +36,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"types",
|
||||
"validator_dir",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2561,8 +2562,6 @@ dependencies = [
|
||||
name = "eth2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"account_utils",
|
||||
"bytes",
|
||||
"derivative",
|
||||
"eth2_keystore",
|
||||
"ethereum_serde_utils",
|
||||
@@ -2570,7 +2569,6 @@ dependencies = [
|
||||
"ethereum_ssz_derive",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"libsecp256k1",
|
||||
"lighthouse_network",
|
||||
"mediatype",
|
||||
"pretty_reqwest_error",
|
||||
@@ -2578,7 +2576,6 @@ dependencies = [
|
||||
"proto_array",
|
||||
"psutil",
|
||||
"reqwest",
|
||||
"ring 0.16.20",
|
||||
"sensitive_url",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2587,6 +2584,7 @@ dependencies = [
|
||||
"store",
|
||||
"tokio",
|
||||
"types",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4433,6 +4431,7 @@ dependencies = [
|
||||
"url",
|
||||
"validator_dir",
|
||||
"validator_metrics",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5287,6 +5286,7 @@ dependencies = [
|
||||
"validator_client",
|
||||
"validator_dir",
|
||||
"validator_manager",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9584,6 +9584,7 @@ dependencies = [
|
||||
"validator_store",
|
||||
"warp",
|
||||
"warp_utils",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9627,6 +9628,7 @@ dependencies = [
|
||||
"tree_hash",
|
||||
"types",
|
||||
"validator_http_api",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10562,6 +10564,7 @@ version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ tree_hash_derive = "0.8"
|
||||
url = "2"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
warp = { version = "0.3.7", default-features = false, features = ["tls"] }
|
||||
zeroize = { version = "1", features = ["zeroize_derive"] }
|
||||
zeroize = { version = "1", features = ["zeroize_derive", "serde"] }
|
||||
zip = "0.6"
|
||||
|
||||
# Local crates.
|
||||
|
||||
@@ -27,6 +27,7 @@ safe_arith = { workspace = true }
|
||||
slot_clock = { workspace = true }
|
||||
filesystem = { workspace = true }
|
||||
sensitive_url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -294,7 +294,7 @@ pub fn read_wallet_password_from_cli(
|
||||
eprintln!();
|
||||
eprintln!("{}", WALLET_PASSWORD_PROMPT);
|
||||
let password =
|
||||
PlainText::from(read_password_from_user(stdin_inputs)?.as_ref().to_vec());
|
||||
PlainText::from(read_password_from_user(stdin_inputs)?.as_bytes().to_vec());
|
||||
Ok(password)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use account_utils::{
|
||||
recursively_find_voting_keystores, PasswordStorage, ValidatorDefinition,
|
||||
ValidatorDefinitions, CONFIG_FILENAME,
|
||||
},
|
||||
ZeroizeString, STDIN_INPUTS_FLAG,
|
||||
STDIN_INPUTS_FLAG,
|
||||
};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap_utils::FLAG_HEADER;
|
||||
@@ -16,6 +16,7 @@ use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub const CMD: &str = "import";
|
||||
pub const KEYSTORE_FLAG: &str = "keystore";
|
||||
@@ -148,7 +149,7 @@ pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), Strin
|
||||
// Skip keystores that already exist, but exit early if any operation fails.
|
||||
// Reuses the same password for all keystores if the `REUSE_PASSWORD_FLAG` flag is set.
|
||||
let mut num_imported_keystores = 0;
|
||||
let mut previous_password: Option<ZeroizeString> = None;
|
||||
let mut previous_password: Option<Zeroizing<String>> = None;
|
||||
|
||||
for src_keystore in &keystore_paths {
|
||||
let keystore = Keystore::from_json_file(src_keystore)
|
||||
@@ -182,14 +183,17 @@ pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), Strin
|
||||
|
||||
let password = match keystore_password_path.as_ref() {
|
||||
Some(path) => {
|
||||
let password_from_file: ZeroizeString = fs::read_to_string(path)
|
||||
let password_from_file: Zeroizing<String> = fs::read_to_string(path)
|
||||
.map_err(|e| format!("Unable to read {:?}: {:?}", path, e))?
|
||||
.into();
|
||||
password_from_file.without_newlines()
|
||||
password_from_file
|
||||
.trim_end_matches(['\r', '\n'])
|
||||
.to_string()
|
||||
.into()
|
||||
}
|
||||
None => {
|
||||
let password_from_user = read_password_from_user(stdin_inputs)?;
|
||||
if password_from_user.as_ref().is_empty() {
|
||||
if password_from_user.is_empty() {
|
||||
eprintln!("Continuing without password.");
|
||||
sleep(Duration::from_secs(1)); // Provides nicer UX.
|
||||
break None;
|
||||
@@ -314,7 +318,7 @@ pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), Strin
|
||||
/// Otherwise, returns the keystore error.
|
||||
fn check_password_on_keystore(
|
||||
keystore: &Keystore,
|
||||
password: &ZeroizeString,
|
||||
password: &Zeroizing<String>,
|
||||
) -> Result<bool, String> {
|
||||
match keystore.decrypt_keypair(password.as_ref()) {
|
||||
Ok(_) => {
|
||||
|
||||
@@ -226,14 +226,14 @@ pub fn read_new_wallet_password_from_cli(
|
||||
eprintln!();
|
||||
eprintln!("{}", NEW_WALLET_PASSWORD_PROMPT);
|
||||
let password =
|
||||
PlainText::from(read_password_from_user(stdin_inputs)?.as_ref().to_vec());
|
||||
PlainText::from(read_password_from_user(stdin_inputs)?.as_bytes().to_vec());
|
||||
|
||||
// Ensure the password meets the minimum requirements.
|
||||
match is_password_sufficiently_complex(password.as_bytes()) {
|
||||
Ok(_) => {
|
||||
eprintln!("{}", RETYPE_PASSWORD_PROMPT);
|
||||
let retyped_password =
|
||||
PlainText::from(read_password_from_user(stdin_inputs)?.as_ref().to_vec());
|
||||
PlainText::from(read_password_from_user(stdin_inputs)?.as_bytes().to_vec());
|
||||
if retyped_password == password {
|
||||
break Ok(password);
|
||||
} else {
|
||||
|
||||
@@ -8,18 +8,14 @@ use eth2_wallet::{
|
||||
};
|
||||
use filesystem::{create_with_600_perms, Error as FsError};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
str::FromStr,
|
||||
};
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub mod validator_definitions;
|
||||
|
||||
@@ -69,8 +65,8 @@ pub fn read_password<P: AsRef<Path>>(path: P) -> Result<PlainText, io::Error> {
|
||||
fs::read(path).map(strip_off_newlines).map(Into::into)
|
||||
}
|
||||
|
||||
/// Reads a password file into a `ZeroizeString` struct, with new-lines removed.
|
||||
pub fn read_password_string<P: AsRef<Path>>(path: P) -> Result<ZeroizeString, String> {
|
||||
/// Reads a password file into a `Zeroizing<String>` struct, with new-lines removed.
|
||||
pub fn read_password_string<P: AsRef<Path>>(path: P) -> Result<Zeroizing<String>, String> {
|
||||
fs::read(path)
|
||||
.map_err(|e| format!("Error opening file: {:?}", e))
|
||||
.map(strip_off_newlines)
|
||||
@@ -112,8 +108,8 @@ pub fn random_password() -> PlainText {
|
||||
random_password_raw_string().into_bytes().into()
|
||||
}
|
||||
|
||||
/// Generates a random alphanumeric password of length `DEFAULT_PASSWORD_LEN` as `ZeroizeString`.
|
||||
pub fn random_password_string() -> ZeroizeString {
|
||||
/// Generates a random alphanumeric password of length `DEFAULT_PASSWORD_LEN` as `Zeroizing<String>`.
|
||||
pub fn random_password_string() -> Zeroizing<String> {
|
||||
random_password_raw_string().into()
|
||||
}
|
||||
|
||||
@@ -141,7 +137,7 @@ pub fn strip_off_newlines(mut bytes: Vec<u8>) -> Vec<u8> {
|
||||
}
|
||||
|
||||
/// Reads a password from TTY or stdin if `use_stdin == true`.
|
||||
pub fn read_password_from_user(use_stdin: bool) -> Result<ZeroizeString, String> {
|
||||
pub fn read_password_from_user(use_stdin: bool) -> Result<Zeroizing<String>, String> {
|
||||
let result = if use_stdin {
|
||||
rpassword::prompt_password_stderr("")
|
||||
.map_err(|e| format!("Error reading from stdin: {}", e))
|
||||
@@ -150,7 +146,7 @@ pub fn read_password_from_user(use_stdin: bool) -> Result<ZeroizeString, String>
|
||||
.map_err(|e| format!("Error reading from tty: {}", e))
|
||||
};
|
||||
|
||||
result.map(ZeroizeString::from)
|
||||
result.map(Zeroizing::from)
|
||||
}
|
||||
|
||||
/// Reads a mnemonic phrase from TTY or stdin if `use_stdin == true`.
|
||||
@@ -210,46 +206,6 @@ pub fn mnemonic_from_phrase(phrase: &str) -> Result<Mnemonic, String> {
|
||||
Mnemonic::from_phrase(phrase, Language::English).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Provides a new-type wrapper around `String` that is zeroized on `Drop`.
|
||||
///
|
||||
/// Useful for ensuring that password memory is zeroed-out on drop.
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
#[serde(transparent)]
|
||||
pub struct ZeroizeString(String);
|
||||
|
||||
impl FromStr for ZeroizeString {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ZeroizeString {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZeroizeString {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Remove any number of newline or carriage returns from the end of a vector of bytes.
|
||||
pub fn without_newlines(&self) -> ZeroizeString {
|
||||
let stripped_string = self.0.trim_end_matches(['\r', '\n']).into();
|
||||
Self(stripped_string)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for ZeroizeString {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_mnemonic_from_cli(
|
||||
mnemonic_path: Option<PathBuf>,
|
||||
stdin_inputs: bool,
|
||||
@@ -294,54 +250,6 @@ pub fn read_mnemonic_from_cli(
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_strip_off() {
|
||||
let expected = "hello world";
|
||||
|
||||
assert_eq!(
|
||||
ZeroizeString::from("hello world\n".to_string())
|
||||
.without_newlines()
|
||||
.as_str(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
ZeroizeString::from("hello world\n\n\n\n".to_string())
|
||||
.without_newlines()
|
||||
.as_str(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
ZeroizeString::from("hello world\r".to_string())
|
||||
.without_newlines()
|
||||
.as_str(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
ZeroizeString::from("hello world\r\r\r\r\r".to_string())
|
||||
.without_newlines()
|
||||
.as_str(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
ZeroizeString::from("hello world\r\n".to_string())
|
||||
.without_newlines()
|
||||
.as_str(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
ZeroizeString::from("hello world\r\n\r\n".to_string())
|
||||
.without_newlines()
|
||||
.as_str(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
ZeroizeString::from("hello world".to_string())
|
||||
.without_newlines()
|
||||
.as_str(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_off() {
|
||||
let expected = b"hello world".to_vec();
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
//! Serves as the source-of-truth of which validators this validator client should attempt (or not
|
||||
//! attempt) to load into the `crate::intialized_validators::InitializedValidators` struct.
|
||||
|
||||
use crate::{
|
||||
default_keystore_password_path, read_password_string, write_file_via_temporary, ZeroizeString,
|
||||
};
|
||||
use crate::{default_keystore_password_path, read_password_string, write_file_via_temporary};
|
||||
use directory::ensure_dir_exists;
|
||||
use eth2_keystore::Keystore;
|
||||
use regex::Regex;
|
||||
@@ -17,6 +15,7 @@ use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use types::{graffiti::GraffitiString, Address, PublicKey};
|
||||
use validator_dir::VOTING_KEYSTORE_FILE;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// The file name for the serialized `ValidatorDefinitions` struct.
|
||||
pub const CONFIG_FILENAME: &str = "validator_definitions.yml";
|
||||
@@ -52,7 +51,7 @@ pub enum Error {
|
||||
/// Defines how a password for a validator keystore will be persisted.
|
||||
pub enum PasswordStorage {
|
||||
/// Store the password in the `validator_definitions.yml` file.
|
||||
ValidatorDefinitions(ZeroizeString),
|
||||
ValidatorDefinitions(Zeroizing<String>),
|
||||
/// Store the password in a separate, dedicated file (likely in the "secrets" directory).
|
||||
File(PathBuf),
|
||||
/// Don't store the password at all.
|
||||
@@ -93,7 +92,7 @@ pub enum SigningDefinition {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
voting_keystore_password_path: Option<PathBuf>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
voting_keystore_password: Option<ZeroizeString>,
|
||||
voting_keystore_password: Option<Zeroizing<String>>,
|
||||
},
|
||||
/// A validator that defers to a Web3Signer HTTP server for signing.
|
||||
///
|
||||
@@ -107,7 +106,7 @@ impl SigningDefinition {
|
||||
matches!(self, SigningDefinition::LocalKeystore { .. })
|
||||
}
|
||||
|
||||
pub fn voting_keystore_password(&self) -> Result<Option<ZeroizeString>, Error> {
|
||||
pub fn voting_keystore_password(&self) -> Result<Option<Zeroizing<String>>, Error> {
|
||||
match self {
|
||||
SigningDefinition::LocalKeystore {
|
||||
voting_keystore_password: Some(password),
|
||||
|
||||
@@ -16,10 +16,7 @@ lighthouse_network = { workspace = true }
|
||||
proto_array = { workspace = true }
|
||||
ethereum_serde_utils = { workspace = true }
|
||||
eth2_keystore = { workspace = true }
|
||||
libsecp256k1 = { workspace = true }
|
||||
ring = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
account_utils = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
sensitive_url = { workspace = true }
|
||||
ethereum_ssz = { workspace = true }
|
||||
ethereum_ssz_derive = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::types::*;
|
||||
use crate::Error;
|
||||
use account_utils::ZeroizeString;
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderValue},
|
||||
IntoUrl,
|
||||
@@ -14,6 +13,7 @@ use std::path::Path;
|
||||
pub use reqwest;
|
||||
pub use reqwest::{Response, StatusCode, Url};
|
||||
use types::graffiti::GraffitiString;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// A wrapper around `reqwest::Client` which provides convenience methods for interfacing with a
|
||||
/// Lighthouse Validator Client HTTP server (`validator_client/src/http_api`).
|
||||
@@ -21,7 +21,7 @@ use types::graffiti::GraffitiString;
|
||||
pub struct ValidatorClientHttpClient {
|
||||
client: reqwest::Client,
|
||||
server: SensitiveUrl,
|
||||
api_token: Option<ZeroizeString>,
|
||||
api_token: Option<Zeroizing<String>>,
|
||||
authorization_header: AuthorizationHeader,
|
||||
}
|
||||
|
||||
@@ -79,18 +79,18 @@ impl ValidatorClientHttpClient {
|
||||
}
|
||||
|
||||
/// Get a reference to this client's API token, if any.
|
||||
pub fn api_token(&self) -> Option<&ZeroizeString> {
|
||||
pub fn api_token(&self) -> Option<&Zeroizing<String>> {
|
||||
self.api_token.as_ref()
|
||||
}
|
||||
|
||||
/// Read an API token from the specified `path`, stripping any trailing whitespace.
|
||||
pub fn load_api_token_from_file(path: &Path) -> Result<ZeroizeString, Error> {
|
||||
pub fn load_api_token_from_file(path: &Path) -> Result<Zeroizing<String>, Error> {
|
||||
let token = fs::read_to_string(path).map_err(|e| Error::TokenReadError(path.into(), e))?;
|
||||
Ok(ZeroizeString::from(token.trim_end().to_string()))
|
||||
Ok(token.trim_end().to_string().into())
|
||||
}
|
||||
|
||||
/// Add an authentication token to use when making requests.
|
||||
pub fn add_auth_token(&mut self, token: ZeroizeString) -> Result<(), Error> {
|
||||
pub fn add_auth_token(&mut self, token: Zeroizing<String>) -> Result<(), Error> {
|
||||
self.api_token = Some(token);
|
||||
self.authorization_header = AuthorizationHeader::Bearer;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use account_utils::ZeroizeString;
|
||||
use eth2_keystore::Keystore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use types::{Address, Graffiti, PublicKeyBytes};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub use slashing_protection::interchange::Interchange;
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct SingleKeystoreResponse {
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ImportKeystoresRequest {
|
||||
pub keystores: Vec<KeystoreJsonStr>,
|
||||
pub passwords: Vec<ZeroizeString>,
|
||||
pub passwords: Vec<Zeroizing<String>>,
|
||||
pub slashing_protection: Option<InterchangeJsonStr>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use account_utils::ZeroizeString;
|
||||
pub use crate::lighthouse::Health;
|
||||
pub use crate::lighthouse_vc::std_types::*;
|
||||
pub use crate::types::{GenericResponse, VersionData};
|
||||
use eth2_keystore::Keystore;
|
||||
use graffiti::GraffitiString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub use crate::lighthouse::Health;
|
||||
pub use crate::lighthouse_vc::std_types::*;
|
||||
pub use crate::types::{GenericResponse, VersionData};
|
||||
pub use types::*;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ValidatorData {
|
||||
@@ -44,7 +43,7 @@ pub struct ValidatorRequest {
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CreateValidatorsMnemonicRequest {
|
||||
pub mnemonic: ZeroizeString,
|
||||
pub mnemonic: Zeroizing<String>,
|
||||
#[serde(with = "serde_utils::quoted_u32")]
|
||||
pub key_derivation_path_offset: u32,
|
||||
pub validators: Vec<ValidatorRequest>,
|
||||
@@ -74,7 +73,7 @@ pub struct CreatedValidator {
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PostValidatorsResponseData {
|
||||
pub mnemonic: ZeroizeString,
|
||||
pub mnemonic: Zeroizing<String>,
|
||||
pub validators: Vec<CreatedValidator>,
|
||||
}
|
||||
|
||||
@@ -102,7 +101,7 @@ pub struct ValidatorPatchRequest {
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct KeystoreValidatorsPostRequest {
|
||||
pub password: ZeroizeString,
|
||||
pub password: Zeroizing<String>,
|
||||
pub enable: bool,
|
||||
pub keystore: Keystore,
|
||||
#[serde(default)]
|
||||
@@ -191,7 +190,7 @@ pub struct SingleExportKeystoresResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub validating_keystore: Option<KeystoreJsonStr>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub validating_keystore_password: Option<ZeroizeString>,
|
||||
pub validating_keystore_password: Option<Zeroizing<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
||||
@@ -26,7 +26,7 @@ use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// The byte-length of a BLS secret key.
|
||||
const SECRET_KEY_LEN: usize = 32;
|
||||
@@ -60,45 +60,6 @@ pub const HASH_SIZE: usize = 32;
|
||||
/// The default iteraction count, `c`, for PBKDF2.
|
||||
pub const DEFAULT_PBKDF2_C: u32 = 262_144;
|
||||
|
||||
/// Provides a new-type wrapper around `String` that is zeroized on `Drop`.
|
||||
///
|
||||
/// Useful for ensuring that password memory is zeroed-out on drop.
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
#[serde(transparent)]
|
||||
struct ZeroizeString(String);
|
||||
|
||||
impl From<String> for ZeroizeString {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for ZeroizeString {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ZeroizeString {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for ZeroizeString {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<char> for ZeroizeString {
|
||||
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
|
||||
ZeroizeString(String::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InvalidSecretKeyLen { len: usize, expected: usize },
|
||||
@@ -451,11 +412,12 @@ fn is_control_character(c: char) -> bool {
|
||||
/// Takes a slice of bytes and returns a NFKD normalized string representation.
|
||||
///
|
||||
/// Returns an error if the bytes are not valid utf8.
|
||||
fn normalize(bytes: &[u8]) -> Result<ZeroizeString, Error> {
|
||||
fn normalize(bytes: &[u8]) -> Result<Zeroizing<String>, Error> {
|
||||
Ok(str::from_utf8(bytes)
|
||||
.map_err(|_| Error::InvalidPasswordBytes)?
|
||||
.nfkd()
|
||||
.collect::<ZeroizeString>())
|
||||
.collect::<String>()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Generates a checksum to indicate that the `derived_key` is associated with the
|
||||
|
||||
@@ -73,6 +73,7 @@ eth2 = { workspace = true }
|
||||
beacon_processor = { workspace = true }
|
||||
beacon_node_fallback = { workspace = true }
|
||||
initialized_validators = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
|
||||
[[test]]
|
||||
|
||||
@@ -15,7 +15,7 @@ use account_manager::{
|
||||
use account_utils::{
|
||||
eth2_keystore::KeystoreBuilder,
|
||||
validator_definitions::{SigningDefinition, ValidatorDefinition, ValidatorDefinitions},
|
||||
ZeroizeString, STDIN_INPUTS_FLAG,
|
||||
STDIN_INPUTS_FLAG,
|
||||
};
|
||||
use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME};
|
||||
use std::env;
|
||||
@@ -27,6 +27,7 @@ use std::str::from_utf8;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use types::{Keypair, PublicKey};
|
||||
use validator_dir::ValidatorDir;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// Returns the `lighthouse account` command.
|
||||
fn account_cmd() -> Command {
|
||||
@@ -498,7 +499,7 @@ fn validator_import_launchpad() {
|
||||
signing_definition: SigningDefinition::LocalKeystore {
|
||||
voting_keystore_path,
|
||||
voting_keystore_password_path: None,
|
||||
voting_keystore_password: Some(ZeroizeString::from(PASSWORD.to_string())),
|
||||
voting_keystore_password: Some(Zeroizing::from(PASSWORD.to_string())),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -650,7 +651,7 @@ fn validator_import_launchpad_no_password_then_add_password() {
|
||||
signing_definition: SigningDefinition::LocalKeystore {
|
||||
voting_keystore_path: dst_keystore_dir.join(KEYSTORE_NAME),
|
||||
voting_keystore_password_path: None,
|
||||
voting_keystore_password: Some(ZeroizeString::from(PASSWORD.to_string())),
|
||||
voting_keystore_password: Some(Zeroizing::from(PASSWORD.to_string())),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -753,7 +754,7 @@ fn validator_import_launchpad_password_file() {
|
||||
signing_definition: SigningDefinition::LocalKeystore {
|
||||
voting_keystore_path,
|
||||
voting_keystore_password_path: None,
|
||||
voting_keystore_password: Some(ZeroizeString::from(PASSWORD.to_string())),
|
||||
voting_keystore_password: Some(Zeroizing::from(PASSWORD.to_string())),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ validator_services = { workspace = true }
|
||||
url = { workspace = true }
|
||||
warp_utils = { workspace = true }
|
||||
warp = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
itertools = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,7 @@ use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition}
|
||||
use account_utils::{
|
||||
eth2_keystore::Keystore,
|
||||
eth2_wallet::{bip39::Mnemonic, WalletBuilder},
|
||||
random_mnemonic, random_password, ZeroizeString,
|
||||
random_mnemonic, random_password,
|
||||
};
|
||||
use eth2::lighthouse_vc::types::{self as api_types};
|
||||
use slot_clock::SlotClock;
|
||||
@@ -11,6 +11,7 @@ use types::ChainSpec;
|
||||
use types::EthSpec;
|
||||
use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder};
|
||||
use validator_store::ValidatorStore;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// Create some validator EIP-2335 keystores and store them on disk. Then, enroll the validators in
|
||||
/// this validator client.
|
||||
@@ -59,7 +60,7 @@ pub async fn create_validators_mnemonic<P: AsRef<Path>, T: 'static + SlotClock,
|
||||
for request in validator_requests {
|
||||
let voting_password = random_password();
|
||||
let withdrawal_password = random_password();
|
||||
let voting_password_string = ZeroizeString::from(
|
||||
let voting_password_string = Zeroizing::from(
|
||||
String::from_utf8(voting_password.as_bytes().to_vec()).map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"locally generated password is not utf8: {:?}",
|
||||
@@ -199,7 +200,7 @@ pub async fn create_validators_web3signer<T: 'static + SlotClock, E: EthSpec>(
|
||||
pub fn get_voting_password_storage(
|
||||
secrets_dir: &Option<PathBuf>,
|
||||
voting_keystore: &Keystore,
|
||||
voting_password_string: &ZeroizeString,
|
||||
voting_password_string: &Zeroizing<String>,
|
||||
) -> Result<PasswordStorage, warp::Rejection> {
|
||||
if let Some(secrets_dir) = &secrets_dir {
|
||||
let password_path = keystore_password_path(secrets_dir, voting_keystore);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Implementation of the standard keystore management API.
|
||||
use account_utils::{validator_definitions::PasswordStorage, ZeroizeString};
|
||||
use account_utils::validator_definitions::PasswordStorage;
|
||||
use eth2::lighthouse_vc::{
|
||||
std_types::{
|
||||
DeleteKeystoreStatus, DeleteKeystoresRequest, DeleteKeystoresResponse,
|
||||
@@ -22,6 +22,7 @@ use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder};
|
||||
use validator_store::ValidatorStore;
|
||||
use warp::Rejection;
|
||||
use warp_utils::reject::{custom_bad_request, custom_server_error};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub fn list<T: SlotClock + 'static, E: EthSpec>(
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
@@ -167,7 +168,7 @@ pub fn import<T: SlotClock + 'static, E: EthSpec>(
|
||||
|
||||
fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
keystore: Keystore,
|
||||
password: ZeroizeString,
|
||||
password: Zeroizing<String>,
|
||||
validator_dir_path: PathBuf,
|
||||
secrets_dir: Option<PathBuf>,
|
||||
validator_store: &ValidatorStore<T, E>,
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::{ApiSecret, Config as HttpConfig, Context};
|
||||
use account_utils::validator_definitions::ValidatorDefinitions;
|
||||
use account_utils::{
|
||||
eth2_wallet::WalletBuilder, mnemonic_from_phrase, random_mnemonic, random_password,
|
||||
ZeroizeString,
|
||||
};
|
||||
use deposit_contract::decode_eth1_tx_data;
|
||||
use doppelganger_service::DoppelgangerService;
|
||||
@@ -28,6 +27,7 @@ use task_executor::test_utils::TestRuntime;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use tokio::sync::oneshot;
|
||||
use validator_store::{Config as ValidatorStoreConfig, ValidatorStore};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub const PASSWORD_BYTES: &[u8] = &[42, 50, 37];
|
||||
pub const TEST_DEFAULT_FEE_RECIPIENT: Address = Address::repeat_byte(42);
|
||||
@@ -321,7 +321,7 @@ impl ApiTester {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (response, mnemonic) = if s.specify_mnemonic {
|
||||
let mnemonic = ZeroizeString::from(random_mnemonic().phrase().to_string());
|
||||
let mnemonic = Zeroizing::from(random_mnemonic().phrase().to_string());
|
||||
let request = CreateValidatorsMnemonicRequest {
|
||||
mnemonic: mnemonic.clone(),
|
||||
key_derivation_path_offset: s.key_derivation_path_offset,
|
||||
|
||||
@@ -9,7 +9,7 @@ use initialized_validators::{Config as InitializedValidatorsConfig, InitializedV
|
||||
use crate::{ApiSecret, Config as HttpConfig, Context};
|
||||
use account_utils::{
|
||||
eth2_wallet::WalletBuilder, mnemonic_from_phrase, random_mnemonic, random_password,
|
||||
random_password_string, validator_definitions::ValidatorDefinitions, ZeroizeString,
|
||||
random_password_string, validator_definitions::ValidatorDefinitions,
|
||||
};
|
||||
use deposit_contract::decode_eth1_tx_data;
|
||||
use eth2::{
|
||||
@@ -33,6 +33,7 @@ use task_executor::test_utils::TestRuntime;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use types::graffiti::GraffitiString;
|
||||
use validator_store::{Config as ValidatorStoreConfig, ValidatorStore};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
const PASSWORD_BYTES: &[u8] = &[42, 50, 37];
|
||||
pub const TEST_DEFAULT_FEE_RECIPIENT: Address = Address::repeat_byte(42);
|
||||
@@ -282,7 +283,7 @@ impl ApiTester {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (response, mnemonic) = if s.specify_mnemonic {
|
||||
let mnemonic = ZeroizeString::from(random_mnemonic().phrase().to_string());
|
||||
let mnemonic = Zeroizing::from(random_mnemonic().phrase().to_string());
|
||||
let request = CreateValidatorsMnemonicRequest {
|
||||
mnemonic: mnemonic.clone(),
|
||||
key_derivation_path_offset: s.key_derivation_path_offset,
|
||||
|
||||
@@ -14,8 +14,9 @@ use std::{collections::HashMap, path::Path};
|
||||
use tokio::runtime::Handle;
|
||||
use types::{attestation::AttestationBase, Address};
|
||||
use validator_store::DEFAULT_GAS_LIMIT;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn new_keystore(password: ZeroizeString) -> Keystore {
|
||||
fn new_keystore(password: Zeroizing<String>) -> Keystore {
|
||||
let keypair = Keypair::random();
|
||||
Keystore(
|
||||
KeystoreBuilder::new(&keypair, password.as_ref(), String::new())
|
||||
|
||||
@@ -24,3 +24,4 @@ tokio = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
filesystem = { workspace = true }
|
||||
validator_metrics = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
@@ -14,7 +14,6 @@ use account_utils::{
|
||||
self, SigningDefinition, ValidatorDefinition, ValidatorDefinitions, Web3SignerDefinition,
|
||||
CONFIG_FILENAME,
|
||||
},
|
||||
ZeroizeString,
|
||||
};
|
||||
use eth2_keystore::Keystore;
|
||||
use lockfile::{Lockfile, LockfileError};
|
||||
@@ -34,6 +33,7 @@ use types::graffiti::GraffitiString;
|
||||
use types::{Address, Graffiti, Keypair, PublicKey, PublicKeyBytes};
|
||||
use url::{ParseError, Url};
|
||||
use validator_dir::Builder as ValidatorDirBuilder;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use key_cache::KeyCache;
|
||||
|
||||
@@ -74,7 +74,7 @@ pub enum OnDecryptFailure {
|
||||
|
||||
pub struct KeystoreAndPassword {
|
||||
pub keystore: Keystore,
|
||||
pub password: Option<ZeroizeString>,
|
||||
pub password: Option<Zeroizing<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -262,7 +262,7 @@ impl InitializedValidator {
|
||||
// If the password is supplied, use it and ignore the path
|
||||
// (if supplied).
|
||||
(_, Some(password)) => (
|
||||
password.as_ref().to_vec().into(),
|
||||
password.as_bytes().to_vec().into(),
|
||||
keystore
|
||||
.decrypt_keypair(password.as_ref())
|
||||
.map_err(Error::UnableToDecryptKeystore)?,
|
||||
@@ -282,7 +282,7 @@ impl InitializedValidator {
|
||||
&keystore,
|
||||
&keystore_path,
|
||||
)?;
|
||||
(password.as_ref().to_vec().into(), keypair)
|
||||
(password.as_bytes().to_vec().into(), keypair)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -455,7 +455,7 @@ fn build_web3_signer_client(
|
||||
fn unlock_keystore_via_stdin_password(
|
||||
keystore: &Keystore,
|
||||
keystore_path: &Path,
|
||||
) -> Result<(ZeroizeString, Keypair), Error> {
|
||||
) -> Result<(Zeroizing<String>, Keypair), Error> {
|
||||
eprintln!();
|
||||
eprintln!(
|
||||
"The {} file does not contain either of the following fields for {:?}:",
|
||||
@@ -1172,14 +1172,14 @@ impl InitializedValidators {
|
||||
voting_keystore_path,
|
||||
} => {
|
||||
let pw = if let Some(p) = voting_keystore_password {
|
||||
p.as_ref().to_vec().into()
|
||||
p.as_bytes().to_vec().into()
|
||||
} else if let Some(path) = voting_keystore_password_path {
|
||||
read_password(path).map_err(Error::UnableToReadVotingKeystorePassword)?
|
||||
} else {
|
||||
let keystore = open_keystore(voting_keystore_path)?;
|
||||
unlock_keystore_via_stdin_password(&keystore, voting_keystore_path)?
|
||||
.0
|
||||
.as_ref()
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
.into()
|
||||
};
|
||||
@@ -1425,7 +1425,7 @@ impl InitializedValidators {
|
||||
/// This should only be used for testing, it's rather destructive.
|
||||
pub fn delete_passwords_from_validator_definitions(
|
||||
&mut self,
|
||||
) -> Result<HashMap<PublicKey, ZeroizeString>, Error> {
|
||||
) -> Result<HashMap<PublicKey, Zeroizing<String>>, Error> {
|
||||
let mut passwords = HashMap::default();
|
||||
|
||||
for def in self.definitions.as_mut_slice() {
|
||||
|
||||
@@ -21,6 +21,7 @@ eth2 = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
derivative = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use account_utils::strip_off_newlines;
|
||||
pub use account_utils::STDIN_INPUTS_FLAG;
|
||||
use account_utils::{strip_off_newlines, ZeroizeString};
|
||||
use eth2::lighthouse_vc::std_types::{InterchangeJsonStr, KeystoreJsonStr};
|
||||
use eth2::{
|
||||
lighthouse_vc::{
|
||||
@@ -14,6 +14,7 @@ use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tree_hash::TreeHash;
|
||||
use types::*;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub const IGNORE_DUPLICATES_FLAG: &str = "ignore-duplicates";
|
||||
pub const COUNT_FLAG: &str = "count";
|
||||
@@ -41,7 +42,7 @@ pub enum UploadError {
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ValidatorSpecification {
|
||||
pub voting_keystore: KeystoreJsonStr,
|
||||
pub voting_keystore_password: ZeroizeString,
|
||||
pub voting_keystore_password: Zeroizing<String>,
|
||||
pub slashing_protection: Option<InterchangeJsonStr>,
|
||||
pub fee_recipient: Option<Address>,
|
||||
pub gas_limit: Option<u64>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::common::*;
|
||||
use crate::DumpConfig;
|
||||
use account_utils::{eth2_keystore::Keystore, ZeroizeString};
|
||||
use account_utils::eth2_keystore::Keystore;
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap_utils::FLAG_HEADER;
|
||||
use derivative::Derivative;
|
||||
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use types::Address;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub const CMD: &str = "import";
|
||||
pub const VALIDATORS_FILE_FLAG: &str = "validators-file";
|
||||
@@ -167,7 +168,7 @@ pub struct ImportConfig {
|
||||
pub vc_token_path: PathBuf,
|
||||
pub ignore_duplicates: bool,
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub password: Option<ZeroizeString>,
|
||||
pub password: Option<Zeroizing<String>>,
|
||||
pub fee_recipient: Option<Address>,
|
||||
pub gas_limit: Option<u64>,
|
||||
pub builder_proposals: Option<bool>,
|
||||
@@ -184,7 +185,7 @@ impl ImportConfig {
|
||||
vc_url: clap_utils::parse_required(matches, VC_URL_FLAG)?,
|
||||
vc_token_path: clap_utils::parse_required(matches, VC_TOKEN_FLAG)?,
|
||||
ignore_duplicates: matches.get_flag(IGNORE_DUPLICATES_FLAG),
|
||||
password: clap_utils::parse_optional(matches, PASSWORD)?,
|
||||
password: clap_utils::parse_optional(matches, PASSWORD)?.map(Zeroizing::new),
|
||||
fee_recipient: clap_utils::parse_optional(matches, FEE_RECIPIENT)?,
|
||||
gas_limit: clap_utils::parse_optional(matches, GAS_LIMIT)?,
|
||||
builder_proposals: clap_utils::parse_optional(matches, BUILDER_PROPOSALS)?,
|
||||
@@ -382,10 +383,7 @@ async fn run<'a>(config: ImportConfig) -> Result<(), String> {
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::create_validators::tests::TestBuilder as CreateTestBuilder;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::fs::{self, File};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use validator_http_api::{test_utils::ApiTester, Config as HttpConfig};
|
||||
|
||||
@@ -419,7 +417,7 @@ pub mod tests {
|
||||
vc_url: vc.url.clone(),
|
||||
vc_token_path,
|
||||
ignore_duplicates: false,
|
||||
password: Some(ZeroizeString::from_str("password").unwrap()),
|
||||
password: Some(Zeroizing::new("password".into())),
|
||||
fee_recipient: None,
|
||||
builder_boost_factor: None,
|
||||
gas_limit: None,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::common::*;
|
||||
use crate::DumpConfig;
|
||||
use account_utils::{read_password_from_user, ZeroizeString};
|
||||
use account_utils::read_password_from_user;
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use eth2::{
|
||||
lighthouse_vc::{
|
||||
@@ -19,6 +19,7 @@ use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use types::{Address, PublicKeyBytes};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub const MOVE_DIR_NAME: &str = "lighthouse-validator-move";
|
||||
pub const VALIDATOR_SPECIFICATION_FILE: &str = "validator-specification.json";
|
||||
@@ -48,7 +49,7 @@ pub enum PasswordSource {
|
||||
}
|
||||
|
||||
impl PasswordSource {
|
||||
fn read_password(&mut self, pubkey: &PublicKeyBytes) -> Result<ZeroizeString, String> {
|
||||
fn read_password(&mut self, pubkey: &PublicKeyBytes) -> Result<Zeroizing<String>, String> {
|
||||
match self {
|
||||
PasswordSource::Interactive { stdin_inputs } => {
|
||||
eprintln!("Please enter a password for keystore {:?}:", pubkey);
|
||||
|
||||
Reference in New Issue
Block a user