From 96660a90e4cd296a2922d7a547de4cd9d0b1928b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 1 Sep 2023 00:00:00 +0000 Subject: feat(login) password login using encrypted nsec Enables the user to only handle the nsec upon first use of the tool by encrypting it with a password and storing it on disk in an application cache. The approach to encryption draws heavily from that used by the gossip nostr client. - unencrypted nsec is zeroed from memory - a salt is used to defend against rainbow tables - computationally expensive key stretching defends against brute-force attacks of passwords with low entropy. There is UX trade-off between decryption speed and key-stretching computation. This UX challenge is exacerbated in a cli tool as decryption must take place more regularly. Thought was put into the selected n_log and a heavily reduced value is provided for long passwords where security benefits are smaller. A more granular reducing in computation was also considered by rejected to avoided to revealing just how weak a password is as most weak passwords are reused. --- src/cli_interactor.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'src/cli_interactor.rs') diff --git a/src/cli_interactor.rs b/src/cli_interactor.rs index 2f28aee..d7de087 100644 --- a/src/cli_interactor.rs +++ b/src/cli_interactor.rs @@ -1,5 +1,5 @@ -use anyhow::{bail, Result}; -use dialoguer::{theme::ColorfulTheme, Input}; +use anyhow::Result; +use dialoguer::{theme::ColorfulTheme, Input, Password}; #[cfg(test)] use mockall::*; @@ -11,6 +11,7 @@ pub struct Interactor { #[cfg_attr(test, automock)] pub trait InteractorPrompt { fn input(&self, parms: PromptInputParms) -> Result; + fn password(&self, parms: PromptPasswordParms) -> Result; } impl InteractorPrompt for Interactor { fn input(&self, parms: PromptInputParms) -> Result { @@ -19,6 +20,15 @@ impl InteractorPrompt for Interactor { .interact_text()?; Ok(input) } + fn password(&self, parms: PromptPasswordParms) -> Result { + let mut p = Password::with_theme(&self.theme); + p.with_prompt(parms.prompt); + if parms.confirm { + p.with_confirmation("confirm password", "passwords didnt match..."); + } + let pass: String = p.interact()?; + Ok(pass) + } } #[derive(Default)] @@ -32,3 +42,20 @@ impl PromptInputParms { self } } + +#[derive(Default)] +pub struct PromptPasswordParms { + pub prompt: String, + pub confirm: bool, +} + +impl PromptPasswordParms { + pub fn with_prompt>(mut self, prompt: S) -> Self { + self.prompt = prompt.into(); + self + } + pub const fn with_confirm(mut self) -> Self { + self.confirm = true; + self + } +} -- cgit v1.2.3