mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-22 21:49:28 -07:00
Allow key imports from asp-tool
This commit is contained in:
parent
8af322b2ad
commit
00ffbdce8d
6 changed files with 96 additions and 23 deletions
|
@ -72,7 +72,10 @@ mod tests {
|
|||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let decoded = AspeRequest::decode_and_verify(crate::test_constants::CREATE_REQUEST, Some(&key.fingerprint));
|
||||
let decoded = AspeRequest::decode_and_verify(
|
||||
crate::test_constants::CREATE_REQUEST,
|
||||
Some(&key.fingerprint),
|
||||
);
|
||||
assert!(
|
||||
decoded.is_ok(),
|
||||
"ASPE request JWS should verify and decode successfully"
|
||||
|
@ -119,7 +122,10 @@ mod tests {
|
|||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let decoded = AspeRequest::decode_and_verify(crate::test_constants::UPDATE_REQUEST, Some(&key.fingerprint));
|
||||
let decoded = AspeRequest::decode_and_verify(
|
||||
crate::test_constants::UPDATE_REQUEST,
|
||||
Some(&key.fingerprint),
|
||||
);
|
||||
assert!(
|
||||
decoded.is_ok(),
|
||||
"ASPE request JWS should verify and decode successfully"
|
||||
|
@ -166,7 +172,10 @@ mod tests {
|
|||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let decoded = AspeRequest::decode_and_verify(crate::test_constants::DELETE_REQUEST, Some(&key.fingerprint));
|
||||
let decoded = AspeRequest::decode_and_verify(
|
||||
crate::test_constants::DELETE_REQUEST,
|
||||
Some(&key.fingerprint),
|
||||
);
|
||||
assert!(
|
||||
decoded.is_ok(),
|
||||
"ASPE request JWS should verify and decode successfully"
|
||||
|
|
|
@ -45,8 +45,8 @@ mod tests {
|
|||
|
||||
use hex_color::HexColor;
|
||||
use josekit::jwk::Jwk;
|
||||
use serde_email::Email;
|
||||
use url::Url;
|
||||
use serde_email::Email;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
keys::AspKey,
|
||||
|
@ -83,8 +83,10 @@ use url::Url;
|
|||
let key = TryInto::<AspKey>::try_into(Jwk::from_bytes(crate::test_constants::KEY).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let profile =
|
||||
AriadneSignatureProfile::decode_and_verify(crate::test_constants::PROFILE, Some(&key.fingerprint));
|
||||
let profile = AriadneSignatureProfile::decode_and_verify(
|
||||
crate::test_constants::PROFILE,
|
||||
Some(&key.fingerprint),
|
||||
);
|
||||
assert!(profile.is_ok(), "Profile should parse and verify correctly");
|
||||
let (_, profile) = profile.unwrap();
|
||||
assert_eq!(
|
||||
|
|
|
@ -20,7 +20,10 @@ pub trait JwtSerializable {}
|
|||
|
||||
pub trait JwtSerialize {
|
||||
fn encode_and_sign(&self, key: &AspKey) -> Result<String, JwtSerializationError>;
|
||||
fn decode_and_verify<S>(jwt: &str, expected_fingerprint: Option<S>) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||
fn decode_and_verify<S>(
|
||||
jwt: &str,
|
||||
expected_fingerprint: Option<S>,
|
||||
) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||
where
|
||||
Self: for<'de> Deserialize<'de> + JwtSerializable,
|
||||
S: AsRef<str>;
|
||||
|
@ -80,10 +83,13 @@ impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize fo
|
|||
.or(Err(JwtSerializationError::SerializationError))
|
||||
}
|
||||
|
||||
fn decode_and_verify<S>(jwt: &str, expected_fingerprint: Option<S>) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||
fn decode_and_verify<S>(
|
||||
jwt: &str,
|
||||
expected_fingerprint: Option<S>,
|
||||
) -> Result<(AspKey, Box<Self>), JwtDeserializationError>
|
||||
where
|
||||
Self: for<'de> serde::Deserialize<'de> + JwtSerializable,
|
||||
S: AsRef<str>
|
||||
S: AsRef<str>,
|
||||
{
|
||||
// Decode the header and check if the key id is correct
|
||||
let header = jwt::decode_header(jwt).or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
||||
|
|
|
@ -10,32 +10,32 @@ use clap::{Parser, ValueEnum};
|
|||
use data_encoding::{BASE64, BASE64_NOPAD};
|
||||
use dialoguer::{theme::ColorfulTheme, Password};
|
||||
use josekit::jwk::Jwk;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
commands::{AspmSubcommand, KeysEntityExt, KeysQueryResult},
|
||||
entities::prelude::*,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "alg", rename = "scrypt")]
|
||||
pub struct ASPToolScryptExport {
|
||||
#[serde(rename = "prm")]
|
||||
parameters: ASPToolScryptExportParam,
|
||||
pub parameters: ASPToolScryptExportParam,
|
||||
#[serde(rename = "slt")]
|
||||
salt: String,
|
||||
pub salt: String,
|
||||
#[serde(rename = "key")]
|
||||
encrypted_key: String,
|
||||
pub encrypted_key: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ASPToolScryptExportParam {
|
||||
#[serde(rename = "N")]
|
||||
cost: u64,
|
||||
pub cost: u64,
|
||||
#[serde(rename = "r")]
|
||||
block_size: u32,
|
||||
pub block_size: u32,
|
||||
#[serde(rename = "p")]
|
||||
parallelism: u32,
|
||||
pub parallelism: u32,
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone)]
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{anyhow, bail, Context};
|
|||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
||||
use asp::keys::AspKey;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use data_encoding::{BASE64URL_NOPAD, BASE64_NOPAD};
|
||||
use data_encoding::{BASE64, BASE64URL_NOPAD, BASE64_NOPAD};
|
||||
use dialoguer::{theme::ColorfulTheme, Input, Password};
|
||||
|
||||
use aes_gcm::{
|
||||
|
@ -13,7 +13,10 @@ use indoc::printdoc;
|
|||
use josekit::jwk::Jwk;
|
||||
use sea_orm::{ActiveValue, EntityTrait};
|
||||
|
||||
use crate::{commands::AspmSubcommand, entities::keys};
|
||||
use crate::{
|
||||
commands::{keys::export::ASPToolScryptExport, AspmSubcommand},
|
||||
entities::keys,
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone)]
|
||||
pub enum KeyImportFormat {
|
||||
|
@ -22,6 +25,8 @@ pub enum KeyImportFormat {
|
|||
// Imports a PGP key from a local GPG store
|
||||
#[cfg(feature = "gpg-compat")]
|
||||
Gpg,
|
||||
// Imports a secret key in the format used by https://asp.keyoxide.org
|
||||
AspTool,
|
||||
}
|
||||
|
||||
/// Imports an ASP from raw JWK format. This only will import JWKs that have supported curves.
|
||||
|
@ -236,6 +241,53 @@ impl AspmSubcommand for KeysImportCommand {
|
|||
.context("Unable to construct Jwk representation of PGP key")?,
|
||||
)?
|
||||
}
|
||||
KeyImportFormat::AspTool => {
|
||||
let parsed: ASPToolScryptExport = serde_json::from_slice(
|
||||
BASE64
|
||||
.decode(self.key.as_bytes())
|
||||
.context("Unable to base64 decode asp-tool secret key")?
|
||||
.as_slice(),
|
||||
)
|
||||
.context("Unable to parse base64-decoded asp-tool secret key as JSON")?;
|
||||
|
||||
let mut derived_key = vec![0u8; parsed.encrypted_key.len()];
|
||||
scrypt::scrypt(
|
||||
key_password.as_bytes(),
|
||||
&BASE64
|
||||
.decode(parsed.salt.as_bytes())
|
||||
.context("Unable to base64 decode salt from parsed asp-tool secret key")?,
|
||||
&scrypt::Params::new(
|
||||
parsed
|
||||
.parameters
|
||||
.cost
|
||||
.ilog2()
|
||||
.try_into()
|
||||
.context("log_2(N) from parsed asp-tool secret key was not a u8")?,
|
||||
parsed.parameters.block_size,
|
||||
parsed.parameters.parallelism,
|
||||
// This length is unused in the scrypt function itself, so the recommended works as a placeholder
|
||||
scrypt::Params::RECOMMENDED_LEN,
|
||||
)
|
||||
.context(
|
||||
"Unable to assemble scrypt parameters from parsed asp-tool secret key",
|
||||
)?,
|
||||
&mut derived_key,
|
||||
)
|
||||
.context("Unable to derive PKCS#8 encryption key with scrypt")?;
|
||||
|
||||
AspKey::from_pkcs8(
|
||||
&BASE64_NOPAD.encode(
|
||||
BASE64.decode(parsed.encrypted_key.as_bytes())
|
||||
.context("Unable to base64 decode encrypted key from parsed asp-tool secret key")?
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, byte)| byte ^ derived_key[i])
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
),
|
||||
)
|
||||
.context("Unable to decode decrypted asp-tool key as PKCS#8")?
|
||||
}
|
||||
};
|
||||
|
||||
let argon_salt = SaltString::from_b64(
|
||||
|
|
|
@ -143,7 +143,11 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
|
|||
.context("Unable to check database for keys table")?);
|
||||
|
||||
// Make the state
|
||||
let state = AspmState { data_dir, db, verbose: parsed.verbose };
|
||||
let state = AspmState {
|
||||
data_dir,
|
||||
db,
|
||||
verbose: parsed.verbose,
|
||||
};
|
||||
|
||||
Ok::<AspmState, anyhow::Error>(state)
|
||||
})?;
|
||||
|
|
Loading…
Reference in a new issue