use crate::config::current_algorithm;
use crate::crypto::encryption::{decrypt_bytes, encrypt_bytes};
use crate::error::AgentError;
use auths_crypto::{CurveType, SecureSeed, TypedSeed};
use auths_verifier::DevicePublicKey;
use ssh_agent_lib::ssh_key::{Algorithm as SshAlgorithm, EcdsaCurve};
use zeroize::Zeroizing;
pub trait SignerKey: Send + Sync + 'static {
fn public_key_bytes(&self) -> Vec<u8>;
fn kind(&self) -> SshAlgorithm;
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, AgentError>;
}
pub struct SeedSignerKey {
seed: SecureSeed,
public_key: DevicePublicKey,
curve: CurveType,
}
impl SeedSignerKey {
pub fn new(seed: SecureSeed, public_key: [u8; 32]) -> Self {
#[allow(clippy::expect_used)] let dpk = DevicePublicKey::try_new(CurveType::Ed25519, &public_key)
.expect("Ed25519 public key is always 32 bytes");
Self {
seed,
public_key: dpk,
curve: CurveType::Ed25519,
}
}
pub fn new_typed(seed: SecureSeed, public_key: DevicePublicKey) -> Self {
let curve = public_key.curve();
Self {
seed,
public_key,
curve,
}
}
pub fn from_seed(seed: SecureSeed) -> Result<Self, AgentError> {
let pk_vec = auths_crypto::typed_public_key(&TypedSeed::Ed25519(*seed.as_bytes()))
.map_err(|e| {
AgentError::CryptoError(format!("Failed to derive public key from seed: {}", e))
})?;
#[allow(clippy::expect_used)] let public_key: [u8; 32] = pk_vec.try_into().expect("Ed25519 public key is 32 bytes");
Ok(Self::new(seed, public_key))
}
pub fn from_typed_seed(seed: TypedSeed) -> Result<Self, AgentError> {
let curve = seed.curve();
let pk_bytes = auths_crypto::typed_public_key(&seed)
.map_err(|e| AgentError::CryptoError(format!("Failed to derive pk: {e}")))?;
let dpk = DevicePublicKey::try_new(curve, &pk_bytes)
.map_err(|e| AgentError::CryptoError(format!("Invalid derived pk: {e}")))?;
Ok(Self {
seed: seed.to_secure_seed(),
public_key: dpk,
curve,
})
}
pub fn curve(&self) -> CurveType {
self.curve
}
pub fn typed_public_key(&self) -> &DevicePublicKey {
&self.public_key
}
}
impl SignerKey for SeedSignerKey {
fn public_key_bytes(&self) -> Vec<u8> {
self.public_key.as_bytes().to_vec()
}
fn kind(&self) -> SshAlgorithm {
match self.curve {
CurveType::Ed25519 => SshAlgorithm::Ed25519,
CurveType::P256 => SshAlgorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
},
}
}
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, AgentError> {
let typed = match self.curve {
CurveType::Ed25519 => TypedSeed::Ed25519(*self.seed.as_bytes()),
CurveType::P256 => TypedSeed::P256(*self.seed.as_bytes()),
};
auths_crypto::typed_sign(&typed, message)
.map_err(|e| AgentError::CryptoError(format!("{} signing failed: {e}", self.curve)))
}
}
pub fn extract_seed_from_key_bytes(bytes: &[u8]) -> Result<SecureSeed, AgentError> {
let parsed = auths_crypto::parse_key_material(bytes)
.map_err(|e| AgentError::KeyDeserializationError(e.to_string()))?;
Ok(parsed.seed.to_secure_seed())
}
pub fn load_seed_and_pubkey(
bytes: &[u8],
) -> Result<(SecureSeed, Vec<u8>, auths_crypto::CurveType), AgentError> {
let parsed = auths_crypto::parse_key_material(bytes)
.map_err(|e| AgentError::KeyDeserializationError(e.to_string()))?;
let curve = parsed.seed.curve();
Ok((parsed.seed.to_secure_seed(), parsed.public_key, curve))
}
pub fn encrypt_keypair(raw: &[u8], passphrase: &str) -> Result<Vec<u8>, AgentError> {
encrypt_bytes(raw, passphrase, current_algorithm())
}
pub fn decrypt_keypair(
encrypted: &[u8],
passphrase: &str,
) -> Result<Zeroizing<Vec<u8>>, AgentError> {
Ok(Zeroizing::new(decrypt_bytes(encrypted, passphrase)?))
}