From 272bd4725a9ce68923e59f1a8765dba0a6b4280f Mon Sep 17 00:00:00 2001
From: dvermd <888-dvermd@users.noreply.git.duniter.org>
Date: Wed, 19 Feb 2020 11:42:44 +0100
Subject: [PATCH] [feat] conf: Replace keys modify salt/password opt by
 interactive input

Closes #201
---
 Cargo.lock                         |   2 +-
 lib/core/conf/Cargo.toml           |   1 +
 lib/core/conf/src/keypairs/cli.rs  | 289 +++++++++++++++--------------
 lib/core/core/Cargo.toml           |   1 -
 lib/core/core/src/commands/keys.rs |  66 +++----
 lib/core/core/src/errors.rs        |  10 +-
 6 files changed, 189 insertions(+), 180 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index afae57ad..f8273605 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -983,6 +983,7 @@ dependencies = [
  "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mockall 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rpassword 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1017,7 +1018,6 @@ dependencies = [
  "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
  "structopt 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "unwrap 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
diff --git a/lib/core/conf/Cargo.toml b/lib/core/conf/Cargo.toml
index 7c2092f4..3428cc94 100644
--- a/lib/core/conf/Cargo.toml
+++ b/lib/core/conf/Cargo.toml
@@ -29,6 +29,7 @@ unwrap = "1.2.1"
 [dev-dependencies]
 durs-module = { path = "../module", features = ["module-test"] }
 maplit = "1.0.2"
+mockall = { version = "0.6.0"}
 once_cell = "1.3.1"
 
 [features]
diff --git a/lib/core/conf/src/keypairs/cli.rs b/lib/core/conf/src/keypairs/cli.rs
index 22b5c245..215432ac 100644
--- a/lib/core/conf/src/keypairs/cli.rs
+++ b/lib/core/conf/src/keypairs/cli.rs
@@ -28,11 +28,25 @@
 )]
 
 use crate::*;
+#[cfg(test)]
+use mockall::*;
 use std::io;
 
+#[cfg_attr(test, automock)]
+trait UserPasswordInput {
+    fn get_password(&self, prompt: &str) -> std::io::Result<String>;
+}
+
+impl UserPasswordInput for std::io::Stdin {
+    #[inline]
+    fn get_password(&self, prompt: &str) -> std::io::Result<String> {
+        Ok(rpassword::prompt_password_stdout(prompt)?)
+    }
+}
+
 #[derive(Debug, Copy, Clone)]
-/// Errors encountered by the wizard
-pub enum WizardError {
+/// Errors encountered by the user interaction
+pub enum CliError {
     /// Canceled
     Canceled,
 
@@ -40,83 +54,87 @@ pub enum WizardError {
     BadInput,
 }
 
-impl From<std::io::Error> for WizardError {
+impl From<std::io::Error> for CliError {
     fn from(_e: std::io::Error) -> Self {
-        WizardError::BadInput
+        CliError::BadInput
     }
 }
 
+#[inline]
 /// Modify network keys command
-pub fn modify_network_keys(
-    salt: String,
-    password: String,
-    mut key_pairs: DuniterKeyPairs,
-) -> DuniterKeyPairs {
-    let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters();
-    key_pairs.network_keypair =
-        KeyPairEnum::Ed25519(generator.generate(ed25519::SaltedPassword::new(salt, password)));
-    key_pairs
+pub fn modify_network_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> {
+    inner_modify_network_keys(std::io::stdin(), key_pairs)
 }
 
-/// Modify member keys command
-pub fn modify_member_keys(
-    salt: String,
-    password: String,
+/// Private function to modify network keys
+fn inner_modify_network_keys<T: UserPasswordInput>(
+    stdin: T,
     mut key_pairs: DuniterKeyPairs,
-) -> DuniterKeyPairs {
-    let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters();
-    key_pairs.member_keypair = Some(KeyPairEnum::Ed25519(
-        generator.generate(ed25519::SaltedPassword::new(salt, password)),
-    ));
-    key_pairs
+) -> Result<DuniterKeyPairs, CliError> {
+    key_pairs.network_keypair = salt_password_prompt(stdin)?;
+    Ok(key_pairs)
 }
 
-/// Ask user for confirmation and Clear keys command
-pub fn clear_keys(network: bool, member: bool, key_pairs: DuniterKeyPairs) -> DuniterKeyPairs {
-    inner_clear_keys(
-        if network {
-            if let Ok("y") = question_prompt("Clear your network keypair?", &["y", "n"]) {
-                println!("Generating a new network keypair!");
-                true
-            } else {
-                false
-            }
-        } else {
-            false
-        },
-        if member {
-            if let Ok("y") = question_prompt("Clear your member keypair?", &["y", "n"]) {
-                println!("Deleting member keypair!");
-                true
-            } else {
-                false
-            }
-        } else {
-            false
-        },
-        key_pairs,
-    )
+#[inline]
+/// Modify member keys command
+pub fn modify_member_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> {
+    inner_modify_member_keys(std::io::stdin(), key_pairs)
 }
 
-/// Private function to Clear keys
-fn inner_clear_keys(
-    network: bool,
-    member: bool,
+/// Private function to modify network keys
+fn inner_modify_member_keys<T: UserPasswordInput>(
+    stdin: T,
     mut key_pairs: DuniterKeyPairs,
-) -> DuniterKeyPairs {
+) -> Result<DuniterKeyPairs, CliError> {
+    key_pairs.member_keypair = Some(salt_password_prompt(stdin)?);
+    Ok(key_pairs)
+}
+
+/// Ask user for confirmation and Clear keys command
+pub fn clear_keys(network: bool, member: bool, mut key_pairs: DuniterKeyPairs) -> DuniterKeyPairs {
     if network {
-        key_pairs.network_keypair = super::generate_random_keypair(KeysAlgo::Ed25519);
+        if let Ok("y") = question_prompt("Clear your network keypair?", &["y", "n"]) {
+            println!("Generating a new network keypair!");
+            clear_network_key(&mut key_pairs);
+        }
     }
     if member {
-        key_pairs.member_keypair = None
+        if let Ok("y") = question_prompt("Clear your member keypair?", &["y", "n"]) {
+            println!("Deleting member keypair!");
+            clear_member_key(&mut key_pairs);
+        }
     }
     key_pairs
 }
 
+#[inline]
+/// Private function to Clear keys
+fn clear_network_key(key_pairs: &mut DuniterKeyPairs) {
+    key_pairs.network_keypair = super::generate_random_keypair(KeysAlgo::Ed25519);
+}
+
+#[inline]
+/// Private function to Clear member key
+fn clear_member_key(key_pairs: &mut DuniterKeyPairs) {
+    key_pairs.member_keypair = None;
+}
+
 /// Show keys command
 pub fn show_keys(key_pairs: DuniterKeyPairs) {
+    show_network_keys(&key_pairs);
+    show_member_keys(&key_pairs);
+}
+
+#[inline]
+/// Show network keys
+pub fn show_network_keys(key_pairs: &DuniterKeyPairs) {
     println!("Network key: {}", key_pairs.network_keypair);
-    match key_pairs.member_keypair {
+}
+
+#[inline]
+/// Show member keys
+pub fn show_member_keys(key_pairs: &DuniterKeyPairs) {
+    match &key_pairs.member_keypair {
         None => println!("No member key configured"),
         Some(key) => println!("Member key: {}", key),
     }
@@ -126,7 +144,7 @@ pub fn show_keys(key_pairs: DuniterKeyPairs) {
 pub fn save_keypairs(
     profile_path: PathBuf,
     keypairs_file_path: &Option<PathBuf>,
-    key_pairs: DuniterKeyPairs,
+    key_pairs: &DuniterKeyPairs,
 ) -> Result<(), std::io::Error> {
     let conf_keys_path: PathBuf = if let Some(keypairs_file_path) = keypairs_file_path {
         keypairs_file_path.to_path_buf()
@@ -135,32 +153,30 @@ pub fn save_keypairs(
         conf_keys_path.push(crate::constants::KEYPAIRS_FILENAME);
         conf_keys_path
     };
-    super::write_keypairs_file(&conf_keys_path, &key_pairs)?;
-    Ok(())
+    super::write_keypairs_file(&conf_keys_path, &key_pairs)
 }
 
-fn question_prompt<'a>(question: &str, answers: &[&'a str]) -> Result<&'a str, WizardError> {
+fn question_prompt<'a>(question: &str, answers: &[&'a str]) -> Result<&'a str, CliError> {
     let mut buf = String::new();
 
     println!("{} ({}):", question, answers.join("/"));
     let res = io::stdin().read_line(&mut buf);
 
-    match res {
-        Ok(_) => {
-            let answer = answers.iter().find(|x| **x == buf.trim());
-            match answer {
-                Some(&value) => Ok(value),
-                None => Err(WizardError::Canceled),
-            }
-        }
-        Err(_) => Err(WizardError::Canceled),
+    if res.is_ok() {
+        answers
+            .iter()
+            .find(|x| **x == buf.trim())
+            .copied()
+            .ok_or(CliError::Canceled)
+    } else {
+        Err(CliError::Canceled)
     }
 }
 
-fn salt_password_prompt() -> Result<KeyPairEnum, WizardError> {
-    let salt = rpassword::prompt_password_stdout("Salt: ")?;
+fn salt_password_prompt<T: UserPasswordInput>(stdin: T) -> Result<KeyPairEnum, CliError> {
+    let salt = stdin.get_password("Salt: ")?;
     if !salt.is_empty() {
-        let password = rpassword::prompt_password_stdout("Password: ")?;
+        let password = stdin.get_password("Password: ")?;
         if !password.is_empty() {
             let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters();
             let key_pairs = KeyPairEnum::Ed25519(
@@ -168,26 +184,26 @@ fn salt_password_prompt() -> Result<KeyPairEnum, WizardError> {
             );
             Ok(key_pairs)
         } else {
-            Err(WizardError::BadInput)
+            Err(CliError::BadInput)
         }
     } else {
-        Err(WizardError::BadInput)
+        Err(CliError::BadInput)
     }
 }
 
 /// The wizard key function
-pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, WizardError> {
+pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> {
     let mut answer = question_prompt("Modify your network keypair?", &["y", "n"])?;
     if answer == "y" {
-        key_pairs.network_keypair = salt_password_prompt()?;
+        key_pairs.network_keypair = salt_password_prompt(std::io::stdin())?;
     }
 
     answer = question_prompt("Modify your member keypair?", &["y", "n", "d"])?;
     if answer == "y" {
-        key_pairs.member_keypair = Some(salt_password_prompt()?);
+        key_pairs.member_keypair = Some(salt_password_prompt(std::io::stdin())?);
     } else if answer == "d" {
         println!("Deleting member keypair!");
-        key_pairs.member_keypair = None;
+        clear_member_key(&mut key_pairs);
     }
 
     Ok(key_pairs)
@@ -201,27 +217,60 @@ mod tests {
 
     static BASE58_SEED_INIT: &'static str = "4iXXx5GgRkZ85BVPwn8vFXvztdXAAa5yB573ErcAnngA";
     static BASE58_PUB_INIT: &'static str = "otDgSpKvKAPPmE1MUYxc3UQ3RtEnKYz4iGD3BmwKPzM";
-    //static SALT_INIT: &'static str = "initsalt";
-    //static PASSWORD_INIT: &'static str = "initpassword";
 
     static BASE58_SEED_TEST: &'static str = "ELjDWGPyCGMuhr7R7H2aip6UJA9qLRepmK77pcD41UqQ";
     static BASE58_PUB_TEST: &'static str = "6sewkaNWyEMqkEa2PVRWrDb3hxWtjPdUSB1zXVCqhdWV";
     static SALT_TEST: &'static str = "testsalt";
     static PASSWORD_TEST: &'static str = "testpassword";
 
-    #[test]
-    fn test_modify_member_keys() {
-        let key_pairs = DuniterKeyPairs {
+    fn setup_user_password_input() -> MockUserPasswordInput {
+        let mut stdin_mock = MockUserPasswordInput::new();
+        stdin_mock
+            .expect_get_password()
+            .returning(|prompt| {
+                if prompt.starts_with("Salt:") {
+                    Ok(SALT_TEST.to_owned())
+                } else if prompt.starts_with("Password:") {
+                    Ok(PASSWORD_TEST.to_owned())
+                } else {
+                    Err(std::io::Error::new(
+                        std::io::ErrorKind::InvalidInput,
+                        format!("should not be called with {}", prompt),
+                    ))
+                }
+            })
+            .times(2);
+        stdin_mock
+    }
+
+    fn setup_keys(both_keys: bool) -> DuniterKeyPairs {
+        let member_keypair = if both_keys {
+            Some(KeyPairEnum::Ed25519(ed25519::Ed25519KeyPair {
+                seed: Seed32::from_base58(BASE58_SEED_INIT)
+                    .expect("conf : keypairs file : fail to parse network_seed !"),
+                pubkey: ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
+                    .expect("conf : keypairs file : fail to parse network_pub !"),
+            }))
+        } else {
+            None
+        };
+        DuniterKeyPairs {
             network_keypair: KeyPairEnum::Ed25519(ed25519::Ed25519KeyPair {
                 seed: Seed32::from_base58(BASE58_SEED_INIT)
                     .expect("conf : keypairs file : fail to parse network_seed !"),
                 pubkey: ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
                     .expect("conf : keypairs file : fail to parse network_pub !"),
             }),
-            member_keypair: None,
-        };
+            member_keypair,
+        }
+    }
+
+    #[test]
+    fn test_modify_member_keys() {
+        let key_pairs = setup_keys(false);
+        let stdin_mock = setup_user_password_input();
         let result_key_pairs =
-            modify_member_keys(SALT_TEST.to_owned(), PASSWORD_TEST.to_owned(), key_pairs);
+            inner_modify_member_keys(stdin_mock, key_pairs).expect("Fail to read new member keys");
         // We expect network key not to change
         assert_eq!(
             result_key_pairs.network_keypair.public_key(),
@@ -260,17 +309,10 @@ mod tests {
 
     #[test]
     fn test_modify_network_keys() {
-        let key_pairs = DuniterKeyPairs {
-            network_keypair: KeyPairEnum::Ed25519(ed25519::Ed25519KeyPair {
-                seed: Seed32::from_base58(BASE58_SEED_INIT)
-                    .expect("conf : keypairs file : fail to parse network_seed !"),
-                pubkey: ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
-                    .expect("conf : keypairs file : fail to parse network_pub !"),
-            }),
-            member_keypair: None,
-        };
-        let result_key_pairs =
-            modify_network_keys(SALT_TEST.to_owned(), PASSWORD_TEST.to_owned(), key_pairs);
+        let key_pairs = setup_keys(false);
+        let stdin_mock = setup_user_password_input();
+        let result_key_pairs = inner_modify_network_keys(stdin_mock, key_pairs)
+            .expect("Fail to read new network keys");
         // We expect network key to update
         assert_eq!(
             result_key_pairs.network_keypair.public_key(),
@@ -289,31 +331,18 @@ mod tests {
 
     #[test]
     fn test_clear_network_keys() {
-        let key_pairs = DuniterKeyPairs {
-            network_keypair: KeyPairEnum::Ed25519(ed25519::Ed25519KeyPair {
-                seed: Seed32::from_base58(BASE58_SEED_INIT)
-                    .expect("conf : keypairs file : fail to parse network_seed !"),
-                pubkey: ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
-                    .expect("conf : keypairs file : fail to parse network_pub !"),
-            }),
-            member_keypair: Some(KeyPairEnum::Ed25519(ed25519::Ed25519KeyPair {
-                seed: Seed32::from_base58(BASE58_SEED_INIT)
-                    .expect("conf : keypairs file : fail to parse network_seed !"),
-                pubkey: ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
-                    .expect("conf : keypairs file : fail to parse network_pub !"),
-            })),
-        };
-        let result_key_pairs = inner_clear_keys(true, false, key_pairs);
+        let mut key_pairs = setup_keys(true);
+        clear_network_key(&mut key_pairs);
         // We expect network key to be reset to a new random key
         assert_ne!(
-            result_key_pairs.network_keypair.public_key(),
+            key_pairs.network_keypair.public_key(),
             PubKey::Ed25519(
                 ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
                     .expect("Wrong data in BASE58_PUB_TEST")
             )
         );
         assert_ne!(
-            result_key_pairs.network_keypair.seed().clone(),
+            key_pairs.network_keypair.seed().clone(),
             unwrap!(
                 Seed32::from_base58(BASE58_SEED_INIT),
                 "Wrong data in BASE58_SEED_TEST"
@@ -323,8 +352,8 @@ mod tests {
         // We expect member key not to change
         assert_eq!(
             unwrap!(
-                result_key_pairs.member_keypair.clone(),
-                "conf: result_keypair must have a value"
+                key_pairs.member_keypair.clone(),
+                "conf: keypair must have a value"
             )
             .public_key(),
             PubKey::Ed25519(
@@ -333,10 +362,10 @@ mod tests {
             )
         );
         assert_eq!(
-            result_key_pairs
+            key_pairs
                 .member_keypair
                 .clone()
-                .expect("conf: result_keypair must have a value")
+                .expect("conf: keypair must have a value")
                 .seed()
                 .clone(),
             Seed32::from_base58(BASE58_SEED_INIT).expect("Wrong data in BASE58_SEED_TEST")
@@ -345,36 +374,22 @@ mod tests {
 
     #[test]
     fn test_clear_member_keys() {
-        let key_pairs = DuniterKeyPairs {
-            network_keypair: KeyPairEnum::Ed25519(ed25519::Ed25519KeyPair {
-                seed: Seed32::from_base58(BASE58_SEED_INIT)
-                    .expect("conf : keypairs file : fail to parse network_seed !"),
-                pubkey: ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
-                    .expect("conf : keypairs file : fail to parse network_pub !"),
-            }),
-            member_keypair: Some(KeyPairEnum::Ed25519(ed25519::Ed25519KeyPair {
-                seed: Seed32::from_base58(BASE58_SEED_INIT)
-                    .expect("conf : keypairs file : fail to parse network_seed !"),
-                pubkey: ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
-                    .expect("conf : keypairs file : fail to parse network_pub !"),
-            })),
-        };
-        let result_key_pairs = inner_clear_keys(false, true, key_pairs);
+        let mut key_pairs = setup_keys(true);
+        clear_member_key(&mut key_pairs);
         // We expect network key not to change
         assert_eq!(
-            result_key_pairs.network_keypair.public_key(),
+            key_pairs.network_keypair.public_key(),
             PubKey::Ed25519(
                 ed25519::PublicKey::from_base58(BASE58_PUB_INIT)
                     .expect("Wrong data in BASE58_PUB_TEST")
             )
         );
         assert_eq!(
-            result_key_pairs.network_keypair.seed().clone(),
+            key_pairs.network_keypair.seed().clone(),
             Seed32::from_base58(BASE58_SEED_INIT).expect("Wrong data in BASE58_SEED_TEST")
         );
 
         // We expect member key to change
-        assert_eq!(result_key_pairs.member_keypair, None);
-        assert_eq!(result_key_pairs.member_keypair, None);
+        assert_eq!(key_pairs.member_keypair, None);
     }
 }
diff --git a/lib/core/core/Cargo.toml b/lib/core/core/Cargo.toml
index 96b60338..92ba2279 100644
--- a/lib/core/core/Cargo.toml
+++ b/lib/core/core/Cargo.toml
@@ -32,6 +32,5 @@ serde_derive = "1.0.*"
 serde_json = "1.0.*"
 structopt= "0.3.9"
 unwrap = "1.2.1"
-zeroize = { version = "1.1.0", features = ["zeroize_derive"] }
 
 [features]
diff --git a/lib/core/core/src/commands/keys.rs b/lib/core/core/src/commands/keys.rs
index 8686a4b6..a1c49ed7 100644
--- a/lib/core/core/src/commands/keys.rs
+++ b/lib/core/core/src/commands/keys.rs
@@ -21,9 +21,8 @@ use crate::DursCore;
 use clap::arg_enum;
 use durs_conf::keypairs::cli::*;
 use durs_conf::DuRsConf;
-use zeroize::Zeroize;
 
-#[derive(StructOpt, Debug, Clone)]
+#[derive(StructOpt, Debug, Clone, Copy)]
 #[structopt(
     name = "keys",
     author = "inso <inso@tuta.io>",
@@ -36,7 +35,7 @@ pub struct KeysOpt {
     pub subcommand: KeysSubCommand,
 }
 
-#[derive(StructOpt, Debug, Clone)]
+#[derive(StructOpt, Debug, Clone, Copy)]
 /// keys subcommands
 pub enum KeysSubCommand {
     /// Modify keys
@@ -72,7 +71,7 @@ pub enum KeysSubCommand {
     Wizard(WizardOpt),
 }
 
-#[derive(StructOpt, Debug, Clone)]
+#[derive(StructOpt, Debug, Clone, Copy)]
 /// ModifyOpt
 pub struct ModifyOpt {
     #[structopt(subcommand)]
@@ -80,16 +79,16 @@ pub struct ModifyOpt {
     pub subcommand: ModifySubCommand,
 }
 
-#[derive(StructOpt, Debug, Clone)]
+#[derive(StructOpt, Debug, Clone, Copy)]
 /// keys modify subcommands
 pub enum ModifySubCommand {
     #[structopt(name = "member", setting(structopt::clap::AppSettings::ColoredHelp))]
     /// Salt and password of member key
-    MemberSaltPassword(SaltPasswordOpt),
+    MemberSaltPassword,
 
     #[structopt(name = "network", setting(structopt::clap::AppSettings::ColoredHelp))]
     /// Salt and password of network key    
-    NetworkSaltPassword(SaltPasswordOpt),
+    NetworkSaltPassword,
 }
 
 arg_enum! {
@@ -121,19 +120,6 @@ pub struct ClearOpt {
     key: KeyKind,
 }
 
-#[derive(StructOpt, Debug, Clone, Zeroize)]
-#[zeroize(drop)]
-/// SaltPasswordOpt
-pub struct SaltPasswordOpt {
-    #[structopt(long = "salt")]
-    /// Salt of key generator
-    pub salt: String,
-
-    #[structopt(long = "password")]
-    /// Password of key generator
-    pub password: String,
-}
-
 #[derive(StructOpt, Debug, Copy, Clone)]
 /// WizardOpt
 pub struct WizardOpt {}
@@ -151,27 +137,31 @@ impl DursExecutableCoreCommand for KeysOpt {
         match self.subcommand {
             KeysSubCommand::Wizard(_) => {
                 let new_keypairs = key_wizard(keypairs)?;
-                save_keypairs(profile_path, &keypairs_file, new_keypairs)
+                save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                     .map_err(DursCoreError::FailWriteKeypairsFile)
+                    .and_then(|_| {
+                        show_keys(new_keypairs);
+                        Ok(())
+                    })
             }
             KeysSubCommand::Modify(modify_opt) => match modify_opt.subcommand {
-                ModifySubCommand::NetworkSaltPassword(network_opt) => {
-                    let new_keypairs = modify_network_keys(
-                        network_opt.salt.clone(),
-                        network_opt.password.clone(),
-                        keypairs,
-                    );
-                    save_keypairs(profile_path, &keypairs_file, new_keypairs)
+                ModifySubCommand::NetworkSaltPassword => {
+                    let new_keypairs = modify_network_keys(keypairs)?;
+                    save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                         .map_err(DursCoreError::FailWriteKeypairsFile)
+                        .and_then(|_| {
+                            show_network_keys(&new_keypairs);
+                            Ok(())
+                        })
                 }
-                ModifySubCommand::MemberSaltPassword(member_opt) => {
-                    let new_keypairs = modify_member_keys(
-                        member_opt.salt.clone(),
-                        member_opt.password.clone(),
-                        keypairs,
-                    );
-                    save_keypairs(profile_path, &keypairs_file, new_keypairs)
+                ModifySubCommand::MemberSaltPassword => {
+                    let new_keypairs = modify_member_keys(keypairs)?;
+                    save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                         .map_err(DursCoreError::FailWriteKeypairsFile)
+                        .and_then(|_| {
+                            show_member_keys(&new_keypairs);
+                            Ok(())
+                        })
                 }
             },
             KeysSubCommand::Clear(clear_opt) => {
@@ -180,8 +170,12 @@ impl DursExecutableCoreCommand for KeysOpt {
                     clear_opt.key.is_member(),
                     keypairs,
                 );
-                save_keypairs(profile_path, &keypairs_file, new_keypairs)
+                save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                     .map_err(DursCoreError::FailWriteKeypairsFile)
+                    .and_then(|_| {
+                        show_keys(new_keypairs);
+                        Ok(())
+                    })
             }
             KeysSubCommand::Show(_) => {
                 show_keys(keypairs);
diff --git a/lib/core/core/src/errors.rs b/lib/core/core/src/errors.rs
index 9e40dd0e..dd9d85ab 100644
--- a/lib/core/core/src/errors.rs
+++ b/lib/core/core/src/errors.rs
@@ -17,7 +17,7 @@
 
 use crate::logger::InitLoggerError;
 use dubp_currency_params::db::CurrencyParamsDbError;
-use durs_conf::keypairs::cli::WizardError;
+use durs_conf::keypairs::cli::CliError;
 use durs_module::{ModuleStaticName, PlugModuleError};
 use failure::{Error, Fail};
 
@@ -66,8 +66,8 @@ pub enum DursCoreError {
     #[fail(display = "Please specify the url of a trusted node or use the --local option.")]
     SyncWithoutSource,
     /// Error on keys sub-command
-    #[fail(display = "Error en keys sub-command")]
-    WizardKeysError(WizardError),
+    #[fail(display = "Error on keys sub-command")]
+    WizardKeysError(CliError),
 }
 
 impl From<InitLoggerError> for DursCoreError {
@@ -76,8 +76,8 @@ impl From<InitLoggerError> for DursCoreError {
     }
 }
 
-impl From<WizardError> for DursCoreError {
-    fn from(e: WizardError) -> Self {
+impl From<CliError> for DursCoreError {
+    fn from(e: CliError) -> Self {
         DursCoreError::WizardKeysError(e)
     }
 }
-- 
GitLab