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

Start on keys import subcommand, move import-gpg

This commit is contained in:
Tyler Beckman 2023-09-28 11:56:11 -06:00
parent ec1b9f3bf9
commit 43851d1f98
Signed by: Ty
GPG key ID: 2813440C772555A4
7 changed files with 143 additions and 9 deletions

View file

@ -1,5 +1,5 @@
# aspm # aspm
This is the **A**riadne **S**ignature **P**rofile **M**anager, a command line program and rust library implementing the [Ariadne Signature Profile specification v0](https://ariadne.id/related/ariadne-signature-profile-0/). Currently, it is updated to the latest version of the spec as of [ariadne/ariadne-identity-specification@84da4128b9](https://codeberg.org/ariadne/ariadne-identity-specification/commit/84da4128b90bd452544aaf422131efac5e8c312e). This is the **A**riadne **S**ignature **P**rofile **M**anager, a command line program and rust library implementing the [Ariadne Signature Profile specification v0](https://ariadne.id/related/ariadne-signature-profile-0/). Currently, it is updated to the latest version of the spec as of [ariadne/ariadne-identity-specification@92f280bf83](https://codeberg.org/ariadne/ariadne-identity-specification/commit/92f280bf83e2d5957e5a53a6f1b6974bc975517d).
The command line program is located in `src/`, and the library it uses to do ASP-related things (like creating and signing profiles, or generating keys) is located in `crates/asp`. The command line program is located in `src/`, and the library it uses to do ASP-related things (like creating and signing profiles, or generating keys) is located in `crates/asp`.

View file

@ -14,7 +14,7 @@ use thiserror::Error;
use crate::utils::jwk::JwtExt; use crate::utils::jwk::JwtExt;
/// An enum representing the possible types of JWK for ASPs /// An enum representing the possible types of JWK for ASPs
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum AspKeyType { pub enum AspKeyType {
Ed25519, Ed25519,
ES256, ES256,

View file

@ -0,0 +1,113 @@
use anyhow::{anyhow, bail, Context};
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
use asp::keys::{AspKey, AspKeyError};
use clap::{Parser, ValueEnum};
use clap_stdin::FileOrStdin;
use data_encoding::BASE64_NOPAD;
use dialoguer::{theme::ColorfulTheme, Input, Password};
use indoc::printdoc;
use josekit::jwk::Jwk;
use sea_orm::{ActiveValue, EntityTrait};
use crate::{commands::AspmSubcommand, entities::keys};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum KeyGenerationType {
Ed25519,
ES256,
}
/// Imports an ASP from raw JWK format. This only will import JWKs that have supported curves.
#[derive(Parser, Debug)]
pub struct KeysImportJwkCommand {
/// The key to import, as a file or "-" for stdin. This must be a valid JWK
key: FileOrStdin,
/// The alias of the key to import. This can be anything, and it can also be omitted to prompt interactively. This has no purpose other than providing a way to nicely name keys, rather than having to remember a fingerprint.
#[arg(short = 'n', long)]
key_alias: Option<String>,
}
#[async_trait::async_trait]
impl AspmSubcommand for KeysImportJwkCommand {
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
let alias = if let Some(alias) = &self.key_alias {
alias.clone()
} else {
Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter an alias to give to this key")
.allow_empty(false)
.interact()
.context("Unable to prompt on stderr")?
};
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
Password::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter a password to store and encrypt the key with")
.with_confirmation(
"Please confirm the password",
"The two inputs did not match!",
)
.interact()
.context("Unable to prompt on stderr")
})?;
let key = match AspKey::from_jwk(
Jwk::from_bytes(self.key.as_bytes()).context("Unable to parse provided JWK")?,
)
.context("Unable to convert parsed JWK to an AspKey")
{
Ok(key) => key,
Err(e) => match e
.downcast_ref::<AspKeyError>()
.context("Invalid error returned from asp parsing")?
{
AspKeyError::InvalidJwkType => {
eprintln!("The provided JWK used a type and curve that is unsupported by ASPs, please use a correct key type");
return Ok(());
}
_ => return Err(e),
},
};
let argon_salt =
SaltString::from_b64(&BASE64_NOPAD.encode(key.fingerprint.to_uppercase().as_bytes()))
.context("Unable to derive 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];
let encrypted = key
.export_encrypted(aes_key)
.context("Unable to derive the encryption key")?;
// Write to db
let entry = keys::ActiveModel {
fingerprint: ActiveValue::Set(key.fingerprint.clone()),
key_type: ActiveValue::Set(key.key_type.clone().into()),
alias: ActiveValue::Set(alias),
encrypted: ActiveValue::Set(encrypted),
};
let res = keys::Entity::insert(entry)
.exec(&state.db)
.await
.context("Unable to add key to database")?;
if res.last_insert_id != key.fingerprint {
bail!("The key was unable to be saved to the database")
}
printdoc! {
"
Successfully imported key!
Fingerprint: {fpr}
Type: {type:?}
",
fpr = key.fingerprint,
r#type = key.key_type
};
Ok(())
}
}

View file

@ -0,0 +1,18 @@
use clap::{Parser, Subcommand};
#[cfg(feature = "gpg-compat")]
mod gpg;
mod jwk;
/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles.
#[derive(Parser)]
pub struct KeysImportCommand {
#[command(subcommand)]
pub subcommand: KeysImportSubcommands,
}
#[derive(Subcommand)]
pub enum KeysImportSubcommands {
Gpg(gpg::KeysImportGpgCommand),
Jwk(jwk::KeysImportJwkCommand),
}

View file

@ -3,8 +3,7 @@ use clap::{Parser, Subcommand};
pub mod delete; pub mod delete;
pub mod export; pub mod export;
pub mod generate; pub mod generate;
#[cfg(feature = "gpg-compat")] pub mod import;
pub mod import_gpg;
pub mod list; pub mod list;
/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles. /// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles.
@ -19,7 +18,6 @@ pub enum KeysSubcommands {
Generate(generate::KeysGenerateCommand), Generate(generate::KeysGenerateCommand),
List(list::KeysListCommand), List(list::KeysListCommand),
Export(export::KeysExportCommand), Export(export::KeysExportCommand),
#[cfg(feature = "gpg-compat")]
ImportGpg(import_gpg::KeysImportGpgCommand),
Delete(delete::KeysDeleteCommand), Delete(delete::KeysDeleteCommand),
Import(import::KeysImportCommand),
} }

View file

@ -6,7 +6,10 @@ use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle};
use anyhow::Context; use anyhow::Context;
use app_dirs2::{AppDataType, AppInfo}; use app_dirs2::{AppDataType, AppInfo};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use commands::{keys::KeysSubcommands, AspmSubcommand}; use commands::{
keys::{import::KeysImportSubcommands, KeysSubcommands},
AspmSubcommand,
};
use migrations::Migrator; use migrations::Migrator;
use sea_orm::{Database, DatabaseConnection}; use sea_orm::{Database, DatabaseConnection};
use sea_orm_migration::{MigratorTrait, SchemaManager}; use sea_orm_migration::{MigratorTrait, SchemaManager};
@ -150,8 +153,10 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
KeysSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime), KeysSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
KeysSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime), KeysSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime), KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime),
#[cfg(feature = "gpg-compat")] KeysSubcommands::Import(subcommand) => match &subcommand.subcommand {
KeysSubcommands::ImportGpg(subcommand) => subcommand.execute_sync(state, runtime), KeysImportSubcommands::Gpg(subcommand) => subcommand.execute_sync(state, runtime),
KeysImportSubcommands::Jwk(subcommand) => subcommand.execute_sync(state, runtime),
},
}, },
} }
} }