1
0
Fork 0
mirror of https://codeberg.org/tyy/aspm synced 2024-12-22 19:29:28 -07:00

I had this sitting on my computer for months and to be honest no clue what i did

This commit is contained in:
Tyler Beckman 2024-07-04 17:12:18 -04:00
parent ecbb551e34
commit cf6bbfeecf
Signed by: Ty
GPG key ID: 2813440C772555A4
6 changed files with 129 additions and 119 deletions

View file

@ -23,7 +23,7 @@ use crate::{
entities::{prelude::*, profiles}, entities::{prelude::*, profiles},
}; };
/// /// Upload an existing profile to an ASPE server
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
pub struct AspeCreateCommand { pub struct AspeCreateCommand {
/// The fingerprint or alias of the profile to upload /// The fingerprint or alias of the profile to upload
@ -130,7 +130,10 @@ impl AspmSubcommand for AspeCreateCommand {
.context("Unable to encode the profile as a JWT and sign it")?; .context("Unable to encode the profile as a JWT and sign it")?;
match server.post_request(encoded_request).await { match server.post_request(encoded_request).await {
Ok(_) => Ok(()), Ok(_) => {
println!("Successfully uploaded profile!");
Ok(())
},
Err(AspeRequestFailure::BadRequest) => bail!("The ASPE server rejected the request due to invalid data"), Err(AspeRequestFailure::BadRequest) => bail!("The ASPE server rejected the request due to invalid data"),
Err(AspeRequestFailure::TooLarge) => bail!("The ASPE server rejected the request as being too large"), Err(AspeRequestFailure::TooLarge) => bail!("The ASPE server rejected the request as being too large"),
Err(AspeRequestFailure::RateLimited) => bail!("The ASPE server rejected the request due to a ratelimit"), Err(AspeRequestFailure::RateLimited) => bail!("The ASPE server rejected the request due to a ratelimit"),

View file

@ -1,16 +1,16 @@
use anstyle::{AnsiColor, Color as AnstyleColor, Reset, Style as Anstyle}; use anstyle::{AnsiColor, Reset, Style as Anstyle};
use anyhow::Context; use anyhow::Context;
use asp::keys::AspKeyType; use asp::keys::AspKeyType;
use clap::Parser; use clap::Parser;
use dialoguer::{console::Term, theme::ColorfulTheme, Confirm}; use dialoguer::{console::Term, theme::ColorfulTheme, Confirm};
use indoc::writedoc; use indoc::writedoc;
use sea_orm::ModelTrait; use sea_orm::ModelTrait as _;
use std::io::Write; use std::io::Write;
use crate::{ use crate::{
commands::{AspmSubcommand, KeysEntityExt, KeysQueryResult}, commands::AspmSubcommand,
entities::prelude::*, utils,
}; };
/// Deletes a saved key, after asking for confirmation. /// Deletes a saved key, after asking for confirmation.
@ -27,32 +27,7 @@ pub struct KeysDeleteCommand {
impl AspmSubcommand for KeysDeleteCommand { impl AspmSubcommand for KeysDeleteCommand {
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> { async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
// Fetch key from db // Fetch key from db
let entry = Keys::query_key(&state.db, &self.key) let key = utils::keys::query_key(&state.db, self.key).await?;
.await
.context("Unable to query keys from database")?;
let key = match entry {
KeysQueryResult::None => {
eprintln!(
"{style}No keys matching the given query were found{reset}",
style = Anstyle::new()
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightRed)))
.render(),
reset = Reset.render()
);
std::process::exit(1);
}
KeysQueryResult::One(key) => key,
KeysQueryResult::Many(mut keys) => {
eprintln!(
"{style}More than one keys matching the given query were found{reset}",
style = Anstyle::new()
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightYellow)))
.render(),
reset = Reset.render()
);
keys.remove(0)
}
};
if !self.no_confirm { if !self.no_confirm {
// Construct styles // Construct styles

View file

@ -3,8 +3,7 @@ use aes_gcm::{
Aes256Gcm, Key, Aes256Gcm, Key,
}; };
use anstyle::{AnsiColor, Color as AnstyleColor, Reset, Style as Anstyle}; use anstyle::{AnsiColor, Color as AnstyleColor, Reset, Style as Anstyle};
use anyhow::{anyhow, Context}; use anyhow::Context as _;
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
use asp::keys::AspKey; use asp::keys::AspKey;
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use data_encoding::{BASE64, BASE64_NOPAD}; use data_encoding::{BASE64, BASE64_NOPAD};
@ -12,10 +11,7 @@ use dialoguer::{theme::ColorfulTheme, Password};
use josekit::jwk::Jwk; use josekit::jwk::Jwk;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{commands::AspmSubcommand, utils};
commands::{AspmSubcommand, KeysEntityExt, KeysQueryResult},
entities::prelude::*,
};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(tag = "alg", rename = "scrypt")] #[serde(tag = "alg", rename = "scrypt")]
@ -63,32 +59,7 @@ pub struct KeysExportCommand {
impl AspmSubcommand for KeysExportCommand { impl AspmSubcommand for KeysExportCommand {
async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> { async fn execute(self, state: crate::AspmState) -> Result<(), anyhow::Error> {
// Fetch key from db // Fetch key from db
let entry = Keys::query_key(&state.db, &self.key) let key = utils::keys::query_key(&state.db, self.key).await?;
.await
.context("Unable to query keys from database")?;
let key = match entry {
KeysQueryResult::None => {
eprintln!(
"{style}No keys matching the given query were found{reset}",
style = Anstyle::new()
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightRed)))
.render(),
reset = Reset.render()
);
std::process::exit(1);
}
KeysQueryResult::One(key) => key,
KeysQueryResult::Many(mut keys) => {
eprintln!(
"{style}More than one keys matching the given query were found{reset}",
style = Anstyle::new()
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightYellow)))
.render(),
reset = Reset.render()
);
keys.remove(0)
}
};
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| { let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
Password::with_theme(&ColorfulTheme::default()) Password::with_theme(&ColorfulTheme::default())
@ -97,18 +68,10 @@ impl AspmSubcommand for KeysExportCommand {
.context("Unable to prompt on stderr") .context("Unable to prompt on stderr")
})?; })?;
let argon_salt = let derived_key = utils::keys::derive_encryption_key(key.fingerprint, &key_password)?;
SaltString::from_b64(&BASE64_NOPAD.encode(key.fingerprint.to_uppercase().as_bytes()))
.context("Unable to decode argon2 salt")?;
let argon2 = Argon2::default();
let hash = argon2
.hash_password(key_password.as_bytes(), &argon_salt)
.or(Err(anyhow!("Unable to derive encryption key")))?;
let aes_key = hash.hash.context("Unable to derive encryption key")?;
let aes_key = &aes_key.as_bytes()[0..32];
if let Ok(decrypted) = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(aes_key)) if let Ok(decrypted) = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&derived_key))
.decrypt((&aes_key[0..12]).into(), key.cipher_text.as_slice()) .decrypt((&derived_key[0..12]).into(), key.cipher_text.as_slice())
{ {
let decrypted = AspKey::from_jwk(Jwk::from_bytes(decrypted)?)?; let decrypted = AspKey::from_jwk(Jwk::from_bytes(decrypted)?)?;

View file

@ -3,10 +3,8 @@ pub mod profiles;
pub mod aspe; pub mod aspe;
use clap::Parser; use clap::Parser;
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use crate::entities::keys::{Column as KeysColumn, Entity as KeysEntity, Model as KeysModel};
use crate::AspmState; use crate::AspmState;
#[async_trait::async_trait] #[async_trait::async_trait]
@ -18,45 +16,3 @@ pub trait AspmSubcommand: Parser + Sync + Send {
runtime.block_on(self.execute(state)) runtime.block_on(self.execute(state))
} }
} }
#[async_trait::async_trait]
trait KeysEntityExt {
type ResultEnum;
/// Queries the database for a specific key entity, first checking by fingerprint and then checking
async fn query_key(db: &DatabaseConnection, query: &str) -> anyhow::Result<Self::ResultEnum>;
}
enum KeysQueryResult {
None,
One(KeysModel),
Many(Vec<KeysModel>),
}
#[async_trait::async_trait]
impl KeysEntityExt for KeysEntity {
type ResultEnum = KeysQueryResult;
async fn query_key(db: &DatabaseConnection, query: &str) -> anyhow::Result<Self::ResultEnum> {
let mut models = KeysEntity::find()
.filter(
Condition::any()
.add(KeysColumn::Fingerprint.eq(query))
.add(KeysColumn::Alias.contains(query)),
)
.order_by(
// This order_by will assign a higher order priority if the fingerprint is EXACTLY the query, and everything else gets lower
// This effectively means that if there was an exact fingerprint match, it is ordered first, and any LIKE `%alias%` matches come after
KeysColumn::Fingerprint,
sea_orm::Order::Field(sea_orm::Values(vec![query.into()])),
)
.all(db)
.await?;
Ok(match models.len() {
0 => Self::ResultEnum::None,
1 => Self::ResultEnum::One(models.remove(0)),
_ => Self::ResultEnum::Many(models),
})
}
}

View file

@ -1,6 +1,7 @@
mod commands; mod commands;
#[allow(warnings)] // This is autogenerated, no use in showing warnings #[allow(warnings)] // This is autogenerated, no use in showing warnings
mod entities; mod entities;
mod utils;
use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle}; use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle};
use anyhow::Context; use anyhow::Context;

112
src/utils.rs Normal file
View file

@ -0,0 +1,112 @@
pub mod keys {
use anstyle::{AnsiColor, Color as AnstyleColor, Reset, Style as Anstyle};
use anyhow::{anyhow, Context as _};
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
use data_encoding::BASE64_NOPAD;
use sea_orm::{
ColumnTrait as _, Condition, DatabaseConnection, EntityTrait as _, QueryFilter as _,
QueryOrder as _,
};
use crate::entities::{
keys::{Column as KeysColumn, Entity as KeysEntity, Model as KeysModel},
prelude::Keys,
};
pub fn derive_encryption_key<S: AsRef<str>, P: AsRef<[u8]>>(
fingerprint: S,
password: P,
) -> anyhow::Result<Vec<u8>> {
let argon_salt =
SaltString::from_b64(&BASE64_NOPAD.encode(fingerprint.as_ref().to_uppercase().as_bytes()))
.context("Unable to derive argon2 salt")?;
let argon2 = Argon2::default();
let hash = argon2
.hash_password(password.as_ref(), &argon_salt)
.or(Err(anyhow!("Unable to derive encryption key")))?;
let aes_key = hash.hash.context("Unable to derive encryption key")?;
let aes_key = &aes_key.as_bytes()[0..32];
Ok(aes_key.to_vec())
}
#[async_trait::async_trait]
trait KeysEntityExt {
type ResultEnum;
/// Queries the database for a specific key entity, first checking by fingerprint and then checking
async fn query_key(
db: &DatabaseConnection,
query: &str,
) -> anyhow::Result<Self::ResultEnum>;
}
pub enum KeysQueryResult {
None,
One(KeysModel),
Many(Vec<KeysModel>),
}
#[async_trait::async_trait]
impl KeysEntityExt for KeysEntity {
type ResultEnum = KeysQueryResult;
async fn query_key(
db: &DatabaseConnection,
query: &str,
) -> anyhow::Result<Self::ResultEnum> {
let mut models = KeysEntity::find()
.filter(
Condition::any()
.add(KeysColumn::Fingerprint.eq(query))
.add(KeysColumn::Alias.contains(query)),
)
.order_by(
// This order_by will assign a higher order priority if the fingerprint is EXACTLY the query, and everything else gets lower
// This effectively means that if there was an exact fingerprint match, it is ordered first, and any LIKE `%alias%` matches come after
KeysColumn::Fingerprint,
sea_orm::Order::Field(sea_orm::Values(vec![query.into()])),
)
.all(db)
.await?;
Ok(match models.len() {
0 => Self::ResultEnum::None,
1 => Self::ResultEnum::One(models.remove(0)),
_ => Self::ResultEnum::Many(models),
})
}
}
pub async fn query_key<S: AsRef<str>>(
db: &DatabaseConnection,
query: S,
) -> anyhow::Result<KeysModel> {
let entry = Keys::query_key(db, query.as_ref())
.await
.context("Unable to query keys from database")?;
match entry {
KeysQueryResult::None => {
eprintln!(
"{style}No keys matching the given query were found{reset}",
style = Anstyle::new()
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightRed)))
.render(),
reset = Reset.render()
);
std::process::exit(1);
}
KeysQueryResult::One(key) => Ok(key),
KeysQueryResult::Many(mut keys) => {
eprintln!(
"{style}More than one keys matching the given query were found{reset}",
style = Anstyle::new()
.fg_color(Some(AnstyleColor::Ansi(AnsiColor::BrightYellow)))
.render(),
reset = Reset.render()
);
Ok(keys.remove(0))
}
}
}
}