get backend running

This commit is contained in:
2024-06-19 05:10:55 -04:00
parent d0a2532c27
commit 9928996fab
11 changed files with 310 additions and 207 deletions

View File

@@ -1,9 +1,15 @@
use std::fmt::{self, Formatter};
use std::time::{SystemTime, UNIX_EPOCH};
use aws_smithy_types::date_time::{DateTime, Format};
use chacha20poly1305::XNonce;
use serde::{
Serialize,
Deserialize,
Serializer,
Deserializer,
};
use serde::de::{self, Visitor};
use sqlx::SqlitePool;
use super::{Crypto, PersistentCredential};
@@ -27,40 +33,46 @@ impl AwsBaseCredential {
}
impl PersistentCredential for AwsBaseCredential {
pub async fn save(&self, crypt: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError> {
async fn save(&self, crypto: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError> {
let (nonce, ciphertext) = crypto.encrypt(self.secret_access_key.as_bytes())?;
let nonce_bytes = &nonce.as_slice();
sqlx::query!(
"INSERT INTO aws_credentials (
name,
key_id,
access_key_id,
secret_key_enc,
nonce,
updated_at
created_at
)
VALUES ('main', ?, ?, ? strftime('%s'))
VALUES ('default', ?, ?, ?, strftime('%s'))
ON CONFLICT DO UPDATE SET
key_id = excluded.key_id,
access_key_id = excluded.access_key_id,
secret_key_enc = excluded.secret_key_enc,
nonce = excluded.nonce
updated_at = excluded.updated_at",
nonce = excluded.nonce,
created_at = excluded.created_at",
self.access_key_id,
ciphertext,
nonce,
nonce_bytes,
).execute(pool).await?;
Ok(())
}
pub async fn load(crypt: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError> {
let row = sqlx::query!("SELECT * FROM aws_credentials WHERE name = 'main'")
async fn load(crypto: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError> {
let row = sqlx::query!("SELECT * FROM aws_credentials WHERE name = 'default'")
.fetch_optional(pool)
.await?
.ok_or(LoadCredentialsError::NoCredentials);
.ok_or(LoadCredentialsError::NoCredentials)?;
// note: switch to try_from eventually
let nonce = XNonce::clone_from_slice(&row.nonce);
let secret_key_bytes = crypto.decrypt(&nonce, &row.secret_key_enc)?;
let secret_key = String::from_utf8(secret_key_bytes)
.map_err(|_| LoadCredentialsError::InvalidData)?;
let secret_key = crypto.decrypt(&row.nonce, &row.secret_key_enc)?;
let creds = Self {
version: 1,
access_key_id: row.key_id,
access_key_id: row.access_key_id,
secret_access_key: secret_key,
};
Ok(creds)
@@ -82,7 +94,7 @@ pub struct AwsSessionCredential {
}
impl AwsSessionCredential {
pub async fn from_base(base: &BaseCredentials) -> Result<Self, GetSessionError> {
pub async fn from_base(base: &AwsBaseCredential) -> Result<Self, GetSessionError> {
let req_creds = aws_sdk_sts::Credentials::new(
&base.access_key_id,
&base.secret_access_key,
@@ -116,7 +128,7 @@ impl AwsSessionCredential {
.ok_or(GetSessionError::EmptyResponse)?
.clone();
let session_creds = SessionCredentials {
let session_creds = AwsSessionCredential {
version: 1,
access_key_id,
secret_access_key,
@@ -143,6 +155,9 @@ impl AwsSessionCredential {
}
fn default_credentials_version() -> usize { 1 }
struct DateTimeVisitor;
impl<'de> Visitor<'de> for DateTimeVisitor {

View File

@@ -1,3 +1,5 @@
use std::fmt::{Debug, Formatter};
use argon2::{
Argon2,
Algorithm,
@@ -12,32 +14,25 @@ use chacha20poly1305::{
Aead,
AeadCore,
KeyInit,
Error as AeadError,
generic_array::GenericArray,
},
};
use serde::{Serialize, Deserialize};
use sqlx::{FromRow, SqlitePool};
use serde::Deserialize;
use sqlx::SqlitePool;
use crate::errors::*;
use crate::kv;
mod aws;
pub use aws::{AwsBaseCredential, AwsSessionCredential};
pub enum CredentialKind {
AwsBase,
AwsSession,
}
pub trait PersistentCredential {
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)]
#[derive(Clone, Debug)]
pub enum AppSession {
Unlocked {
salt: [u8; 32],
@@ -54,14 +49,14 @@ pub enum AppSession {
impl AppSession {
pub fn new(passphrase: &str) -> Result<Self, CryptoError> {
let salt = Crypto::salt();
let crypto = Crypto::new(passphrase, &salt);
let crypto = Crypto::new(passphrase, &salt)?;
Ok(Self::Unlocked {salt, crypto})
}
pub fn unlock(self, passphrase: &str) -> Result<Self, UnlockError> {
pub fn unlock(&mut self, passphrase: &str) -> Result<(), UnlockError> {
let (salt, nonce, blob) = match self {
Self::Empty => return Err(UnlockError::NoCredentials),
Self::Unlocked => return Err(UnlockError::NotLocked),
Self::Unlocked {..} => return Err(UnlockError::NotLocked),
Self::Locked {salt, verify_nonce, verify_blob} => (salt, verify_nonce, verify_blob),
};
@@ -69,61 +64,78 @@ impl AppSession {
.map_err(|e| CryptoError::Argon2(e))?;
// if passphrase is incorrect, this will fail
let verify = crypto.decrypt(&nonce, &blob)?;
let _verify = crypto.decrypt(&nonce, &blob)?;
Ok(Self::Unlocked{crypto, salt})
*self = Self::Unlocked {crypto, salt: *salt};
Ok(())
}
pub async fn load(pool: &SqlitePool) -> Result<Self, LoadKvError> {
pub async fn load(pool: &SqlitePool) -> Result<Self, LoadCredentialsError> {
match kv::load_bytes_multi!(pool, "salt", "verify_nonce", "verify_blob").await? {
Some((salt, verify_nonce, verify_blob)) => {
Ok(Self::Locked {salt, verify_nonce, verify_blob}),
Some((salt, nonce, blob)) => {
Ok(Self::Locked {
salt: salt.try_into().map_err(|_| LoadCredentialsError::InvalidData)?,
// note: replace this with try_from at some point
verify_nonce: XNonce::clone_from_slice(&nonce),
verify_blob: blob,
})
},
None => Ok(Self::Empty),
}
}
pub async fn save(&self, pool: &SqlitePool) -> Result<(), LockError> {
let (salt, nonce, blob) = match self {
pub async fn save(&self, pool: &SqlitePool) -> Result<(), SaveCredentialsError> {
match self {
Self::Unlocked {salt, crypto} => {
let (nonce, blob) = crypto.encrypt(b"correct horse battery staple")
.map_err(|e| CryptoError::Aead(e))?;
(salt, nonce, blob)
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?;
},
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?;
},
Self::Locked {salt, verify_nonce, verify_blob} => (salt, verify_nonce, verify_blob),
// "saving" an empty session just means doing nothing
Self::Empty => return Ok(()),
Self::Empty => (),
};
kv::save(pool, "salt", salt).await?;
kv::save(pool, "verify_nonce", nonce).await?;
kv::save(pool, "verify_blob", blob).await?;
Ok(())
}
pub fn try_encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), CryptoError> {
let crypto = match self {
pub fn try_get_crypto(&self) -> Result<&Crypto, GetCredentialsError> {
match self {
Self::Empty => Err(GetCredentialsError::Empty),
Self::Locked => Err(GetCredentialsError::Locked),
Self::Locked {..} => Err(GetCredentialsError::Locked),
Self::Unlocked {crypto, ..} => Ok(crypto),
}
}
pub fn try_encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), GetCredentialsError> {
let crypto = match self {
Self::Empty => return Err(GetCredentialsError::Empty),
Self::Locked {..} => return Err(GetCredentialsError::Locked),
Self::Unlocked {crypto, ..} => crypto,
}?;
};
let res = crypto.encrypt(data)?;
Ok(res)
}
pub fn try_decrypt(&self, nonce: XNonce, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
pub fn try_decrypt(&self, nonce: XNonce, data: &[u8]) -> Result<Vec<u8>, GetCredentialsError> {
let crypto = match self {
Self::Empty => Err(GetCredentialsError::Empty),
Self::Locked => Err(GetCredentialsError::Locked),
Self::Empty => return Err(GetCredentialsError::Empty),
Self::Locked {..} => return Err(GetCredentialsError::Locked),
Self::Unlocked {crypto, ..} => crypto,
}?;
let res = crypto.decrypt(nonce, data)?;
};
let res = crypto.decrypt(&nonce, data)?;
Ok(res)
}
}
#[derive(Clone)]
pub struct Crypto {
cipher: XChaCha20Poly1305,
}
@@ -181,13 +193,20 @@ impl Crypto {
salt
}
fn encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), AeadError> {
fn encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), CryptoError> {
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)
fn decrypt(&self, nonce: &XNonce, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let plaintext = self.cipher.decrypt(nonce, data)?;
Ok(plaintext)
}
}
impl Debug for Crypto {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "Crypto {{ [...] }}")
}
}