diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2023-09-01 00:00:00 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2023-09-01 00:00:00 +0000 |
| commit | 96660a90e4cd296a2922d7a547de4cd9d0b1928b (patch) | |
| tree | e5216e22ee1a3e1653d8d1ecd856f4f03615d6a1 /src/cli_interactor.rs | |
| parent | 6423baebd92e45c9be85157c443dff42e65d8d14 (diff) | |
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.
Diffstat (limited to 'src/cli_interactor.rs')
| -rw-r--r-- | src/cli_interactor.rs | 31 |
1 files changed, 29 insertions, 2 deletions
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 @@ | |||
| 1 | use anyhow::{bail, Result}; | 1 | use anyhow::Result; |
| 2 | use dialoguer::{theme::ColorfulTheme, Input}; | 2 | use dialoguer::{theme::ColorfulTheme, Input, Password}; |
| 3 | #[cfg(test)] | 3 | #[cfg(test)] |
| 4 | use mockall::*; | 4 | use mockall::*; |
| 5 | 5 | ||
| @@ -11,6 +11,7 @@ pub struct Interactor { | |||
| 11 | #[cfg_attr(test, automock)] | 11 | #[cfg_attr(test, automock)] |
| 12 | pub trait InteractorPrompt { | 12 | pub trait InteractorPrompt { |
| 13 | fn input(&self, parms: PromptInputParms) -> Result<String>; | 13 | fn input(&self, parms: PromptInputParms) -> Result<String>; |
| 14 | fn password(&self, parms: PromptPasswordParms) -> Result<String>; | ||
| 14 | } | 15 | } |
| 15 | impl InteractorPrompt for Interactor { | 16 | impl InteractorPrompt for Interactor { |
| 16 | fn input(&self, parms: PromptInputParms) -> Result<String> { | 17 | fn input(&self, parms: PromptInputParms) -> Result<String> { |
| @@ -19,6 +20,15 @@ impl InteractorPrompt for Interactor { | |||
| 19 | .interact_text()?; | 20 | .interact_text()?; |
| 20 | Ok(input) | 21 | Ok(input) |
| 21 | } | 22 | } |
| 23 | fn password(&self, parms: PromptPasswordParms) -> Result<String> { | ||
| 24 | let mut p = Password::with_theme(&self.theme); | ||
| 25 | p.with_prompt(parms.prompt); | ||
| 26 | if parms.confirm { | ||
| 27 | p.with_confirmation("confirm password", "passwords didnt match..."); | ||
| 28 | } | ||
| 29 | let pass: String = p.interact()?; | ||
| 30 | Ok(pass) | ||
| 31 | } | ||
| 22 | } | 32 | } |
| 23 | 33 | ||
| 24 | #[derive(Default)] | 34 | #[derive(Default)] |
| @@ -32,3 +42,20 @@ impl PromptInputParms { | |||
| 32 | self | 42 | self |
| 33 | } | 43 | } |
| 34 | } | 44 | } |
| 45 | |||
| 46 | #[derive(Default)] | ||
| 47 | pub struct PromptPasswordParms { | ||
| 48 | pub prompt: String, | ||
| 49 | pub confirm: bool, | ||
| 50 | } | ||
| 51 | |||
| 52 | impl PromptPasswordParms { | ||
| 53 | pub fn with_prompt<S: Into<String>>(mut self, prompt: S) -> Self { | ||
| 54 | self.prompt = prompt.into(); | ||
| 55 | self | ||
| 56 | } | ||
| 57 | pub const fn with_confirm(mut self) -> Self { | ||
| 58 | self.confirm = true; | ||
| 59 | self | ||
| 60 | } | ||
| 61 | } | ||