good stuff

This commit is contained in:
nora 2024-08-23 02:14:16 +02:00
parent d340ff0861
commit c4bb37e570
8 changed files with 342 additions and 54 deletions

View file

@ -11,3 +11,4 @@ tracing.workspace = true
ssh-keys = { path = "../../lib/ssh-keys" }
base64 = "0.22.1"
rpassword = "7.3.1"
pem = "3.0.4"

View file

@ -1,9 +1,12 @@
use std::path::PathBuf;
use std::{
io::Write,
path::{Path, PathBuf},
};
use base64::Engine;
use clap::Parser;
use eyre::{bail, Context};
use ssh_keys::PrivateKeyType;
use ssh_keys::{KeyEncryptionParams, PrivateKeyType};
#[derive(clap::Parser)]
struct Args {
@ -13,6 +16,10 @@ struct Args {
#[derive(clap::Subcommand)]
enum Subcommand {
/// Strips PEM armor
Unpem { id_file: PathBuf },
/// Extract the encrypted part of the private key
ExtractEncrypted { id_file: PathBuf },
Info {
/// Decrypt the key to get more information. Will not display private information unless --show-private is used
#[arg(short, long)]
@ -22,55 +29,127 @@ enum Subcommand {
show_private: bool,
id_file: PathBuf,
},
Generate {
#[arg(short, long = "type")]
type_: KeyType,
#[arg(short, long)]
comment: String,
#[arg(short, long)]
path: PathBuf,
},
}
#[derive(clap::ValueEnum, Clone)]
enum KeyType {
Ed25519,
}
fn main() -> eyre::Result<()> {
let args = Args::parse();
match args.command {
Subcommand::Unpem { id_file } => {
let file = std::fs::read(&id_file)
.wrap_err_with(|| format!("reading file {}", id_file.display()))?;
let raw = pem::parse(&file)?;
std::io::stdout().lock().write_all(raw.contents())?;
Ok(())
}
Subcommand::ExtractEncrypted { id_file } => {
let file = std::fs::read(&id_file)
.wrap_err_with(|| format!("reading file {}", id_file.display()))?;
let keys = ssh_keys::EncryptedPrivateKeys::parse(&file)?;
let passphrase = if keys.requires_passphrase() {
let phrase = rpassword::prompt_password("passphrase: ")?;
Some(phrase)
} else {
None
};
let data = keys.decrypt_encrypted_part(passphrase.as_deref())?;
std::io::stdout().lock().write_all(&data)?;
Ok(())
}
Subcommand::Info {
id_file,
decrypt,
show_private,
} => {
if show_private && !decrypt {
bail!("cannot --show-private without --decrypt");
}
} => info(&id_file, decrypt, show_private),
Subcommand::Generate {
type_,
comment,
path,
} => generate(type_, comment, &path),
}
}
let file = std::fs::read(&id_file)
.wrap_err_with(|| format!("reading file {}", id_file.display()))?;
fn info(id_file: &Path, decrypt: bool, show_private: bool) -> eyre::Result<()> {
if show_private && !decrypt {
bail!("cannot --show-private without --decrypt");
}
let keys = ssh_keys::EncryptedPrivateKeys::parse_unencrypted(&file)?;
let file =
std::fs::read(&id_file).wrap_err_with(|| format!("reading file {}", id_file.display()))?;
if decrypt {
let passphrase = if keys.requires_passphrase() {
let phrase = rpassword::prompt_password("passphrase: ")?;
Some(phrase)
} else {
None
};
let keys = ssh_keys::EncryptedPrivateKeys::parse(&file)?;
let keys = keys.parse_private(passphrase.as_deref())?;
for key in keys {
println!("{} {}", key.private_key.public_key(), key.comment);
if show_private {
match key.private_key {
PrivateKeyType::Ed25519 { private_key, .. } => {
println!(
" private key: {}",
base64::prelude::BASE64_STANDARD_NO_PAD.encode(private_key)
)
}
}
if decrypt {
let passphrase = if keys.requires_passphrase() {
let phrase = rpassword::prompt_password("passphrase: ")?;
Some(phrase)
} else {
None
};
let keys = keys.parse_private(passphrase.as_deref())?;
for key in keys {
println!("{} {}", key.private_key.public_key(), key.comment);
if show_private {
match key.private_key {
PrivateKeyType::Ed25519 { private_key, .. } => {
println!(
" private key: {}",
base64::prelude::BASE64_STANDARD_NO_PAD.encode(private_key)
)
}
}
} else {
for key in keys.public_keys {
println!("{key}");
}
}
}
} else {
for key in keys.public_keys {
println!("{key}");
}
}
Ok(())
}
fn generate(type_: KeyType, comment: String, path: &Path) -> eyre::Result<()> {
let type_ = match type_ {
KeyType::Ed25519 => ssh_keys::KeyType::Ed25519,
};
let passphrase = rpassword::prompt_password("Enter passphrase (empty for no passphrase): ")?;
let key = ssh_keys::PlaintextPrivateKey::generate(
comment,
ssh_keys::KeyGenerationParams { key_type: type_ },
);
println!("{} {}", key.private_key.public_key(), key.comment);
let keys = key.encrypt(KeyEncryptionParams::secure_or_none(passphrase))?;
let mut pubkey_path = path.to_path_buf().into_os_string();
pubkey_path.push(".pub");
std::fs::write(
&pubkey_path,
format!("{} {}\n", key.private_key.public_key(), key.comment),
)
.wrap_err_with(|| format!("writing to {:?}", pubkey_path))?;
let privkey = keys.to_bytes_armored();
std::fs::write(path, privkey).wrap_err_with(|| format!("writing to {}", path.display()))?;
Ok(())
}