Skip to content
This repository was archived by the owner on Feb 3, 2023. It is now read-only.

hc-dpki cli for DeepKey #2117

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
486 changes: 486 additions & 0 deletions crates/cli/src/cli/dpki.rs

Large diffs are not rendered by default.

189 changes: 156 additions & 33 deletions crates/cli/src/cli/keygen.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,106 @@
use crate::NEW_RELIC_LICENSE_KEY;
use error::DefaultResult;
use holochain_common::paths::keys_directory;
use holochain_conductor_lib::{key_loaders::mock_passphrase_manager, keystore::Keystore};
use rpassword;
use holochain_conductor_lib::{
key_loaders::mock_passphrase_manager,
keystore::{Keystore, PRIMARY_KEYBUNDLE_ID},
};
use holochain_core_types::error::HcResult;
use holochain_dpki::seed::{SeedType, TypedSeed};
use holochain_locksmith::Mutex;
use std::{
fs::create_dir_all,
io::{self, Write},
path::PathBuf,
sync::Arc,
};
use util::{get_secure_string_double_check, get_seed, user_prompt};

#[holochain_tracing_macros::newrelic_autotrace(HOLOCHAIN_CLI)]
pub fn keygen(path: Option<PathBuf>, passphrase: Option<String>, quiet: bool) -> DefaultResult<()> {
let passphrase = passphrase.unwrap_or_else(|| {
if !quiet {
println!(
"
This will create a new agent keystore and populate it with an agent keybundle
pub fn keygen(
path: Option<PathBuf>,
keystore_passphrase: Option<String>,
nullpass: bool,
mnemonic_passphrase: Option<String>,
root_seed_mnemonic: Option<String>,
device_derivation_index: Option<u64>,
quiet: bool,
) -> HcResult<()> {
user_prompt(
"This will create a new agent keystore and populate it with an agent keybundle
containing a public and a private key, for signing and encryption by the agent.
This keybundle will be stored encrypted by passphrase within the keystore file.
The passphrase is securing the keys and will be needed, together with the file,
in order to use the key.
Please enter a secret passphrase below. You will have to enter it again
when unlocking the keybundle to use within a Holochain conductor."
in order to use the key.\n",
quiet,
);

let keystore_passphrase = match (keystore_passphrase, nullpass) {
(None, true) => String::from(holochain_common::DEFAULT_PASSPHRASE),
(Some(s), false) => s,
(Some(_), true) => panic!(
"Invalid combination of args. Cannot pass --nullpass and also provide a passphrase"
),
(None, false) => {
// prompt for the passphrase
user_prompt(
"Please enter a secret passphrase below. You will have to enter it again
when unlocking the keybundle to use within a Holochain conductor.\n",
quiet,
);
print!("Passphrase: ");
io::stdout().flush().expect("Could not flush stdout");
}
let passphrase1 = rpassword::read_password().unwrap();
if !quiet {
print!("Re-enter passphrase: ");
io::stdout().flush().expect("Could not flush stdout");
get_secure_string_double_check("keystore Passphrase", quiet)
.expect("Could not retrieve passphrase")
}
let passphrase2 = rpassword::read_password().unwrap();
if passphrase1 != passphrase2 {
println!("Passphrases do not match. Please retry...");
::std::process::exit(1);
}
passphrase1
});
};

if !quiet {
println!("Generating keystore (this will take a few moments)...");
}
let (keystore, pub_key) = Keystore::new_standalone(mock_passphrase_manager(passphrase), None)?;
let (keystore, pub_key) = if let Some(derivation_index) = device_derivation_index {
user_prompt("This keystore is to be generated from a DPKI root seed. You can regenerate this keystore at any time by using the same root key mnemonic and device derivation index.", quiet);

let root_seed_mnemonic = root_seed_mnemonic.unwrap_or_else(|| {
get_secure_string_double_check("Root Seed Mnemonic", quiet)
.expect("Could not retrieve mnemonic")
});

match root_seed_mnemonic.split(' ').count() {
24 => {
// unencrypted mnemonic
user_prompt(
"Generating keystore (this will take a few moments)...",
quiet,
);
keygen_dpki(
root_seed_mnemonic,
None,
derivation_index,
keystore_passphrase,
)?
}
48 => {
// encrypted mnemonic
let mnemonic_passphrase = mnemonic_passphrase.unwrap_or_else(|| {
get_secure_string_double_check("Root Seed Mnemonic passphrase", quiet)
.expect("Could not retrieve mnemonic passphrase")
});
user_prompt(
"Generating keystore (this will take a few moments)...",
quiet,
);
keygen_dpki(
root_seed_mnemonic,
Some(mnemonic_passphrase),
derivation_index,
keystore_passphrase,
)?
}
_ => panic!(
"Invalid number of words in mnemonic. Must be 24 (unencrypted) or 48 (encrypted)"
),
}
} else {
user_prompt(
"Generating keystore (this will take a few moments)...",
quiet,
);
keygen_standalone(keystore_passphrase)?
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed a few panics in here even though we are returning an HCResult, is this because we cannot recover from them if they happen?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, these expects should be replaced with ?


let path = if None == path {
let p = keys_directory();
Expand Down Expand Up @@ -70,21 +128,52 @@ when unlocking the keybundle to use within a Holochain conductor."
Ok(())
}

fn keygen_standalone(keystore_passphrase: String) -> HcResult<(Keystore, String)> {
Keystore::new_standalone(mock_passphrase_manager(keystore_passphrase), None)
}

fn keygen_dpki(
root_seed_mnemonic: String,
root_seed_passphrase: Option<String>,
derivation_index: u64,
keystore_passphrase: String,
) -> HcResult<(Keystore, String)> {
let mut root_seed = match get_seed(root_seed_mnemonic, root_seed_passphrase, SeedType::Root)? {
TypedSeed::Root(s) => s,
_ => unreachable!(),
};
let mut keystore = Keystore::new(mock_passphrase_manager(keystore_passphrase), None)?;
let device_seed = root_seed.generate_device_seed(derivation_index)?;
keystore.add("device_seed", Arc::new(Mutex::new(device_seed.into())))?;
let (pub_key, _) = keystore.add_keybundle_from_seed("device_seed", PRIMARY_KEYBUNDLE_ID)?;
Ok((keystore, pub_key))
}

#[cfg(test)]
pub mod test {
use super::*;
use cli::dpki;
use holochain_conductor_lib::{
key_loaders::mock_passphrase_manager,
keystore::{Keystore, PRIMARY_KEYBUNDLE_ID},
};
use std::{fs::remove_file, path::PathBuf};

#[test]
fn keygen_roundtrip() {
fn keygen_roundtrip_no_dpki() {
let path = PathBuf::new().join("test.key");
let passphrase = String::from("secret");

keygen(Some(path.clone()), Some(passphrase.clone()), true).expect("Keygen should work");
keygen(
Some(path.clone()),
Some(passphrase.clone()),
false,
None,
None,
None,
true,
)
.expect("Keygen should work");

let mut keystore =
Keystore::new_from_file(path.clone(), mock_passphrase_manager(passphrase), None)
Expand All @@ -96,4 +185,38 @@ pub mod test {

let _ = remove_file(path);
}

#[test]
fn keygen_roundtrip_with_dpki() {
let path = PathBuf::new().join("test_dpki.key");
let keystore_passphrase = String::from("secret_dpki");
let mnemonic_passphrase = String::from("dummy passphrase");

let mnemonic = dpki::genroot_inner(Some(mnemonic_passphrase.clone()))
.expect("Could not generate root seed mneomonic");

keygen(
Some(path.clone()),
Some(keystore_passphrase.clone()),
false,
Some(mnemonic_passphrase),
Some(mnemonic),
Some(1),
true,
)
.expect("Keygen should work");

let mut keystore = Keystore::new_from_file(
path.clone(),
mock_passphrase_manager(keystore_passphrase),
None,
)
.unwrap();

let keybundle = keystore.get_keybundle(PRIMARY_KEYBUNDLE_ID);

assert!(keybundle.is_ok());

let _ = remove_file(path);
}
}
2 changes: 2 additions & 0 deletions crates/cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod chain_log;
mod dpki;
mod generate;
mod hash_dna;
pub mod init;
Expand All @@ -10,6 +11,7 @@ pub mod test;

pub use self::{
chain_log::{chain_list, chain_log},
dpki::Dpki,
generate::generate,
hash_dna::hash_dna,
init::init,
Expand Down
46 changes: 35 additions & 11 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern crate serde_json;
extern crate dns_lookup;
extern crate flate2;
extern crate glob;
extern crate holochain_dpki;
extern crate ignore;
extern crate in_stream;
extern crate rpassword;
Expand All @@ -40,7 +41,10 @@ mod config_files;
mod error;
mod util;

use crate::error::{HolochainError, HolochainResult};
use crate::{
cli::Dpki,
error::{HolochainError, HolochainResult},
};
use holochain_conductor_lib::happ_bundle::HappBundle;
use std::{fs::File, io::Read, path::PathBuf, str::FromStr};
use structopt::{clap::arg_enum, StructOpt};
Expand Down Expand Up @@ -129,10 +133,25 @@ enum Cli {
#[structopt(long, short)]
/// Only print machine-readable output; intended for use by programs and scripts
quiet: bool,
#[structopt(long, short)]
#[structopt(
long,
short,
help = "Use insecure, hard-wired passphrase for testing and Don't ask for passphrase"
)]
/// Don't ask for passphrase
nullpass: bool,
#[structopt(
long,
short,
help = "Set passphrase via argument and don't prompt for it (not reccomended)"
)]
passphrase: Option<String>,
},
#[structopt(
name = "dpki-init",
alias = "d",
about = "Generates a new DPKI root seed and outputs the encrypted key as a BIP39 mnemonic"
)]
#[structopt(name = "chain")]
/// View the contents of a source chain
ChainLog {
Expand All @@ -156,6 +175,12 @@ enum Cli {
/// Property (in the form 'name=value') that gets set/overwritten before calculating hash
property: Option<Vec<String>>,
},
#[structopt(
name = "dpki",
alias = "d",
about = "Operations to manage keys for DPKI"
)]
Dpki(Dpki),
Sim2hClient {
#[structopt(long, short = "u")]
/// url of the sim2h server
Expand Down Expand Up @@ -294,15 +319,14 @@ fn run() -> HolochainResult<()> {
path,
quiet,
nullpass,
} => {
let passphrase = if nullpass {
Some(String::from(holochain_common::DEFAULT_PASSPHRASE))
} else {
None
};
cli::keygen(path, passphrase, quiet)
.map_err(|e| HolochainError::Default(format_err!("{}", e)))?
}
passphrase,
} => cli::keygen(path, passphrase, nullpass, None, None, None, quiet)
.map_err(|e| HolochainError::Default(format_err!("{}", e)))?,

Cli::Dpki(dpki) => dpki
.execute()
.map(|result| println!("{}", result))
.map_err(|e| HolochainError::Default(format_err!("{}", e)))?,

Cli::ChainLog {
instance_id,
Expand Down
Loading