still in progress
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use argon2::{
|
||||
Argon2,
|
||||
@@ -17,8 +17,15 @@ use chacha20poly1305::{
|
||||
generic_array::GenericArray,
|
||||
},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde::{
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Serializer,
|
||||
Deserializer,
|
||||
};
|
||||
use serde::de::{self, Visitor};
|
||||
use sqlx::SqlitePool;
|
||||
use sqlx::types::Uuid;
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::kv;
|
||||
@@ -27,11 +34,73 @@ mod aws;
|
||||
pub use aws::{AwsBaseCredential, AwsSessionCredential};
|
||||
|
||||
|
||||
pub trait PersistentCredential: for<'a> Deserialize<'a> + Sized {
|
||||
async fn load(crypt: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError>;
|
||||
async fn save(&self, crypt: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError>;
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Credential {
|
||||
AwsBase(AwsBaseCredential),
|
||||
AwsSession(AwsSessionCredential),
|
||||
}
|
||||
|
||||
|
||||
// we need a special type for listing structs because
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SaveCredential {
|
||||
#[serde(serialize_with = "serialize_uuid")]
|
||||
#[serde(deserialize_with = "deserialize_uuid")]
|
||||
id: Uuid, // UUID so it can be generated on the frontend
|
||||
name: String, // user-facing identifier so it can be changed
|
||||
credential: Credential,
|
||||
}
|
||||
|
||||
impl SaveCredential {
|
||||
pub async fn save(&self, crypt: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError> {
|
||||
let cred = match &self.credential {
|
||||
Credential::AwsBase(b) => b,
|
||||
Credential::AwsSession(_) => return Err(SaveCredentialsError::NotPersistent),
|
||||
};
|
||||
|
||||
cred.save(&self.id, &self.name, crypt, pool).await
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_uuid<S: Serializer>(u: &Uuid, s: S) -> Result<S::Ok, S::Error> {
|
||||
let mut buf = Vec::new();
|
||||
s.serialize_str(u.as_hyphenated().encode_lower(&mut buf))
|
||||
}
|
||||
|
||||
struct UuidVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for UuidVisitor {
|
||||
type Value = Uuid;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
write!(formatter, "a hyphenated UUID")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, v: &str) -> Result<Uuid, E> {
|
||||
Uuid::try_parse(v)
|
||||
.map_err(|_| E::custom(format!("Could not interpret string as UUID: {v}")))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_uuid<'de, D: Deserializer<'de>>(ds: D) -> Result<Uuid, D::Error> {
|
||||
ds.deserialize_str(UuidVisitor)
|
||||
}
|
||||
|
||||
|
||||
pub trait PersistentCredential: for<'a> Deserialize<'a> + Sized {
|
||||
async fn load(name: &str, crypt: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError>;
|
||||
async fn list(crypt: &Crypto, pool: &SqlitePool) -> Result<Vec<SaveCredential>, LoadCredentialsError>;
|
||||
async fn save(&self, id: &Uuid, name: &str, crypt: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError>;
|
||||
|
||||
async fn rekey(old: &Crypto, new: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError> {
|
||||
for cred in Self::list(old, pool).await? {
|
||||
cred.save(new, pool).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AppSession {
|
||||
Unlocked {
|
||||
@@ -89,14 +158,14 @@ impl AppSession {
|
||||
match self {
|
||||
Self::Unlocked {salt, crypto} => {
|
||||
let (nonce, blob) = crypto.encrypt(b"correct horse battery staple")?;
|
||||
kv::save(pool, "salt", salt).await?;
|
||||
kv::save(pool, "verify_nonce", &nonce.as_slice()).await?;
|
||||
kv::save(pool, "verify_blob", &blob).await?;
|
||||
kv::save_bytes(pool, "salt", salt).await?;
|
||||
kv::save_bytes(pool, "verify_nonce", &nonce.as_slice()).await?;
|
||||
kv::save_bytes(pool, "verify_blob", &blob).await?;
|
||||
},
|
||||
Self::Locked {salt, verify_nonce, verify_blob} => {
|
||||
kv::save(pool, "salt", salt).await?;
|
||||
kv::save(pool, "verify_nonce", &verify_nonce.as_slice()).await?;
|
||||
kv::save(pool, "verify_blob", verify_blob).await?;
|
||||
kv::save_bytes(pool, "salt", salt).await?;
|
||||
kv::save_bytes(pool, "verify_nonce", &verify_nonce.as_slice()).await?;
|
||||
kv::save_bytes(pool, "verify_blob", verify_blob).await?;
|
||||
},
|
||||
// "saving" an empty session just means doing nothing
|
||||
Self::Empty => (),
|
||||
@@ -187,6 +256,25 @@ impl Crypto {
|
||||
Ok(Crypto { cipher })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn random() -> Crypto {
|
||||
// salt and key are the same length, so we can just use this
|
||||
let key = Crypto::salt();
|
||||
let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
|
||||
Crypto { cipher }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn fixed() -> Crypto {
|
||||
let key = [
|
||||
1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
|
||||
let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
|
||||
Crypto { cipher }
|
||||
}
|
||||
|
||||
fn salt() -> [u8; 32] {
|
||||
let mut salt = [0; 32];
|
||||
OsRng.fill_bytes(&mut salt);
|
||||
@@ -210,3 +298,16 @@ impl Debug for Crypto {
|
||||
write!(f, "Crypto {{ [...] }}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[sqlx::test(fixtures("uuid_test"))]
|
||||
// async fn save_uuid(pool: SqlitePool) {
|
||||
// let u = Uuid::try_parse("7140b90c-bfbd-4394-9008-01b94f94ecf8").unwrap();
|
||||
// sqlx::query!("INSERT INTO uuids (uuid) VALUES (?)", u).execute(pool).unwrap();
|
||||
// panic!("done, go check db");
|
||||
// }
|
||||
// }
|
||||
|
Reference in New Issue
Block a user