switch crypto implementation and add spinner
This commit is contained in:
parent
e866a4a643
commit
ddd1005067
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "creddy",
|
"name": "creddy",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
123
src-tauri/Cargo.lock
generated
123
src-tauri/Cargo.lock
generated
@ -8,6 +8,16 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
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]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@ -60,13 +70,15 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
"auto-launch",
|
"auto-launch",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
"aws-sdk-sts",
|
"aws-sdk-sts",
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
"aws-types",
|
"aws-types",
|
||||||
|
"chacha20poly1305",
|
||||||
"clap",
|
"clap",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
@ -86,6 +98,17 @@ dependencies = [
|
|||||||
"tokio",
|
"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]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -545,12 +568,27 @@ dependencies = [
|
|||||||
"simd-abstraction",
|
"simd-abstraction",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block"
|
name = "block"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@ -727,6 +765,41 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.25"
|
version = "3.2.25"
|
||||||
@ -962,6 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core 0.6.4",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1996,6 +2070,15 @@ dependencies = [
|
|||||||
"cfb",
|
"cfb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
@ -2551,6 +2634,12 @@ version = "1.17.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@ -2681,6 +2770,17 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"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]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
@ -2872,6 +2972,17 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@ -4456,6 +4567,16 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
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]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
license = ""
|
license = ""
|
||||||
@ -36,6 +36,8 @@ 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"] }
|
||||||
|
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
use std::fmt::{self, Formatter};
|
use std::fmt::{self, Formatter};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use aws_smithy_types::date_time::{DateTime, Format};
|
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,
|
||||||
|
KeyInit,
|
||||||
|
Error as AeadError,
|
||||||
|
generic_array::GenericArray,
|
||||||
|
},
|
||||||
|
};
|
||||||
use serde::{
|
use serde::{
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
@ -10,12 +28,7 @@ use serde::{
|
|||||||
};
|
};
|
||||||
use serde::de::{self, Visitor};
|
use serde::de::{self, Visitor};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use sodiumoxide::crypto::{
|
|
||||||
pwhash,
|
|
||||||
pwhash::Salt,
|
|
||||||
secretbox,
|
|
||||||
secretbox::{Nonce, Key}
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
|
||||||
@ -40,18 +53,17 @@ impl Session {
|
|||||||
None => {return Ok(Session::Empty);}
|
None => {return Ok(Session::Empty);}
|
||||||
};
|
};
|
||||||
|
|
||||||
let salt_buf: [u8; 32] = row.salt
|
let salt: [u8; 32] = row.salt
|
||||||
.try_into()
|
|
||||||
.map_err(|_e| SetupError::InvalidRecord)?;
|
|
||||||
let nonce_buf: [u8; 24] = row.nonce
|
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_e| SetupError::InvalidRecord)?;
|
.map_err(|_e| SetupError::InvalidRecord)?;
|
||||||
|
let nonce = XNonce::from_exact_iter(row.nonce.into_iter())
|
||||||
|
.ok_or(SetupError::InvalidRecord)?;
|
||||||
|
|
||||||
let creds = LockedCredentials {
|
let creds = LockedCredentials {
|
||||||
access_key_id: row.access_key_id,
|
access_key_id: row.access_key_id,
|
||||||
secret_key_enc: row.secret_key_enc,
|
secret_key_enc: row.secret_key_enc,
|
||||||
salt: Salt(salt_buf),
|
salt,
|
||||||
nonce: Nonce(nonce_buf),
|
nonce,
|
||||||
};
|
};
|
||||||
Ok(Session::Locked(creds))
|
Ok(Session::Locked(creds))
|
||||||
}
|
}
|
||||||
@ -76,8 +88,8 @@ impl Session {
|
|||||||
pub struct LockedCredentials {
|
pub struct LockedCredentials {
|
||||||
pub access_key_id: String,
|
pub access_key_id: String,
|
||||||
pub secret_key_enc: Vec<u8>,
|
pub secret_key_enc: Vec<u8>,
|
||||||
pub salt: Salt,
|
pub salt: [u8; 32],
|
||||||
pub nonce: Nonce,
|
pub nonce: XNonce,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LockedCredentials {
|
impl LockedCredentials {
|
||||||
@ -88,8 +100,8 @@ impl LockedCredentials {
|
|||||||
)
|
)
|
||||||
.bind(&self.access_key_id)
|
.bind(&self.access_key_id)
|
||||||
.bind(&self.secret_key_enc)
|
.bind(&self.secret_key_enc)
|
||||||
.bind(&self.salt.0[0..])
|
.bind(&self.salt[..])
|
||||||
.bind(&self.nonce.0[0..])
|
.bind(&self.nonce[..])
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -97,11 +109,10 @@ impl LockedCredentials {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt(&self, passphrase: &str) -> Result<BaseCredentials, UnlockError> {
|
pub fn decrypt(&self, passphrase: &str) -> Result<BaseCredentials, UnlockError> {
|
||||||
let mut key_buf = [0; secretbox::KEYBYTES];
|
let crypto = Crypto::new(passphrase, &self.salt)
|
||||||
// pretty sure this only fails if we're out of memory
|
.map_err(|e| CryptoError::Argon2(e))?;
|
||||||
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &self.salt).unwrap();
|
let decrypted = crypto.decrypt(&self.nonce, &self.secret_key_enc)
|
||||||
let decrypted = secretbox::open(&self.secret_key_enc, &self.nonce, &Key(key_buf))
|
.map_err(|e| CryptoError::Aead(e))?;
|
||||||
.map_err(|_| UnlockError::BadPassphrase)?;
|
|
||||||
let secret_access_key = String::from_utf8(decrypted)
|
let secret_access_key = String::from_utf8(decrypted)
|
||||||
.map_err(|_| UnlockError::InvalidUtf8)?;
|
.map_err(|_| UnlockError::InvalidUtf8)?;
|
||||||
|
|
||||||
@ -122,21 +133,18 @@ pub struct BaseCredentials {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BaseCredentials {
|
impl BaseCredentials {
|
||||||
pub fn encrypt(&self, passphrase: &str) -> LockedCredentials {
|
pub fn encrypt(&self, passphrase: &str) -> Result<LockedCredentials, CryptoError> {
|
||||||
let salt = pwhash::gen_salt();
|
let salt = Crypto::salt();
|
||||||
let mut key_buf = [0; secretbox::KEYBYTES];
|
let crypto = Crypto::new(passphrase, &salt)?;
|
||||||
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
|
let (nonce, secret_key_enc) = crypto.encrypt(self.secret_access_key.as_bytes())?;
|
||||||
let key = Key(key_buf);
|
|
||||||
let nonce = secretbox::gen_nonce();
|
|
||||||
|
|
||||||
let secret_key_enc = secretbox::seal(self.secret_access_key.as_bytes(), &nonce, &key);
|
let locked = LockedCredentials {
|
||||||
|
|
||||||
LockedCredentials {
|
|
||||||
access_key_id: self.access_key_id.clone(),
|
access_key_id: self.access_key_id.clone(),
|
||||||
secret_key_enc,
|
secret_key_enc,
|
||||||
salt,
|
salt,
|
||||||
nonce,
|
nonce,
|
||||||
}
|
};
|
||||||
|
Ok(locked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,4 +249,73 @@ fn deserialize_expiration<'de, D>(deserializer: D) -> Result<DateTime, D::Error>
|
|||||||
where D: Deserializer<'de>
|
where D: Deserializer<'de>
|
||||||
{
|
{
|
||||||
deserializer.deserialize_str(DateTimeVisitor)
|
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.
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
const MEM_COST: u32 = 128 * 1024;
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
const TIME_COST: u32 = 8;
|
||||||
|
|
||||||
|
/// But since this takes a million years in an unoptimized build,
|
||||||
|
/// we turn it way down in debug builds.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const MEM_COST: u32 = 48 * 1024;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const TIME_COST: u32 = 1;
|
||||||
|
|
||||||
|
|
||||||
|
fn new(passphrase: &str, salt: &[u8]) -> argon2::Result<Crypto> {
|
||||||
|
let params = ParamsBuilder::new()
|
||||||
|
.m_cost(Self::MEM_COST)
|
||||||
|
.p_cost(1)
|
||||||
|
.t_cost(Self::TIME_COST)
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -164,8 +164,8 @@ pub enum UnlockError {
|
|||||||
NotLocked,
|
NotLocked,
|
||||||
#[error("No saved credentials were found")]
|
#[error("No saved credentials were found")]
|
||||||
NoCredentials,
|
NoCredentials,
|
||||||
#[error("Invalid passphrase")]
|
#[error(transparent)]
|
||||||
BadPassphrase,
|
Crypto(#[from] CryptoError),
|
||||||
#[error("Data was found to be corrupt after decryption")]
|
#[error("Data was found to be corrupt after decryption")]
|
||||||
InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
|
InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
@ -175,6 +175,15 @@ pub enum UnlockError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
|
pub enum CryptoError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Argon2(#[from] argon2::Error),
|
||||||
|
#[error("Invalid passphrase")] // I think this is the only way decryption fails
|
||||||
|
Aead(#[from] chacha20poly1305::aead::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Errors encountered while trying to figure out who's on the other end of a request
|
// Errors encountered while trying to figure out who's on the other end of a request
|
||||||
#[derive(Debug, ThisError, AsRefStr)]
|
#[derive(Debug, ThisError, AsRefStr)]
|
||||||
pub enum ClientInfoError {
|
pub enum ClientInfoError {
|
||||||
|
@ -48,7 +48,7 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_creds(&self, base_creds: BaseCredentials, passphrase: &str) -> Result<(), UnlockError> {
|
pub async fn new_creds(&self, base_creds: BaseCredentials, passphrase: &str) -> Result<(), UnlockError> {
|
||||||
let locked = base_creds.encrypt(passphrase);
|
let locked = base_creds.encrypt(passphrase)?;
|
||||||
// do this first so that if it fails we don't save bad credentials
|
// do this first so that if it fails we don't save bad credentials
|
||||||
self.new_session(base_creds).await?;
|
self.new_session(base_creds).await?;
|
||||||
locked.save(&self.pool).await?;
|
locked.save(&self.pool).await?;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "creddy",
|
"productName": "creddy",
|
||||||
"version": "0.2.0"
|
"version": "0.2.1"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
@ -15,7 +15,6 @@ invoke('get_config').then(config => $appState.config = config);
|
|||||||
listen('credentials-request', (tauriEvent) => {
|
listen('credentials-request', (tauriEvent) => {
|
||||||
$appState.pendingRequests.put(tauriEvent.payload);
|
$appState.pendingRequests.put(tauriEvent.payload);
|
||||||
});
|
});
|
||||||
window.state = $appState;
|
|
||||||
|
|
||||||
acceptRequest();
|
acceptRequest();
|
||||||
</script>
|
</script>
|
||||||
|
113
src/ui/Spinner.svelte
Normal file
113
src/ui/Spinner.svelte
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<script>
|
||||||
|
export let color = 'base-content';
|
||||||
|
export let thickness = '2px';
|
||||||
|
let classes = '';
|
||||||
|
export { classes as class };
|
||||||
|
|
||||||
|
const colorVars = {
|
||||||
|
'primary': 'p',
|
||||||
|
'primary-focus': 'pf',
|
||||||
|
'primary-content': 'pc',
|
||||||
|
'secondary': 's',
|
||||||
|
'secondary-focus': 'sf',
|
||||||
|
'secondary-content': 'sc',
|
||||||
|
'accent': 'a',
|
||||||
|
'accent-focus': 'af',
|
||||||
|
'accent-content': 'ac',
|
||||||
|
'neutral': 'n',
|
||||||
|
'neutral-focus': 'nf',
|
||||||
|
'neutral-content': 'nc',
|
||||||
|
'base-100': 'b1',
|
||||||
|
'base-200': 'b2',
|
||||||
|
'base-300': 'b3',
|
||||||
|
'base-content': 'bc',
|
||||||
|
'info': 'in',
|
||||||
|
'info-content': 'inc',
|
||||||
|
'success': 'su',
|
||||||
|
'success-content': 'suc',
|
||||||
|
'warning': 'wa',
|
||||||
|
'warning-content': 'wac',
|
||||||
|
'error': 'er',
|
||||||
|
'error-content': 'erc',
|
||||||
|
}
|
||||||
|
|
||||||
|
let arcStyle = `border-width: ${thickness};`;
|
||||||
|
arcStyle += `border-color: hsl(var(--${colorVars[color]})) transparent transparent transparent;`;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#spinner {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
animation: spin;
|
||||||
|
animation-duration: 1.5s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
50% { transform: rotate(225deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc-top {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc-right {
|
||||||
|
animation: spin-right;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc-bottom {
|
||||||
|
animation: spin-bottom;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc-left {
|
||||||
|
animation: spin-left;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-top {
|
||||||
|
0% { transform: rotate(-45deg); }
|
||||||
|
50% { transform: rotate(315deg); }
|
||||||
|
100% { transform: rotate(-45deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-right {
|
||||||
|
0% { transform: rotate(45deg); }
|
||||||
|
50% { transform: rotate(315deg); }
|
||||||
|
100% { transform: rotate(405deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-bottom {
|
||||||
|
0% { transform: rotate(135deg); }
|
||||||
|
50% { transform: rotate(315deg); }
|
||||||
|
100% { transform: rotate(495deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-left {
|
||||||
|
0% { transform: rotate(225deg); }
|
||||||
|
50% { transform: rotate(315deg); }
|
||||||
|
100% { transform: rotate(585deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="spinner" class="w-6 h-6 {classes}">
|
||||||
|
<div class="arc arc-top w-full h-full" style={arcStyle}></div>
|
||||||
|
<div class="arc arc-right w-full h-full" style={arcStyle}></div>
|
||||||
|
<div class="arc arc-bottom w-full h-full" style={arcStyle}></div>
|
||||||
|
<div class="arc arc-left w-full h-full" style={arcStyle}></div>
|
||||||
|
</div>
|
@ -7,6 +7,7 @@
|
|||||||
import { navigate } from '../lib/routing.js';
|
import { navigate } from '../lib/routing.js';
|
||||||
import Link from '../ui/Link.svelte';
|
import Link from '../ui/Link.svelte';
|
||||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||||
|
import Spinner from '../ui/Spinner.svelte';
|
||||||
|
|
||||||
|
|
||||||
let errorMsg = null;
|
let errorMsg = null;
|
||||||
@ -19,6 +20,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let saving = false;
|
||||||
async function save() {
|
async function save() {
|
||||||
if (passphrase !== confirmPassphrase) {
|
if (passphrase !== confirmPassphrase) {
|
||||||
alert.shake();
|
alert.shake();
|
||||||
@ -27,6 +29,7 @@
|
|||||||
|
|
||||||
let credentials = {AccessKeyId, SecretAccessKey};
|
let credentials = {AccessKeyId, SecretAccessKey};
|
||||||
try {
|
try {
|
||||||
|
saving = true;
|
||||||
await invoke('save_credentials', {credentials, passphrase});
|
await invoke('save_credentials', {credentials, passphrase});
|
||||||
if ($appState.currentRequest) {
|
if ($appState.currentRequest) {
|
||||||
navigate('Approve');
|
navigate('Approve');
|
||||||
@ -47,6 +50,8 @@
|
|||||||
if (alert) {
|
if (alert) {
|
||||||
alert.shake();
|
alert.shake();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saving = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -65,7 +70,13 @@
|
|||||||
<input type="password" placeholder="Passphrase" bind:value="{passphrase}" class="input input-bordered" />
|
<input type="password" placeholder="Passphrase" bind:value="{passphrase}" class="input input-bordered" />
|
||||||
<input type="password" placeholder="Re-enter passphrase" bind:value={confirmPassphrase} class="input input-bordered" on:change={confirm} />
|
<input type="password" placeholder="Re-enter passphrase" bind:value={confirmPassphrase} class="input input-bordered" on:change={confirm} />
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary" />
|
<button type="submit" class="btn btn-primary">
|
||||||
|
{#if saving}
|
||||||
|
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
|
||||||
|
{:else}
|
||||||
|
Submit
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
<Link target="Home" hotkey="Escape">
|
<Link target="Home" hotkey="Escape">
|
||||||
<button class="btn btn-sm btn-outline w-full">Cancel</button>
|
<button class="btn btn-sm btn-outline w-full">Cancel</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -7,12 +7,14 @@
|
|||||||
import { getRootCause } from '../lib/errors.js';
|
import { getRootCause } from '../lib/errors.js';
|
||||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||||
import Link from '../ui/Link.svelte';
|
import Link from '../ui/Link.svelte';
|
||||||
|
import Spinner from '../ui/Spinner.svelte';
|
||||||
|
|
||||||
|
|
||||||
let errorMsg = null;
|
let errorMsg = null;
|
||||||
let alert;
|
let alert;
|
||||||
let passphrase = '';
|
let passphrase = '';
|
||||||
let loadTime = 0;
|
let loadTime = 0;
|
||||||
|
let saving = false;
|
||||||
async function unlock() {
|
async function unlock() {
|
||||||
// The hotkey for navigating here from homepage is Enter, which also
|
// The hotkey for navigating here from homepage is Enter, which also
|
||||||
// happens to trigger the form submit event
|
// happens to trigger the form submit event
|
||||||
@ -21,6 +23,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
saving = true;
|
||||||
let r = await invoke('unlock', {passphrase});
|
let r = await invoke('unlock', {passphrase});
|
||||||
$appState.credentialStatus = 'unlocked';
|
$appState.credentialStatus = 'unlocked';
|
||||||
if ($appState.currentRequest) {
|
if ($appState.currentRequest) {
|
||||||
@ -43,6 +46,8 @@
|
|||||||
if (alert) {
|
if (alert) {
|
||||||
alert.shake();
|
alert.shake();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saving = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +67,14 @@
|
|||||||
<!-- svelte-ignore a11y-autofocus -->
|
<!-- svelte-ignore a11y-autofocus -->
|
||||||
<input autofocus name="password" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" class="input input-bordered" />
|
<input autofocus name="password" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" class="input input-bordered" />
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary" />
|
<button type="submit" class="btn btn-primary">
|
||||||
|
{#if saving}
|
||||||
|
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
|
||||||
|
{:else}
|
||||||
|
Submit
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
<Link target="Home" hotkey="Escape">
|
<Link target="Home" hotkey="Escape">
|
||||||
<button class="btn btn-outline btn-sm w-full">Cancel</button>
|
<button class="btn btn-outline btn-sm w-full">Cancel</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user