use clap::Parser; /// program that generates ed25519 keypairs seeded by a passphrase and an optional ID. #[derive(Parser)] #[clap( name = "duralumin-keygen", version = "0.2.0", author = "No One " )] struct Opts { #[clap(short, long, default_value = "duralumin")] file: String, } pub mod keygen { use std::str::FromStr; use osshkeys::{error::OsshResult, keys::ed25519::Ed25519KeyPair, KeyPair}; use rand::{Rng, SeedableRng}; use sha2::Digest; use thiserror::Error; use zeroize::Zeroizing; #[derive(Debug, Error)] pub enum Error { #[error("Failed to parse")] ParseError, } #[derive(Debug, Clone)] pub enum KeyType { SSH, PGP, } impl FromStr for KeyType { type Err = Error; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "ssh" => Ok(Self::SSH), "pgp" => Ok(Self::PGP), _ => Err(Error::ParseError), } } } #[derive(Debug, Clone)] pub enum HashType { Sha256, Argon2, } impl FromStr for HashType { type Err = Error; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "sha256" => Ok(Self::Sha256), "argon2" | "argon" => Ok(Self::Argon2), _ => Err(Error::ParseError), } } } #[derive(Debug, Clone)] pub struct SshKeyBuilder { hash_type: HashType, iterations: i16, tag: Zeroizing, passphrase: Zeroizing, encrypt_key: bool, } impl Default for SshKeyBuilder { fn default() -> Self { Self { hash_type: HashType::Argon2, iterations: 4, tag: Default::default(), passphrase: Default::default(), encrypt_key: true, } } } impl SshKeyBuilder { pub fn with_hash_type(mut self, hash_type: HashType) -> Self { self.hash_type = hash_type; self } pub fn with_encrypt(mut self, encrypt: bool) -> Self { self.encrypt_key = encrypt; self } pub fn with_hash_type_and_iterations(self, hash_type: HashType, iterations: i16) -> Self { self.with_hash_type(hash_type).with_iterations(iterations) } pub fn with_iterations(mut self, iterations: i16) -> Self { self.iterations = iterations; self } pub fn with_maybe_tag(self, tag: Option) -> Self { match tag { Some(tag) => self.with_tag(tag), None => self, } } pub fn with_tag(mut self, tag: String) -> Self { self.tag = Zeroizing::new(tag); self } pub fn with_passphrase>>(mut self, passphrase: Z) -> Self { self.passphrase = passphrase.into(); self } pub fn build_keypair(self) -> OsshResult<((Zeroizing, String), KeyPair)> { let hash_seed = Zeroizing::new( self.passphrase .chars() .chain(self.tag.chars()) .collect::(), ); let mut seed = [0u8; 32]; match self.hash_type { HashType::Sha256 => { let mut digest = sha2::Sha256::digest(hash_seed.as_bytes()); assert!(self.iterations <= 1); for _ in 0..(self.iterations - 1) { digest = sha2::Sha256::digest(digest.as_slice()); } seed.copy_from_slice(&digest[..32]); } HashType::Argon2 => { // TODO: make more random salt let salt = b"thissaltneedsupdating"; let argon = argon2::Argon2::new( argon2::Algorithm::Argon2i, argon2::Version::V0x13, argon2::Params::new(65536, self.iterations as u32, 4, Some(32)) .expect("argon2 params"), ); argon .hash_password_into(hash_seed.as_bytes(), salt, &mut seed) .expect("hash passphrase"); } }; let rng = rand_chacha::ChaChaRng::from_seed(seed).gen::<[u8; 32]>(); let keypair = Ed25519KeyPair::from_seed(&rng).map(|key| Into::::into(key))?; let private_key = Zeroizing::new(keypair.serialize_openssh( self.encrypt_key.then(|| self.passphrase.as_str()), osshkeys::cipher::Cipher::Aes256_Ctr, )?); Ok(((private_key, keypair.serialize_publickey()?), keypair)) } } } fn main() -> anyhow::Result<()> { let opts = Opts::parse(); println!("Generating ed25519 ssh keypair:"); let desc = libduralumin::key_gen::cli::keygen_desc_from_stdin()?; let keypair = libduralumin::key_gen::generate_key(desc)?; println!( "Your key fingerprint is: Sha256:{}", keypair.fingerprint_base64() ); println!( "RandomArt:\n{}", keypair.randomart().render("ED25519 256", "SHA256")? ); let private_path = opts.file.clone(); let public_path = opts.file.clone() + ".pub"; let (private_key, public_key) = keypair.encode_keys()?; std::fs::write(&private_path, private_key)?; std::fs::write(&public_path, public_key)?; #[cfg(target_family = "unix")] { use std::fs::Permissions; use std::os::unix::fs::PermissionsExt; std::fs::set_permissions(private_path, Permissions::from_mode(0o0600))?; std::fs::set_permissions(public_path, Permissions::from_mode(0o0600))?; } Ok(()) }