mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
Add cluelessh-key debug create-fake-privkey
This command creates a fake private key that looks like it's the real private key for a corresponding public key. Even `ssh-keygen -y` gets confused.
This commit is contained in:
parent
e36f416c54
commit
a03eb38461
5 changed files with 99 additions and 35 deletions
|
|
@ -6,8 +6,9 @@ use std::{
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cluelessh_keys::private::{
|
use cluelessh_keys::{
|
||||||
EncryptedPrivateKeys, KeyEncryptionParams, PlaintextPrivateKey, PrivateKey,
|
private::{EncryptedPrivateKeys, KeyEncryptionParams, PlaintextPrivateKey, PrivateKey},
|
||||||
|
public::{PublicKey, PublicKeyWithComment},
|
||||||
};
|
};
|
||||||
use eyre::{bail, Context};
|
use eyre::{bail, Context};
|
||||||
|
|
||||||
|
|
@ -51,6 +52,8 @@ enum DebugCommand {
|
||||||
Unpem { id_file: PathBuf },
|
Unpem { id_file: PathBuf },
|
||||||
/// Extract the encrypted part of the private key
|
/// Extract the encrypted part of the private key
|
||||||
ExtractEncrypted { id_file: PathBuf },
|
ExtractEncrypted { id_file: PathBuf },
|
||||||
|
/// Create a fake private key that contains random private key bytes and the actual public key bytes.
|
||||||
|
CreateFakePrivkey { public_key: PathBuf },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::ValueEnum, Clone)]
|
#[derive(clap::ValueEnum, Clone)]
|
||||||
|
|
@ -98,6 +101,53 @@ fn main() -> eyre::Result<()> {
|
||||||
std::io::stdout().lock().write_all(&data)?;
|
std::io::stdout().lock().write_all(&data)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Subcommand::Debug {
|
||||||
|
cmd: DebugCommand::CreateFakePrivkey { public_key },
|
||||||
|
} => {
|
||||||
|
let file = std::fs::read_to_string(&public_key)
|
||||||
|
.wrap_err_with(|| format!("reading file {}", public_key.display()))?;
|
||||||
|
let public_key = file
|
||||||
|
.parse::<PublicKeyWithComment>()
|
||||||
|
.wrap_err("failed to parse public key")?;
|
||||||
|
|
||||||
|
let mut fake_private_key = PlaintextPrivateKey::generate(
|
||||||
|
"".into(),
|
||||||
|
cluelessh_keys::KeyGenerationParams {
|
||||||
|
key_type: match public_key.key {
|
||||||
|
PublicKey::Ed25519 { .. } => cluelessh_keys::KeyType::Ed25519,
|
||||||
|
PublicKey::EcdsaSha2NistP256 { .. } => cluelessh_keys::KeyType::Ecdsa,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
match public_key.key {
|
||||||
|
PublicKey::Ed25519 { public_key } => {
|
||||||
|
let PrivateKey::Ed25519 {
|
||||||
|
public_key: fake_public_key,
|
||||||
|
..
|
||||||
|
} = &mut fake_private_key.private_key
|
||||||
|
else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
*fake_public_key = public_key;
|
||||||
|
}
|
||||||
|
PublicKey::EcdsaSha2NistP256 { public_key } => {
|
||||||
|
let PrivateKey::EcdsaSha2NistP256 {
|
||||||
|
public_key: fake_public_key,
|
||||||
|
..
|
||||||
|
} = &mut fake_private_key.private_key
|
||||||
|
else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
*fake_public_key = public_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fake_private_key = fake_private_key.encrypt(KeyEncryptionParams::plaintext())?;
|
||||||
|
|
||||||
|
println!("{}", fake_private_key.to_bytes_armored());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Subcommand::Info {
|
Subcommand::Info {
|
||||||
id_file,
|
id_file,
|
||||||
decrypt,
|
decrypt,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use eyre::{bail, eyre, Context, Result};
|
||||||
use rustix::fs::MemfdFlags;
|
use rustix::fs::MemfdFlags;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tracing::{ error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use base64::Engine;
|
|
||||||
|
|
||||||
use crate::public::{PublicKey, PublicKeyWithComment};
|
use crate::public::{PublicKey, PublicKeyWithComment};
|
||||||
|
|
||||||
pub struct AuthorizedKeys {
|
pub struct AuthorizedKeys {
|
||||||
|
|
@ -16,33 +14,10 @@ impl AuthorizedKeys {
|
||||||
let mut keys: Vec<PublicKeyWithComment> = Vec::new();
|
let mut keys: Vec<PublicKeyWithComment> = Vec::new();
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
let mut parts = line.split_whitespace();
|
let key = line
|
||||||
let alg = parts
|
.parse::<PublicKeyWithComment>()
|
||||||
.next()
|
.map_err(|err| Error(err.0))?;
|
||||||
.ok_or_else(|| Error("missing algorithm on line".to_owned()))?;
|
keys.push(key);
|
||||||
let key_blob = parts
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| Error("missing key on line".to_owned()))?;
|
|
||||||
let key_blob = base64::prelude::BASE64_STANDARD
|
|
||||||
.decode(key_blob)
|
|
||||||
.map_err(|err| Error(format!("invalid base64 encoding for key: {err}")))?;
|
|
||||||
let comment = parts.next().unwrap_or_default();
|
|
||||||
|
|
||||||
let public_key = PublicKey::from_wire_encoding(&key_blob)
|
|
||||||
.map_err(|err| Error(format!("unsupported key: {err}")))?;
|
|
||||||
|
|
||||||
if public_key.algorithm_name() != alg {
|
|
||||||
return Err(Error(format!(
|
|
||||||
"algorithm name mismatch: {} != {}",
|
|
||||||
public_key.algorithm_name(),
|
|
||||||
alg
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.push(PublicKeyWithComment {
|
|
||||||
key: public_key,
|
|
||||||
comment: comment.to_owned(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { keys })
|
Ok(Self { keys })
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,6 @@ impl EncryptedPrivateKeys {
|
||||||
p.array(*MAGIC);
|
p.array(*MAGIC);
|
||||||
p.string(self.cipher.name().as_bytes());
|
p.string(self.cipher.name().as_bytes());
|
||||||
p.string(self.kdf.name().as_bytes());
|
p.string(self.kdf.name().as_bytes());
|
||||||
dbg!(self.kdf.options());
|
|
||||||
p.string(self.kdf.options());
|
p.string(self.kdf.options());
|
||||||
|
|
||||||
p.u32(self.public_keys.len() as u32);
|
p.u32(self.public_keys.len() as u32);
|
||||||
|
|
@ -302,9 +301,13 @@ impl KeyEncryptionParams {
|
||||||
impl PlaintextPrivateKey {
|
impl PlaintextPrivateKey {
|
||||||
pub fn generate(comment: String, params: KeyGenerationParams) -> Self {
|
pub fn generate(comment: String, params: KeyGenerationParams) -> Self {
|
||||||
let keytype = crypto::generate_private_key(params);
|
let keytype = crypto::generate_private_key(params);
|
||||||
|
Self::new(comment, keytype)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(comment: String, private_key: PrivateKey) -> Self {
|
||||||
Self {
|
Self {
|
||||||
comment,
|
comment,
|
||||||
private_key: keytype,
|
private_key,
|
||||||
checkint: rand::random(),
|
checkint: rand::random(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
// <https://datatracker.ietf.org/doc/html/rfc4716> exists but is kinda weird
|
// <https://datatracker.ietf.org/doc/html/rfc4716> exists but is kinda weird
|
||||||
|
|
||||||
use std::fmt::{Debug, Display};
|
use std::{
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
|
||||||
|
|
@ -26,6 +29,39 @@ pub struct PublicKeyWithComment {
|
||||||
pub comment: String,
|
pub comment: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for PublicKeyWithComment {
|
||||||
|
type Err = ParseError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut parts = s.split_ascii_whitespace();
|
||||||
|
let alg = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| ParseError("missing algorithm on line".to_owned()))?;
|
||||||
|
let key_blob = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| ParseError("missing key on line".to_owned()))?;
|
||||||
|
let key_blob = base64::prelude::BASE64_STANDARD
|
||||||
|
.decode(key_blob)
|
||||||
|
.map_err(|err| ParseError(format!("invalid base64 encoding for key: {err}")))?;
|
||||||
|
let comment = parts.next().unwrap_or_default();
|
||||||
|
|
||||||
|
let public_key = PublicKey::from_wire_encoding(&key_blob)
|
||||||
|
.map_err(|err| ParseError(format!("unsupported key: {err}")))?;
|
||||||
|
|
||||||
|
if public_key.algorithm_name() != alg {
|
||||||
|
return Err(ParseError(format!(
|
||||||
|
"algorithm name mismatch: {} != {}",
|
||||||
|
public_key.algorithm_name(),
|
||||||
|
alg
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
key: public_key,
|
||||||
|
comment: comment.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PublicKey {
|
impl PublicKey {
|
||||||
/// Parses an SSH public key from its wire encoding as specified in
|
/// Parses an SSH public key from its wire encoding as specified in
|
||||||
/// RFC4253, RFC5656, and RFC8709.
|
/// RFC4253, RFC5656, and RFC8709.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue