Compare commits

...

2 Commits

10 changed files with 242 additions and 47 deletions

View File

@ -1,6 +1,6 @@
{
"name": "creddy",
"version": "0.1.0",
"version": "0.2.0",
"scripts": {
"dev": "vite",
"build": "vite build",

View File

@ -1 +1 @@
DATABASE_URL=sqlite://creddy.db?mode=rwc
DATABASE_URL=sqlite://C:/Users/Joe/AppData/Roaming/creddy/creddy.dev.db

136
src-tauri/Cargo.lock generated
View File

@ -8,6 +8,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "ahash"
version = "0.7.6"
@ -60,15 +70,18 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "app"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"argon2",
"auto-launch",
"aws-config",
"aws-sdk-sts",
"aws-smithy-types",
"aws-types",
"chacha20poly1305",
"clap",
"dirs 5.0.1",
"is-terminal",
"netstat2",
"once_cell",
"serde",
@ -85,6 +98,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "argon2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
dependencies = [
"base64ct",
"blake2",
"password-hash",
]
[[package]]
name = "async-broadcast"
version = "0.5.1"
@ -544,12 +568,27 @@ dependencies = [
"simd-abstraction",
]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block"
version = "0.1.6"
@ -726,6 +765,41 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
"chacha20",
"cipher",
"poly1305",
"zeroize",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]]
name = "clap"
version = "3.2.25"
@ -961,6 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core 0.6.4",
"typenum",
]
@ -1995,6 +2070,15 @@ dependencies = [
"cfb",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -2015,6 +2099,18 @@ dependencies = [
"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]]
name = "itertools"
version = "0.10.5"
@ -2538,6 +2634,12 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl-probe"
version = "0.1.5"
@ -2668,6 +2770,17 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "paste"
version = "1.0.12"
@ -2859,6 +2972,17 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -4443,6 +4567,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "untrusted"
version = "0.7.1"

View File

@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.0"
version = "0.2.0"
description = "A Tauri App"
authors = ["you"]
license = ""
@ -35,6 +35,10 @@ strum_macros = "0.24"
auto-launch = "0.4.0"
dirs = "5.0"
clap = { version = "3.2.23", features = ["derive"] }
is-terminal = "0.4.7"
argon2 = "0.5.0"
chacha20poly1305 = "0.10.1"
generic_array = "0.14.6"
[features]
# by default Tauri runs in production mode

View File

@ -66,6 +66,7 @@ pub async fn connect_db() -> Result<SqlitePool, SetupError> {
.create_if_missing(true);
let pool_opts = SqlitePoolOptions::new();
let pool: SqlitePool = pool_opts.connect_with(conn_opts).await?;
sqlx::migrate!().run(&pool).await?;
Ok(pool)
}
@ -74,8 +75,6 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
APP.set(app.handle()).unwrap();
let pool = connect_db().await?;
sqlx::migrate!().run(&pool).await?;
let conf = AppConfig::load(&pool).await?;
let session = Session::load(&pool).await?;
let srv = Server::new(conf.listen_addr, conf.listen_port, app.handle()).await?;

View File

@ -2,6 +2,7 @@ use std::net::Ipv4Addr;
use std::path::PathBuf;
use auto_launch::AutoLaunchBuilder;
use is_terminal::IsTerminal;
use serde::{Serialize, Deserialize};
use sqlx::SqlitePool;
@ -90,16 +91,17 @@ pub fn set_auto_launch(is_configured: bool) -> Result<(), SetupError> {
pub fn get_or_create_db_path() -> Result<PathBuf, DataDirError> {
// debug_assertions doesn't always mean we are running in dev
if cfg!(debug_assertions) && std::env::var("HOME").is_ok() {
return Ok(PathBuf::from("./creddy.db"));
}
let mut path = dirs::data_dir()
.ok_or(DataDirError::NotFound)?;
path.push("Creddy");
std::fs::create_dir_all(&path)?;
path.push("creddy.db");
if cfg!(debug_assertions) && std::io::stdout().is_terminal() {
path.push("creddy.dev.db");
}
else {
path.push("creddy.db");
}
Ok(path)
}

View File

@ -2,6 +2,25 @@ use std::fmt::{self, Formatter};
use std::time::{SystemTime, UNIX_EPOCH};
use aws_smithy_types::date_time::{DateTime, Format};
use argon2::{
Argon2,
Algorithm,
Version,
ParamsBuilder,
password_hash::rand_core::{RngCore, OsRng},
};
use chacha20poly1305::{
XChaCha20Poly1305,
XNonce,
aead::{
Aead,
AeadCore,
Key,
KeyInit,
Error as AeadError,
generic_array::GenericArray,
},
};
use serde::{
Serialize,
Deserialize,
@ -10,12 +29,7 @@ use serde::{
};
use serde::de::{self, Visitor};
use sqlx::SqlitePool;
use sodiumoxide::crypto::{
pwhash,
pwhash::Salt,
secretbox,
secretbox::{Nonce, Key}
};
use crate::errors::*;
@ -76,8 +90,8 @@ impl Session {
pub struct LockedCredentials {
pub access_key_id: String,
pub secret_key_enc: Vec<u8>,
pub salt: Salt,
pub nonce: Nonce,
pub salt: [u8; 32],
pub nonce: XNonce,
}
impl LockedCredentials {
@ -97,10 +111,8 @@ impl LockedCredentials {
}
pub fn decrypt(&self, passphrase: &str) -> Result<BaseCredentials, UnlockError> {
let mut key_buf = [0; secretbox::KEYBYTES];
// pretty sure this only fails if we're out of memory
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &self.salt).unwrap();
let decrypted = secretbox::open(&self.secret_key_enc, &self.nonce, &Key(key_buf))
let crypto = Crypto::new(passphrase, &self.salt);
let decrypted = crypto.decrypt(&self.nonce, &self.secret_key_enc)
.map_err(|_| UnlockError::BadPassphrase)?;
let secret_access_key = String::from_utf8(decrypted)
.map_err(|_| UnlockError::InvalidUtf8)?;
@ -122,21 +134,18 @@ pub struct BaseCredentials {
}
impl BaseCredentials {
pub fn encrypt(&self, passphrase: &str) -> LockedCredentials {
let salt = pwhash::gen_salt();
let mut key_buf = [0; secretbox::KEYBYTES];
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
let key = Key(key_buf);
let nonce = secretbox::gen_nonce();
pub fn encrypt(&self, passphrase: &str) -> Result<CryptoError, LockedCredentials> {
let salt = Crypto::salt();
let crypto = Crypto::new(passphrase, &salt)?;
let (nonce, secret_key_enc) = crypto.encrypt(self.secret_access_key.as_bytes());
let secret_key_enc = secretbox::seal(self.secret_access_key.as_bytes(), &nonce, &key);
LockedCredentials {
let locked = LockedCredentials {
access_key_id: self.access_key_id.clone(),
secret_key_enc,
salt,
nonce,
}
};
Ok(locked)
}
}
@ -241,4 +250,60 @@ fn deserialize_expiration<'de, D>(deserializer: D) -> Result<DateTime, D::Error>
where D: Deserializer<'de>
{
deserializer.deserialize_str(DateTimeVisitor)
}
}
struct Crypto {
cipher: XChaCha20Poly1305,
}
impl Crypto {
/// Argon2 params rationale:
///
/// m_cost is measured in KiB, so 128 * 1024 gives us 128MiB.
/// This should roughly double the memory usage of the application
/// while deriving the key.
///
/// p_cost is irrelevant since (at present) there isn't any parallelism
/// implemented, so we leave it at 1.
///
/// With the above m_cost, t_cost = 8 results in about 800ms to derive
/// a key on my (somewhat older) CPU. This is probably overkill, but
/// given that it should only have to happen ~once a day for most
/// usage, it should be acceptable.
fn new(passphrase: &str, salt: &[u8]) -> argon2::Result<Crypto> {
let params = ParamsBuilder::new()
.m_cost(128 * 1024)
.p_cost(1)
.t_cost(8)
.build()
.unwrap(); // only errors if the given params are invalid
let hasher = Argon2::new(
Algorithm::Argon2id,
Version::V0x13,
params,
);
let mut key = [0; 32];
hasher.hash_password_into(passphrase.as_bytes(), &salt, &mut key)?;
let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
Ok(Crypto { cipher })
}
fn salt() -> [u8; 32] {
let mut salt = [0; 32];
OsRng.fill_bytes(&mut salt);
salt
}
fn encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), AeadError> {
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
let ciphertext = self.cipher.encrypt(&nonce, data)?;
Ok((nonce, ciphertext))
}
fn decrypt(&self, nonce: &XNonce, data: &[u8]) -> Result<Vec<u8>, AeadError> {
self.cipher.decrypt(nonce, data)
}
}

View File

@ -59,16 +59,7 @@ impl Handler {
return Ok(())
}
let base = req_path == b"/creddy/base-credentials";
if base {
if clients.len() != 1
|| clients[0].is_none()
|| clients[0].as_ref().unwrap().exe != std::env::current_exe()?
{
self.stream.write(b"HTTP/1.0 403 Access Denied\r\n\r\n").await?;
return Ok(())
}
}
let req = Request {id: self.request_id, clients, base};
self.app.emit_all("credentials-request", &req)?;
let starting_visibility = self.show_window()?;

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "creddy",
"version": "0.1.0"
"version": "0.2.0"
},
"tauri": {
"allowlist": {

View File

@ -86,7 +86,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
<span>
WARNING: This application is requesting your base (long-lived) AWS credentials.
These crednetials are less secure than session credentials, since they don't expire automatically.
These credentials are less secure than session credentials, since they don't expire automatically.
</span>
</div>
</div>