diff --git a/CHANGELOG.md b/CHANGELOG.md index e105413bff..11ecb03f59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adds `nix-shell` support for Mac OS X [#1132](https://github.com/holochain/holochain-rust/pull/1132) - Adds `hc-test-all` command to `nix-shell` [#1132](https://github.com/holochain/holochain-rust/pull/1132) - Adds `./scripts/nix/pod.sh` script to isolate/debug `nix-shell` commands [#1139](https://github.com/holochain/holochain-rust/pull/1139) +- Adds keystore and passphrase management service [#1104](https://github.com/holochain/holochain-rust/pull/1104) - Adds tooling to manage dependencies in Cargo.toml [#1140](https://github.com/holochain/holochain-rust/pull/1140) ### Changed - `nix-shell` is now the recommended development approach on supported platforms [#1132](https://github.com/holochain/holochain-rust/pull/1132) - Pins every dependant crate version with `=x.y.z` at the Cargo.toml level [#1140](https://github.com/holochain/holochain-rust/pull/1140) +- Breaking Change: `key_file` value now renamed to `keystore_file` in both config.toml files and the conductor's `admin/agent/add` interface [#1104](https://github.com/holochain/holochain-rust/pull/1104) ### Deprecated diff --git a/Cargo.toml b/Cargo.toml index 22c8797e74..b60dfd0d75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ "net", "nodejs_waiter", "sodium", - "hc_dpki", + "dpki", "test_bin", ] exclude = [ diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a86e274875..9b2d524169 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,7 +8,7 @@ holochain_core_types = { path = "../core_types" } holochain_core = { path = "../core" } holochain_common = { path = "../common" } holochain_conductor_api = { path = "../conductor_api" } -holochain_dpki = { path = "../hc_dpki" } +holochain_dpki = { path = "../dpki" } holochain_sodium = { path = "../sodium" } holochain_wasm_utils = { path = "../wasm_utils" } structopt = "=0.2.15" diff --git a/cli/src/cli/keygen.rs b/cli/src/cli/keygen.rs index e823a7b116..fd72843ef5 100644 --- a/cli/src/cli/keygen.rs +++ b/cli/src/cli/keygen.rs @@ -1,21 +1,19 @@ use error::DefaultResult; use holochain_common::paths::keys_directory; -use holochain_dpki::{key_blob::Blobbable, key_bundle::KeyBundle, seed::SeedType, SEED_SIZE}; -use holochain_sodium::secbuf::SecBuf; -use rpassword; -use std::{ - fs::{create_dir_all, File}, - io::prelude::*, - path::PathBuf, +use holochain_conductor_api::{ + key_loaders::mock_passphrase_manager, + keystore::{Keystore, PRIMARY_KEYBUNDLE_ID}, }; +use holochain_dpki::SEED_SIZE; +use rpassword; +use std::{fs::create_dir_all, path::PathBuf}; pub fn keygen(path: Option, passphrase: Option) -> DefaultResult<()> { - println!( - "This will create a new agent key bundle - that is all keys needed to represent one agent." - ); - println!("This key bundle will be stored in a file, encrypted with a passphrase."); - println!("The passphrase is securing the keys and will be needed, together with the key file, in order to use the key."); - println!("Please enter a secret passphrase below, you will have to enter it again when unlocking this key to use within a Holochain conductor."); + println!("This will create a new agent keystore and populate it with an agent keybundle"); + println!("(=all keys needed to represent an agent: public/private keys for signing/encryption"); + println!("This keybundle will be stored encrypted by passphrase within the keystore file."); + println!("The passphrase is securing the keys and will be needed, together with the file, in order to use the key."); + println!("Please enter a secret passphrase below, you will have to enter it again when unlocking these keys to use within a Holochain conductor."); let passphrase = passphrase.unwrap_or_else(|| { let passphrase1 = rpassword::read_password_from_tty(Some("Passphrase: ")).unwrap(); @@ -27,49 +25,36 @@ pub fn keygen(path: Option, passphrase: Option) -> DefaultResul passphrase1 }); - let mut seed = SecBuf::with_secure(SEED_SIZE); - seed.randomize(); - - let mut keybundle = KeyBundle::new_from_seed_buf(&mut seed, SeedType::Mock) - .expect("Failed to generate keybundle"); - 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 mut keystore = Keystore::new(mock_passphrase_manager(passphrase), None)?; + keystore.add_random_seed("root_seed", SEED_SIZE)?; - let blob = keybundle - .as_blob(&mut passphrase_buf, "hint".to_string(), None) - .expect("Failed to encrypt with passphrase."); + let (pub_key, _) = keystore.add_keybundle_from_seed("root_seed", PRIMARY_KEYBUNDLE_ID)?; let path = if None == path { let p = keys_directory(); create_dir_all(p.clone())?; - p.join(keybundle.get_id().clone()) + p.join(pub_key.clone()) } else { path.unwrap() }; - let mut file = File::create(path.clone())?; - file.write_all(serde_json::to_string(&blob).unwrap().as_bytes())?; + keystore.save(path.clone())?; + println!(""); - println!("Succesfully created new agent keys."); + println!("Succesfully created new agent keystore."); println!(""); - println!("Public address: {}", keybundle.get_id()); + println!("Public address: {}", pub_key); println!("Bundle written to: {}.", path.to_str().unwrap()); println!(""); - println!("You can set this file in a conductor config as key_file for an agent."); + println!("You can set this file in a conductor config as keystore_file for an agent."); Ok(()) } #[cfg(test)] pub mod test { use super::*; - use holochain_dpki::key_blob::KeyBlob; - use std::{ - fs::{remove_file, File}, - path::PathBuf, - }; + use holochain_conductor_api::{key_loaders::mock_passphrase_manager, keystore::Keystore}; + use std::{fs::remove_file, path::PathBuf}; #[test] fn keygen_roundtrip() { @@ -78,13 +63,11 @@ pub mod test { 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 mut keystore = + Keystore::new_from_file(path.clone(), mock_passphrase_manager(passphrase), None) + .unwrap(); - let blob: KeyBlob = serde_json::from_str(&contents).unwrap(); - let mut passphrase = SecBuf::with_insecure_from_string(passphrase); - let keybundle = KeyBundle::from_blob(&blob, &mut passphrase, None); + let keybundle = keystore.get_keybundle(PRIMARY_KEYBUNDLE_ID); assert!(keybundle.is_ok()); diff --git a/cli/src/cli/run.rs b/cli/src/cli/run.rs index 7dea5ed8ba..17e3621b6a 100644 --- a/cli/src/cli/run.rs +++ b/cli/src/cli/run.rs @@ -5,7 +5,8 @@ use holochain_common::env_vars::EnvVar; use holochain_conductor_api::{ conductor::{mount_conductor_from_config, CONDUCTOR}, config::*, - key_loaders::{test_keybundle, test_keybundle_loader}, + key_loaders::{test_keystore, test_keystore_loader}, + keystore::PRIMARY_KEYBUNDLE_ID, logger::LogRules, }; use holochain_core_types::agent::AgentId; @@ -26,7 +27,7 @@ pub fn run( mount_conductor_from_config(conductor_config); let mut conductor_guard = CONDUCTOR.lock().unwrap(); let conductor = conductor_guard.as_mut().expect("Conductor must be mounted"); - conductor.key_loader = test_keybundle_loader(); + conductor.key_loader = test_keystore_loader(); conductor .load_config() @@ -97,13 +98,17 @@ fn agent_configuration() -> AgentConfiguration { .value() .ok() .unwrap_or_else(|| String::from(AGENT_NAME_DEFAULT)); - let keybundle = test_keybundle(&agent_name); - let agent_id = AgentId::new(&agent_name, keybundle.get_id()); + let mut keystore = test_keystore(&agent_name); + let pub_key = keystore + .get_keybundle(PRIMARY_KEYBUNDLE_ID) + .expect("should be able to get keybundle") + .get_id(); + let agent_id = AgentId::new(&agent_name, pub_key); AgentConfiguration { id: AGENT_CONFIG_ID.into(), name: agent_id.nick, public_address: agent_id.pub_sign_key, - key_file: agent_name, + keystore_file: agent_name, holo_remote_key: None, } } @@ -274,7 +279,7 @@ mod tests { name: "testAgent".to_string(), public_address: "HcScjN8wBwrn3tuyg89aab3a69xsIgdzmX5P9537BqQZ5A7TEZu7qCY4Xzzjhma" .to_string(), - key_file: "testAgent".to_string(), + keystore_file: "testAgent".to_string(), holo_remote_key: None, }, ); diff --git a/conductor/example-config/basic.toml b/conductor/example-config/basic.toml index d1bc5e6ddf..df21f21cab 100644 --- a/conductor/example-config/basic.toml +++ b/conductor/example-config/basic.toml @@ -2,13 +2,13 @@ bridges = [] [[agents]] id = "test agent 1" -key_file = "holo_tester.key" +keystore_file = "holo_tester.key" name = "Holo Tester 1" public_address = "HoloTester1-----------------------------------------------------------------------AAACZp4xHB" [[agents]] id = "test agent 2" -key_file = "holo_tester.key" +keystore_file = "holo_tester.key" name = "Holo Tester 2" public_address = "HoloTester2-----------------------------------------------------------------------AAAGy4WW9e" diff --git a/conductor/example-config/empty-container.toml b/conductor/example-config/empty-container.toml index ecf5ec720c..955ee37793 100644 --- a/conductor/example-config/empty-container.toml +++ b/conductor/example-config/empty-container.toml @@ -2,7 +2,7 @@ id = "test agent 1" name = "Holo Tester 1" public_address = "HoloTester1-----------------------------------------------------------------------AAACZp4xHB" -key_file = "holo_tester.key" +keystore_file = "holo_tester.key" dnas = [] diff --git a/conductor/example-config/holo.toml b/conductor/example-config/holo.toml index cf74a25290..40da14c3bb 100644 --- a/conductor/example-config/holo.toml +++ b/conductor/example-config/holo.toml @@ -3,7 +3,7 @@ signing_service_uri = "http://localhost:8888" [[agents]] id = "test agent 1" -key_file = "holo_tester.key" +keystore_file = "holo_tester.key" name = "Holo Tester 1" public_address = "HoloTester1-----------------------------------------------------------------------AAACZp4xHB" holo_remote_key = true diff --git a/conductor/example-config/static-only.toml b/conductor/example-config/static-only.toml index 58c9b50b9a..c83e8a6e7b 100644 --- a/conductor/example-config/static-only.toml +++ b/conductor/example-config/static-only.toml @@ -2,7 +2,7 @@ id = "test agent 1" name = "Holo Tester 1" public_address = "HoloTester1-----------------------------------------------------------------------AAACZp4xHB" -key_file = "holo_tester.key" +keystore_file = "holo_tester.key" [[ui_bundles]] id = "bundle1" diff --git a/conductor_api/Cargo.toml b/conductor_api/Cargo.toml index 54b1e78cdb..ecbda76920 100644 --- a/conductor_api/Cargo.toml +++ b/conductor_api/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Holochain Core Dev Team "] holochain_cas_implementations = { path = "../cas_implementations" } holochain_core = { path = "../core" } holochain_core_types = { path = "../core_types" } -holochain_dpki = { path = "../hc_dpki" } +holochain_dpki = { path = "../dpki" } holochain_net = { path = "../net" } holochain_sodium = { path = "../sodium" } holochain_common = { path = "../common" } diff --git a/conductor_api/src/conductor/admin.rs b/conductor_api/src/conductor/admin.rs index 45a88d0db6..4792684cde 100644 --- a/conductor_api/src/conductor/admin.rs +++ b/conductor_api/src/conductor/admin.rs @@ -566,7 +566,7 @@ pub mod tests { format!( r#"[[agents]] id = 'test-agent-1' -key_file = 'holo_tester1.key' +keystore_file = 'holo_tester1.key' name = 'Holo Tester 1' public_address = '{}'"#, test_keybundle(1).get_id() @@ -577,7 +577,7 @@ public_address = '{}'"#, format!( r#"[[agents]] id = 'test-agent-2' -key_file = 'holo_tester2.key' +keystore_file = 'holo_tester2.key' name = 'Holo Tester 2' public_address = '{}'"#, test_keybundle(2).get_id() @@ -1379,7 +1379,7 @@ type = 'websocket'"#, id: String::from("new-agent"), name: String::from("Mr. New"), public_address: AgentId::generate_fake("new").address().to_string(), - key_file: String::from("new-test-path"), + keystore_file: String::from("new-test-path"), holo_remote_key: None, }; @@ -1399,7 +1399,7 @@ type = 'websocket'"#, String::from( r#"[[agents]] id = 'new-agent' -key_file = 'new-test-path' +keystore_file = 'new-test-path' name = 'Mr. New' public_address = 'HcScIkRaAaaaaaaaaaAaaaAAAAaaaaaaaaAaaaaAaaaaaaaaAaaAAAAatzu4aqa'"#, ), diff --git a/conductor_api/src/conductor/base.rs b/conductor_api/src/conductor/base.rs index 7e70e2b22e..7c261b4cc8 100644 --- a/conductor_api/src/conductor/base.rs +++ b/conductor_api/src/conductor/base.rs @@ -5,6 +5,7 @@ use crate::{ }, context_builder::ContextBuilder, error::HolochainInstanceError, + keystore::{Keystore, PRIMARY_KEYBUNDLE_ID}, logger::DebugLogger, Holochain, }; @@ -17,11 +18,7 @@ use holochain_core_types::{ agent::AgentId, cas::content::AddressableContent, dna::Dna, error::HolochainError, json::JsonString, }; -use holochain_dpki::{ - key_blob::{Blobbable, KeyBlob}, - key_bundle::KeyBundle, -}; -use holochain_sodium::secbuf::SecBuf; +use holochain_dpki::key_bundle::KeyBundle; use jsonrpc_ws_server::jsonrpc_core::IoHandler; use std::{ clone::Clone, @@ -81,7 +78,7 @@ pub fn mount_conductor_from_config(config: Configuration) { /// Dna object for a given path string) has to be injected on creation. pub struct Conductor { pub(in crate::conductor) instances: InstanceMap, - agent_keys: HashMap>>, + agent_keys: HashMap>>, pub(in crate::conductor) config: Configuration, pub(in crate::conductor) static_servers: HashMap, pub(in crate::conductor) interface_threads: HashMap>, @@ -92,7 +89,7 @@ pub struct Conductor { logger: DebugLogger, p2p_config: Option, network_spawn: Option, - passphrase_manager: PassphraseManager, + passphrase_manager: Arc, } impl Drop for Conductor { @@ -107,7 +104,7 @@ impl Drop for Conductor { type SignalSender = SyncSender; pub type KeyLoader = Arc< - Box Result + Send + Sync>, + Box) -> Result + Send + Sync>, >; pub type DnaLoader = Arc Result + Send + Sync>>; pub type UiDirCopier = @@ -135,9 +132,9 @@ impl Conductor { logger: DebugLogger::new(rules), p2p_config: None, network_spawn: None, - passphrase_manager: PassphraseManager::new(Arc::new(Mutex::new( + passphrase_manager: Arc::new(PassphraseManager::new(Arc::new(Mutex::new( PassphraseServiceCmd {}, - ))), + )))), } } @@ -550,46 +547,68 @@ impl Conductor { // !!!!!!!!!!!!!!!!!!!!!!! return Ok(()); } - self.get_keybundle_for_agent(agent_id)?; + self.get_keystore_for_agent(agent_id)?; Ok(()) } - /// Get reference to key for given agent ID. + /// Get reference to keystore for given agent ID. /// If the key was not loaded (into secure memory) yet, this will use the KeyLoader /// to do so. - fn get_keybundle_for_agent( + fn get_keystore_for_agent( &mut self, agent_id: &String, - ) -> Result>, String> { + ) -> Result>, String> { if !self.agent_keys.contains_key(agent_id) { let agent_config = self .config .agent_by_id(agent_id) .ok_or(format!("Agent '{}' not found", agent_id))?; - let key_file_path = PathBuf::from(agent_config.key_file.clone()); - let keybundle = Arc::get_mut(&mut self.key_loader).unwrap()( - &key_file_path, - &self.passphrase_manager, + let keystore_file_path = PathBuf::from(agent_config.keystore_file.clone()); + let mut keystore = Arc::get_mut(&mut self.key_loader).unwrap()( + &keystore_file_path, + self.passphrase_manager.clone(), ) .map_err(|_| { HolochainError::ConfigError(format!( - "Could not load key file \"{}\"", - agent_config.key_file, + "Could not load keystore \"{}\"", + agent_config.keystore_file, )) })?; + let keybundle = keystore + .get_keybundle(PRIMARY_KEYBUNDLE_ID) + .map_err(|err| format!("{}", err,))?; + if agent_config.public_address != keybundle.get_id() { return Err(format!( "Key from file '{}' ('{}') does not match public address {} mentioned in config!", - key_file_path.to_str().unwrap(), + keystore_file_path.to_str().unwrap(), keybundle.get_id(), agent_config.public_address, )); } + self.agent_keys - .insert(agent_id.clone(), Arc::new(Mutex::new(keybundle))); + .insert(agent_id.clone(), Arc::new(Mutex::new(keystore))); } - let keybundle_ref = self.agent_keys.get(agent_id).unwrap(); - Ok(keybundle_ref.clone()) + let keystore_ref = self.agent_keys.get(agent_id).unwrap(); + Ok(keystore_ref.clone()) + } + + /// Get reference to the keybundle stored in the keystore for given agent ID. + /// If the key was not loaded (into secure memory) yet, this will use the KeyLoader + /// to do so. + fn get_keybundle_for_agent( + &mut self, + agent_id: &String, + ) -> Result>, String> { + let keystore = self + .get_keystore_for_agent(agent_id) + .map_err(|err| format!("{}", err))?; + let mut keystore = keystore.lock().unwrap(); + let keybundle = keystore + .get_keybundle(PRIMARY_KEYBUNDLE_ID) + .map_err(|err| format!("{}", err))?; + Ok(Arc::new(Mutex::new(keybundle))) } fn start_interface(&mut self, config: &InterfaceConfiguration) -> Result<(), String> { @@ -614,25 +633,12 @@ impl Conductor { /// Default KeyLoader that actually reads files from the filesystem fn load_key( file: &PathBuf, - passphrase_manager: &PassphraseManager, - ) -> Result { - notify(format!("Reading agent key from {}", file.display())); + passphrase_manager: Arc, + ) -> Result { + notify(format!("Reading keystore from {}", file.display())); - // Read key file - let mut file = File::open(file)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let blob: KeyBlob = serde_json::from_str(&contents)?; - - // Try default passphrase: - let mut default_passphrase = - SecBuf::with_insecure_from_string(holochain_common::DEFAULT_PASSPHRASE.to_string()); - KeyBundle::from_blob(&blob, &mut default_passphrase, None).or_else(|_| { - let mut passphrase_buf = passphrase_manager.get_passphrase()?; - - // Unblob into KeyBundle - KeyBundle::from_blob(&blob, &mut passphrase_buf, None) - }) + let keystore = Keystore::new_from_file(file.clone(), passphrase_manager, None)?; + Ok(keystore) } fn copy_ui_dir(source: &PathBuf, dest: &PathBuf) -> Result<(), HolochainError> { @@ -775,6 +781,9 @@ impl Logger for NullLogger { #[cfg(test)] pub mod tests { use super::*; + use conductor::passphrase_manager::PassphraseManager; + use key_loaders::mock_passphrase_manager; + use keystore::{test_hash_config, Keystore, Secret, PRIMARY_KEYBUNDLE_ID}; extern crate tempfile; use crate::config::load_configuration; use holochain_core::{ @@ -806,34 +815,51 @@ pub mod tests { } pub fn test_key_loader() -> KeyLoader { - let loader = Box::new(|path: &PathBuf, _pm: &PassphraseManager| { + let loader = Box::new(|path: &PathBuf, _pm: Arc| { match path.to_str().unwrap().as_ref() { - "holo_tester1.key" => Ok(test_keybundle(1)), - "holo_tester2.key" => Ok(test_keybundle(2)), - "holo_tester3.key" => Ok(test_keybundle(3)), + "holo_tester1.key" => Ok(test_keystore(1)), + "holo_tester2.key" => Ok(test_keystore(2)), + "holo_tester3.key" => Ok(test_keystore(3)), unknown => Err(HolochainError::ErrorGeneric(format!( - "No test key for {}", + "No test keystore for {}", unknown ))), } }) as Box< - FnMut(&PathBuf, &PassphraseManager) -> Result + FnMut(&PathBuf, Arc) -> Result + Send + Sync, >; Arc::new(loader) } - pub fn test_keybundle(index: u8) -> KeyBundle { + pub fn test_keystore(index: u8) -> Keystore { + let agent_name = format!("test-agent-{}", index); + let mut keystore = Keystore::new( + mock_passphrase_manager(agent_name.clone()), + test_hash_config(), + ) + .unwrap(); + // Create deterministic seed let mut seed = SecBuf::with_insecure(SEED_SIZE); let mock_seed: Vec = (1..SEED_SIZE).map(|e| e as u8 + index).collect(); seed.write(0, mock_seed.as_slice()) .expect("SecBuf must be writeable"); - // Create KeyBundle from seed - KeyBundle::new_from_seed_buf(&mut seed, holochain_dpki::seed::SeedType::Mock).unwrap() + let secret = Arc::new(Mutex::new(Secret::Seed(seed))); + keystore.add("root_seed", secret).unwrap(); + + keystore + .add_keybundle_from_seed("root_seed", PRIMARY_KEYBUNDLE_ID) + .unwrap(); + keystore + } + + pub fn test_keybundle(index: u8) -> KeyBundle { + let mut keystore = test_keystore(index); + keystore.get_keybundle(PRIMARY_KEYBUNDLE_ID).unwrap() } pub fn test_toml() -> String { @@ -843,19 +869,19 @@ pub mod tests { id = "test-agent-1" name = "Holo Tester 1" public_address = "{}" - key_file = "holo_tester1.key" + keystore_file = "holo_tester1.key" [[agents]] id = "test-agent-2" name = "Holo Tester 2" public_address = "{}" - key_file = "holo_tester2.key" + keystore_file = "holo_tester2.key" [[agents]] id = "test-agent-3" name = "Holo Tester 3" public_address = "{}" - key_file = "holo_tester3.key" + keystore_file = "holo_tester3.key" [[dnas]] id = "test-dna" @@ -1246,7 +1272,7 @@ pub mod tests { id = "test-agent-1" name = "Holo Tester 1" public_address = "HoloTester1-----------------------------------------------------------------------AAACZp4xHB" - key_file = "holo_tester1.key" + keystore_file = "holo_tester1.key" [[dnas]] id = "test-dna" diff --git a/conductor_api/src/conductor/passphrase_manager.rs b/conductor_api/src/conductor/passphrase_manager.rs index 662d2489b2..d08f01d729 100644 --- a/conductor_api/src/conductor/passphrase_manager.rs +++ b/conductor_api/src/conductor/passphrase_manager.rs @@ -108,3 +108,13 @@ impl PassphraseService for PassphraseServiceCmd { Ok(passphrase_buf) } } + +pub struct PassphraseServiceMock { + pub passphrase: String, +} + +impl PassphraseService for PassphraseServiceMock { + fn request_passphrase(&self) -> Result { + Ok(SecBuf::with_insecure_from_string(self.passphrase.clone())) + } +} diff --git a/conductor_api/src/config.rs b/conductor_api/src/config.rs index abd4a23616..97ab36a350 100644 --- a/conductor_api/src/config.rs +++ b/conductor_api/src/config.rs @@ -344,8 +344,8 @@ pub struct AgentConfiguration { pub id: String, pub name: String, pub public_address: Base32, - pub key_file: String, - /// If set to true conductor will ignore key_file and instead use the remote signer + pub keystore_file: String, + /// If set to true conductor will ignore keystore_file and instead use the remote signer /// accessible through signing_service_uri to request signatures. pub holo_remote_key: Option, } @@ -591,13 +591,13 @@ pub mod tests { id = "bob" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file="file/to/serialize" + keystore_file = "file/to/serialize" [[agents]] id="alex" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file="another/file" + keystore_file = "another/file" [[dnas]] id="dna" @@ -611,7 +611,7 @@ pub mod tests { .get(0) .expect("expected at least 2 agents") .clone() - .key_file, + .keystore_file, "file/to/serialize" ); assert_eq!( @@ -627,7 +627,7 @@ pub mod tests { id="agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file="whatever" + keystore_file = "whatever" [[dnas]] id = "app spec rust" @@ -648,7 +648,7 @@ pub mod tests { id = "test agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file = "holo_tester.key" + keystore_file = "holo_tester.key" [[dnas]] id = "app spec rust" @@ -735,7 +735,7 @@ pub mod tests { id = "test agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file = "holo_tester.key" + keystore_file = "holo_tester.key" [[dnas]] id = "app spec rust" @@ -820,7 +820,7 @@ pub mod tests { id = "test agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file = "holo_tester.key" + keystore_file = "holo_tester.key" [[dnas]] id = "app spec rust" @@ -850,7 +850,7 @@ pub mod tests { id = "test agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file = "holo_tester.key" + keystore_file = "holo_tester.key" [[dnas]] id = "app spec rust" @@ -893,7 +893,7 @@ pub mod tests { id = "test agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file = "holo_tester.key" + keystore_file = "holo_tester.key" [[dnas]] id = "app spec rust" @@ -936,7 +936,7 @@ pub mod tests { id = "test agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file = "holo_tester.key" + keystore_file = "holo_tester.key" [[dnas]] id = "app spec rust" @@ -1114,7 +1114,7 @@ pub mod tests { id = "test agent" name = "Holo Tester 1" public_address = "HoloTester1-------------------------------------------------------------------------AHi1" - key_file = "holo_tester.key" + keystore_file = "holo_tester.key" [[dnas]] id = "app spec rust" diff --git a/conductor_api/src/holochain.rs b/conductor_api/src/holochain.rs index 6062281dcb..a6069db220 100644 --- a/conductor_api/src/holochain.rs +++ b/conductor_api/src/holochain.rs @@ -44,7 +44,7 @@ //! let mut seed = SecBuf::with_insecure(SEED_SIZE); //! seed.randomize(); //! -//! let keybundle = KeyBundle::new_from_seed_buf(&mut seed, SeedType::Mock).unwrap(); +//! let keybundle = KeyBundle::new_from_seed_buf(&mut seed).unwrap(); //! //! // The keybundle's public part is the agent's address //! let agent = AgentId::new("bob", keybundle.get_id()); diff --git a/conductor_api/src/interface.rs b/conductor_api/src/interface.rs index 012e0834dd..ef819b8c8a 100644 --- a/conductor_api/src/interface.rs +++ b/conductor_api/src/interface.rs @@ -444,7 +444,7 @@ impl ConductorApiBuilder { /// * `name`: Nickname of this agent configuration /// * `public_address`: Public part of this agents key. Has to match the private key in the /// given key file. - /// * `key_file`: Local path to the file that holds this agent configuration's private key + /// * `keystore_file`: Local path to the file that holds this agent configuration's private key /// /// * `admin/agent/remove` /// Remove an agent from the conductor config. @@ -665,7 +665,7 @@ impl ConductorApiBuilder { let id = Self::get_as_string("id", ¶ms_map)?; let name = Self::get_as_string("name", ¶ms_map)?; let public_address = Self::get_as_string("public_address", ¶ms_map)?; - let key_file = Self::get_as_string("key_file", ¶ms_map)?; + let keystore_file = Self::get_as_string("keystore_file", ¶ms_map)?; let holo_remote_key = params_map .get("holo_remote_key") .map(|k| k.as_bool()) @@ -675,7 +675,7 @@ impl ConductorApiBuilder { id, name, public_address, - key_file, + keystore_file, holo_remote_key, }; conductor_call!(|c| c.add_agent(agent))?; diff --git a/conductor_api/src/key_loaders.rs b/conductor_api/src/key_loaders.rs index d093415677..8753a34b89 100644 --- a/conductor_api/src/key_loaders.rs +++ b/conductor_api/src/key_loaders.rs @@ -1,30 +1,56 @@ -use crate::conductor::base::KeyLoader; -use conductor::passphrase_manager::PassphraseManager; +use crate::{ + conductor::base::KeyLoader, + keystore::{Keystore, Secret, PRIMARY_KEYBUNDLE_ID}, +}; +use conductor::passphrase_manager::{PassphraseManager, PassphraseServiceMock}; use holochain_core_types::error::HolochainError; -use holochain_dpki::{key_bundle::KeyBundle, seed::SeedType, SEED_SIZE}; +use holochain_dpki::SEED_SIZE; use holochain_sodium::{hash::sha256, secbuf::SecBuf}; -use std::{path::PathBuf, sync::Arc}; +use keystore::test_hash_config; +use std::{ + path::PathBuf, + sync::{Arc, Mutex}, +}; /// Key loader callback to use with conductor_api. /// This replaces filesystem access for getting keys mentioned in the config. /// Uses `test_keybundle` to create a deterministic key dependent on the (virtual) file name. -pub fn test_keybundle_loader() -> KeyLoader { - let loader = Box::new(|path: &PathBuf, _pm: &PassphraseManager| { - Ok(test_keybundle(&path.to_str().unwrap().to_string())) +pub fn test_keystore_loader() -> KeyLoader { + let loader = Box::new(|path: &PathBuf, _pm: Arc| { + Ok(test_keystore(&path.to_str().unwrap().to_string())) }) as Box< - FnMut(&PathBuf, &PassphraseManager) -> Result + Send + Sync, + FnMut(&PathBuf, Arc) -> Result + + Send + + Sync, >; Arc::new(loader) } /// Create a deterministic test key from the SHA256 of the given name string. -pub fn test_keybundle(name: &String) -> KeyBundle { +pub fn test_keystore(agent_name: &String) -> Keystore { + let mut keystore = Keystore::new( + mock_passphrase_manager(agent_name.clone()), + test_hash_config(), + ) + .unwrap(); + // Create seed from name - let mut name = SecBuf::with_insecure_from_string(name.clone()); + let mut name = SecBuf::with_insecure_from_string(agent_name.clone()); let mut seed = SecBuf::with_insecure(SEED_SIZE); sha256(&mut name, &mut seed).expect("Could not hash test agent name"); - // Create KeyBundle from seed - KeyBundle::new_from_seed_buf(&mut seed, SeedType::Mock).unwrap() + let secret = Arc::new(Mutex::new(Secret::Seed(seed))); + keystore.add("root_seed", secret).unwrap(); + + keystore + .add_keybundle_from_seed("root_seed", PRIMARY_KEYBUNDLE_ID) + .unwrap(); + keystore +} + +pub fn mock_passphrase_manager(passphrase: String) -> Arc { + Arc::new(PassphraseManager::new(Arc::new(Mutex::new( + PassphraseServiceMock { passphrase }, + )))) } diff --git a/conductor_api/src/keystore.rs b/conductor_api/src/keystore.rs new file mode 100644 index 0000000000..38adfc8846 --- /dev/null +++ b/conductor_api/src/keystore.rs @@ -0,0 +1,776 @@ +use holochain_core_types::{ + agent::Base32, + cas::content::Address, + error::{HcResult, HolochainError}, + signature::Signature, +}; +use holochain_dpki::{ + key_blob::{BlobType, Blobbable, KeyBlob}, + key_bundle::KeyBundle, + keypair::{generate_random_sign_keypair, EncryptingKeyPair, KeyPair, SigningKeyPair}, + seed::Seed, + utils::{ + decrypt_with_passphrase_buf, encrypt_with_passphrase_buf, generate_derived_seed_buf, + generate_random_buf, verify as signingkey_verify, SeedContext, + }, + SEED_SIZE, +}; + +use holochain_sodium::{ + pwhash::{ALG_ARGON2ID13, MEMLIMIT_INTERACTIVE, OPSLIMIT_INTERACTIVE}, + secbuf::SecBuf, +}; + +use conductor::passphrase_manager::PassphraseManager; +use holochain_dpki::{password_encryption::PwHashConfig, seed::SeedType}; +use std::{ + collections::{BTreeMap, HashMap}, + fs::File, + io::prelude::*, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +const PCHECK_HEADER_SIZE: usize = 8; +const PCHECK_HEADER: [u8; 8] = *b"PHCCHECK"; +const PCHECK_RANDOM_SIZE: usize = 32; +const PCHECK_SIZE: usize = PCHECK_RANDOM_SIZE + PCHECK_HEADER_SIZE; +const KEYBUNDLE_SIGNKEY_SUFFIX: &str = ":sign_key"; +const KEYBUNDLE_ENCKEY_SUFFIX: &str = ":enc_key"; +pub const PRIMARY_KEYBUNDLE_ID: &str = "primary_keybundle"; + +pub enum Secret { + SigningKey(SigningKeyPair), + EncryptingKey(EncryptingKeyPair), + Seed(SecBuf), +} + +enum KeyType { + Signing, + Encrypting, +} + +/// A type for providing high-level crypto functions and managing secrets securely. +/// Keystore can store an arbitrary number of named secrets such as key pairs and seeds. +/// It can be serialized and deserialized with serde and stores secrets in encrypted [KeyBlob]s, +/// both in the serialized format as well as in memory, as long as secrets are not used. +/// Once a secret is requested, it gets decrypted and cached in the clear in secure memory +/// ([SecBuf]). +/// +/// Passphrases for de-/encryption are requested from the [PassphraseManager] that has to be +/// provided on creation. +/// +/// Keystore makes sure that all secrets are encrypted with the same passphrase, so although +/// every secrete is stored separately in its own [KeyBlob], the whole Keystore *has* a logical +/// passphrase. If a function that requires a passphrase receives a different passphrase from the +/// [PassphraseManager] as was used to create this keystore, it will fail. +/// +/// It provides high-level functions for key/seed derivation such as: +/// * [add_seed_from_seed] +/// * [add_key_from_seed] +/// * [add_signing_key_from_seed] +/// * [add_encrypting_key_from_seed] +/// +/// and a [sign] function for using stored keys to create signatures. +/// +#[derive(Serialize, Deserialize)] +pub struct Keystore { + /// This stores the cipher text of [PCHECK_HEADER] plus 32 random bytes encrypted + /// with the keystore's passphrase. + /// Any encryption will only happen after checking the provided passphrase to be + /// able to decrypt this cipher and get back the [PCHECK_HEADER], as to make sure + /// that every (separately) encrypted secret within this store is encrypted with + /// the same passphrase. + passphrase_check: String, + + /// These are the secrets (keys/seeds) stored encrypted, by name. + secrets: BTreeMap, + + // The following fields are transient, i.e. not serialized to the keystore file: + /// Using a secret from [secrets] will result in decrypting the secret and + /// storing it in this cache. + /// TODO: maybe clear the cache for certain (not agent keys) items after some time? + #[serde(skip_serializing, skip_deserializing)] + cache: HashMap>>, + + /// Requested for passphrases needed to decrypt secrets + #[serde(skip_serializing, skip_deserializing)] + passphrase_manager: Option>, + + /// Hash config used for hashing passphrases. + /// Gets sets to non-default for quick tests. + #[serde(skip_serializing, skip_deserializing)] + hash_config: Option, +} + +fn make_passphrase_check( + passphrase: &mut SecBuf, + hash_config: Option, +) -> HcResult { + let mut check_buf = SecBuf::with_secure(PCHECK_SIZE); + check_buf.randomize(); + check_buf.write(0, &PCHECK_HEADER).unwrap(); + encrypt_with_passphrase_buf(&mut check_buf, passphrase, hash_config) +} + +impl Keystore { + /// Create a new keystore. + /// This will query `passphrase_manager` immediately to set a passphrase for the keystore. + pub fn new( + passphrase_manager: Arc, + hash_config: Option, + ) -> HcResult { + Ok(Keystore { + passphrase_check: make_passphrase_check( + &mut passphrase_manager.get_passphrase()?, + hash_config.clone(), + )?, + secrets: BTreeMap::new(), + cache: HashMap::new(), + passphrase_manager: Some(passphrase_manager), + hash_config, + }) + } + + /// Load a keystore from file. + /// This won't ask for a passphrase until a secret is used via the other functions. + /// Secrets will get loaded to memory instantly but stay encrypted until requested. + pub fn new_from_file( + path: PathBuf, + passphrase_manager: Arc, + hash_config: Option, + ) -> HcResult { + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let mut keystore: Keystore = serde_json::from_str(&contents)?; + keystore.hash_config = hash_config; + keystore.passphrase_manager = Some(passphrase_manager); + Ok(keystore) + } + + /// This tries to decrypt `passphrase_check` with the given passphrase and + /// expects to read `PCHECK_HEADER` from the decrypted text, ignoring the + /// random bytes following the header. + fn check_passphrase(&self, mut passphrase: &mut SecBuf) -> HcResult { + let mut decrypted_buf = decrypt_with_passphrase_buf( + &self.passphrase_check, + &mut passphrase, + self.hash_config.clone(), + PCHECK_SIZE, + )?; + let mut decrypted_header = SecBuf::with_insecure(PCHECK_HEADER_SIZE); + let decrypted_buf = decrypted_buf.read_lock(); + decrypted_header.write(0, &decrypted_buf[0..PCHECK_HEADER_SIZE])?; + let mut expected_header = SecBuf::with_secure(PCHECK_HEADER_SIZE); + expected_header.write(0, &PCHECK_HEADER)?; + Ok(decrypted_header.compare(&mut expected_header) == 0) + } + + pub fn change_passphrase( + &mut self, + old_passphrase: &mut SecBuf, + new_passphrase: &mut SecBuf, + ) -> HcResult<()> { + if !self.check_passphrase(old_passphrase)? { + return Err(HolochainError::ErrorGeneric("Bad passphrase".to_string())); + } + self.passphrase_check = make_passphrase_check(new_passphrase, self.hash_config.clone())?; + Ok(()) + } + + /// This function expects the named secret in `secrets`, decrypts it and stores the decrypted + /// representation in `cache`. + fn decrypt(&mut self, id_str: &String) -> HcResult<()> { + let blob = self + .secrets + .get(id_str) + .ok_or(HolochainError::new("Secret not found"))?; + let mut passphrase = self.passphrase_manager.as_ref()?.get_passphrase()?; + let secret = match blob.blob_type { + BlobType::Seed => { + Secret::Seed(Seed::from_blob(blob, &mut passphrase, self.hash_config.clone())?.buf) + } + BlobType::SigningKey => Secret::SigningKey(SigningKeyPair::from_blob( + blob, + &mut passphrase, + self.hash_config.clone(), + )?), + BlobType::EncryptingKey => Secret::EncryptingKey(EncryptingKeyPair::from_blob( + blob, + &mut passphrase, + self.hash_config.clone(), + )?), + _ => { + return Err(HolochainError::ErrorGeneric(format!( + "Tried to decrypt unsupported BlobType in Keystore: {}", + id_str + ))); + } + }; + self.cache + .insert(id_str.clone(), Arc::new(Mutex::new(secret))); + Ok(()) + } + + /// This expects an unencrypted named secret in `cache`, encrypts it and stores the + /// encrypted representation in `secrets`. + fn encrypt(&mut self, id_str: &String) -> HcResult<()> { + let secret = self + .cache + .get(id_str) + .ok_or(HolochainError::new("Secret not found"))?; + let mut passphrase = self.passphrase_manager.as_ref()?.get_passphrase()?; + self.check_passphrase(&mut passphrase)?; + let blob = match *secret.lock()? { + Secret::Seed(ref mut buf) => { + let mut owned_buf = SecBuf::with_insecure(buf.len()); + owned_buf.write(0, &*buf.read_lock())?; + Seed::new(owned_buf, SeedType::OneShot).as_blob( + &mut passphrase, + "".to_string(), + self.hash_config.clone(), + ) + } + Secret::SigningKey(ref mut key) => { + key.as_blob(&mut passphrase, "".to_string(), self.hash_config.clone()) + } + Secret::EncryptingKey(ref mut key) => { + key.as_blob(&mut passphrase, "".to_string(), self.hash_config.clone()) + } + }?; + self.secrets.insert(id_str.clone(), blob); + Ok(()) + } + + /// Serialize the keystore to a file. + pub fn save(&self, path: PathBuf) -> HcResult<()> { + let json_string = serde_json::to_string(self)?; + let mut file = File::create(path)?; + file.write_all(&json_string.as_bytes())?; + Ok(()) + } + + /// return a list of the identifiers stored in the keystore + pub fn list(&self) -> Vec { + self.secrets.keys().map(|k| k.to_string()).collect() + } + + /// adds a secret to the keystore + pub fn add(&mut self, dst_id_str: &str, secret: Arc>) -> HcResult<()> { + let dst_id = self.check_dst_identifier(dst_id_str)?; + self.cache.insert(dst_id.clone(), secret); + self.encrypt(&dst_id)?; + Ok(()) + } + + /// adds a random root seed into the keystore + pub fn add_random_seed(&mut self, dst_id_str: &str, size: usize) -> HcResult<()> { + let dst_id = self.check_dst_identifier(dst_id_str)?; + let seed_buf = generate_random_buf(size); + let secret = Arc::new(Mutex::new(Secret::Seed(seed_buf))); + self.cache.insert(dst_id.clone(), secret); + self.encrypt(&dst_id)?; + Ok(()) + } + + fn check_dst_identifier(&self, dst_id_str: &str) -> HcResult { + let dst_id = dst_id_str.to_string(); + if self.secrets.contains_key(&dst_id) { + return Err(HolochainError::ErrorGeneric( + "identifier already exists".to_string(), + )); + } + Ok(dst_id) + } + + /// gets a secret from the keystore + pub fn get(&mut self, src_id_str: &str) -> HcResult>> { + let src_id = src_id_str.to_string(); + if !self.secrets.contains_key(&src_id) { + return Err(HolochainError::ErrorGeneric( + "unknown source identifier".to_string(), + )); + } + + if !self.cache.contains_key(&src_id) { + self.decrypt(&src_id)?; + } + + Ok(self.cache.get(&src_id).unwrap().clone()) // unwrap ok because we made sure src exists + } + + fn check_identifiers( + &mut self, + src_id_str: &str, + dst_id_str: &str, + ) -> HcResult<(Arc>, String)> { + let dst_id = self.check_dst_identifier(dst_id_str)?; + let src_secret = self.get(src_id_str)?; + Ok((src_secret, dst_id)) + } + + /// adds a derived seed into the keystore + pub fn add_seed_from_seed( + &mut self, + src_id_str: &str, + dst_id_str: &str, + context: &SeedContext, + index: u64, + ) -> HcResult<()> { + let (src_secret, dst_id) = self.check_identifiers(src_id_str, dst_id_str)?; + let secret = { + let mut src_secret = src_secret.lock().unwrap(); + match *src_secret { + Secret::Seed(ref mut src) => { + let seed = generate_derived_seed_buf(src, context, index, SEED_SIZE)?; + Arc::new(Mutex::new(Secret::Seed(seed))) + } + _ => { + return Err(HolochainError::ErrorGeneric( + "source secret is not a root seed".to_string(), + )); + } + } + }; + self.cache.insert(dst_id.clone(), secret); + self.encrypt(&dst_id)?; + + Ok(()) + } + + /// adds a keypair into the keystore based on a seed already in the keystore + /// returns the public key + fn add_key_from_seed( + &mut self, + src_id_str: &str, + dst_id_str: &str, + key_type: KeyType, + ) -> HcResult { + let (src_secret, dst_id) = self.check_identifiers(src_id_str, dst_id_str)?; + let (secret, public_key) = { + let mut src_secret = src_secret.lock().unwrap(); + let ref mut seed_buf = match *src_secret { + Secret::Seed(ref mut src) => src, + _ => { + return Err(HolochainError::ErrorGeneric( + "source secret is not a seed".to_string(), + )); + } + }; + match key_type { + KeyType::Signing => { + let key_pair = SigningKeyPair::new_from_seed(seed_buf)?; + let public_key = key_pair.public(); + ( + Arc::new(Mutex::new(Secret::SigningKey(key_pair))), + public_key, + ) + } + KeyType::Encrypting => { + let key_pair = EncryptingKeyPair::new_from_seed(seed_buf)?; + let public_key = key_pair.public(); + ( + Arc::new(Mutex::new(Secret::EncryptingKey(key_pair))), + public_key, + ) + } + } + }; + self.cache.insert(dst_id.clone(), secret); + self.encrypt(&dst_id)?; + + Ok(public_key) + } + + /// adds a signing keypair into the keystore based on a seed already in the keystore + /// returns the public key + pub fn add_signing_key_from_seed( + &mut self, + src_id_str: &str, + dst_id_str: &str, + ) -> HcResult { + self.add_key_from_seed(src_id_str, dst_id_str, KeyType::Signing) + } + + /// adds an encrypting keypair into the keystore based on a seed already in the keystore + /// returns the public key + pub fn add_encrypting_key_from_seed( + &mut self, + src_id_str: &str, + dst_id_str: &str, + ) -> HcResult { + self.add_key_from_seed(src_id_str, dst_id_str, KeyType::Encrypting) + } + + /// adds a keybundle into the keystore based on a seed already in the keystore by + /// adding two keypair secrets (signing and encrypting) under the named prefix + /// returns the public keys of the secrets + pub fn add_keybundle_from_seed( + &mut self, + src_id_str: &str, + dst_id_prefix_str: &str, + ) -> HcResult<(Base32, Base32)> { + let dst_sign_id_str = [dst_id_prefix_str, KEYBUNDLE_SIGNKEY_SUFFIX].join(""); + let dst_enc_id_str = [dst_id_prefix_str, KEYBUNDLE_ENCKEY_SUFFIX].join(""); + + let sign_pub_key = + self.add_key_from_seed(src_id_str, &dst_sign_id_str, KeyType::Signing)?; + let enc_pub_key = + self.add_key_from_seed(src_id_str, &dst_enc_id_str, KeyType::Encrypting)?; + Ok((sign_pub_key, enc_pub_key)) + } + + /// adds a keybundle into the keystore based on a seed already in the keystore by + /// adding two keypair secrets (signing and encrypting) under the named prefix + /// returns the public keys of the secrets + pub fn get_keybundle(&mut self, src_id_prefix_str: &str) -> HcResult { + let src_sign_id_str = [src_id_prefix_str, KEYBUNDLE_SIGNKEY_SUFFIX].join(""); + let src_enc_id_str = [src_id_prefix_str, KEYBUNDLE_ENCKEY_SUFFIX].join(""); + + let sign_secret = self.get(&src_sign_id_str)?; + let mut sign_secret = sign_secret.lock().unwrap(); + let sign_key = match *sign_secret { + Secret::SigningKey(ref mut key_pair) => { + let mut buf = SecBuf::with_secure(key_pair.private().len()); + let pub_key = key_pair.public(); + let lock = key_pair.private().read_lock(); + buf.write(0, &**lock)?; + SigningKeyPair::new(pub_key, buf) + } + _ => { + return Err(HolochainError::ErrorGeneric( + "source secret is not a signing key".to_string(), + )); + } + }; + + let enc_secret = self.get(&src_enc_id_str)?; + let mut enc_secret = enc_secret.lock().unwrap(); + let enc_key = match *enc_secret { + Secret::EncryptingKey(ref mut key_pair) => { + let mut buf = SecBuf::with_secure(key_pair.private().len()); + let pub_key = key_pair.public(); + let lock = key_pair.private().read_lock(); + buf.write(0, &**lock)?; + EncryptingKeyPair::new(pub_key, buf) + } + _ => { + return Err(HolochainError::ErrorGeneric( + "source secret is not an encrypting key".to_string(), + )); + } + }; + + Ok(KeyBundle::new(sign_key, enc_key)?) + } + + /// signs some data using a keypair in the keystore + /// returns the signature + pub fn sign(&mut self, src_id_str: &str, data: String) -> HcResult { + let src_secret = self.get(src_id_str)?; + let mut src_secret = src_secret.lock().unwrap(); + match *src_secret { + Secret::SigningKey(ref mut key_pair) => { + let mut data_buf = SecBuf::with_insecure_from_string(data); + + let mut signature_buf = key_pair.sign(&mut data_buf)?; + let buf = signature_buf.read_lock(); + // Return as base64 encoded string + let signature_str = base64::encode(&**buf); + Ok(Signature::from(signature_str)) + } + _ => { + return Err(HolochainError::ErrorGeneric( + "source secret is not a signing key".to_string(), + )); + } + } + } +} + +/// verifies data and signature against a public key +pub fn verify(public_key: Base32, data: String, signature: Signature) -> HcResult { + signingkey_verify(Address::from(public_key), data, signature) +} + +/// creates a one-time private key and sign data returning the signature and the public key +pub fn sign_one_time(data: String) -> HcResult<(Base32, Signature)> { + let mut data_buf = SecBuf::with_insecure_from_string(data); + let mut sign_keys = generate_random_sign_keypair()?; + + let mut signature_buf = sign_keys.sign(&mut data_buf)?; + let buf = signature_buf.read_lock(); + // Return as base64 encoded string + let signature_str = base64::encode(&**buf); + Ok((sign_keys.public, Signature::from(signature_str))) +} + +pub fn test_hash_config() -> Option { + Some(PwHashConfig( + OPSLIMIT_INTERACTIVE, + MEMLIMIT_INTERACTIVE, + ALG_ARGON2ID13, + )) +} + +#[cfg(test)] +pub mod tests { + use super::*; + use base64; + use conductor::passphrase_manager::PassphraseServiceMock; + use holochain_dpki::utils; + + fn mock_passphrase_manager(passphrase: String) -> Arc { + Arc::new(PassphraseManager::new(Arc::new(Mutex::new( + PassphraseServiceMock { passphrase }, + )))) + } + + fn new_test_keystore(passphrase: String) -> Keystore { + Keystore::new(mock_passphrase_manager(passphrase), test_hash_config()).unwrap() + } + + fn random_test_passphrase() -> String { + let mut buf = utils::generate_random_buf(10); + let read_lock = buf.read_lock(); + String::from_utf8_lossy(&*read_lock).to_string() + } + + #[test] + fn test_keystore_new() { + let random_passphrase = random_test_passphrase(); + let keystore = new_test_keystore(random_passphrase.clone()); + let mut random_passphrase = SecBuf::with_insecure_from_string(random_passphrase); + assert!(keystore.list().is_empty()); + assert_eq!(keystore.check_passphrase(&mut random_passphrase), Ok(true)); + let mut another_random_passphrase = utils::generate_random_buf(10); + assert_eq!( + keystore.check_passphrase(&mut another_random_passphrase), + Ok(false) + ); + } + + #[test] + fn test_save_load_roundtrip() { + let random_passphrase = random_test_passphrase(); + let mut keystore = new_test_keystore(random_passphrase.clone()); + assert_eq!(keystore.add_random_seed("my_root_seed", SEED_SIZE), Ok(())); + assert_eq!(keystore.list(), vec!["my_root_seed".to_string()]); + + let mut path = PathBuf::new(); + path.push("tmp-test/test-keystore"); + keystore.save(path.clone()).unwrap(); + + let mut loaded_keystore = Keystore::new_from_file( + path.clone(), + mock_passphrase_manager(random_passphrase), + test_hash_config(), + ) + .unwrap(); + assert_eq!(loaded_keystore.list(), vec!["my_root_seed".to_string()]); + + let secret1 = keystore.get("my_root_seed").unwrap(); + let expected_seed = match *secret1.lock().unwrap() { + Secret::Seed(ref mut buf) => { + let lock = buf.read_lock(); + String::from_utf8_lossy(&**lock).to_string() + } + _ => unreachable!(), + }; + + let secret2 = loaded_keystore.get("my_root_seed").unwrap(); + let loaded_seed = match *secret2.lock().unwrap() { + Secret::Seed(ref mut buf) => { + let lock = buf.read_lock(); + String::from_utf8_lossy(&**lock).to_string() + } + _ => unreachable!(), + }; + + assert_eq!(expected_seed, loaded_seed); + } + + #[test] + fn test_keystore_change_passphrase() { + let random_passphrase = random_test_passphrase(); + let mut keystore = new_test_keystore(random_passphrase.clone()); + let mut random_passphrase = SecBuf::with_insecure_from_string(random_passphrase); + let mut another_random_passphrase = utils::generate_random_buf(10); + assert!( + // wrong passphrase + keystore + .change_passphrase(&mut another_random_passphrase, &mut random_passphrase) + .is_err() + ); + assert_eq!( + keystore.change_passphrase(&mut random_passphrase, &mut another_random_passphrase), + Ok(()) + ); + // check that passphrase was actually changed + assert_eq!(keystore.check_passphrase(&mut random_passphrase), Ok(false)); + assert_eq!( + keystore.check_passphrase(&mut another_random_passphrase), + Ok(true) + ); + } + + #[test] + fn test_keystore_add_random_seed() { + let mut keystore = new_test_keystore(random_test_passphrase()); + + assert_eq!(keystore.add_random_seed("my_root_seed", SEED_SIZE), Ok(())); + assert_eq!(keystore.list(), vec!["my_root_seed".to_string()]); + assert_eq!( + keystore.add_random_seed("my_root_seed", SEED_SIZE), + Err(HolochainError::ErrorGeneric( + "identifier already exists".to_string() + )) + ); + } + + #[test] + fn test_keystore_add_seed_from_seed() { + let mut keystore = new_test_keystore(random_test_passphrase()); + + let context = SeedContext::new(*b"SOMECTXT"); + + assert_eq!( + keystore.add_seed_from_seed("my_root_seed", "my_second_seed", &context, 1), + Err(HolochainError::ErrorGeneric( + "unknown source identifier".to_string() + )) + ); + + let _ = keystore.add_random_seed("my_root_seed", SEED_SIZE); + + assert_eq!( + keystore.add_seed_from_seed("my_root_seed", "my_second_seed", &context, 1), + Ok(()) + ); + + assert!(keystore.list().contains(&"my_root_seed".to_string())); + assert!(keystore.list().contains(&"my_second_seed".to_string())); + + assert_eq!( + keystore.add_seed_from_seed("my_root_seed", "my_second_seed", &context, 1), + Err(HolochainError::ErrorGeneric( + "identifier already exists".to_string() + )) + ); + } + + #[test] + fn test_keystore_add_signing_key_from_seed() { + let mut keystore = new_test_keystore(random_test_passphrase()); + + assert_eq!( + keystore.add_signing_key_from_seed("my_root_seed", "my_keypair"), + Err(HolochainError::ErrorGeneric( + "unknown source identifier".to_string() + )) + ); + + let _ = keystore.add_random_seed("my_root_seed", SEED_SIZE); + + let result = keystore.add_signing_key_from_seed("my_root_seed", "my_keypair"); + assert!(!result.is_err()); + let pubkey = result.unwrap(); + assert!(format!("{}", pubkey).starts_with("Hc")); + + assert_eq!( + keystore.add_signing_key_from_seed("my_root_seed", "my_keypair"), + Err(HolochainError::ErrorGeneric( + "identifier already exists".to_string() + )) + ); + } + + #[test] + fn test_keystore_sign() { + let mut keystore = new_test_keystore(random_test_passphrase()); + let _ = keystore.add_random_seed("my_root_seed", SEED_SIZE); + + let data = base64::encode("the data to sign"); + + assert_eq!( + keystore.sign("my_keypair", data.clone()), + Err(HolochainError::ErrorGeneric( + "unknown source identifier".to_string() + )) + ); + + let public_key = keystore + .add_signing_key_from_seed("my_root_seed", "my_keypair") + .unwrap(); + + let result = keystore.sign("my_keypair", data.clone()); + assert!(!result.is_err()); + + let signature = result.unwrap(); + assert_eq!(String::from(signature.clone()).len(), 88); //88 is the size of a base64ized signature buf + + let result = verify(public_key, data.clone(), signature); + assert!(!result.is_err()); + assert!(result.unwrap()); + + keystore + .add_encrypting_key_from_seed("my_root_seed", "my_enc_keypair") + .unwrap(); + assert_eq!( + keystore.sign("my_enc_keypair", data.clone()), + Err(HolochainError::ErrorGeneric( + "source secret is not a signing key".to_string() + )) + ); + } + + #[test] + fn test_keystore_sign_one_time() { + let data = base64::encode("the data to sign"); + let result = sign_one_time(data.clone()); + assert!(!result.is_err()); + + let (public_key, signature) = result.unwrap(); + + assert_eq!(String::from(signature.clone()).len(), 88); //88 is the size of a base64ized signature buf + + let result = verify(public_key, data, signature); + assert!(!result.is_err()); + assert!(result.unwrap()); + } + + #[test] + fn test_keystore_keybundle() { + let mut keystore = new_test_keystore(random_test_passphrase()); + + assert_eq!( + keystore.add_keybundle_from_seed("my_root_seed", "my_keybundle"), + Err(HolochainError::ErrorGeneric( + "unknown source identifier".to_string() + )) + ); + + let _ = keystore.add_random_seed("my_root_seed", SEED_SIZE); + + let result = keystore.add_keybundle_from_seed("my_root_seed", "my_keybundle"); + assert!(!result.is_err()); + let (sign_pubkey, enc_pubkey) = result.unwrap(); + assert!(format!("{}", sign_pubkey).starts_with("Hc")); + + assert_eq!( + keystore.add_keybundle_from_seed("my_root_seed", "my_keybundle"), + Err(HolochainError::ErrorGeneric( + "identifier already exists".to_string() + )) + ); + + let result = keystore.get_keybundle("my_keybundle"); + assert!(!result.is_err()); + let key_bundle = result.unwrap(); + + assert_eq!(key_bundle.sign_keys.public(), sign_pubkey); + assert_eq!(key_bundle.enc_keys.public(), enc_pubkey); + } + +} diff --git a/conductor_api/src/lib.rs b/conductor_api/src/lib.rs index ac62f56fbb..c428ed57a6 100644 --- a/conductor_api/src/lib.rs +++ b/conductor_api/src/lib.rs @@ -140,6 +140,7 @@ pub mod holochain; pub mod interface; pub mod interface_impls; pub mod key_loaders; +pub mod keystore; pub mod logger; pub mod static_file_server; diff --git a/core/Cargo.toml b/core/Cargo.toml index 5af04ab422..3c1d60988b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -33,7 +33,7 @@ holochain_common = { path = "../common" } holochain_core_types_derive = { path = "../core_types_derive" } holochain_core_types = { path = "../core_types" } holochain_cas_implementations = { path = "../cas_implementations" } -holochain_dpki = { path = "../hc_dpki" } +holochain_dpki = { path = "../dpki" } holochain_sodium = { path = "../sodium" } boolinator = "=2.4.0" jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc" } diff --git a/core/src/nucleus/actions/call_zome_function.rs b/core/src/nucleus/actions/call_zome_function.rs index 0a613c141b..1f385cd777 100644 --- a/core/src/nucleus/actions/call_zome_function.rs +++ b/core/src/nucleus/actions/call_zome_function.rs @@ -18,8 +18,7 @@ use holochain_core_types::{ json::JsonString, signature::{Provenance, Signature}, }; -use holochain_dpki::utils; -use holochain_sodium::secbuf::SecBuf; +use holochain_dpki::utils::Verify; use base64; use futures::{ @@ -203,20 +202,7 @@ pub fn verify_call_sig>( parameters: J, ) -> bool { let what_was_signed = encode_call_data_for_signing(function, parameters); - let mut data = SecBuf::with_insecure_from_string(what_was_signed); - let sig_string = String::from(provenance.signature()); - let signature_bytes: Vec = base64::decode(&sig_string).expect("should decode"); - let mut signature_buf = SecBuf::with_insecure(signature_bytes.len()); - signature_buf - .write(0, signature_bytes.as_slice()) - .expect("SecBuf must be writeable"); - - utils::verify( - provenance.source().to_string(), - &mut data, - &mut signature_buf, - ) - .unwrap() + provenance.verify(what_was_signed).unwrap() } /// creates a capability request for a zome call by signing the function name and parameters diff --git a/core/src/nucleus/ribosome/api/verify_signature.rs b/core/src/nucleus/ribosome/api/verify_signature.rs index 0ab2f858a0..fd011969ea 100644 --- a/core/src/nucleus/ribosome/api/verify_signature.rs +++ b/core/src/nucleus/ribosome/api/verify_signature.rs @@ -1,6 +1,5 @@ use crate::nucleus::ribosome::{api::ZomeApiResult, Runtime}; -use holochain_core_types::signature::Provenance; -use holochain_sodium::secbuf::SecBuf; +use holochain_dpki::utils::Verify; use holochain_wasm_utils::api_serialization::verify_signature::VerifySignatureArgs; use std::convert::TryFrom; use wasmi::{RuntimeArgs, RuntimeValue}; @@ -27,14 +26,10 @@ pub fn invoke_verify_signature(runtime: &mut Runtime, args: &RuntimeArgs) -> Zom } }; - let Provenance(addr, signature) = verification_args.provenance; - - let mut message_data = SecBuf::with_insecure_from_string(verification_args.payload.clone()); - let mut signature_data = SecBuf::with_insecure_from_string(signature.into()); - - let verification_result = - holochain_dpki::utils::verify(addr.into(), &mut message_data, &mut signature_data) - .unwrap_or(false); + let verification_result = verification_args + .provenance + .verify(verification_args.payload.clone()) + .unwrap_or(false); runtime.store_as_json_string(verification_result) } diff --git a/core/src/nucleus/validation/provenances.rs b/core/src/nucleus/validation/provenances.rs index 8895fa3e92..f864c1401e 100644 --- a/core/src/nucleus/validation/provenances.rs +++ b/core/src/nucleus/validation/provenances.rs @@ -1,8 +1,7 @@ use crate::nucleus::validation::{ValidationError, ValidationResult}; use boolinator::Boolinator; use holochain_core_types::validation::ValidationData; -use holochain_dpki; -use holochain_sodium::secbuf::SecBuf; +use holochain_dpki::utils::Verify; pub fn validate_provenances(validation_data: &ValidationData) -> ValidationResult { let header = &validation_data.package.chain_header; @@ -10,35 +9,20 @@ pub fn validate_provenances(validation_data: &ValidationData) -> ValidationResul .provenances() .iter() .map(|provenance| { - let author_id = &provenance.source(); - let signature = &provenance.signature(); - let signature_string: String = signature.clone().into(); - let signature_bytes: Vec = base64::decode(&signature_string).map_err(|_| { - ValidationError::Fail("Signature syntactically invalid".to_string()) - })?; - - let mut signature_buf = SecBuf::with_insecure(signature_bytes.len()); - signature_buf - .write(0, signature_bytes.as_slice()) - .expect("SecBuf must be writeable"); - - let mut message_buf = - SecBuf::with_insecure_from_string(header.entry_address().to_string()); - - let maybe_has_authored = holochain_dpki::utils::verify(author_id.to_string(), &mut message_buf, &mut signature_buf); + let maybe_has_authored = provenance.verify(header.entry_address().to_string()); match maybe_has_authored { Err(_) => { Err(ValidationError::Fail(format!( "Signature of entry {} from author {} failed to verify public signing key. Key might be invalid.", header.entry_address(), - author_id, + provenance.source(), ))) }, Ok(has_authored) => { has_authored.ok_or(ValidationError::Fail(format!( "Signature of entry {} from author {} invalid", header.entry_address(), - author_id, + provenance.source(), ))) }, } diff --git a/doc/holochain_101/src/conductor_agents.md b/doc/holochain_101/src/conductor_agents.md index cd90456f87..3f4752eab9 100644 --- a/doc/holochain_101/src/conductor_agents.md +++ b/doc/holochain_101/src/conductor_agents.md @@ -13,10 +13,10 @@ Give an ID of your choice to the agent Give a name of your choice to the agent #### `public_address`: `string` -A public address for the agent. Run ```hc keygen``` and copy the public address to this value +A public address for the agent. Run ```hc keygen``` and copy the public address to this value -#### `key_file`: `string` -Path to the private key file for this agent. Copy the path from when you ran ```hc keygen``` into this value. +#### `keystore_file`: `string` +Path to the keystore file for this agent. Copy the path from when you ran ```hc keygen``` into this value. ### Example @@ -25,5 +25,5 @@ Path to the private key file for this agent. Copy the path from when you ran ``` id = "test_agent2" name = "HoloTester2" public_address = "HcSCJts3fQ6Y4c4xr795Zj6inhTjecrfrsSFOrU9Jmnhnj5bdoXkoPSJivrm3wi" -key_file = "/org.holochain.holochain/keys/HcSCJts3fQ6Y4c4xr795Zj6inhTjecrfrsSFOrU9Jmnhnj5bdoXkoPSJivrm3wi" +keystore_file = "/org.holochain.holochain/keys/HcSCJts3fQ6Y4c4xr795Zj6inhTjecrfrsSFOrU9Jmnhnj5bdoXkoPSJivrm3wi" ``` diff --git a/hc_dpki/Cargo.toml b/dpki/Cargo.toml similarity index 100% rename from hc_dpki/Cargo.toml rename to dpki/Cargo.toml diff --git a/hc_dpki/src/key_blob.rs b/dpki/src/key_blob.rs old mode 100644 new mode 100755 similarity index 52% rename from hc_dpki/src/key_blob.rs rename to dpki/src/key_blob.rs index 10c83dcde4..aafeea17d7 --- a/hc_dpki/src/key_blob.rs +++ b/dpki/src/key_blob.rs @@ -32,8 +32,9 @@ pub struct KeyBlob { pub enum BlobType { Seed, KeyBundle, + SigningKey, + EncryptingKey, // TODO futur blobbables? - // KeyPair, // Key, } @@ -71,12 +72,8 @@ pub trait Blobbable { "Invalid buf size for Blobbing".to_string(), )); } - // encrypt buffer - let encrypted_blob = password_encryption::pw_enc(data_buf, passphrase, config)?; - // Serialize and convert to base64 - let serialized_blob = - serde_json::to_string(&encrypted_blob).expect("Failed to serialize Blob"); - Ok(base64::encode(&serialized_blob)) + + utils::encrypt_with_passphrase_buf(data_buf, passphrase, config) } /// Get the data buf back from a Blob @@ -91,22 +88,7 @@ pub trait Blobbable { "Blob type mismatch while unblobbing".to_string(), )); } - // Decode base64 - let blob_b64 = base64::decode(&blob.data)?; - // Deserialize - let blob_json = str::from_utf8(&blob_b64)?; - let encrypted_blob: EncryptedData = serde_json::from_str(&blob_json)?; - // Decrypt - let mut decrypted_data = SecBuf::with_secure(Self::blob_size()); - pw_dec(&encrypted_blob, passphrase, &mut decrypted_data, config)?; - // Check size - if decrypted_data.len() != Self::blob_size() { - return Err(HolochainError::ErrorGeneric( - "Invalid Blob size".to_string(), - )); - } - // Done - Ok(decrypted_data) + utils::decrypt_with_passphrase_buf(&blob.data, passphrase, config, Self::blob_size()) } } @@ -232,7 +214,7 @@ impl Blobbable for KeyBundle { // Done Ok(KeyBlob { - seed_type: self.seed_type.clone(), + seed_type: SeedType::Mock, blob_type: BlobType::KeyBundle, hint, data: encoded_blob, @@ -279,23 +261,223 @@ impl Blobbable for KeyBundle { EncryptingKeyPair::encode_pub_key(&mut pub_enc), priv_enc, ), - seed_type: blob.seed_type.clone(), }) } } +//-------------------------------------------------------------------------------------------------- +// SigningKey +//-------------------------------------------------------------------------------------------------- + +const SIGNING_KEY_BLOB_FORMAT_VERSION: u8 = 1; + +const SIGNING_KEY_BLOB_SIZE: usize = 1 // version byte + + sign::PUBLICKEYBYTES + + sign::SECRETKEYBYTES; + +pub const SIGNING_KEY_BLOB_SIZE_ALIGNED: usize = ((SIGNING_KEY_BLOB_SIZE + 8 - 1) / 8) * 8; + +impl Blobbable for SigningKeyPair { + fn blob_type() -> BlobType { + BlobType::SigningKey + } + + fn blob_size() -> usize { + SIGNING_KEY_BLOB_SIZE_ALIGNED + } + + /// Generate an encrypted blob for persistence + /// @param {SecBuf} passphrase - the encryption passphrase + /// @param {string} hint - additional info / description for the bundle + /// @param {Option} config - Settings for pwhash + fn as_blob( + &mut self, + passphrase: &mut SecBuf, + hint: String, + config: Option, + ) -> HcResult { + // Initialize buffer + let mut data_buf = SecBuf::with_secure(SIGNING_KEY_BLOB_SIZE_ALIGNED); + let mut offset: usize = 0; + // Write version + data_buf + .write(0, &[SIGNING_KEY_BLOB_FORMAT_VERSION]) + .unwrap(); + offset += 1; + // Write public signing key + let key = self.decode_pub_key(); + assert_eq!(sign::PUBLICKEYBYTES, key.len()); + data_buf + .write(offset, &key) + .expect("Failed blobbing public signing key"); + offset += sign::PUBLICKEYBYTES; + // Write private signing key + data_buf + .write(offset, &**self.private.read_lock()) + .expect("Failed blobbing private signing key"); + offset += sign::SECRETKEYBYTES; + + // Finalize + let encoded_blob = Self::finalize_blobbing(&mut data_buf, passphrase, config)?; + + // Done + Ok(KeyBlob { + seed_type: SeedType::Mock, + blob_type: BlobType::SigningKey, + hint, + data: encoded_blob, + }) + } + + /// Construct the pairs from an encrypted blob + /// @param {object} bundle - persistence info + /// @param {SecBuf} passphrase - decryption passphrase + /// @param {Option} config - Settings for pwhash + fn from_blob( + blob: &KeyBlob, + passphrase: &mut SecBuf, + config: Option, + ) -> HcResult { + // Retrieve data buf from blob + let mut keybundle_blob = Self::unblob(blob, passphrase, config)?; + + // Deserialize manually + let mut pub_sign = SecBuf::with_insecure(sign::PUBLICKEYBYTES); + let mut priv_sign = SecBuf::with_secure(sign::SECRETKEYBYTES); + { + let keybundle_blob = keybundle_blob.read_lock(); + if keybundle_blob[0] != SIGNING_KEY_BLOB_FORMAT_VERSION { + return Err(HolochainError::ErrorGeneric(format!( + "Invalid SigningKey Blob Format: v{:?} != v{:?}", + keybundle_blob[0], SIGNING_KEY_BLOB_FORMAT_VERSION + ))); + } + pub_sign.write(0, &keybundle_blob[1..33])?; + priv_sign.write(0, &keybundle_blob[33..97])?; + } + // Done + Ok(SigningKeyPair::new( + SigningKeyPair::encode_pub_key(&mut pub_sign), + priv_sign, + )) + } +} + +//-------------------------------------------------------------------------------------------------- +// EncryptingKey +//-------------------------------------------------------------------------------------------------- + +const ENCRYPTING_KEY_BLOB_FORMAT_VERSION: u8 = 1; + +const ENCRYPTING_KEY_BLOB_SIZE: usize = 1 // version byte + + kx::PUBLICKEYBYTES + + kx::SECRETKEYBYTES; + +pub const ENCRYPTING_KEY_BLOB_SIZE_ALIGNED: usize = ((ENCRYPTING_KEY_BLOB_SIZE + 8 - 1) / 8) * 8; + +impl Blobbable for EncryptingKeyPair { + fn blob_type() -> BlobType { + BlobType::EncryptingKey + } + + fn blob_size() -> usize { + ENCRYPTING_KEY_BLOB_SIZE_ALIGNED + } + + /// Generate an encrypted blob for persistence + /// @param {SecBuf} passphrase - the encryption passphrase + /// @param {string} hint - additional info / description for the bundle + /// @param {Option} config - Settings for pwhash + fn as_blob( + &mut self, + passphrase: &mut SecBuf, + hint: String, + config: Option, + ) -> HcResult { + // Initialize buffer + let mut data_buf = SecBuf::with_secure(ENCRYPTING_KEY_BLOB_SIZE_ALIGNED); + let mut offset: usize = 0; + // Write version + data_buf + .write(0, &[ENCRYPTING_KEY_BLOB_FORMAT_VERSION]) + .unwrap(); + offset += 1; + // Write public encrypting key + let key = self.decode_pub_key(); + assert_eq!(kx::PUBLICKEYBYTES, key.len()); + data_buf + .write(offset, &key) + .expect("Failed blobbing public encrypting key"); + offset += kx::PUBLICKEYBYTES; + // Write private encyrpting key + data_buf + .write(offset, &**self.private.read_lock()) + .expect("Failed blobbing private ecrypting key"); + offset += kx::SECRETKEYBYTES; + + // Finalize + let encoded_blob = Self::finalize_blobbing(&mut data_buf, passphrase, config)?; + + // Done + Ok(KeyBlob { + seed_type: SeedType::Mock, + blob_type: BlobType::EncryptingKey, + hint, + data: encoded_blob, + }) + } + + /// Construct the pairs from an encrypted blob + /// @param {object} bundle - persistence info + /// @param {SecBuf} passphrase - decryption passphrase + /// @param {Option} config - Settings for pwhash + fn from_blob( + blob: &KeyBlob, + passphrase: &mut SecBuf, + config: Option, + ) -> HcResult { + // Retrieve data buf from blob + let mut keybundle_blob = Self::unblob(blob, passphrase, config)?; + + // Deserialize manually + let mut pub_sign = SecBuf::with_insecure(kx::PUBLICKEYBYTES); + let mut priv_sign = SecBuf::with_secure(kx::SECRETKEYBYTES); + { + let keybundle_blob = keybundle_blob.read_lock(); + if keybundle_blob[0] != ENCRYPTING_KEY_BLOB_FORMAT_VERSION { + return Err(HolochainError::ErrorGeneric(format!( + "Invalid EncryptingKey Blob Format: v{:?} != v{:?}", + keybundle_blob[0], ENCRYPTING_KEY_BLOB_FORMAT_VERSION + ))); + } + pub_sign.write(0, &keybundle_blob[1..33])?; + priv_sign.write(0, &keybundle_blob[33..65])?; + } + // Done + Ok(EncryptingKeyPair::new( + EncryptingKeyPair::encode_pub_key(&mut pub_sign), + priv_sign, + )) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::key_bundle::tests::*; + use crate::{ + key_bundle::tests::*, + keypair::{generate_random_enc_keypair, generate_random_sign_keypair}, + utils::generate_random_seed_buf, + SEED_SIZE, + }; use holochain_sodium::pwhash; #[test] fn it_should_blob_keybundle() { - let mut seed_buf = test_generate_random_seed(); - let mut passphrase = test_generate_random_seed(); + let mut seed_buf = generate_random_seed_buf(); + let mut passphrase = generate_random_seed_buf(); - let mut bundle = KeyBundle::new_from_seed_buf(&mut seed_buf, SeedType::Mock).unwrap(); + let mut bundle = KeyBundle::new_from_seed_buf(&mut seed_buf).unwrap(); let blob = bundle .as_blob(&mut passphrase, "hint".to_string(), TEST_CONFIG) @@ -316,10 +498,62 @@ mod tests { assert!(maybe_unblob.is_err()); } + #[test] + fn it_should_blob_signing_key() { + let mut passphrase = generate_random_seed_buf(); + + let mut signing_key = generate_random_sign_keypair().unwrap(); + + let blob = signing_key + .as_blob(&mut passphrase, "hint".to_string(), TEST_CONFIG) + .unwrap(); + + println!("blob.data: {}", blob.data); + + assert_eq!(SeedType::Mock, blob.seed_type); + assert_eq!("hint", blob.hint); + + let mut unblob = SigningKeyPair::from_blob(&blob, &mut passphrase, TEST_CONFIG).unwrap(); + + assert_eq!(0, unblob.private().compare(&mut signing_key.private())); + assert_eq!(unblob.public(), signing_key.public()); + + // Test with wrong passphrase + passphrase.randomize(); + let maybe_unblob = SigningKeyPair::from_blob(&blob, &mut passphrase, TEST_CONFIG); + assert!(maybe_unblob.is_err()); + } + + #[test] + fn it_should_blob_encrypting_key() { + let mut passphrase = generate_random_seed_buf(); + + let mut enc_key = generate_random_enc_keypair().unwrap(); + + let blob = enc_key + .as_blob(&mut passphrase, "hint".to_string(), TEST_CONFIG) + .unwrap(); + + println!("blob.data: {}", blob.data); + + assert_eq!(SeedType::Mock, blob.seed_type); + assert_eq!("hint", blob.hint); + + let mut unblob = EncryptingKeyPair::from_blob(&blob, &mut passphrase, TEST_CONFIG).unwrap(); + + assert_eq!(0, unblob.private().compare(&mut enc_key.private())); + assert_eq!(unblob.public(), enc_key.public()); + + // Test with wrong passphrase + passphrase.randomize(); + let maybe_unblob = EncryptingKeyPair::from_blob(&blob, &mut passphrase, TEST_CONFIG); + assert!(maybe_unblob.is_err()); + } + #[test] fn it_should_blob_seed() { - let mut passphrase = test_generate_random_seed(); - let mut seed_buf = test_generate_random_seed(); + let mut passphrase = generate_random_seed_buf(); + let mut seed_buf = generate_random_seed_buf(); let mut initial_seed = Seed::new(seed_buf, SeedType::Root); let blob = initial_seed @@ -333,8 +567,8 @@ mod tests { #[test] fn it_should_blob_device_pin_seed() { - let mut passphrase = test_generate_random_seed(); - let mut seed_buf = test_generate_random_seed(); + let mut passphrase = generate_random_seed_buf(); + let mut seed_buf = generate_random_seed_buf(); let mut initial_device_pin_seed = DevicePinSeed::new(seed_buf); let blob = initial_device_pin_seed diff --git a/hc_dpki/src/key_bundle.rs b/dpki/src/key_bundle.rs old mode 100644 new mode 100755 similarity index 77% rename from hc_dpki/src/key_bundle.rs rename to dpki/src/key_bundle.rs index 4fe3bd2b2f..945b979ada --- a/hc_dpki/src/key_bundle.rs +++ b/dpki/src/key_bundle.rs @@ -17,28 +17,33 @@ use serde_derive::{Deserialize, Serialize}; pub struct KeyBundle { pub sign_keys: SigningKeyPair, pub enc_keys: EncryptingKeyPair, - pub seed_type: SeedType, } impl KeyBundle { + /// create a new KeyBundle + pub fn new(sign_keys: SigningKeyPair, enc_keys: EncryptingKeyPair) -> HcResult { + Ok(KeyBundle { + sign_keys, + enc_keys, + }) + } + /// Derive the keys from a Seed pub fn new_from_seed(seed: &mut Seed) -> HcResult { Ok(KeyBundle { sign_keys: SigningKeyPair::new_from_seed(&mut seed.buf)?, enc_keys: EncryptingKeyPair::new_from_seed(&mut seed.buf)?, - seed_type: seed.kind.clone(), }) } /// Derive the keys from a 32 bytes seed buffer /// @param {SecBuf} seed - the seed buffer /// @param {SeedType} seed_type - seed type of the buffer - pub fn new_from_seed_buf(seed_buf: &mut SecBuf, seed_type: SeedType) -> HcResult { + pub fn new_from_seed_buf(seed_buf: &mut SecBuf) -> HcResult { assert_eq!(seed_buf.len(), SEED_SIZE); Ok(KeyBundle { sign_keys: SigningKeyPair::new_from_seed(seed_buf)?, enc_keys: EncryptingKeyPair::new_from_seed(seed_buf)?, - seed_type, }) } @@ -64,16 +69,14 @@ impl KeyBundle { /// pub fn is_same(&mut self, other: &mut KeyBundle) -> bool { - self.sign_keys.is_same(&mut other.sign_keys) - && self.enc_keys.is_same(&mut other.enc_keys) - && self.seed_type == other.seed_type + self.sign_keys.is_same(&mut other.sign_keys) && self.enc_keys.is_same(&mut other.enc_keys) } } #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::SIGNATURE_SIZE; + use crate::{keypair::*, utils::generate_random_seed_buf, SIGNATURE_SIZE}; use holochain_sodium::pwhash; pub(crate) const TEST_CONFIG: Option = Some(PwHashConfig( @@ -82,21 +85,26 @@ pub(crate) mod tests { pwhash::ALG_ARGON2ID13, )); - pub(crate) fn test_generate_random_seed() -> SecBuf { - let mut seed = SecBuf::with_insecure(SEED_SIZE); - seed.randomize(); - seed + fn test_generate_random_bundle() -> KeyBundle { + let mut seed = generate_random_seed_buf(); + KeyBundle::new_from_seed_buf(&mut seed).unwrap() } - fn test_generate_random_bundle() -> KeyBundle { - let mut seed = test_generate_random_seed(); - KeyBundle::new_from_seed_buf(&mut seed, SeedType::Mock).unwrap() + #[test] + fn it_should_create_keybundle_from_pairs() { + let mut seed = generate_random_seed_buf(); + let sign_keys = SigningKeyPair::new_from_seed(&mut seed).unwrap(); + let enc_keys = EncryptingKeyPair::new_from_seed(&mut seed).unwrap(); + let result = KeyBundle::new(sign_keys, enc_keys); + assert!(result.is_ok()); + let bundle = result.unwrap(); + assert_eq!(64, bundle.sign_keys.private.len()); + assert_eq!(32, bundle.enc_keys.private.len()); } #[test] fn it_should_create_keybundle_from_seed() { let bundle = test_generate_random_bundle(); - assert_eq!(SeedType::Mock, bundle.seed_type); assert_eq!(64, bundle.sign_keys.private.len()); assert_eq!(32, bundle.enc_keys.private.len()); diff --git a/hc_dpki/src/keypair.rs b/dpki/src/keypair.rs old mode 100644 new mode 100755 similarity index 92% rename from hc_dpki/src/keypair.rs rename to dpki/src/keypair.rs index 0f72482959..432a88ae0d --- a/hc_dpki/src/keypair.rs +++ b/dpki/src/keypair.rs @@ -164,6 +164,16 @@ impl EncryptingKeyPair { // TODO: Encrypt and decrypt functions } +pub fn generate_random_sign_keypair() -> HcResult { + let mut seed = utils::generate_random_seed_buf(); + SigningKeyPair::new_from_seed(&mut seed) +} + +pub fn generate_random_enc_keypair() -> HcResult { + let mut seed = utils::generate_random_seed_buf(); + EncryptingKeyPair::new_from_seed(&mut seed) +} + //-------------------------------------------------------------------------------------------------- // Test //-------------------------------------------------------------------------------------------------- @@ -171,21 +181,14 @@ impl EncryptingKeyPair { #[cfg(test)] mod tests { use super::*; + use crate::SEED_SIZE; - fn test_generate_random_seed() -> SecBuf { - let mut seed = SecBuf::with_insecure(SEED_SIZE); - seed.randomize(); - seed - } - - fn test_generate_random_sign_keypair() -> SigningKeyPair { - let mut seed = test_generate_random_seed(); - SigningKeyPair::new_from_seed(&mut seed).unwrap() + pub fn test_generate_random_sign_keypair() -> SigningKeyPair { + generate_random_sign_keypair().unwrap() } - fn test_generate_random_enc_keypair() -> EncryptingKeyPair { - let mut seed = test_generate_random_seed(); - EncryptingKeyPair::new_from_seed(&mut seed).unwrap() + pub fn test_generate_random_enc_keypair() -> EncryptingKeyPair { + generate_random_enc_keypair().unwrap() } #[test] diff --git a/hc_dpki/src/lib.rs b/dpki/src/lib.rs similarity index 89% rename from hc_dpki/src/lib.rs rename to dpki/src/lib.rs index 9323b240fd..d6d2ba0e43 100644 --- a/hc_dpki/src/lib.rs +++ b/dpki/src/lib.rs @@ -6,7 +6,9 @@ #[macro_use] extern crate lazy_static; +pub const CONTEXT_SIZE: usize = 8; pub const SEED_SIZE: usize = 32; +pub const AGENT_ID_CTX: [u8; 8] = *b"HCAGNTID"; pub(crate) const SIGNATURE_SIZE: usize = 64; lazy_static! { diff --git a/hc_dpki/src/password_encryption.rs b/dpki/src/password_encryption.rs similarity index 99% rename from hc_dpki/src/password_encryption.rs rename to dpki/src/password_encryption.rs index 5bc62d8f3d..9a555cc044 100644 --- a/hc_dpki/src/password_encryption.rs +++ b/dpki/src/password_encryption.rs @@ -6,6 +6,7 @@ pub type OpsLimit = u64; pub type MemLimit = usize; pub type PwHashAlgo = i8; +#[derive(Clone)] pub struct PwHashConfig(pub OpsLimit, pub MemLimit, pub PwHashAlgo); /// Struct holding the result of a passphrase encryption diff --git a/hc_dpki/src/seed.rs b/dpki/src/seed.rs similarity index 78% rename from hc_dpki/src/seed.rs rename to dpki/src/seed.rs index 865a47de1e..28c279b05a 100644 --- a/hc_dpki/src/seed.rs +++ b/dpki/src/seed.rs @@ -1,4 +1,9 @@ -use crate::{key_bundle::KeyBundle, password_encryption::*, SEED_SIZE}; +use crate::{ + key_bundle::KeyBundle, + password_encryption::*, + utils::{generate_derived_seed_buf, SeedContext}, + AGENT_ID_CTX, SEED_SIZE, +}; use bip39::{Language, Mnemonic}; use holochain_core_types::error::{HcResult, HolochainError}; use holochain_sodium::{kdf, pwhash, secbuf::SecBuf}; @@ -26,12 +31,12 @@ pub enum SeedType { Root, /// Revocation seed Revocation, - /// Device specific seed + /// Device seed Device, /// Derivative of a Device seed with a PIN DevicePin, - /// Application specific seed - Application, + /// DNA specific seed + DNA, /// Seed for a one use only key OneShot, /// Seed used only in tests or mocks @@ -149,19 +154,14 @@ impl RootSeed { } /// Generate Device Seed - /// @param {number} index - device index, must not be zero - pub fn generate_device_seed(&mut self, index: u64) -> HcResult { - if index == 0 { - return Err(HolochainError::ErrorGeneric("Invalid index".to_string())); - } - let mut device_seed_buf = SecBuf::with_secure(SEED_SIZE); - let mut context = SecBuf::with_insecure_from_string("HCDEVICE".to_string()); - kdf::derive( - &mut device_seed_buf, - index, - &mut context, - &mut self.inner.buf, - )?; + /// @param {number} index - the index number in this seed group, must not be zero + pub fn generate_device_seed( + &mut self, + seed_context: &SeedContext, + index: u64, + ) -> HcResult { + let device_seed_buf = + generate_derived_seed_buf(&mut self.inner.buf, seed_context, index, SEED_SIZE)?; Ok(DeviceSeed::new(device_seed_buf)) } } @@ -232,21 +232,19 @@ impl DevicePinSeed { } } - /// generate an application KeyBundle given an index based on this seed - /// @param {number} index - device index, must not be zero + /// generate a DNA agent KeyBundle given an index based on this seed + /// @param {number} index - must not be zero /// @return {KeyBundle} Resulting keybundle - pub fn generate_application_key(&mut self, index: u64) -> HcResult { + pub fn generate_dna_key(&mut self, index: u64) -> HcResult { if index == 0 { return Err(HolochainError::ErrorGeneric("Invalid index".to_string())); } - let mut app_seed_buf = SecBuf::with_secure(SEED_SIZE); - let mut context = SecBuf::with_insecure_from_string("HCAPPLIC".to_string()); - kdf::derive(&mut app_seed_buf, index, &mut context, &mut self.inner.buf)?; - - Ok(KeyBundle::new_from_seed_buf( - &mut app_seed_buf, - SeedType::Application, - )?) + let mut dna_seed_buf = SecBuf::with_secure(SEED_SIZE); + let context = SeedContext::new(AGENT_ID_CTX); + let mut context = context.to_sec_buf(); + kdf::derive(&mut dna_seed_buf, index, &mut context, &mut self.inner.buf)?; + + Ok(KeyBundle::new_from_seed_buf(&mut dna_seed_buf)?) } } @@ -257,17 +255,15 @@ impl DevicePinSeed { #[cfg(test)] mod tests { use super::*; - use crate::password_encryption::tests::TEST_CONFIG; - - fn test_generate_random_seed(s: usize) -> SecBuf { - let mut seed_buf = SecBuf::with_insecure(s); - seed_buf.randomize(); - seed_buf - } + use crate::{ + password_encryption::tests::TEST_CONFIG, + utils::{self, generate_random_seed_buf}, + SEED_SIZE, + }; #[test] fn it_should_create_a_new_seed() { - let seed_buf = test_generate_random_seed(32); + let seed_buf = utils::generate_random_seed_buf(); let seed_type = SeedType::OneShot; let seed = Seed::new_with_initializer(SeedInitializer::Seed(seed_buf), seed_type.clone()); assert_eq!(seed_type, seed.kind); @@ -275,21 +271,22 @@ mod tests { #[test] fn it_should_create_a_new_root_seed() { - let seed_buf = test_generate_random_seed(32); + let seed_buf = generate_random_seed_buf(); let root_seed = RootSeed::new(seed_buf); assert_eq!(SeedType::Root, root_seed.seed().kind); } #[test] fn it_should_create_a_device_seed() { - let seed_buf = test_generate_random_seed(32); + let seed_buf = generate_random_seed_buf(); + let context = SeedContext::new(*b"HCDEVICE"); let mut root_seed = RootSeed::new(seed_buf); - let mut device_seed_3 = root_seed.generate_device_seed(3).unwrap(); + let mut device_seed_3 = root_seed.generate_device_seed(&context, 3).unwrap(); assert_eq!(SeedType::Device, device_seed_3.seed().kind); - let _ = root_seed.generate_device_seed(0).unwrap_err(); - let mut device_seed_1 = root_seed.generate_device_seed(1).unwrap(); - let mut device_seed_3_b = root_seed.generate_device_seed(3).unwrap(); + let _ = root_seed.generate_device_seed(&context, 0).unwrap_err(); + let mut device_seed_1 = root_seed.generate_device_seed(&context, 1).unwrap(); + let mut device_seed_3_b = root_seed.generate_device_seed(&context, 3).unwrap(); assert!( device_seed_3 .seed_mut() @@ -308,11 +305,12 @@ mod tests { #[test] fn it_should_create_a_device_pin_seed() { - let seed_buf = test_generate_random_seed(32); - let mut pin = test_generate_random_seed(32); + let seed_buf = generate_random_seed_buf(); + let mut pin = generate_random_seed_buf(); + let context = SeedContext::new(*b"HCDEVICE"); let mut root_seed = RootSeed::new(seed_buf); - let mut device_seed = root_seed.generate_device_seed(3).unwrap(); + let mut device_seed = root_seed.generate_device_seed(&context, 3).unwrap(); let device_pin_seed = device_seed .generate_device_pin_seed(&mut pin, TEST_CONFIG) .unwrap(); @@ -320,31 +318,31 @@ mod tests { } #[test] - fn it_should_create_app_key_from_root_seed() { - let seed_buf = test_generate_random_seed(32); - let mut pin = test_generate_random_seed(32); + fn it_should_create_dna_key_from_root_seed() { + let seed_buf = generate_random_seed_buf(); + let mut pin = generate_random_seed_buf(); + let context = SeedContext::new(*b"HCDEVICE"); let mut rs = RootSeed::new(seed_buf); - let mut ds = rs.generate_device_seed(3).unwrap(); + let mut ds = rs.generate_device_seed(&context, 3).unwrap(); let mut dps = ds.generate_device_pin_seed(&mut pin, TEST_CONFIG).unwrap(); - let mut keybundle_5 = dps.generate_application_key(5).unwrap(); + let mut keybundle_5 = dps.generate_dna_key(5).unwrap(); assert_eq!(crate::SIGNATURE_SIZE, keybundle_5.sign_keys.private.len()); assert_eq!(SEED_SIZE, keybundle_5.enc_keys.private.len()); - assert_eq!(SeedType::Application, keybundle_5.seed_type); - let res = dps.generate_application_key(0); + let res = dps.generate_dna_key(0); assert!(res.is_err()); - let mut keybundle_1 = dps.generate_application_key(1).unwrap(); - let mut keybundle_5_b = dps.generate_application_key(5).unwrap(); + let mut keybundle_1 = dps.generate_dna_key(1).unwrap(); + let mut keybundle_5_b = dps.generate_dna_key(5).unwrap(); assert!(keybundle_5.is_same(&mut keybundle_5_b)); assert!(!keybundle_5.is_same(&mut keybundle_1)); } #[test] fn it_should_roundtrip_mnemonic() { - let mut seed_buf = SecBuf::with_insecure(32); + let mut seed_buf = SecBuf::with_insecure(SEED_SIZE); { let mut seed_buf = seed_buf.write_lock(); seed_buf[0] = 12; @@ -363,7 +361,7 @@ mod tests { #[test] fn it_should_change_into_typed() { // Root - let seed_buf = test_generate_random_seed(32); + let seed_buf = generate_random_seed_buf(); let seed = Seed::new(seed_buf, SeedType::Root); let unknown_seed = seed.into_typed().unwrap(); let _ = match unknown_seed { @@ -371,7 +369,7 @@ mod tests { _ => unreachable!(), }; // Device - let seed_buf = test_generate_random_seed(32); + let seed_buf = generate_random_seed_buf(); let seed = Seed::new(seed_buf, SeedType::Device); let unknown_seed = seed.into_typed().unwrap(); let _ = match unknown_seed { @@ -379,7 +377,7 @@ mod tests { _ => unreachable!(), }; // DevicePin - let seed_buf = test_generate_random_seed(32); + let seed_buf = generate_random_seed_buf(); let seed = Seed::new(seed_buf, SeedType::DevicePin); let unknown_seed = seed.into_typed().unwrap(); let _ = match unknown_seed { @@ -387,8 +385,8 @@ mod tests { _ => unreachable!(), }; // App - let seed_buf = test_generate_random_seed(32); - let seed = Seed::new(seed_buf, SeedType::Application); + let seed_buf = generate_random_seed_buf(); + let seed = Seed::new(seed_buf, SeedType::DNA); let maybe_seed = seed.into_typed(); assert!(maybe_seed.is_err()); } diff --git a/dpki/src/utils.rs b/dpki/src/utils.rs new file mode 100644 index 0000000000..f256dd684b --- /dev/null +++ b/dpki/src/utils.rs @@ -0,0 +1,245 @@ +use crate::{ + password_encryption::{pw_dec, pw_enc, EncryptedData, PwHashConfig}, + CODEC_HCS0, CONTEXT_SIZE, SEED_SIZE, +}; +use hcid::*; +use holochain_core_types::{ + agent::Base32, + cas::content::Address, + error::{HcResult, HolochainError}, + signature::{Provenance, Signature}, +}; +use holochain_sodium::{kdf, secbuf::SecBuf, sign}; +use std::str; + +/// a trait for things that have a provenance that can be verified +pub trait Verify { + fn verify(&self, data: String) -> HcResult; +} + +impl Verify for Provenance { + fn verify(&self, data: String) -> HcResult { + crate::utils::verify(self.source(), data, self.signature()) + } +} + +/// Decode an HCID-encoded key into a SecBuf +/// @param {Base32} pub_key_b32 - Public signing key to decode +/// @param {HcidEncoding} codec - The configured HCID decoder to use +/// @return {SecBuf} Resulting decoded key +pub(crate) fn decode_pub_key(pub_key_b32: Base32, codec: &HcidEncoding) -> HcResult { + // Decode Base32 public key + let pub_key = codec.decode(&pub_key_b32)?; + // convert to SecBuf + let mut pub_key_sec = SecBuf::with_insecure(sign::PUBLICKEYBYTES); + pub_key_sec.from_array(&pub_key)?; + // Done + Ok(pub_key_sec) +} + +/// Encode with HCID a public key given as a SecBuf +/// @param {SecBuf} pub_key_sec - Public signing key to encode +/// @param {HcidEncoding} codec - The configured HCID encoder to use +/// @return {Base32} Resulting HCID encoded key +pub(crate) fn encode_pub_key(pub_key_sec: &mut SecBuf, codec: &HcidEncoding) -> HcResult { + let locker = pub_key_sec.read_lock(); + Ok(codec.encode(&locker[0..SEED_SIZE])?) +} + +/// Verify that an address signed some data +pub fn verify(source: Address, data: String, signature: Signature) -> HcResult { + let signature_string: String = signature.into(); + let signature_bytes: Vec = base64::decode(&signature_string) + .map_err(|_| HolochainError::ErrorGeneric("Signature syntactically invalid".to_string()))?; + + let mut signature_buf = SecBuf::with_insecure(signature_bytes.len()); + signature_buf + .write(0, signature_bytes.as_slice()) + .expect("SecBuf must be writeable"); + + let mut message_buf = SecBuf::with_insecure_from_string(data); + verify_bufs(source.to_string(), &mut message_buf, &mut signature_buf) +} + +/// Verify data that was signed +/// @param {Base32} pub_sign_key_b32 - Public signing key to verify with +/// @param {SecBuf} data - Data buffer to verify +/// @param {SecBuf} signature - Candidate signature for that data buffer +/// @return true if verification succeeded +pub fn verify_bufs( + pub_sign_key_b32: Base32, + data: &mut SecBuf, + signature: &mut SecBuf, +) -> HcResult { + let mut pub_key = decode_pub_key(pub_sign_key_b32, &CODEC_HCS0)?; + Ok(holochain_sodium::sign::verify( + signature, + data, + &mut pub_key, + )) +} + +pub struct SeedContext { + inner: [u8; 8], +} + +impl SeedContext { + pub fn new(data: [u8; 8]) -> Self { + assert_eq!(data.len(), CONTEXT_SIZE); + assert!(data.is_ascii()); + SeedContext { inner: data } + } + + pub fn to_sec_buf(&self) -> SecBuf { + let mut buf = SecBuf::with_insecure(8); + buf.write(0, &self.inner).expect("SecBuf must be writeable"); + buf + } +} + +/// derive a seed from a source seed +pub fn generate_derived_seed_buf( + mut src_seed: &mut SecBuf, + seed_context: &SeedContext, + index: u64, + size: usize, +) -> HcResult { + if index == 0 { + return Err(HolochainError::ErrorGeneric("Invalid index".to_string())); + } + let mut derived_seed_buf = SecBuf::with_secure(size); + let mut context = seed_context.to_sec_buf(); + kdf::derive(&mut derived_seed_buf, index, &mut context, &mut src_seed)?; + Ok(derived_seed_buf) +} + +/// returns a random buf +pub fn generate_random_buf(size: usize) -> SecBuf { + let mut seed = SecBuf::with_insecure(size); + seed.randomize(); + seed +} + +/// returns a random seed buf +pub fn generate_random_seed_buf() -> SecBuf { + generate_random_buf(SEED_SIZE) +} + +/// encrypt and base64 encode a secbuf +pub fn encrypt_with_passphrase_buf( + data_buf: &mut SecBuf, + passphrase: &mut SecBuf, + config: Option, +) -> HcResult { + // encrypt buffer + let encrypted_blob = pw_enc(data_buf, passphrase, config)?; + // Serialize and convert to base64 + let serialized_blob = serde_json::to_string(&encrypted_blob).expect("Failed to serialize Blob"); + Ok(base64::encode(&serialized_blob)) +} + +/// unencode base64 and decrypt a passphrase encrypted blob +pub fn decrypt_with_passphrase_buf( + blob: &String, + passphrase: &mut SecBuf, + config: Option, + size: usize, +) -> HcResult { + // Decode base64 + let blob_b64 = base64::decode(blob)?; + // Deserialize + let blob_json = str::from_utf8(&blob_b64)?; + let encrypted_blob: EncryptedData = serde_json::from_str(&blob_json)?; + // Decrypt + let mut decrypted_data = SecBuf::with_secure(size); + pw_dec(&encrypted_blob, passphrase, &mut decrypted_data, config)?; + // Check size + if decrypted_data.len() != size { + return Err(HolochainError::ErrorGeneric( + "Invalid Blob size".to_string(), + )); + } + // Done + Ok(decrypted_data) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::SIGNATURE_SIZE; + use hcid::with_hcs0; + use holochain_sodium::{secbuf::SecBuf, sign}; + + #[test] + fn it_should_hcid_roundtrip() { + let mut pub_sec_buf = SecBuf::with_insecure(sign::PUBLICKEYBYTES); + pub_sec_buf.randomize(); + + let codec = with_hcs0().expect("HCID failed miserably with_hcs0"); + let pub_key_b32 = encode_pub_key(&mut pub_sec_buf, &codec).unwrap(); + + let mut roundtrip = decode_pub_key(pub_key_b32, &codec) + .expect("Public key decoding failed. Key was not properly encoded."); + + assert!(pub_sec_buf.compare(&mut roundtrip) == 0); + } + + #[test] + fn it_should_verify_bufs() { + let codec = with_hcs0().expect("HCID failed miserably with_hcs0"); + // Create random seed + let mut seed = SecBuf::with_insecure(SEED_SIZE); + seed.randomize(); + // Create keys + let mut public_key = SecBuf::with_insecure(sign::PUBLICKEYBYTES); + let mut secret_key = SecBuf::with_secure(sign::SECRETKEYBYTES); + holochain_sodium::sign::seed_keypair(&mut public_key, &mut secret_key, &mut seed).unwrap(); + let pub_key_b32 = encode_pub_key(&mut public_key, &codec).unwrap(); + // Create signing buffers + let mut message = SecBuf::with_insecure(42); + message.randomize(); + let mut signature = SecBuf::with_insecure(SIGNATURE_SIZE); + holochain_sodium::sign::sign(&mut message, &mut secret_key, &mut signature).unwrap(); + let res = verify_bufs(pub_key_b32, &mut message, &mut signature); + assert!(res.unwrap()); + } + + #[test] + fn it_should_round_trip_passphrase_encryption() { + let data_size = 32; + let mut random_data = generate_random_buf(data_size); + + let mut random_passphrase = generate_random_buf(10); + + let encrypted_result = + encrypt_with_passphrase_buf(&mut random_data, &mut random_passphrase, None); + assert!(encrypted_result.is_ok()); + + let encrypted_data = encrypted_result.unwrap(); + + let decrypted_result = + decrypt_with_passphrase_buf(&encrypted_data, &mut random_passphrase, None, data_size); + assert!(decrypted_result.is_ok()); + let mut decrypted_data = decrypted_result.unwrap(); + + assert_eq!(0, decrypted_data.compare(&mut random_data)); + + // totally bogus data will return an error + let bogus_encrypted_data = "askdfklasjdasldkfjlkasdjflkasdjfasdf".to_string(); + let decrypted_result = decrypt_with_passphrase_buf( + &bogus_encrypted_data, + &mut random_passphrase, + None, + data_size, + ); + assert!(decrypted_result.is_err()); + + // a bogus passphrase will not decrypt to the correct data + let mut bogus_passphrase = generate_random_buf(10); + let decrypted_result = + decrypt_with_passphrase_buf(&encrypted_data, &mut bogus_passphrase, None, data_size); + assert!(decrypted_result.is_ok()); + let mut decrypted_data = decrypted_result.unwrap(); + assert!(0 != decrypted_data.compare(&mut random_data)); + } +} diff --git a/hc_dpki/src/utils.rs b/hc_dpki/src/utils.rs deleted file mode 100644 index 4630e955f0..0000000000 --- a/hc_dpki/src/utils.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{CODEC_HCS0, SEED_SIZE}; -use hcid::*; -use holochain_core_types::{agent::Base32, error::HcResult}; -use holochain_sodium::{secbuf::SecBuf, sign}; - -/// Decode an HCID-encoded key into a SecBuf -/// @param {Base32} pub_key_b32 - Public signing key to decode -/// @param {HcidEncoding} codec - The configured HCID decoder to use -/// @return {SecBuf} Resulting decoded key -pub(crate) fn decode_pub_key(pub_key_b32: Base32, codec: &HcidEncoding) -> HcResult { - // Decode Base32 public key - let pub_key = codec.decode(&pub_key_b32)?; - // convert to SecBuf - let mut pub_key_sec = SecBuf::with_insecure(sign::PUBLICKEYBYTES); - pub_key_sec.from_array(&pub_key)?; - // Done - Ok(pub_key_sec) -} - -/// Encode with HCID a public key given as a SecBuf -/// @param {SecBuf} pub_key_sec - Public signing key to encode -/// @param {HcidEncoding} codec - The configured HCID encoder to use -/// @return {Base32} Resulting HCID encoded key -pub(crate) fn encode_pub_key(pub_key_sec: &mut SecBuf, codec: &HcidEncoding) -> HcResult { - let locker = pub_key_sec.read_lock(); - Ok(codec.encode(&locker[0..SEED_SIZE])?) -} - -/// Verify data that was signed -/// @param {Base32} pub_sign_key_b32 - Public signing key to verify with -/// @param {SecBuf} data - Data buffer to verify -/// @param {SecBuf} signature - Candidate signature for that data buffer -/// @return true if verification succeeded -pub fn verify( - pub_sign_key_b32: Base32, - data: &mut SecBuf, - signature: &mut SecBuf, -) -> HcResult { - let mut pub_key = decode_pub_key(pub_sign_key_b32, &CODEC_HCS0)?; - Ok(holochain_sodium::sign::verify( - signature, - data, - &mut pub_key, - )) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::SIGNATURE_SIZE; - use hcid::with_hcs0; - use holochain_sodium::{secbuf::SecBuf, sign}; - - #[test] - fn it_should_hcid_roundtrip() { - let mut pub_sec_buf = SecBuf::with_insecure(sign::PUBLICKEYBYTES); - pub_sec_buf.randomize(); - - let codec = with_hcs0().expect("HCID failed miserably with_hcs0"); - let pub_key_b32 = encode_pub_key(&mut pub_sec_buf, &codec).unwrap(); - - let mut roundtrip = decode_pub_key(pub_key_b32, &codec) - .expect("Public key decoding failed. Key was not properly encoded."); - - assert!(pub_sec_buf.compare(&mut roundtrip) == 0); - } - - #[test] - fn it_should_verify() { - let codec = with_hcs0().expect("HCID failed miserably with_hcs0"); - // Create random seed - let mut seed = SecBuf::with_insecure(SEED_SIZE); - seed.randomize(); - // Create keys - let mut public_key = SecBuf::with_insecure(sign::PUBLICKEYBYTES); - let mut secret_key = SecBuf::with_secure(sign::SECRETKEYBYTES); - holochain_sodium::sign::seed_keypair(&mut public_key, &mut secret_key, &mut seed).unwrap(); - let pub_key_b32 = encode_pub_key(&mut public_key, &codec).unwrap(); - // Create signing buffers - let mut message = SecBuf::with_insecure(42); - message.randomize(); - let mut signature = SecBuf::with_insecure(SIGNATURE_SIZE); - holochain_sodium::sign::sign(&mut message, &mut secret_key, &mut signature).unwrap(); - let res = verify(pub_key_b32, &mut message, &mut signature); - assert!(res.unwrap()); - } -} diff --git a/nodejs_conductor/native/src/config.rs b/nodejs_conductor/native/src/config.rs index 212c4e6d40..f45a2c2ff4 100644 --- a/nodejs_conductor/native/src/config.rs +++ b/nodejs_conductor/native/src/config.rs @@ -3,7 +3,8 @@ use holochain_conductor_api::{ AgentConfiguration, Configuration, DnaConfiguration, InstanceConfiguration, LoggerConfiguration, StorageConfiguration, }, - key_loaders::test_keybundle, + keystore::PRIMARY_KEYBUNDLE_ID, + key_loaders::test_keystore, logger::LogRules, }; use neon::prelude::*; @@ -64,12 +65,14 @@ fn make_config(instance_data: Vec, logger: LoggerConfiguration) -> let agent_name = instance.agent.name; let mut dna_data = instance.dna; let agent_config = agent_configs.entry(agent_name.clone()).or_insert_with(|| { - let keybundle = test_keybundle(&agent_name); + let mut keystore = test_keystore(&agent_name); + let keybundle = keystore.get_keybundle(PRIMARY_KEYBUNDLE_ID) + .expect("Couldn't get KeyBundle that was just added back from Keystore"); let config = AgentConfiguration { id: agent_name.clone(), name: agent_name.clone(), public_address: keybundle.get_id(), - key_file: agent_name.clone(), + keystore_file: agent_name.clone(), holo_remote_key: None, }; config diff --git a/nodejs_conductor/native/src/js_test_conductor.rs b/nodejs_conductor/native/src/js_test_conductor.rs index e968fc013d..d58e7ecb85 100644 --- a/nodejs_conductor/native/src/js_test_conductor.rs +++ b/nodejs_conductor/native/src/js_test_conductor.rs @@ -11,7 +11,7 @@ use std::{ use holochain_conductor_api::{ conductor::Conductor as RustConductor, - key_loaders::test_keybundle_loader, + key_loaders::test_keystore_loader, config::{load_configuration, Configuration}, }; use holochain_core::{ @@ -75,7 +75,7 @@ declare_types! { panic!("Invalid type specified for config, must be object or string"); }; let mut conductor = RustConductor::from_config(config); - conductor.key_loader = test_keybundle_loader(); + conductor.key_loader = test_keystore_loader(); let is_running = Arc::new(Mutex::new(false)); Ok(TestConductor { conductor, sender_tx: None, is_running, is_started: false }) diff --git a/nodejs_conductor/test/test-toml-config.js b/nodejs_conductor/test/test-toml-config.js index d0d0aa4cc0..3c3477958d 100644 --- a/nodejs_conductor/test/test-toml-config.js +++ b/nodejs_conductor/test/test-toml-config.js @@ -6,13 +6,13 @@ const toml = ` [[agents]] id = "test/agent/1" name = "Holo Tester 1" -key_file = "holo_tester1.key" +keystore_file = "holo_tester1.key" public_address = "HcScJdXW5uHo9y8jryEwW8N59akhrgxh93acu33qe53ximagfiWu98j7J6Ofiur" [[agents]] id = "test/agent/2" name = "Holo Tester 2" -key_file = "holo_tester2.key" +keystore_file = "holo_tester2.key" public_address = "HcScIrhJ5ECmano9jwiE9FWmacTybe7u9bpDURFGZixr7k5sVdAR4ABMpnywu5a" [[dnas]] diff --git a/nodejs_waiter/Cargo.toml b/nodejs_waiter/Cargo.toml index f3261c9ff9..9a04bd7e42 100644 --- a/nodejs_waiter/Cargo.toml +++ b/nodejs_waiter/Cargo.toml @@ -9,5 +9,5 @@ colored = "=1.7.0" neon = "=0.2.0" holochain_core = { path = "../core" } holochain_core_types = { path = "../core_types" } -holochain_dpki = { path = "../hc_dpki" } +holochain_dpki = { path = "../dpki" } holochain_sodium = { path = "../sodium" } diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index d151b14e8e..219309e0c4 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -10,7 +10,7 @@ holochain_core = { path = "../core" } holochain_conductor_api = { path = "../conductor_api" } holochain_cas_implementations = { path = "../cas_implementations" } holochain_core_types = { path = "../core_types" } -holochain_dpki = { path = "../hc_dpki" } +holochain_dpki = { path = "../dpki" } holochain_sodium = { path = "../sodium" } wabt = "=0.7.4" tempfile = "=3.0.7" diff --git a/test_utils/src/mock_signing.rs b/test_utils/src/mock_signing.rs index cf884158aa..23d13588ce 100644 --- a/test_utils/src/mock_signing.rs +++ b/test_utils/src/mock_signing.rs @@ -4,7 +4,7 @@ use holochain_core_types::{ }; use holochain_dpki::{ key_bundle::KeyBundle, - seed::SeedType, SEED_SIZE, + SEED_SIZE, }; use holochain_sodium::secbuf::SecBuf; use jsonrpc_ws_server::jsonrpc_core::{self, types::params::Params, IoHandler}; @@ -35,7 +35,7 @@ pub fn registered_test_agent>(nick: S) -> AgentId { .expect("SecBuf must be writeable"); // Create KeyBundle from seed - let keybundle = KeyBundle::new_from_seed_buf(&mut seed, SeedType::Mock).unwrap(); + let keybundle = KeyBundle::new_from_seed_buf(&mut seed).unwrap(); let agent_id = AgentId::new(&nick, keybundle.get_id()); // Register key in static TEST_AGENT_KEYS