mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
stuff
This commit is contained in:
parent
bb55a1c334
commit
46f77b7f58
5 changed files with 177 additions and 65 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use cluelessh_format::Writer;
|
||||
use cluelessh_format::{ParseError, Reader, Writer};
|
||||
|
||||
use crate::{private::PrivateKey, public::PublicKey};
|
||||
|
||||
|
|
@ -24,6 +24,49 @@ pub enum Signature {
|
|||
}
|
||||
|
||||
impl Signature {
|
||||
pub fn from_wire_encoding(data: &[u8]) -> Result<Self, ParseError> {
|
||||
let mut sig = Reader::new(data);
|
||||
|
||||
let algorithm_name = sig.utf8_string()?;
|
||||
|
||||
let signature = match algorithm_name {
|
||||
"ssh-ed25519" => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc8709#name-signature-format>
|
||||
let signature = sig
|
||||
.string()?
|
||||
.try_into()
|
||||
.map_err(|_| ParseError(format!("invalid signature length")))?;
|
||||
Self::Ed25519 {
|
||||
signature: ed25519_dalek::Signature::from_bytes(&signature),
|
||||
}
|
||||
}
|
||||
"ecdsa-sha2-nistp256" => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc5656#section-3.1.2>
|
||||
let r: [u8; 32] = sig
|
||||
.mpint()?
|
||||
.try_into()
|
||||
.map_err(|_| ParseError(format!("invalid r scaler byte length")))?;
|
||||
let s: [u8; 32] = sig
|
||||
.mpint()?
|
||||
.try_into()
|
||||
.map_err(|_| ParseError(format!("invalid s scaler byte length")))?;
|
||||
|
||||
// TODO: fix stupid length
|
||||
|
||||
let signature = p256::ecdsa::Signature::from_scalars(r, s)
|
||||
.map_err(|_| ParseError(format!("invalid signature")))?;
|
||||
|
||||
Self::EcdsaSha2NistP256 { signature }
|
||||
}
|
||||
_ => {
|
||||
return Err(ParseError(format!(
|
||||
"unsupported signature algorithm: {algorithm_name}"
|
||||
)))
|
||||
}
|
||||
};
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn to_wire_encoding(&self) -> Vec<u8> {
|
||||
let mut data = Writer::new();
|
||||
data.string(self.algorithm_name());
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ pub struct ClientConnection {
|
|||
|
||||
plaintext_packets: VecDeque<Packet>,
|
||||
|
||||
supported_algorithms: SupportedAlgorithms,
|
||||
|
||||
pub abort_for_dos: bool,
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ impl ClientConnection {
|
|||
},
|
||||
packet_transport,
|
||||
rng: Box::new(rng),
|
||||
|
||||
supported_algorithms: SupportedAlgorithms::secure(&[]),
|
||||
plaintext_packets: VecDeque::new(),
|
||||
abort_for_dos: false,
|
||||
}
|
||||
|
|
@ -160,43 +162,44 @@ impl ClientConnection {
|
|||
let _cookie = kexinit.array::<16>()?;
|
||||
|
||||
let kex_algorithm = kexinit.name_list()?;
|
||||
let kex_algorithm = sup_algs.key_exchange.find(kex_algorithm.0)?;
|
||||
let kex_algorithm = sup_algs.key_exchange.find(true, kex_algorithm.0)?;
|
||||
debug!(name = %kex_algorithm.name(), "Using KEX algorithm");
|
||||
|
||||
let server_hostkey_algorithm = kexinit.name_list()?;
|
||||
let server_hostkey_algorithm =
|
||||
sup_algs.hostkey_verify.find(server_hostkey_algorithm.0)?;
|
||||
let server_hostkey_algorithm = sup_algs
|
||||
.hostkey_verify
|
||||
.find(true, server_hostkey_algorithm.0)?;
|
||||
debug!(name = %server_hostkey_algorithm.name(), "Using host key algorithm");
|
||||
|
||||
let encryption_algorithms_client_to_server = kexinit.name_list()?;
|
||||
let encryption_client_to_server = sup_algs
|
||||
.encryption_to_peer
|
||||
.find(encryption_algorithms_client_to_server.0)?;
|
||||
.find(true, encryption_algorithms_client_to_server.0)?;
|
||||
debug!(name = %encryption_client_to_server.name(), "Using encryption algorithm C->S");
|
||||
|
||||
let encryption_algorithms_server_to_client = kexinit.name_list()?;
|
||||
let encryption_server_to_client = sup_algs
|
||||
.encryption_from_peer
|
||||
.find(encryption_algorithms_server_to_client.0)?;
|
||||
.find(true, encryption_algorithms_server_to_client.0)?;
|
||||
debug!(name = %encryption_server_to_client.name(), "Using encryption algorithm S->C");
|
||||
|
||||
let mac_algorithms_client_to_server = kexinit.name_list()?;
|
||||
let _mac_client_to_server = sup_algs
|
||||
.mac_to_peer
|
||||
.find(mac_algorithms_client_to_server.0)?;
|
||||
.find(true, mac_algorithms_client_to_server.0)?;
|
||||
let mac_algorithms_server_to_client = kexinit.name_list()?;
|
||||
let _mac_server_to_client = sup_algs
|
||||
.mac_from_peer
|
||||
.find(mac_algorithms_server_to_client.0)?;
|
||||
.find(true, mac_algorithms_server_to_client.0)?;
|
||||
|
||||
let compression_algorithms_client_to_server = kexinit.name_list()?;
|
||||
let _compression_client_to_server = sup_algs
|
||||
.compression_to_peer
|
||||
.find(compression_algorithms_client_to_server.0)?;
|
||||
.find(true, compression_algorithms_client_to_server.0)?;
|
||||
let compression_algorithms_server_to_client = kexinit.name_list()?;
|
||||
let _compression_server_to_client = sup_algs
|
||||
.compression_from_peer
|
||||
.find(compression_algorithms_server_to_client.0)?;
|
||||
.find(true, compression_algorithms_server_to_client.0)?;
|
||||
|
||||
let _languages_client_to_server = kexinit.name_list()?;
|
||||
let _languages_server_to_client = kexinit.name_list()?;
|
||||
|
|
@ -361,18 +364,16 @@ impl ClientConnection {
|
|||
let mut kexinit = Writer::new();
|
||||
kexinit.u8(numbers::SSH_MSG_KEXINIT);
|
||||
kexinit.array(cookie);
|
||||
kexinit.name_list(NameList::multi("curve25519-sha256,ecdh-sha2-nistp256")); // kex_algorithms
|
||||
kexinit.name_list(NameList::multi("ssh-ed25519,ecdsa-sha2-nistp256")); // server_host_key_algorithms
|
||||
kexinit.name_list(NameList::multi(
|
||||
"chacha20-poly1305@openssh.com,aes256-gcm@openssh.com",
|
||||
)); // encryption_algorithms_client_to_server
|
||||
kexinit.name_list(NameList::multi(
|
||||
"chacha20-poly1305@openssh.com,aes256-gcm@openssh.com",
|
||||
)); // encryption_algorithms_server_to_client
|
||||
kexinit.name_list(NameList::one("hmac-sha2-256")); // mac_algorithms_client_to_server
|
||||
kexinit.name_list(NameList::one("hmac-sha2-256")); // mac_algorithms_server_to_client
|
||||
kexinit.name_list(NameList::one("none")); // compression_algorithms_client_to_server
|
||||
kexinit.name_list(NameList::one("none")); // compression_algorithms_server_to_client
|
||||
|
||||
let algs = &self.supported_algorithms;
|
||||
kexinit.name_list(NameList::multi(&algs.key_exchange.to_name_list())); // kex_algorithms
|
||||
kexinit.name_list(NameList::multi(&algs.hostkey_verify.to_name_list())); // server_host_key_algorithms
|
||||
kexinit.name_list(NameList::multi(&algs.encryption_to_peer.to_name_list())); // encryption_algorithms_client_to_server
|
||||
kexinit.name_list(NameList::multi(&algs.encryption_from_peer.to_name_list())); // encryption_algorithms_server_to_client
|
||||
kexinit.name_list(NameList::multi(&algs.mac_to_peer.to_name_list())); // mac_algorithms_client_to_server
|
||||
kexinit.name_list(NameList::multi(&algs.mac_from_peer.to_name_list())); // mac_algorithms_server_to_client
|
||||
kexinit.name_list(NameList::multi(&algs.compression_to_peer.to_name_list())); // compression_algorithms_client_to_server
|
||||
kexinit.name_list(NameList::multi(&algs.compression_from_peer.to_name_list())); // compression_algorithms_server_to_client
|
||||
kexinit.name_list(NameList::none()); // languages_client_to_server
|
||||
kexinit.name_list(NameList::none()); // languages_server_to_client
|
||||
kexinit.bool(false); // first_kex_packet_follows
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
pub mod encrypt;
|
||||
|
||||
use cluelessh_format::Reader;
|
||||
use cluelessh_keys::{
|
||||
private::{PlaintextPrivateKey, PrivateKey},
|
||||
public::PublicKey,
|
||||
signature::Signature,
|
||||
};
|
||||
use p256::ecdsa::signature::Verifier;
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -148,51 +148,81 @@ impl AlgorithmName for HostKeyVerifyAlgorithm {
|
|||
const HOSTKEY_VERIFY_ED25519: HostKeyVerifyAlgorithm = HostKeyVerifyAlgorithm {
|
||||
name: "ssh-ed25519",
|
||||
verify: |public_key, message, signature| {
|
||||
// Parse out public key
|
||||
let mut public_key = Reader::new(public_key);
|
||||
let public_key_alg = public_key.string()?;
|
||||
if public_key_alg != b"ssh-ed25519" {
|
||||
return Err(peer_error!("incorrect algorithm public host key"));
|
||||
}
|
||||
let public_key = public_key.string()?;
|
||||
let Ok(public_key) = public_key.try_into() else {
|
||||
return Err(peer_error!("incorrect length for public host key"));
|
||||
};
|
||||
let public_key = ed25519_dalek::VerifyingKey::from_bytes(public_key)
|
||||
let public_key = PublicKey::from_wire_encoding(public_key)
|
||||
.map_err(|err| peer_error!("incorrect public host key: {err}"))?;
|
||||
|
||||
// Parse out signature
|
||||
let mut signature = Reader::new(&signature.0);
|
||||
let alg = signature.string()?;
|
||||
if alg != b"ssh-ed25519" {
|
||||
return Err(peer_error!("incorrect algorithm for signature"));
|
||||
}
|
||||
let signature = signature.string()?;
|
||||
let Ok(signature) = signature.try_into() else {
|
||||
return Err(peer_error!("incorrect length for signature"));
|
||||
let PublicKey::Ed25519 { public_key } = public_key else {
|
||||
return Err(peer_error!("incorrect algorithm public host key"));
|
||||
};
|
||||
|
||||
let signature = Signature::from_wire_encoding(&signature.0)
|
||||
.map_err(|err| peer_error!("incorrect signature: {err}"))?;
|
||||
let Signature::Ed25519 { signature } = signature else {
|
||||
return Err(peer_error!("incorrect algorithm for signature"));
|
||||
};
|
||||
let signature = ed25519_dalek::Signature::from_bytes(signature);
|
||||
|
||||
// Verify
|
||||
public_key
|
||||
.verify_strict(message, &signature)
|
||||
.map_err(|err| peer_error!("incorrect signature: {err}"))
|
||||
},
|
||||
};
|
||||
const HOSTKEY_VERIFY_ECDSA_SHA2_NISTP256: HostKeyVerifyAlgorithm = HostKeyVerifyAlgorithm {
|
||||
name: "ecdsa-sha2-nistp256",
|
||||
verify: |public_key, message, signature| {
|
||||
let public_key = PublicKey::from_wire_encoding(public_key)
|
||||
.map_err(|err| peer_error!("incorrect public host key: {err}"))?;
|
||||
|
||||
dbg!(&public_key);
|
||||
let PublicKey::EcdsaSha2NistP256 { public_key } = public_key else {
|
||||
return Err(peer_error!("incorrect algorithm for public host key"));
|
||||
};
|
||||
|
||||
let signature = Signature::from_wire_encoding(&signature.0)
|
||||
.map_err(|err| peer_error!("incorrect signature: {err}"))?;
|
||||
let Signature::EcdsaSha2NistP256 { signature } = signature else {
|
||||
return Err(peer_error!("incorrect algorithm for signature"));
|
||||
};
|
||||
|
||||
public_key
|
||||
.verify(message, &signature)
|
||||
.map_err(|err| peer_error!("incorrect signature: {err}"))
|
||||
},
|
||||
};
|
||||
pub struct AlgorithmNegotiation<T> {
|
||||
pub supported: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: AlgorithmName> AlgorithmNegotiation<T> {
|
||||
pub fn find(mut self, peer_supports: &str) -> Result<T> {
|
||||
for client_alg in peer_supports.split(',') {
|
||||
if let Some(alg) = self
|
||||
.supported
|
||||
.iter()
|
||||
.position(|alg| alg.name() == client_alg)
|
||||
{
|
||||
return Ok(self.supported.remove(alg));
|
||||
pub fn to_name_list(&self) -> String {
|
||||
self.supported
|
||||
.iter()
|
||||
.map(|alg| alg.name())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(",")
|
||||
}
|
||||
|
||||
pub fn find(mut self, this_is_client: bool, peer_supports: &str) -> Result<T> {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.1>
|
||||
// We let the client guide the algorithm search.
|
||||
|
||||
let my_algs = self
|
||||
.supported
|
||||
.iter()
|
||||
.map(|alg| alg.name())
|
||||
.collect::<Vec<_>>();
|
||||
let peer_algs = peer_supports.split(',').collect();
|
||||
|
||||
let (client_algs, server_algs) = if this_is_client {
|
||||
(my_algs, peer_algs)
|
||||
} else {
|
||||
(peer_algs, my_algs)
|
||||
};
|
||||
|
||||
for alg_name in client_algs {
|
||||
if server_algs.iter().any(|peer| *peer == alg_name) {
|
||||
// Algorithm is supported
|
||||
if let Some(alg) = self.supported.iter().position(|alg| alg.name() == alg_name) {
|
||||
return Ok(self.supported.remove(alg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +267,7 @@ impl SupportedAlgorithms {
|
|||
supported: supported_host_keys,
|
||||
},
|
||||
hostkey_verify: AlgorithmNegotiation {
|
||||
supported: vec![HOSTKEY_VERIFY_ED25519], // TODO: p256
|
||||
supported: vec![HOSTKEY_VERIFY_ECDSA_SHA2_NISTP256, HOSTKEY_VERIFY_ED25519],
|
||||
},
|
||||
encryption_to_peer: AlgorithmNegotiation {
|
||||
supported: vec![encrypt::CHACHA20POLY1305, encrypt::AES256_GCM],
|
||||
|
|
@ -491,3 +521,41 @@ pub fn key_exchange_hash(
|
|||
let hash = hash.finalize();
|
||||
hash.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AlgorithmNegotiation;
|
||||
|
||||
#[test]
|
||||
fn alg_negotation() {
|
||||
let server_algs = [
|
||||
"ssh-ed25519",
|
||||
"ecdsa-sha2-nistp256",
|
||||
"rsa-sha2-512,rsa-sha2-256",
|
||||
];
|
||||
let client_algs = ["ssh-ed25519", "ecdsa-sha2-nistp256"];
|
||||
|
||||
let we_are_client_negotiation = AlgorithmNegotiation {
|
||||
supported: client_algs.to_vec(),
|
||||
};
|
||||
|
||||
let chosen = we_are_client_negotiation
|
||||
.find(
|
||||
true,
|
||||
&server_algs.iter().copied().collect::<Vec<&str>>().join(","),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(chosen, "ssh-ed25519");
|
||||
|
||||
let we_are_server_negotiation = AlgorithmNegotiation {
|
||||
supported: server_algs.to_vec(),
|
||||
};
|
||||
let chosen = we_are_server_negotiation
|
||||
.find(
|
||||
true,
|
||||
&client_algs.iter().copied().collect::<Vec<&str>>().join(","),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(chosen, "ssh-ed25519");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use cluelessh_format::{NameList, Reader, Writer};
|
|||
use tracing::{debug, info, trace};
|
||||
|
||||
// This is definitely who we are.
|
||||
// TODO: dont make cluelesshd do this
|
||||
pub const SERVER_IDENTIFICATION: &[u8] = b"SSH-2.0-OpenSSH_9.7\r\n";
|
||||
|
||||
pub struct ServerConnection {
|
||||
|
|
@ -142,12 +143,12 @@ impl ServerConnection {
|
|||
|
||||
let sup_algs = SupportedAlgorithms::secure(&self.config.host_keys);
|
||||
|
||||
let kex_algorithm = sup_algs.key_exchange.find(kex.kex_algorithms.0)?;
|
||||
let kex_algorithm = sup_algs.key_exchange.find(false, kex.kex_algorithms.0)?;
|
||||
debug!(name = %kex_algorithm.name(), "Using KEX algorithm");
|
||||
|
||||
let server_host_key_algorithm = sup_algs
|
||||
.hostkey_sign
|
||||
.find(kex.server_host_key_algorithms.0)?;
|
||||
.find(false, kex.server_host_key_algorithms.0)?;
|
||||
debug!(name = %server_host_key_algorithm.name(), "Using host key algorithm");
|
||||
|
||||
// TODO: Implement aes128-ctr
|
||||
|
|
@ -155,27 +156,27 @@ impl ServerConnection {
|
|||
|
||||
let encryption_client_to_server = sup_algs
|
||||
.encryption_from_peer
|
||||
.find(kex.encryption_algorithms_client_to_server.0)?;
|
||||
.find(false, kex.encryption_algorithms_client_to_server.0)?;
|
||||
debug!(name = %encryption_client_to_server.name(), "Using encryption algorithm C->S");
|
||||
|
||||
let encryption_server_to_client = sup_algs
|
||||
.encryption_to_peer
|
||||
.find(kex.encryption_algorithms_server_to_client.0)?;
|
||||
.find(false, kex.encryption_algorithms_server_to_client.0)?;
|
||||
debug!(name = %encryption_server_to_client.name(), "Using encryption algorithm S->C");
|
||||
|
||||
let mac_algorithm_client_to_server = sup_algs
|
||||
.mac_from_peer
|
||||
.find(kex.mac_algorithms_client_to_server.0)?;
|
||||
.find(false, kex.mac_algorithms_client_to_server.0)?;
|
||||
let mac_algorithm_server_to_client = sup_algs
|
||||
.mac_to_peer
|
||||
.find(kex.mac_algorithms_server_to_client.0)?;
|
||||
.find(false, kex.mac_algorithms_server_to_client.0)?;
|
||||
|
||||
let compression_algorithm_client_to_server = sup_algs
|
||||
.compression_from_peer
|
||||
.find(kex.compression_algorithms_client_to_server.0)?;
|
||||
.find(false, kex.compression_algorithms_client_to_server.0)?;
|
||||
let compression_algorithm_server_to_client = sup_algs
|
||||
.compression_to_peer
|
||||
.find(kex.compression_algorithms_server_to_client.0)?;
|
||||
.find(false, kex.compression_algorithms_server_to_client.0)?;
|
||||
|
||||
let _ = kex.languages_client_to_server;
|
||||
let _ = kex.languages_server_to_client;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue