Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tarpaulin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
exclude-files = [
"crates/cli/src/logic/init_logging.rs",
"crates/cli/src/logic/run.rs",
"crates/cli/src/logic/get_input.rs",
"crates/cli/src/main.rs",
]
verbose = false
Expand Down
41 changes: 33 additions & 8 deletions crates/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ cargo install --git https://github.com/sajjon/svar
```

## Usage

### Seal (Encrypt)
Simply run the `svar` command in your terminal after installation:
```sh,no_run
svar
svar seal
```

If run for the first time this will prompt you to enter a secret to protect
This will prompt you to enter a secret to protect
and then prompt you to answer a set of security questions. The secret will
be encrypted using the answers to the security questions.

Expand All @@ -37,15 +39,40 @@ as a JSON file in the local data directory, which on macOS is
`$HOME/.local/share/svar/sealed_secret.json` and on Windows:
`C:\Users\<YOUR_USER>\AppData\Local\svar\sealed_secret.json`.

On subsequent runs the program will try to read the sealed secret from
the file and prompt you to answer the security questions again. If you
answer enough questions correctly, the secret will be decrypted and the
program will ask you if you want to print the secret in the terminal.
#### Custom paths
Alternatively you can specify a custom path to read the secret from
and a custom path to save the sealed secret to using the `-i` and
`-o` flags respectively:
```sh,no_run
svar seal -i /path/to/your/secret.txt -o /path/to/save/sealed_secret.json
```

When the `-i` flag is provided, the program will not prompt you to input
the secret, but will read it from the specified file instead. The sealed
secret will be written to the path specified by the `-o` flag.

### Open (Decrypt)
You can open a sealed secret using the `open` command:
```sh,no_run
svar open
```

This will try to read the a sealed secret at the default path and prompt you
to answer the security questions again. If you answer enough questions
correctly, the secret will be decrypted and the program will ask you if you
want to print the secret in the terminal.

If you which to change the secret, simply delete the sealed secret file
and run the program again. It will then prompt you to enter a new secret
and answer the security questions again.

#### Custom path
You can also specify a custom path to read the sealed secret from
using the `-i` flag:
```sh,no_run
svar open -i /path/to/sealed_secret.json
```

> [!TIP]
> When decrypting a sealed secret, try inputting an incorrect answer to any
> of the questions and it will still decrypt the secret. You can also notice
Expand All @@ -55,8 +82,6 @@ and answer the security questions again.
Later we might make this example CLI application more advanced by
allowing you to specify the number of questions and answers, and the
minimum number of correct answers required to decrypt the secret.
We might also allow you to pass a path allowing you to have multiple
sealed secrets.

# Etymology

Expand Down
124 changes: 124 additions & 0 deletions crates/cli/src/logic/get_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use crate::prelude::*;

use clap::{Args, Parser, Subcommand};

const BINARY_NAME: &str = env!("CARGO_PKG_NAME");

#[derive(Debug, Parser)]
#[command(name = BINARY_NAME, about = "Protect a secret using security questions and answers.")]
#[command(version = env!("CARGO_PKG_VERSION"))]
pub struct CliArgs {
#[command(subcommand)]
pub command: CommandArgs,
}

#[derive(Debug, Subcommand)]
pub enum CommandArgs {
Open(OpenArgs),
Seal(SealArgs),
}

pub enum Command {
Open(OpenInput),
Seal(SealInput),
}

#[derive(Debug, Args, PartialEq)]
#[command(name = "open", about = "Decrypts a sealed secret.")]
pub struct OpenArgs {
/// An optional override of where to read the sealed secret from.
/// If not provided, the default data local directory will be used.
#[arg(
long,
short = 'i',
help = "Path to the sealed secret file, if not provided the default data local directory will be used."
)]
sealed_path: Option<PathBuf>,
}

impl OpenArgs {
pub fn non_existent_path_to_sealed_secret(&self) -> Option<PathBuf> {
let path = self.sealed_path.clone().unwrap_or(
default_path_for_sealed_secret_without_checking_existence()
.expect("Failed to get default data local directory"),
);
if !path.exists() {
Some(path.clone())
} else {
None
}
}

pub fn to_input(self) -> Result<OpenInput> {
if let Some(path) = self.sealed_path {
Ok(OpenInput { sealed_path: path })
} else {
let dir = default_path_for_sealed_secret(false)?;
Ok(OpenInput { sealed_path: dir })
}
}
}

#[derive(Debug, PartialEq)]
pub struct OpenInput {
sealed_path: PathBuf,
}
impl OpenInput {
pub fn sealed_path(&self) -> &PathBuf {
&self.sealed_path
}
}

#[derive(Debug, Args, PartialEq)]
#[command(name = "seal", about = "Encrypts a secret using security questions.")]
pub struct SealArgs {
/// An optional override of where to read the secret from, if not
/// provided the user will be prompted to enter a secret.
#[arg(
long,
short = 'i',
help = "Path to a file containing the secret to protect, if not provided the user will be prompted to enter a secret."
)]
secret_path: Option<PathBuf>,

/// An optional override of where to save the output sealed secret, if not
/// provided the default data local directory will be used.
#[arg(
long,
short = 'o',
help = "Path to the output sealed secret file, if not provided the default data local directory will be used."
)]
sealed_path: Option<PathBuf>,
}

impl SealArgs {
pub fn to_input(self) -> Result<SealInput> {
if let Some(path) = self.sealed_path {
Ok(SealInput {
sealed_path: path,
secret_path: self.secret_path,
})
} else {
let sealed_path = default_path_for_sealed_secret(true)?;
Ok(SealInput {
sealed_path,
secret_path: self.secret_path,
})
}
}
}

#[derive(Debug, PartialEq)]
pub struct SealInput {
secret_path: Option<PathBuf>,
sealed_path: PathBuf,
}
impl SealInput {
pub fn secret_path(&self) -> Option<PathBuf> {
self.secret_path.clone()
}

pub fn sealed_path(&self) -> &PathBuf {
&self.sealed_path
}
}
2 changes: 2 additions & 0 deletions crates/cli/src/logic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod get_input;
mod init_logging;
mod run;

pub use get_input::*;
pub use init_logging::*;
pub(crate) use run::*;
Loading
Loading