remove unneeded osshkeys dependency

This commit is contained in:
Janis 2024-07-30 15:30:01 +02:00
parent 70693fe768
commit fe5245fc31
5 changed files with 82 additions and 316 deletions

View file

@ -9,7 +9,7 @@ path = "src/lib.rs"
[features] [features]
default = ["passphrase-gen", "password-gen", "ed25519", "clap", "rpassword", "base64"] default = ["passphrase-gen", "password-gen", "ed25519", "clap", "rpassword", "base64"]
ed25519 = ["osshkeys", "sha2"] ed25519 = ["sha2"]
passphrase-gen = [] passphrase-gen = []
password-gen = [] password-gen = []
@ -29,7 +29,6 @@ rand_chacha = "0.3"
clap = {version = "3.0.0-beta.5", optional = true, features = ["derive"]} clap = {version = "3.0.0-beta.5", optional = true, features = ["derive"]}
base64 = {version = "0.13", optional = true} base64 = {version = "0.13", optional = true}
bytes = {version = "1.1", optional = true} bytes = {version = "1.1", optional = true}
osshkeys = {git = "https://github.com/noonebtw/rust-osshkeys.git", branch = "master", optional = true}
sha2 = {version = "0.9", optional = true} sha2 = {version = "0.9", optional = true}
rpassword = {version = "5.0", optional = true} rpassword = {version = "5.0", optional = true}
zeroize = {version = "1.5"} zeroize = {version = "1.5"}

View file

@ -12,166 +12,6 @@ struct Opts {
file: String, 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<Self, Self::Err> {
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<Self, Self::Err> {
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<String>,
passphrase: Zeroizing<String>,
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<String>) -> 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<Z: Into<Zeroizing<String>>>(mut self, passphrase: Z) -> Self {
self.passphrase = passphrase.into();
self
}
pub fn build_keypair(self) -> OsshResult<((Zeroizing<String>, String), KeyPair)> {
let hash_seed = Zeroizing::new(
self.passphrase
.chars()
.chain(self.tag.chars())
.collect::<String>(),
);
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::<KeyPair>::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<()> { fn main() -> anyhow::Result<()> {
let opts = Opts::parse(); let opts = Opts::parse();
println!("Generating ed25519 ssh keypair:"); println!("Generating ed25519 ssh keypair:");

View file

@ -1,176 +1,103 @@
use osshkeys::{error::OsshResult, keys::ed25519::Ed25519KeyPair, KeyPair}; use std::fmt::Write;
use sha2::{Digest, Sha256};
pub fn generate_ed25519_keypair<S1, S2>( const WIDTH: usize = 17;
passphrase: S1, const HEIGHT: usize = 9;
id: Option<S2>, const TILES: &[char] = &[
) -> OsshResult<KeyPair> ' ', '.', 'o', '+', '=', '*', 'B', 'O', 'X', '@', '%', '&', '#', '/', '^',
where ];
S1: AsRef<str>,
S2: AsRef<str>,
{
let hash = {
let mut hasher = Sha256::new();
hasher.update(passphrase.as_ref());
if let Some(id) = id {
hasher.update(id.as_ref());
}
hasher.finalize() pub struct RandomArt {
}; tiles: [[i8; WIDTH]; HEIGHT],
Ed25519KeyPair::from_seed(&hash).map(|key| Into::<KeyPair>::into(key))
} }
pub mod randomart { impl RandomArt {
use std::fmt::Write; pub fn render(&self, title: &str, subtitle: &str) -> Result<String, std::fmt::Error> {
let mut string = String::new();
const WIDTH: usize = 17; string.write_str(&format!("+{:-^17}+\n", format!("[{}]", title)))?;
const HEIGHT: usize = 9;
const TILES: &[char] = &[
' ', '.', 'o', '+', '=', '*', 'B', 'O', 'X', '@', '%', '&', '#', '/',
'^',
];
pub struct RandomArt { for row in self.tiles {
tiles: [[i8; WIDTH]; HEIGHT], string.write_char('|')?;
} for cell in row {
if cell == -1 {
impl RandomArt { string.write_char('S')?;
pub fn render( } else if cell == -2 {
&self, string.write_char('E')?;
title: &str, } else {
subtitle: &str, let cell = (TILES.len() - 1).min(cell as usize);
) -> Result<String, std::fmt::Error> { string.write_char(TILES[cell])?;
let mut string = String::new();
string
.write_str(&format!("+{:-^17}+\n", format!("[{}]", title)))?;
for row in self.tiles {
string.write_char('|')?;
for cell in row {
if cell == -1 {
string.write_char('S')?;
} else if cell == -2 {
string.write_char('E')?;
} else {
let cell = (TILES.len() - 1).min(cell as usize);
string.write_char(TILES[cell])?;
}
}
string.write_char('|')?;
string.write_char('\n')?;
}
string.write_str(&format!(
"+{:-^17}+\n",
format!("[{}]", subtitle)
))?;
Ok(string)
}
pub fn from_digest(digest: &[u8]) -> Self {
let mut tiles = [[0i8; WIDTH]; HEIGHT];
let mut x = WIDTH / 2;
let mut y = HEIGHT / 2;
tiles[y][x] = -1;
for byte in digest.iter() {
for i in 0..4 {
let b = (*byte >> (i * 2)) & 3;
match b {
0 | 1 => {
if y > 0 {
y -= 1;
}
}
2 | 3 => {
if y < HEIGHT - 1 {
y += 1;
}
}
_ => {}
}
match b {
0 | 2 => {
if x > 0 {
x -= 1;
}
}
1 | 3 => {
if x < WIDTH - 1 {
x += 1;
}
}
_ => {}
}
if tiles[y][x] >= 0 {
tiles[y][x] += 1;
}
} }
} }
string.write_char('|')?;
tiles[y][x] = -2; string.write_char('\n')?;
Self { tiles }
} }
string.write_str(&format!("+{:-^17}+\n", format!("[{}]", subtitle)))?;
Ok(string)
} }
#[cfg(test)] pub fn from_digest(digest: &[u8]) -> Self {
mod tests { let mut tiles = [[0i8; WIDTH]; HEIGHT];
use super::*; let mut x = WIDTH / 2;
let mut y = HEIGHT / 2;
const FINGERPRINT: &str = tiles[y][x] = -1;
"L5N7A2PETaGegW5P3qh/Vjd8AW6Mn4B+VB2SHK+eZCY=";
#[test] for byte in digest.iter() {
fn render() { for i in 0..4 {
let randomart = let b = (*byte >> (i * 2)) & 3;
RandomArt::from_digest(&base64::decode(FINGERPRINT).unwrap()) match b {
.render("Title", "Subtitle") 0 | 1 => {
.unwrap(); if y > 0 {
y -= 1;
}
}
2 | 3 => {
if y < HEIGHT - 1 {
y += 1;
}
}
_ => {}
}
match b {
0 | 2 => {
if x > 0 {
x -= 1;
}
}
1 | 3 => {
if x < WIDTH - 1 {
x += 1;
}
}
_ => {}
}
println!("{}", randomart); if tiles[y][x] >= 0 {
tiles[y][x] += 1;
}
}
} }
tiles[y][x] = -2;
Self { tiles }
} }
} }
#[cfg(test)]
mod tests { mod tests {
#![allow(dead_code)] use super::*;
#![allow(unused_imports)]
use osshkeys::{
keys::ed25519::Ed25519KeyPair, KeyPair, PrivateParts, PublicParts,
};
use sha2::Digest;
use super::randomart::RandomArt; const FINGERPRINT: &str = "L5N7A2PETaGegW5P3qh/Vjd8AW6Mn4B+VB2SHK+eZCY=";
const PASSPHRASE: &str = "the spice must flow";
const DATA: &str = "Id just like to interject for a moment. What youre refering to as Linux, is in fact, GNU/Linux, or as Ive recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX.";
const SIGNATURE: &[u8] = &[
115, 18, 156, 87, 192, 149, 107, 105, 13, 87, 219, 90, 26, 146, 41,
114, 20, 143, 253, 206, 216, 236, 222, 66, 252, 136, 38, 216, 184, 127,
94, 255, 68, 246, 64, 228, 141, 64, 63, 64, 236, 222, 184, 214, 3, 157,
73, 186, 73, 156, 20, 100, 76, 241, 113, 81, 38, 131, 174, 31, 103,
181, 220, 11,
];
#[test] #[test]
fn test() { fn render() {
let mut hasher = sha2::Sha256::new(); let randomart = RandomArt::from_digest(&base64::decode(FINGERPRINT).unwrap())
hasher.update(PASSPHRASE); .render("Title", "Subtitle")
.unwrap();
let hash = hasher.finalize(); println!("{}", randomart);
let keypair: KeyPair = Ed25519KeyPair::from_seed(&hash).unwrap().into();
let asdf = keypair.sign(DATA.as_bytes()).unwrap();
assert_eq!(keypair.verify(DATA.as_bytes(), SIGNATURE).unwrap(), true);
} }
} }

View file

@ -2,7 +2,7 @@ use russh_keys::PublicKeyBase64;
use sha2::Digest; use sha2::Digest;
use zeroize::Zeroizing; use zeroize::Zeroizing;
use crate::ed25519::randomart; use crate::randomart;
pub mod cli { pub mod cli {
use zeroize::Zeroizing; use zeroize::Zeroizing;

View file

@ -6,8 +6,8 @@ pub mod passphrase_gen;
#[cfg(feature = "password-gen")] #[cfg(feature = "password-gen")]
pub mod password_gen; pub mod password_gen;
#[cfg(feature = "ed25519")] #[path = "ed25519.rs"]
pub mod ed25519; pub mod randomart;
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
pub mod key_gen; pub mod key_gen;