From 9cde67086f7c2949b3f0b8a48cad09caa44ad9c4 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 10 Oct 2019 17:01:03 -0600 Subject: [PATCH] solana-keygen - Poor mans keypair encryption (#6259) * SDK: Refactor (read|write)_keypair Split file opening and data writing operations Drop filename == "-" stdio signal. It is an app-level feature * keygen: Move all non-key printing to stderr * keygen: Adapt to SDK refactor * keygen: Factor keypair output out to a helper function --- bench-exchange/src/cli.rs | 4 ++-- bench-tps/src/cli.rs | 4 ++-- cli/src/cli.rs | 4 ++-- cli/src/input_parsers.rs | 10 ++++---- cli/src/input_validators.rs | 4 ++-- cli/src/main.rs | 4 ++-- drone/src/bin/drone.rs | 6 ++--- genesis/src/main.rs | 12 +++++----- install/src/command.rs | 6 ++--- keygen/src/keygen.rs | 47 +++++++++++++++++++++++-------------- replicator/src/main.rs | 8 +++---- sdk/src/signature.rs | 45 ++++++++++++++++++++++------------- validator/src/lib.rs | 10 ++++---- 13 files changed, 95 insertions(+), 69 deletions(-) diff --git a/bench-exchange/src/cli.rs b/bench-exchange/src/cli.rs index 8291b88abe..e1124505cc 100644 --- a/bench-exchange/src/cli.rs +++ b/bench-exchange/src/cli.rs @@ -1,7 +1,7 @@ use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches}; use solana_core::gen_keys::GenKeys; use solana_drone::drone::DRONE_PORT; -use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; +use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil}; use std::net::SocketAddr; use std::process::exit; use std::time::Duration; @@ -179,7 +179,7 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config { }); if matches.is_present("identity") { - args.identity = read_keypair(matches.value_of("identity").unwrap()) + args.identity = read_keypair_file(matches.value_of("identity").unwrap()) .expect("can't read client identity"); } else { args.identity = { diff --git a/bench-tps/src/cli.rs b/bench-tps/src/cli.rs index a04b8d0809..461dfa024c 100644 --- a/bench-tps/src/cli.rs +++ b/bench-tps/src/cli.rs @@ -1,7 +1,7 @@ use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches}; use solana_drone::drone::DRONE_PORT; use solana_sdk::fee_calculator::FeeCalculator; -use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; +use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil}; use std::{net::SocketAddr, process::exit, time::Duration}; const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = 64 * 1024; @@ -181,7 +181,7 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config { } if matches.is_present("identity") { - args.id = read_keypair(matches.value_of("identity").unwrap()) + args.id = read_keypair_file(matches.value_of("identity").unwrap()) .expect("can't read client identity"); } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 7223c93402..8165e20a8c 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1385,7 +1385,7 @@ mod tests { use serde_json::Value; use solana_client::mock_rpc_client_request::SIGNATURE; use solana_sdk::{ - signature::{gen_keypair_file, read_keypair}, + signature::{gen_keypair_file, read_keypair_file}, transaction::TransactionError, }; use std::path::PathBuf; @@ -1442,7 +1442,7 @@ mod tests { // Test Balance Subcommand, incl pubkey and keypair-file inputs let keypair_file = make_tmp_path("keypair_file"); gen_keypair_file(&keypair_file).unwrap(); - let keypair = read_keypair(&keypair_file).unwrap(); + let keypair = read_keypair_file(&keypair_file).unwrap(); let test_balance = test_commands.clone().get_matches_from(vec![ "test", "balance", diff --git a/cli/src/input_parsers.rs b/cli/src/input_parsers.rs index ba7e0bba1f..1cf23abcb7 100644 --- a/cli/src/input_parsers.rs +++ b/cli/src/input_parsers.rs @@ -2,7 +2,7 @@ use clap::ArgMatches; use solana_sdk::{ native_token::sol_to_lamports, pubkey::Pubkey, - signature::{read_keypair, Keypair, KeypairUtil}, + signature::{read_keypair_file, Keypair, KeypairUtil}, }; // Return parsed values from matches at `name` @@ -32,7 +32,7 @@ where // Return the keypair for an argument with filename `name` or None if not present. pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option { if let Some(value) = matches.value_of(name) { - read_keypair(value).ok() + read_keypair_file(value).ok() } else { None } @@ -56,7 +56,7 @@ pub fn amount_of(matches: &ArgMatches<'_>, name: &str, unit: &str) -> Option() -> App<'ab, 'v> { @@ -120,7 +120,7 @@ mod tests { fn test_keypair_of() { let keypair = Keypair::new(); let outfile = tmp_file_path("test_gen_keypair_file.json", &keypair.pubkey()); - let _ = write_keypair(&keypair, &outfile).unwrap(); + let _ = write_keypair_file(&keypair, &outfile).unwrap(); let matches = app() .clone() @@ -141,7 +141,7 @@ mod tests { fn test_pubkey_of() { let keypair = Keypair::new(); let outfile = tmp_file_path("test_gen_keypair_file.json", &keypair.pubkey()); - let _ = write_keypair(&keypair, &outfile).unwrap(); + let _ = write_keypair_file(&keypair, &outfile).unwrap(); let matches = app() .clone() diff --git a/cli/src/input_validators.rs b/cli/src/input_validators.rs index cca7c76b50..594a9a56dc 100644 --- a/cli/src/input_validators.rs +++ b/cli/src/input_validators.rs @@ -1,5 +1,5 @@ use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::read_keypair; +use solana_sdk::signature::read_keypair_file; // Return an error if a pubkey cannot be parsed. pub fn is_pubkey(string: String) -> Result<(), String> { @@ -11,7 +11,7 @@ pub fn is_pubkey(string: String) -> Result<(), String> { // Return an error if a keypair file cannot be parsed. pub fn is_keypair(string: String) -> Result<(), String> { - read_keypair(&string) + read_keypair_file(&string) .map(|_| ()) .map_err(|err| format!("{:?}", err)) } diff --git a/cli/src/main.rs b/cli/src/main.rs index 237d48b64e..72de05cbdd 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -6,7 +6,7 @@ use solana_cli::{ display::{println_name_value, println_name_value_or}, input_validators::is_url, }; -use solana_sdk::signature::{read_keypair, KeypairUtil}; +use solana_sdk::signature::{read_keypair_file, KeypairUtil}; use std::error; fn parse_settings(matches: &ArgMatches<'_>) -> Result> { @@ -94,7 +94,7 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result Result<(), Box> { ) .get_matches(); - let mint_keypair = - read_keypair(matches.value_of("keypair").unwrap()).expect("failed to read client keypair"); + let mint_keypair = read_keypair_file(matches.value_of("keypair").unwrap()) + .expect("failed to read client keypair"); let time_slice: Option; if let Some(secs) = matches.value_of("slice") { diff --git a/genesis/src/main.rs b/genesis/src/main.rs index e863019ef9..37b84f3402 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -14,7 +14,7 @@ use solana_sdk::{ poh_config::PohConfig, pubkey::Pubkey, rent_calculator::RentCalculator, - signature::{read_keypair, Keypair, KeypairUtil}, + signature::{read_keypair_file, Keypair, KeypairUtil}, system_program, timing, }; use solana_stake_api::stake_state; @@ -298,11 +298,11 @@ fn main() -> Result<(), Box> { let bootstrap_leader_stake_lamports = value_t_or_exit!(matches, "bootstrap_leader_stake_lamports", u64); - let bootstrap_leader_keypair = read_keypair(bootstrap_leader_keypair_file)?; - let bootstrap_vote_keypair = read_keypair(bootstrap_vote_keypair_file)?; - let bootstrap_stake_keypair = read_keypair(bootstrap_stake_keypair_file)?; - let bootstrap_storage_keypair = read_keypair(bootstrap_storage_keypair_file)?; - let mint_keypair = read_keypair(mint_keypair_file)?; + let bootstrap_leader_keypair = read_keypair_file(bootstrap_leader_keypair_file)?; + let bootstrap_vote_keypair = read_keypair_file(bootstrap_vote_keypair_file)?; + let bootstrap_stake_keypair = read_keypair_file(bootstrap_stake_keypair_file)?; + let bootstrap_storage_keypair = read_keypair_file(bootstrap_storage_keypair_file)?; + let mint_keypair = read_keypair_file(mint_keypair_file)?; let vote_account = vote_state::create_account( &bootstrap_vote_keypair.pubkey(), diff --git a/install/src/command.rs b/install/src/command.rs index b42337721c..fdfec06242 100644 --- a/install/src/command.rs +++ b/install/src/command.rs @@ -9,7 +9,7 @@ use solana_client::rpc_client::RpcClient; use solana_config_api::{config_instruction, get_config_data}; use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil, Signable}; +use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil, Signable}; use solana_sdk::transaction::Transaction; use std::fs::{self, File}; use std::io::{self, BufReader, Read}; @@ -626,9 +626,9 @@ pub fn deploy( download_url: &str, update_manifest_keypair_file: &str, ) -> Result<(), String> { - let from_keypair = read_keypair(from_keypair_file) + let from_keypair = read_keypair_file(from_keypair_file) .map_err(|err| format!("Unable to read {}: {}", from_keypair_file, err))?; - let update_manifest_keypair = read_keypair(update_manifest_keypair_file) + let update_manifest_keypair = read_keypair_file(update_manifest_keypair_file) .map_err(|err| format!("Unable to read {}: {}", update_manifest_keypair_file, err))?; println_name_value("JSON RPC URL:", json_rpc_url); diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index 8ed5646f43..914bf14955 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -3,7 +3,10 @@ use clap::{ crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand, }; use solana_sdk::pubkey::write_pubkey; -use solana_sdk::signature::{keypair_from_seed, read_keypair, write_keypair, KeypairUtil}; +use solana_sdk::signature::{ + keypair_from_seed, read_keypair, read_keypair_file, write_keypair, write_keypair_file, Keypair, + KeypairUtil, +}; use std::error; use std::path::Path; use std::process::exit; @@ -18,6 +21,21 @@ fn check_for_overwrite(outfile: &str, matches: &ArgMatches) { } } +fn output_keypair( + keypair: &Keypair, + outfile: &str, + source: &str, +) -> Result<(), Box> { + if outfile == "-" { + let mut stdout = std::io::stdout(); + write_keypair(&keypair, &mut stdout)?; + } else { + write_keypair_file(&keypair, outfile)?; + eprintln!("Wrote {} keypair to {}", source, outfile); + } + Ok(()) +} + fn main() -> Result<(), Box> { let matches = App::new(crate_name!()) .about(crate_description!()) @@ -45,7 +63,7 @@ fn main() -> Result<(), Box> { Arg::with_name("silent") .short("s") .long("silent") - .help("Do not display mnemonic phrase"), + .help("Do not display mnemonic phrase. Useful when piping output to other programs that prompt for user input, like gpg"), ), ) .subcommand( @@ -104,7 +122,12 @@ fn main() -> Result<(), Box> { path.extend(&[".config", "solana", "id.json"]); path.to_str().unwrap() }; - let keypair = read_keypair(infile)?; + let keypair = if infile == "-" { + let mut stdin = std::io::stdin(); + read_keypair(&mut stdin)? + } else { + read_keypair_file(infile)? + }; if matches.is_present("outfile") { let outfile = matches.value_of("outfile").unwrap(); @@ -132,17 +155,12 @@ fn main() -> Result<(), Box> { let seed = Seed::new(&mnemonic, NO_PASSPHRASE); let keypair = keypair_from_seed(seed.as_bytes())?; - let serialized_keypair = write_keypair(&keypair, outfile)?; - if outfile == "-" { - println!("{}", serialized_keypair); - } else { - println!("Wrote new keypair to {}", outfile); - } + output_keypair(&keypair, &outfile, "new")?; let silent = matches.is_present("silent"); if !silent { let divider = String::from_utf8(vec![b'='; phrase.len()]).unwrap(); - println!( + eprintln!( "{}\nSave this mnemonic phrase to recover your new keypair:\n{}\n{}", ÷r, phrase, ÷r ); @@ -161,17 +179,12 @@ fn main() -> Result<(), Box> { check_for_overwrite(&outfile, &matches); } - let phrase = rpassword::prompt_password_stdout("Mnemonic recovery phrase: ").unwrap(); + let phrase = rpassword::prompt_password_stderr("Mnemonic recovery phrase: ").unwrap(); let mnemonic = Mnemonic::from_phrase(phrase.trim(), Language::English)?; let seed = Seed::new(&mnemonic, NO_PASSPHRASE); let keypair = keypair_from_seed(seed.as_bytes())?; - let serialized_keypair = write_keypair(&keypair, outfile)?; - if outfile == "-" { - println!("{}", serialized_keypair); - } else { - println!("Wrote recovered keypair to {}", outfile); - } + output_keypair(&keypair, &outfile, "recovered")?; } _ => unreachable!(), } diff --git a/replicator/src/main.rs b/replicator/src/main.rs index 3dbdb5050f..437fe32ee3 100644 --- a/replicator/src/main.rs +++ b/replicator/src/main.rs @@ -3,7 +3,7 @@ use console::style; use solana_core::cluster_info::{Node, FULLNODE_PORT_RANGE}; use solana_core::contact_info::ContactInfo; use solana_core::replicator::Replicator; -use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; +use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil}; use std::net::SocketAddr; use std::path::PathBuf; use std::process::exit; @@ -11,7 +11,7 @@ use std::sync::Arc; // Return an error if a keypair file cannot be parsed. fn is_keypair(string: String) -> Result<(), String> { - read_keypair(&string) + read_keypair_file(&string) .map(|_| ()) .map_err(|err| format!("{:?}", err)) } @@ -65,7 +65,7 @@ fn main() { let ledger_path = PathBuf::from(matches.value_of("ledger").unwrap()); let keypair = if let Some(identity) = matches.value_of("identity") { - read_keypair(identity).unwrap_or_else(|err| { + read_keypair_file(identity).unwrap_or_else(|err| { eprintln!("{}: Unable to open keypair file: {}", err, identity); exit(1); }) @@ -73,7 +73,7 @@ fn main() { Keypair::new() }; let storage_keypair = if let Some(storage_keypair) = matches.value_of("storage_keypair") { - read_keypair(storage_keypair).unwrap_or_else(|err| { + read_keypair_file(storage_keypair).unwrap_or_else(|err| { eprintln!("{}: Unable to open keypair file: {}", err, storage_keypair); exit(1); }) diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index 1d16906c61..bcbfd30444 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -12,7 +12,7 @@ use std::borrow::Cow; use std::error; use std::fmt; use std::fs::{self, File}; -use std::io::Write; +use std::io::{Read, Write}; use std::mem; use std::path::Path; use std::str::FromStr; @@ -125,28 +125,41 @@ impl KeypairUtil for Keypair { } } -pub fn read_keypair(path: &str) -> Result> { - let file = File::open(path.to_string())?; - let bytes: Vec = serde_json::from_reader(file)?; +pub fn read_keypair(reader: &mut R) -> Result> { + let bytes: Vec = serde_json::from_reader(reader)?; let keypair = Keypair::from_bytes(&bytes) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; Ok(keypair) } -pub fn write_keypair(keypair: &Keypair, outfile: &str) -> Result> { +pub fn read_keypair_file(path: &str) -> Result> { + assert!(path != "-"); + let mut file = File::open(path.to_string())?; + read_keypair(&mut file) +} + +pub fn write_keypair( + keypair: &Keypair, + writer: &mut W, +) -> Result> { let keypair_bytes = keypair.to_bytes(); let serialized = serde_json::to_string(&keypair_bytes.to_vec())?; - - if outfile != "-" { - if let Some(outdir) = Path::new(outfile).parent() { - fs::create_dir_all(outdir)?; - } - let mut f = File::create(outfile)?; - f.write_all(&serialized.clone().into_bytes())?; - } + writer.write_all(&serialized.clone().into_bytes())?; Ok(serialized) } +pub fn write_keypair_file( + keypair: &Keypair, + outfile: &str, +) -> Result> { + assert!(outfile != "-"); + if let Some(outdir) = Path::new(outfile).parent() { + fs::create_dir_all(outdir)?; + } + let mut f = File::create(outfile)?; + write_keypair(keypair, &mut f) +} + pub fn keypair_from_seed(seed: &[u8]) -> Result> { if seed.len() < ed25519_dalek::SECRET_KEY_LENGTH { return Err("Seed is too short".into()); @@ -159,7 +172,7 @@ pub fn keypair_from_seed(seed: &[u8]) -> Result> } pub fn gen_keypair_file(outfile: &str) -> Result> { - write_keypair(&Keypair::new(), outfile) + write_keypair_file(&Keypair::new(), outfile) } #[cfg(test)] @@ -183,10 +196,10 @@ mod tests { assert!(Path::new(&outfile).exists()); assert_eq!( keypair_vec, - read_keypair(&outfile).unwrap().to_bytes().to_vec() + read_keypair_file(&outfile).unwrap().to_bytes().to_vec() ); assert_eq!( - read_keypair(&outfile).unwrap().pubkey().as_ref().len(), + read_keypair_file(&outfile).unwrap().pubkey().as_ref().len(), mem::size_of::() ); fs::remove_file(&outfile).unwrap(); diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 1e080cc032..c5318d4cdd 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -14,7 +14,7 @@ use solana_core::socketaddr; use solana_core::validator::{Validator, ValidatorConfig}; use solana_sdk::clock::Slot; use solana_sdk::hash::Hash; -use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; +use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil}; use std::fs::{self, File}; use std::io::{self, Read}; use std::net::{SocketAddr, TcpListener}; @@ -219,7 +219,7 @@ fn initialize_ledger_path( // Return an error if a keypair file cannot be parsed. fn is_keypair(string: String) -> Result<(), String> { - read_keypair(&string) + read_keypair_file(&string) .map(|_| ()) .map_err(|err| format!("{:?}", err)) } @@ -419,7 +419,7 @@ pub fn main() { let mut validator_config = ValidatorConfig::default(); let keypair = if let Some(identity) = matches.value_of("identity") { - read_keypair(identity).unwrap_or_else(|err| { + read_keypair_file(identity).unwrap_or_else(|err| { error!("{}: Unable to open keypair file: {}", err, identity); exit(1); }) @@ -428,7 +428,7 @@ pub fn main() { }; let voting_keypair = if let Some(identity) = matches.value_of("voting_keypair") { - read_keypair(identity).unwrap_or_else(|err| { + read_keypair_file(identity).unwrap_or_else(|err| { error!("{}: Unable to open keypair file: {}", err, identity); exit(1); }) @@ -436,7 +436,7 @@ pub fn main() { Keypair::new() }; let storage_keypair = if let Some(storage_keypair) = matches.value_of("storage_keypair") { - read_keypair(storage_keypair).unwrap_or_else(|err| { + read_keypair_file(storage_keypair).unwrap_or_else(|err| { error!("{}: Unable to open keypair file: {}", err, storage_keypair); exit(1); })