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 {
/// Blocking!
pub async fn for_user_and_key(
user: String,
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};
@ -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());

View file

@ -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

View file

@ -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");
}
}

View file

@ -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;