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

Signing 2 - hc keygen #974

Merged
merged 19 commits into from
Feb 11, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Changed
- Added command `hc keygen` which creates a new key pair, asks for a passphrase and writes an encrypted key bundle file to `~/.holochain/keys`.
- `hash` properties for `UiBundleConfiguration` and `DnaConfiguration` in Conductor config files is now optional
- core now depends on `pretty_assertions` crate
- `ChainHeader::sources()` is now `ChainHeader::provenances()`
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

members = [
"cli",
"common",
"conductor",
"conductor_api",
"core_api_c_binding",
Expand Down
5 changes: 5 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ authors = ["Holochain Core Dev Team <devcore@holochain.org>"]

[dependencies]
holochain_net = { path = "../net" }
holochain_common = { path = "../common" }
holochain_core_types = { path = "../core_types" }
holochain_core = { path = "../core" }
holochain_cas_implementations = { path = "../cas_implementations" }
holochain_conductor_api = { path = "../conductor_api" }
holochain_dpki = { path = "../hc_dpki" }
holochain_sodium = { path = "../sodium" }
holochain_wasm_utils = { path = "../wasm_utils" }
structopt = "0.2"
failure = "^0.1"
Expand All @@ -25,3 +28,5 @@ dir-diff = "0.3.1"
colored = "1.6"
ignore = "0.4.3"
rustyline = "^2.1"
rpassword = "2.1.0"
directories = "1.0"
6 changes: 6 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ If you want to use `hc run` with real (as opposed to mock) networking, you will
| unpack | Unpacks a Holochain bundle into its original file system structure |
| test | Runs tests written in the test folder |
| run | Starts a websocket server for the current Holochain app |
| keygen | Creates a new passphrase encrypted agent key bundle |
| agent (u) | Starts a Holochain node as an agent |

### hc init & hc generate: How To Get Started Building An App
Expand All @@ -89,6 +90,11 @@ To read about `hc test`, used for running tests over your source code, see [http

To read about `hc run`, used for spinning up a quick developement version of your app with an HTTP or Websocket interface, that you can connect to from a UI, or any client, see [https://developer.holochain.org/guide/latest/development_conductor.html](https://developer.holochain.org/guide/latest/development_conductor.html).

### hc keygen: Create agent key pair

Every agent is represented by a private/public key pair, which are used to author source chains.
This command creates a new key pair by asking for a passphrase and writing a key bundle file that a Holochain Conductor
can read when starting up an instance.

## Contribute
Holochain is an open source project. We welcome all sorts of participation and are actively working on increasing surface area to accept it. Please see our [contributing guidelines](../CONTRIBUTING.md) for our general practices and protocols on participating in the community.
Expand Down
92 changes: 92 additions & 0 deletions cli/src/cli/keygen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use error::DefaultResult;
use holochain_common::paths::keys_directory;
use holochain_dpki::{
bundle::KeyBundle,
keypair::{Keypair, SEEDSIZE},
util::PwHashConfig,
};
use holochain_sodium::{pwhash, random::random_secbuf, secbuf::SecBuf};
use rpassword;
use std::{
fs::{create_dir_all, File},
io::prelude::*,
path::PathBuf,
};

pub fn keygen(path: Option<PathBuf>, passphrase: Option<String>) -> DefaultResult<()> {
let passphrase = passphrase
.unwrap_or_else(|| rpassword::read_password_from_tty(Some("Passphrase: ")).unwrap());

let mut seed = SecBuf::with_secure(SEEDSIZE);
random_secbuf(&mut seed);
let mut keypair = Keypair::new_from_seed(&mut seed).unwrap();
let passphrase_bytes = passphrase.as_bytes();
let mut passphrase_buf = SecBuf::with_insecure(passphrase_bytes.len());
passphrase_buf
.write(0, passphrase_bytes)
.expect("SecBuf must be writeable");

let bundle: KeyBundle = keypair
.get_bundle(
&mut passphrase_buf,
"hint".to_string(),
Some(PwHashConfig(
pwhash::OPSLIMIT_INTERACTIVE,
pwhash::MEMLIMIT_INTERACTIVE,
pwhash::ALG_ARGON2ID13,
)),
)
.unwrap();

let path = if None == path {
let p = keys_directory();
create_dir_all(p.clone())?;
p.join(keypair.pub_keys.clone())
} else {
path.unwrap()
};

let mut file = File::create(path.clone())?;
file.write_all(serde_json::to_string(&bundle).unwrap().as_bytes())?;
println!("Agent keys with public address: {}", keypair.pub_keys);
println!("written to: {}.", path.to_str().unwrap());
Ok(())
}

#[cfg(test)]
pub mod test {
use super::*;
use holochain_dpki::bundle::KeyBundle;
use std::{
fs::{remove_file, File},
path::PathBuf,
};

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

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

let mut file = File::open(path.clone()).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();

let bundle: KeyBundle = serde_json::from_str(&contents).unwrap();
let mut passphrase = SecBuf::with_insecure_from_string(passphrase);
let keypair = Keypair::from_bundle(
&bundle,
&mut passphrase,
Some(PwHashConfig(
pwhash::OPSLIMIT_INTERACTIVE,
pwhash::MEMLIMIT_INTERACTIVE,
pwhash::ALG_ARGON2ID13,
)),
);

assert!(keypair.is_ok());

let _ = remove_file(path);
}
}
2 changes: 2 additions & 0 deletions cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod agent;
mod generate;
mod init;
mod keygen;
pub mod package;
mod run;
mod scaffold;
Expand All @@ -11,6 +12,7 @@ pub use self::{
agent::agent,
generate::generate,
init::init,
keygen::keygen,
package::{package, unpack},
run::run,
test::{test, TEST_DIR_NAME},
Expand Down
13 changes: 13 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
extern crate holochain_cas_implementations;
extern crate holochain_common;
extern crate holochain_conductor_api;
extern crate holochain_core;
extern crate holochain_core_types;
extern crate holochain_dpki;
extern crate holochain_net;
extern crate holochain_sodium;
extern crate holochain_wasm_utils;
extern crate structopt;
#[macro_use]
Expand All @@ -19,6 +22,7 @@ extern crate toml;
#[macro_use]
extern crate serde_json;
extern crate ignore;
extern crate rpassword;
extern crate rustyline;
extern crate tempfile;
extern crate uuid;
Expand Down Expand Up @@ -142,6 +146,12 @@ enum Cli {
#[structopt(long = "skip-package", short = "s", help = "Skip packaging DNA")]
skip_build: bool,
},
#[structopt(
name = "keygen",
alias = "k",
about = "Creates a new agent key pair, asks for a passphrase and writes an encrypted key bundle to ~/.config/holochain/keys"
)]
KeyGen,
}

fn main() {
Expand Down Expand Up @@ -183,6 +193,9 @@ fn run() -> HolochainResult<()> {
cli::test(&current_path, &dir, &testfile, skip_build)
}
.map_err(HolochainError::Default)?,
Cli::KeyGen => {
cli::keygen(None, None).map_err(|e| HolochainError::Default(format_err!("{}", e)))?
}
}

Ok(())
Expand Down
8 changes: 8 additions & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "holochain_common"
version = "0.0.3"
authors = ["Holochain Core Dev Team <devcore@holochain.org>"]
edition = "2018"

[dependencies]
directories = "1.0"
1 change: 1 addition & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod paths;
22 changes: 22 additions & 0 deletions common/src/paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::path::PathBuf;

pub const QUALIFIER: &'static str = "org";
pub const ORGANIZATION: &'static str = "holochain";
pub const APPLICATION: &'static str = "holochain";
pub const KEYS_DIRECTORY: &'static str = "keys";

/// Returns the path to the root config directory for all of Holochain.
/// If we can get a user directory it will be an XDG compliant path
/// like "/home/peter/.config/holochain".
/// If it can't get a user directory it will default to "/etc/holochain".
pub fn config_root() -> PathBuf {
directories::ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
.map(|dirs| dirs.config_dir().to_owned())
.unwrap_or_else(|| PathBuf::from("/etc").join(APPLICATION))
}

/// Returns the path to where agent keys are stored and looked for by default.
/// Something like "~/.config/holochain/keys".
pub fn keys_directory() -> PathBuf {
config_root().join(KEYS_DIRECTORY)
}
1 change: 1 addition & 0 deletions hc_dpki/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// The bundle_type tells if the bundle is a RootSeed bundle | DeviceSeed bundle | DevicePINSeed Bundle | ApplicationKeys Bundle
///
/// the data includes a base64 encoded string of the ReturnBundleData Struct that was created by combining all the keys in one SecBuf
#[derive(Serialize, Deserialize)]
pub struct KeyBundle {
pub bundle_type: String,
pub hint: String,
Expand Down
5 changes: 3 additions & 2 deletions hc_dpki/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ extern crate holochain_sodium;
#[macro_use]
extern crate arrayref;
extern crate base64;
extern crate rustc_serialize;

extern crate bip39;
extern crate boolinator;
extern crate rustc_serialize;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed again?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't know. I've just moved this by two lines for alphanumeric sorting.

#[macro_use]
extern crate serde;

pub mod bundle;
pub mod error;
Expand Down