Compare commits
2 Commits
wincon
...
c260e37e78
Author | SHA1 | Date | |
---|---|---|---|
c260e37e78 | |||
7501253970 |
9
doc/cryptography.md
Normal file
9
doc/cryptography.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
My original plan was to use [libsodium](https://doc.libsodium.org/) to handle encryption. However, the Rust bindings for libsodium are no longer actively maintained, which left me uncomfortable with using it. Instead, I switched to the [RustCrypto](https://github.com/RustCrypto) implementations of the same (or nearly the same) cryptographic primitives provided by libsodium.
|
||||||
|
|
||||||
|
Creddy makes use of two cryptographic primitives: A key-derivation function, which is currently `argon2id`, and a symmetric encryption algorithm, currently `XChaCha20Poly1305`.
|
||||||
|
* I chose `argon2id` because it's what libsodium uses, and because its difficulty parameters admit of very granular tuning.
|
||||||
|
* I chose `XChaCha20Poly1305` because it's _almost_ what libsodium uses - libsodium uses `XSalsa20Poly1305`, and it's my undersatnding that `XChaCha20Poly1305` is an evolution of the former. In both cases I use the eXtended variants, which make use of longer (24-byte) nonces than the non-X variants. This appealed to me because I wanted to be able to randomly generate a nonce every time I needed one, and I have seen [recommendations](https://latacora.micro.blog/2018/04/03/cryptographic-right-answers.html) that the 12-byte nonces used by the non-X variants are _juuust_ a touch small for that to be truly worry-free. The RustCrypto implementation of `XChaCha20Poly1305` has also been subject to a security audit, which is nice.
|
||||||
|
|
||||||
|
I tuned the `argon2id` parameters so that key-derivation would take ~800ms on my Ryzen 1600X. This is probably overkill, but I don't intend for key-derivation to be a frequent occurrence - no more than once a day, under normal circumstances. Taking in the neighborhood of 1 second seemed about the longest I could reasonably go.
|
||||||
|
|
||||||
|
**DISCLAIMER**: I am not a professional cryptographer, merely an interested amateur. While I've tried to be as careful as possible with selecting and making use of the cryptographic building blocks I've chosen here, there is always the possibility that I've screwed something up. If anyone would like to sponsor an _actual_ security review of Creddy by people who _actually_ know what they're doing instead of just what they've read on the internet, please let me know.
|
37
src-tauri/Cargo.lock
generated
37
src-tauri/Cargo.lock
generated
@ -958,7 +958,7 @@ dependencies = [
|
|||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"clap",
|
"clap",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"gag",
|
"is-terminal",
|
||||||
"netstat2",
|
"netstat2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
@ -973,7 +973,6 @@ dependencies = [
|
|||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"windows 0.48.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1348,17 +1347,6 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "filedescriptor"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"thiserror",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@ -1541,16 +1529,6 @@ dependencies = [
|
|||||||
"byteorder",
|
"byteorder",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gag"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a713bee13966e9fbffdf7193af71d54a6b35a0bb34997cd6c9519ebeb5005972"
|
|
||||||
dependencies = [
|
|
||||||
"filedescriptor",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gdk"
|
name = "gdk"
|
||||||
version = "0.15.4"
|
version = "0.15.4"
|
||||||
@ -2121,6 +2099,18 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.3.1",
|
||||||
|
"io-lifetimes",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@ -4019,7 +4009,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fe7e0f1d535e7cbbbab43c82be4fc992b84f9156c16c160955617e0260ebc449"
|
checksum = "fe7e0f1d535e7cbbbab43c82be4fc992b84f9156c16c160955617e0260ebc449"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
|
@ -9,6 +9,14 @@ default-run = "creddy"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.57"
|
rust-version = "1.57"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "creddy_cli"
|
||||||
|
path = "src/bin/creddy_cli.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "creddy"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
@ -17,7 +25,7 @@ tauri-build = { version = "1.0.4", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tauri = { version = "1.2", features = ["cli", "dialog", "os-all", "system-tray"] }
|
tauri = { version = "1.2", features = ["dialog", "os-all", "system-tray"] }
|
||||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||||
sodiumoxide = "0.2.7"
|
sodiumoxide = "0.2.7"
|
||||||
tokio = { version = ">=1.19", features = ["full"] }
|
tokio = { version = ">=1.19", features = ["full"] }
|
||||||
@ -35,11 +43,9 @@ strum_macros = "0.24"
|
|||||||
auto-launch = "0.4.0"
|
auto-launch = "0.4.0"
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
clap = { version = "3.2.23", features = ["derive"] }
|
clap = { version = "3.2.23", features = ["derive"] }
|
||||||
# is-terminal = "0.4.7"
|
is-terminal = "0.4.7"
|
||||||
argon2 = { version = "0.5.0", features = ["std"] }
|
argon2 = { version = "0.5.0", features = ["std"] }
|
||||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||||
windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Console"] }
|
|
||||||
gag = "1.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
22
src-tauri/conf/cli.wxs
Normal file
22
src-tauri/conf/cli.wxs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
|
<Fragment>
|
||||||
|
|
||||||
|
<DirectoryRef Id="INSTALLDIR">
|
||||||
|
<!-- Create a subdirectory for the console binary so that we can add it to PATH -->
|
||||||
|
<Directory Id="BinDir" Name="bin">
|
||||||
|
<Component Id="CliBinary" Guid="b6358c8e-504f-41fd-b14b-38af821dcd04">
|
||||||
|
<!-- Same name as the main executable, so that it can be invoked as just "creddy" -->
|
||||||
|
<File Id="Bin_Cli" Source="..\..\creddy_cli.exe" Name="creddy.exe" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</Directory>
|
||||||
|
</DirectoryRef>
|
||||||
|
|
||||||
|
<DirectoryRef Id="TARGETDIR">
|
||||||
|
<Component Id="AddToPath" Guid="b5fdaf7e-94f2-4aad-9144-aa3a8edfa675">
|
||||||
|
<Environment Id="CreddyInstallDir" Action="set" Name="PATH" Part="last" Permanent="no" Value="[BinDir]" />
|
||||||
|
</Component>
|
||||||
|
</DirectoryRef>
|
||||||
|
|
||||||
|
</Fragment>
|
||||||
|
</Wix>
|
45
src-tauri/src/bin/creddy_cli.rs
Normal file
45
src-tauri/src/bin/creddy_cli.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Windows isn't really amenable to having a single executable work as both a CLI and GUI app,
|
||||||
|
// so we just have a second binary for CLI usage
|
||||||
|
use creddy::{
|
||||||
|
cli,
|
||||||
|
errors::CliError,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
process::{self, Command},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = cli::parser().get_matches();
|
||||||
|
if let Some(true) = args.get_one::<bool>("help") {
|
||||||
|
cli::parser().print_help().unwrap(); // if we can't print help we can't print an error
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = match args.subcommand() {
|
||||||
|
None | Some(("run", _)) => launch_gui(),
|
||||||
|
Some(("show", m)) => cli::show(m),
|
||||||
|
Some(("exec", m)) => cli::exec(m),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
eprintln!("Error: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn launch_gui() -> Result<(), CliError> {
|
||||||
|
let mut path = env::current_exe()?;
|
||||||
|
path.pop(); // bin dir
|
||||||
|
|
||||||
|
// binaries are colocated in dev, but not in production
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
path.pop(); // install dir
|
||||||
|
|
||||||
|
path.push("creddy.exe"); // exe in main install dir (aka gui exe)
|
||||||
|
|
||||||
|
Command::new(path).spawn()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -13,6 +13,7 @@ use tokio::{
|
|||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
use crate::app;
|
use crate::app;
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::credentials::{BaseCredentials, SessionCredentials};
|
use crate::credentials::{BaseCredentials, SessionCredentials};
|
||||||
@ -22,16 +23,6 @@ use crate::errors::*;
|
|||||||
pub fn parser() -> Command<'static> {
|
pub fn parser() -> Command<'static> {
|
||||||
Command::new("creddy")
|
Command::new("creddy")
|
||||||
.about("A friendly AWS credentials manager")
|
.about("A friendly AWS credentials manager")
|
||||||
// we don't want the default help handling because it early-exits,
|
|
||||||
// and on Windows we need to setup the console before we can output
|
|
||||||
.disable_help_flag(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("help")
|
|
||||||
.short('h')
|
|
||||||
.long("help")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("Print this message or the help of the given subcommand(s)")
|
|
||||||
)
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("run")
|
Command::new("run")
|
||||||
.about("Launch Creddy")
|
.about("Launch Creddy")
|
||||||
|
@ -2,6 +2,7 @@ use std::net::Ipv4Addr;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use auto_launch::AutoLaunchBuilder;
|
use auto_launch::AutoLaunchBuilder;
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ pub fn get_or_create_db_path() -> Result<PathBuf, DataDirError> {
|
|||||||
path.push("Creddy");
|
path.push("Creddy");
|
||||||
|
|
||||||
std::fs::create_dir_all(&path)?;
|
std::fs::create_dir_all(&path)?;
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) && std::io::stdout().is_terminal() {
|
||||||
path.push("creddy.dev.db");
|
path.push("creddy.dev.db");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -228,6 +228,8 @@ pub enum CliError {
|
|||||||
Request(#[from] RequestError),
|
Request(#[from] RequestError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Exec(#[from] ExecError),
|
Exec(#[from] ExecError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
10
src-tauri/src/lib.rs
Normal file
10
src-tauri/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
pub mod app;
|
||||||
|
pub mod cli;
|
||||||
|
mod config;
|
||||||
|
mod credentials;
|
||||||
|
pub mod errors;
|
||||||
|
mod clientinfo;
|
||||||
|
mod ipc;
|
||||||
|
mod state;
|
||||||
|
mod server;
|
||||||
|
mod tray;
|
@ -3,66 +3,21 @@
|
|||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
#[cfg(windows)]
|
use creddy::{
|
||||||
use {
|
app,
|
||||||
std::fs::File,
|
cli,
|
||||||
std::os::windows::io::FromRawHandle,
|
errors::ErrorPopup,
|
||||||
std::os::raw::c_void,
|
|
||||||
gag::Redirect,
|
|
||||||
windows::Win32::System::Console::{
|
|
||||||
AllocConsole,
|
|
||||||
AttachConsole,
|
|
||||||
GetStdHandle,
|
|
||||||
STD_OUTPUT_HANDLE,
|
|
||||||
STD_ERROR_HANDLE,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod app;
|
|
||||||
mod cli;
|
|
||||||
mod config;
|
|
||||||
mod credentials;
|
|
||||||
mod errors;
|
|
||||||
mod clientinfo;
|
|
||||||
mod ipc;
|
|
||||||
mod state;
|
|
||||||
mod server;
|
|
||||||
mod tray;
|
|
||||||
|
|
||||||
use crate::errors::ErrorPopup;
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = cli::parser().get_matches();
|
let res = match cli::parser().get_matches().subcommand() {
|
||||||
let help = matches!(args.get_one::<bool>("help"), Some(true));
|
None | Some(("run", _)) => {
|
||||||
|
|
||||||
// This is the only case that doesn't need a console
|
|
||||||
if let None | Some(("run", _)) = args.subcommand() {
|
|
||||||
if !help {
|
|
||||||
app::run().error_popup("Creddy failed to start");
|
app::run().error_popup("Creddy failed to start");
|
||||||
return;
|
Ok(())
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
// on Windows, need to do the whole allocate-a-console thing
|
|
||||||
#[cfg(windows)]
|
|
||||||
attach_console();
|
|
||||||
// let (out, err) = setup_console();
|
|
||||||
println!("Testing stdout");
|
|
||||||
// writeln!(&mut out, "Testing allocated file");
|
|
||||||
|
|
||||||
if help {
|
|
||||||
// if we can't print help, we can't print an error, so just panic
|
|
||||||
dbg!(args.get_one::<bool>("help"));
|
|
||||||
cli::parser().print_help().unwrap();
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = match args.subcommand() {
|
|
||||||
Some(("show", m)) => cli::show(m),
|
Some(("show", m)) => cli::show(m),
|
||||||
Some(("exec", m)) => cli::exec(m),
|
Some(("exec", m)) => cli::exec(m),
|
||||||
// clap ensures that subcommand is either None or run/show/exec
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,43 +25,3 @@ fn main() {
|
|||||||
eprintln!("Error: {e}");
|
eprintln!("Error: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn attach_console() {
|
|
||||||
unsafe { AttachConsole(u32::MAX); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn setup_console() -> (Redirect<File>, Redirect<File>) {
|
|
||||||
let (mut stdout, stderr) = unsafe {
|
|
||||||
AllocConsole();
|
|
||||||
// if we can't get handles to stdout/err, we can't display these errors,
|
|
||||||
// so there's no point in doing anything other than panicking
|
|
||||||
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
|
|
||||||
let stderr_handle = GetStdHandle(STD_ERROR_HANDLE).unwrap();
|
|
||||||
|
|
||||||
let mut f = File::create("C:\\Users\\Joe\\Downloads\\debug.txt").unwrap();
|
|
||||||
writeln!(&mut f, "{stdout_handle:?}\n{:?}", stdout_handle.0 as *mut c_void).unwrap();
|
|
||||||
|
|
||||||
(
|
|
||||||
File::from_raw_handle(stdout_handle.0 as *mut c_void),
|
|
||||||
File::from_raw_handle(stderr_handle.0 as *mut c_void),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
writeln!(&mut stdout, "Testing stdout before redirect")
|
|
||||||
.map_err(|e| log_err(e))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
(
|
|
||||||
Redirect::stdout(stdout).unwrap(),
|
|
||||||
Redirect::stderr(stderr).unwrap()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn log_err(e: impl std::error::Error) {
|
|
||||||
let mut f = File::create("C:\\Users\\Joe\\Downloads\\log.txt").unwrap();
|
|
||||||
writeln!(&mut f, "{e}").unwrap();
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"distDir": "../dist"
|
"distDir": "../dist"
|
||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Creddy",
|
"productName": "creddy",
|
||||||
"version": "0.2.2"
|
"version": "0.2.2"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
@ -44,14 +44,10 @@
|
|||||||
"windows": {
|
"windows": {
|
||||||
"certificateThumbprint": null,
|
"certificateThumbprint": null,
|
||||||
"digestAlgorithm": "sha256",
|
"digestAlgorithm": "sha256",
|
||||||
"timestampUrl": ""
|
"timestampUrl": "",
|
||||||
}
|
"wix": {
|
||||||
},
|
"fragmentPaths": ["conf/cli.wxs"],
|
||||||
"cli": {
|
"componentRefs": ["CliBinary", "AddToPath"]
|
||||||
"description": "A friendly AWS credentials manager",
|
|
||||||
"subcommands": {
|
|
||||||
"run": {
|
|
||||||
"description": "Launch Creddy"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user