mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-22 21:49:28 -07:00
Start on keys import subcommand, move import-gpg
This commit is contained in:
parent
ec1b9f3bf9
commit
43851d1f98
7 changed files with 143 additions and 9 deletions
|
@ -1,5 +1,5 @@
|
|||
# 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`.
|
|
@ -14,7 +14,7 @@ use thiserror::Error;
|
|||
use crate::utils::jwk::JwtExt;
|
||||
|
||||
/// An enum representing the possible types of JWK for ASPs
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AspKeyType {
|
||||
Ed25519,
|
||||
ES256,
|
||||
|
|
113
src/commands/keys/import/jwk.rs
Normal file
113
src/commands/keys/import/jwk.rs
Normal 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(())
|
||||
}
|
||||
}
|
18
src/commands/keys/import/mod.rs
Normal file
18
src/commands/keys/import/mod.rs
Normal 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),
|
||||
}
|
|
@ -3,8 +3,7 @@ use clap::{Parser, Subcommand};
|
|||
pub mod delete;
|
||||
pub mod export;
|
||||
pub mod generate;
|
||||
#[cfg(feature = "gpg-compat")]
|
||||
pub mod import_gpg;
|
||||
pub mod import;
|
||||
pub mod list;
|
||||
|
||||
/// 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),
|
||||
List(list::KeysListCommand),
|
||||
Export(export::KeysExportCommand),
|
||||
#[cfg(feature = "gpg-compat")]
|
||||
ImportGpg(import_gpg::KeysImportGpgCommand),
|
||||
Delete(delete::KeysDeleteCommand),
|
||||
Import(import::KeysImportCommand),
|
||||
}
|
||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -6,7 +6,10 @@ use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle};
|
|||
use anyhow::Context;
|
||||
use app_dirs2::{AppDataType, AppInfo};
|
||||
use clap::{Parser, Subcommand};
|
||||
use commands::{keys::KeysSubcommands, AspmSubcommand};
|
||||
use commands::{
|
||||
keys::{import::KeysImportSubcommands, KeysSubcommands},
|
||||
AspmSubcommand,
|
||||
};
|
||||
use migrations::Migrator;
|
||||
use sea_orm::{Database, DatabaseConnection};
|
||||
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::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
#[cfg(feature = "gpg-compat")]
|
||||
KeysSubcommands::ImportGpg(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
KeysSubcommands::Import(subcommand) => match &subcommand.subcommand {
|
||||
KeysImportSubcommands::Gpg(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
KeysImportSubcommands::Jwk(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue