This commit is contained in:
nora 2024-08-28 00:11:12 +02:00
parent bb55a1c334
commit 46f77b7f58
5 changed files with 177 additions and 65 deletions

View file

@ -27,7 +27,6 @@ pub enum AuthError {
} }
impl UserPublicKey { impl UserPublicKey {
/// Blocking!
pub async fn for_user_and_key( pub async fn for_user_and_key(
user: String, user: String,
provided_key: &PublicKey, provided_key: &PublicKey,

View file

@ -1,4 +1,4 @@
use cluelessh_format::Writer; use cluelessh_format::{ParseError, Reader, Writer};
use crate::{private::PrivateKey, public::PublicKey}; use crate::{private::PrivateKey, public::PublicKey};
@ -24,6 +24,49 @@ pub enum Signature {
} }
impl 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> { pub fn to_wire_encoding(&self) -> Vec<u8> {
let mut data = Writer::new(); let mut data = Writer::new();
data.string(self.algorithm_name()); data.string(self.algorithm_name());

View file

@ -19,6 +19,8 @@ pub struct ClientConnection {
plaintext_packets: VecDeque<Packet>, plaintext_packets: VecDeque<Packet>,
supported_algorithms: SupportedAlgorithms,
pub abort_for_dos: bool, pub abort_for_dos: bool,
} }
@ -70,7 +72,7 @@ impl ClientConnection {
}, },
packet_transport, packet_transport,
rng: Box::new(rng), rng: Box::new(rng),
supported_algorithms: SupportedAlgorithms::secure(&[]),
plaintext_packets: VecDeque::new(), plaintext_packets: VecDeque::new(),
abort_for_dos: false, abort_for_dos: false,
} }
@ -160,43 +162,44 @@ impl ClientConnection {
let _cookie = kexinit.array::<16>()?; let _cookie = kexinit.array::<16>()?;
let kex_algorithm = kexinit.name_list()?; 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"); debug!(name = %kex_algorithm.name(), "Using KEX algorithm");
let server_hostkey_algorithm = kexinit.name_list()?; let server_hostkey_algorithm = kexinit.name_list()?;
let server_hostkey_algorithm = let server_hostkey_algorithm = sup_algs
sup_algs.hostkey_verify.find(server_hostkey_algorithm.0)?; .hostkey_verify
.find(true, server_hostkey_algorithm.0)?;
debug!(name = %server_hostkey_algorithm.name(), "Using host key algorithm"); debug!(name = %server_hostkey_algorithm.name(), "Using host key algorithm");
let encryption_algorithms_client_to_server = kexinit.name_list()?; let encryption_algorithms_client_to_server = kexinit.name_list()?;
let encryption_client_to_server = sup_algs let encryption_client_to_server = sup_algs
.encryption_to_peer .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"); debug!(name = %encryption_client_to_server.name(), "Using encryption algorithm C->S");
let encryption_algorithms_server_to_client = kexinit.name_list()?; let encryption_algorithms_server_to_client = kexinit.name_list()?;
let encryption_server_to_client = sup_algs let encryption_server_to_client = sup_algs
.encryption_from_peer .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"); debug!(name = %encryption_server_to_client.name(), "Using encryption algorithm S->C");
let mac_algorithms_client_to_server = kexinit.name_list()?; let mac_algorithms_client_to_server = kexinit.name_list()?;
let _mac_client_to_server = sup_algs let _mac_client_to_server = sup_algs
.mac_to_peer .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_algorithms_server_to_client = kexinit.name_list()?;
let _mac_server_to_client = sup_algs let _mac_server_to_client = sup_algs
.mac_from_peer .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_algorithms_client_to_server = kexinit.name_list()?;
let _compression_client_to_server = sup_algs let _compression_client_to_server = sup_algs
.compression_to_peer .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_algorithms_server_to_client = kexinit.name_list()?;
let _compression_server_to_client = sup_algs let _compression_server_to_client = sup_algs
.compression_from_peer .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_client_to_server = kexinit.name_list()?;
let _languages_server_to_client = kexinit.name_list()?; let _languages_server_to_client = kexinit.name_list()?;
@ -361,18 +364,16 @@ impl ClientConnection {
let mut kexinit = Writer::new(); let mut kexinit = Writer::new();
kexinit.u8(numbers::SSH_MSG_KEXINIT); kexinit.u8(numbers::SSH_MSG_KEXINIT);
kexinit.array(cookie); 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 let algs = &self.supported_algorithms;
kexinit.name_list(NameList::multi( kexinit.name_list(NameList::multi(&algs.key_exchange.to_name_list())); // kex_algorithms
"chacha20-poly1305@openssh.com,aes256-gcm@openssh.com", kexinit.name_list(NameList::multi(&algs.hostkey_verify.to_name_list())); // server_host_key_algorithms
)); // encryption_algorithms_client_to_server kexinit.name_list(NameList::multi(&algs.encryption_to_peer.to_name_list())); // encryption_algorithms_client_to_server
kexinit.name_list(NameList::multi( kexinit.name_list(NameList::multi(&algs.encryption_from_peer.to_name_list())); // encryption_algorithms_server_to_client
"chacha20-poly1305@openssh.com,aes256-gcm@openssh.com", kexinit.name_list(NameList::multi(&algs.mac_to_peer.to_name_list())); // mac_algorithms_client_to_server
)); // encryption_algorithms_server_to_client kexinit.name_list(NameList::multi(&algs.mac_from_peer.to_name_list())); // mac_algorithms_server_to_client
kexinit.name_list(NameList::one("hmac-sha2-256")); // mac_algorithms_client_to_server kexinit.name_list(NameList::multi(&algs.compression_to_peer.to_name_list())); // compression_algorithms_client_to_server
kexinit.name_list(NameList::one("hmac-sha2-256")); // mac_algorithms_server_to_client kexinit.name_list(NameList::multi(&algs.compression_from_peer.to_name_list())); // compression_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
kexinit.name_list(NameList::none()); // languages_client_to_server kexinit.name_list(NameList::none()); // languages_client_to_server
kexinit.name_list(NameList::none()); // languages_server_to_client kexinit.name_list(NameList::none()); // languages_server_to_client
kexinit.bool(false); // first_kex_packet_follows kexinit.bool(false); // first_kex_packet_follows

View file

@ -1,11 +1,11 @@
pub mod encrypt; pub mod encrypt;
use cluelessh_format::Reader;
use cluelessh_keys::{ use cluelessh_keys::{
private::{PlaintextPrivateKey, PrivateKey}, private::{PlaintextPrivateKey, PrivateKey},
public::PublicKey, public::PublicKey,
signature::Signature, signature::Signature,
}; };
use p256::ecdsa::signature::Verifier;
use sha2::Digest; use sha2::Digest;
use crate::{ use crate::{
@ -148,51 +148,81 @@ impl AlgorithmName for HostKeyVerifyAlgorithm {
const HOSTKEY_VERIFY_ED25519: HostKeyVerifyAlgorithm = HostKeyVerifyAlgorithm { const HOSTKEY_VERIFY_ED25519: HostKeyVerifyAlgorithm = HostKeyVerifyAlgorithm {
name: "ssh-ed25519", name: "ssh-ed25519",
verify: |public_key, message, signature| { verify: |public_key, message, signature| {
// Parse out public key let public_key = PublicKey::from_wire_encoding(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)
.map_err(|err| peer_error!("incorrect public host key: {err}"))?; .map_err(|err| peer_error!("incorrect public host key: {err}"))?;
let PublicKey::Ed25519 { public_key } = public_key else {
// Parse out signature return Err(peer_error!("incorrect algorithm public host key"));
let mut signature = Reader::new(&signature.0); };
let alg = signature.string()?;
if alg != b"ssh-ed25519" { let signature = Signature::from_wire_encoding(&signature.0)
return Err(peer_error!("incorrect algorithm for signature")); .map_err(|err| peer_error!("incorrect signature: {err}"))?;
} let Signature::Ed25519 { signature } = signature else {
let signature = signature.string()?; return Err(peer_error!("incorrect algorithm for signature"));
let Ok(signature) = signature.try_into() else {
return Err(peer_error!("incorrect length for signature"));
}; };
let signature = ed25519_dalek::Signature::from_bytes(signature);
// Verify
public_key public_key
.verify_strict(message, &signature) .verify_strict(message, &signature)
.map_err(|err| peer_error!("incorrect signature: {err}")) .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 struct AlgorithmNegotiation<T> {
pub supported: Vec<T>, pub supported: Vec<T>,
} }
impl<T: AlgorithmName> AlgorithmNegotiation<T> { impl<T: AlgorithmName> AlgorithmNegotiation<T> {
pub fn find(mut self, peer_supports: &str) -> Result<T> { pub fn to_name_list(&self) -> String {
for client_alg in peer_supports.split(',') { self.supported
if let Some(alg) = self .iter()
.supported .map(|alg| alg.name())
.iter() .collect::<Vec<&str>>()
.position(|alg| alg.name() == client_alg) .join(",")
{ }
return Ok(self.supported.remove(alg));
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, supported: supported_host_keys,
}, },
hostkey_verify: AlgorithmNegotiation { hostkey_verify: AlgorithmNegotiation {
supported: vec![HOSTKEY_VERIFY_ED25519], // TODO: p256 supported: vec![HOSTKEY_VERIFY_ECDSA_SHA2_NISTP256, HOSTKEY_VERIFY_ED25519],
}, },
encryption_to_peer: AlgorithmNegotiation { encryption_to_peer: AlgorithmNegotiation {
supported: vec![encrypt::CHACHA20POLY1305, encrypt::AES256_GCM], supported: vec![encrypt::CHACHA20POLY1305, encrypt::AES256_GCM],
@ -491,3 +521,41 @@ pub fn key_exchange_hash(
let hash = hash.finalize(); let hash = hash.finalize();
hash.into() 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");
}
}

View file

@ -13,6 +13,7 @@ use cluelessh_format::{NameList, Reader, Writer};
use tracing::{debug, info, trace}; use tracing::{debug, info, trace};
// This is definitely who we are. // 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 const SERVER_IDENTIFICATION: &[u8] = b"SSH-2.0-OpenSSH_9.7\r\n";
pub struct ServerConnection { pub struct ServerConnection {
@ -142,12 +143,12 @@ impl ServerConnection {
let sup_algs = SupportedAlgorithms::secure(&self.config.host_keys); 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"); debug!(name = %kex_algorithm.name(), "Using KEX algorithm");
let server_host_key_algorithm = sup_algs let server_host_key_algorithm = sup_algs
.hostkey_sign .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"); debug!(name = %server_host_key_algorithm.name(), "Using host key algorithm");
// TODO: Implement aes128-ctr // TODO: Implement aes128-ctr
@ -155,27 +156,27 @@ impl ServerConnection {
let encryption_client_to_server = sup_algs let encryption_client_to_server = sup_algs
.encryption_from_peer .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"); debug!(name = %encryption_client_to_server.name(), "Using encryption algorithm C->S");
let encryption_server_to_client = sup_algs let encryption_server_to_client = sup_algs
.encryption_to_peer .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"); debug!(name = %encryption_server_to_client.name(), "Using encryption algorithm S->C");
let mac_algorithm_client_to_server = sup_algs let mac_algorithm_client_to_server = sup_algs
.mac_from_peer .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 let mac_algorithm_server_to_client = sup_algs
.mac_to_peer .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 let compression_algorithm_client_to_server = sup_algs
.compression_from_peer .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 let compression_algorithm_server_to_client = sup_algs
.compression_to_peer .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_client_to_server;
let _ = kex.languages_server_to_client; let _ = kex.languages_server_to_client;