Skip to content

Commit

Permalink
allow providing seed password via env variable
Browse files Browse the repository at this point in the history
  • Loading branch information
nicbus committed Aug 13, 2024
1 parent 4660bf3 commit e57b67c
Showing 1 changed file with 42 additions and 29 deletions.
71 changes: 42 additions & 29 deletions src/hot/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs;
use std::path::{Path, PathBuf};
use std::{env, fs};

use amplify::hex::ToHex;
use amplify::{Display, IoError};
Expand All @@ -35,6 +35,8 @@ use psbt::Psbt;
use crate::hot::{calculate_entropy, DataError, SecureIo, Seed, SeedType};
use crate::Bip43;

const SEED_PASSWORD_ENVVAR: &str = "SEED_PASSWORD";

/// Command-line arguments
#[derive(Parser)]
#[derive(Clone, Eq, PartialEq, Debug)]
Expand All @@ -53,15 +55,19 @@ pub struct HotArgs {

#[derive(Subcommand, Clone, PartialEq, Eq, Debug, Display)]
pub enum HotCommand {
/// Generate new seed and saves it as an encoded file
/// Generate new seed and saves it as an encoded file. The password can be provided via the
/// `SEED_PASSWORD` environment variable (security warning: don't set it on the command line,
/// use instead the shell's builtin `read` and then export it).
#[display("seed")]
Seed {
/// File to save generated seed data and extended master key
output_file: PathBuf,
},

/// Derive new extended private key from the seed and saves it into a separate file as a new
/// signing account
/// signing account. The seed password can be provided via the `SEED_PASSWORD` environment
/// variable (security warning: don't set it on the command line, use instead the shell's
/// builtin `read` and then export it).
#[display("derive")]
Derive {
/// Do not ask for a password and default to an empty-line password. For testing purposes
Expand Down Expand Up @@ -151,24 +157,43 @@ impl HotArgs {
}
}

fn seed(output_file: &Path) -> Result<(), DataError> {
let seed = Seed::random(SeedType::Bit128);
let seed_password = loop {
let seed_password = rpassword::prompt_password("Seed password: ")?;
let entropy = calculate_entropy(&seed_password);
fn get_password(
password_envvar: Option<&str>,
prompt: &str,
accept_weak: bool,
) -> Result<String, std::io::Error> {
let password = loop {
let password =
if let Some(varname) = password_envvar { env::var(varname).ok() } else { None };
let password =
if let Some(pass) = password { pass } else { rpassword::prompt_password(prompt)? };

Check warning on line 169 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L160-L169

Added lines #L160 - L169 were not covered by tests

let entropy = calculate_entropy(&password);

Check warning on line 171 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L171

Added line #L171 was not covered by tests
eprintln!("Password entropy: ~{entropy:.0} bits");
if seed_password.is_empty() || entropy < 64.0 {
if !accept_weak && (password.is_empty() || entropy < 64.0) {

Check warning on line 173 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L173

Added line #L173 was not covered by tests
eprintln!("Entropy is too low, please try with a different password");
continue;
if password_envvar.is_some() {
return Err(std::io::Error::new(std::io::ErrorKind::Other, "low entropy"));

Check warning on line 176 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L175-L176

Added lines #L175 - L176 were not covered by tests
} else {
continue;

Check warning on line 178 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L178

Added line #L178 was not covered by tests
}
}

let password2 = rpassword::prompt_password("Repeat the password: ")?;
if password2 != seed_password {
eprintln!("Passwords do not match, please try again");
continue;
if password_envvar.is_none() {
let repeat = rpassword::prompt_password("Repeat the password: ")?;
if repeat != password {
eprintln!("Passwords do not match, please try again");
continue;
}

Check warning on line 187 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L182-L187

Added lines #L182 - L187 were not covered by tests
}
break seed_password;
break password;

Check warning on line 189 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L189

Added line #L189 was not covered by tests
};
Ok(password)
}

Check warning on line 192 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L191-L192

Added lines #L191 - L192 were not covered by tests

fn seed(output_file: &Path) -> Result<(), DataError> {
let seed = Seed::random(SeedType::Bit128);
let seed_password = get_password(Some(SEED_PASSWORD_ENVVAR), "Seed password:", false)?;

Check warning on line 196 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L194-L196

Added lines #L194 - L196 were not covered by tests

seed.write(output_file, &seed_password)?;
if let Err(e) = Seed::read(output_file, &seed_password) {
Expand Down Expand Up @@ -248,24 +273,12 @@ fn derive(
output_file: &Path,
no_password: bool,
) -> Result<(), DataError> {
let seed_password = rpassword::prompt_password("Seed password: ")?;
let seed_password = get_password(Some(SEED_PASSWORD_ENVVAR), "Seed password:", false)?;

Check warning on line 276 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L276

Added line #L276 was not covered by tests

let account_password = if !mainnet && no_password {
s!("")
} else {
loop {
let account_password = rpassword::prompt_password("Account password: ")?;
let entropy = calculate_entropy(&seed_password);
eprintln!("Password entropy: ~{entropy:.0} bits");
if !account_password.is_empty() && entropy >= 64.0 {
break account_password;
}
if !mainnet {
eprintln!("Entropy is too low, but since we are on testnet we accept that");
break account_password;
}
eprintln!("Entropy is too low, please try with a different password")
}
get_password(None, "Account password:", !mainnet)?

Check warning on line 281 in src/hot/command.rs

View check run for this annotation

Codecov / codecov/patch

src/hot/command.rs#L281

Added line #L281 was not covered by tests
};

let seed = Seed::read(seed_file, &seed_password)?;
Expand Down

0 comments on commit e57b67c

Please sign in to comment.