From 660fa8e10efa22733f9cdde4ab49ff803cc55d84 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:25:49 +0200 Subject: [PATCH] kex works --- .gitignore | 1 + src/lib.rs | 78 +++++++++++++++++++++++++++++----------- src/parse.rs | 2 ++ test-openssh/default.nix | 19 ++++++++++ 4 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 test-openssh/default.nix diff --git a/.gitignore b/.gitignore index ea8c4bf..a17b226 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/test-openssh/result diff --git a/src/lib.rs b/src/lib.rs index 15c66a8..3af5eeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,10 +25,11 @@ macro_rules! client_error { }; } use core::str; -use std::mem::take; +use std::{io::Write, mem::take}; use client_error; use ed25519_dalek::ed25519::signature::Signer; +use eyre::Context; use parse::{MpInt, NameList, Parser, Writer}; use sha2::Digest; use x25519_dalek::{EphemeralSecret, PublicKey}; @@ -194,40 +195,77 @@ impl ServerConnection { let dh = DhKeyExchangeInitPacket::parse(&data.payload)?; let secret = EphemeralSecret::random_from_rng(rand::thread_rng()); - let f = PublicKey::from(&secret); + let server_public_key = PublicKey::from(&secret); // f - let k = secret.diffie_hellman(&dh.e.to_x25519_public_key()?); + let client_public_key = dh.e; // e - let ssh_pubkey = SshPublicKey { + let shared_secret = + secret.diffie_hellman(&client_public_key.to_x25519_public_key()?); // k + + let pub_hostkey = SshPublicKey { format: b"ssh-ed25519", - data: PUBKEY_BYTES, + data: PUB_HOSTKEY_BYTES, }; + let dump = std::fs::File::create("debug.bin").wrap_err("opening debug.bin")?; let mut hash = sha2::Sha256::new(); - let mut hash_string = |bytes: &[u8]| { - hash.update(u32::to_be_bytes(bytes.len() as u32)); + let add_hash = |hash: &mut sha2::Sha256, bytes: &[u8]| { hash.update(bytes); + (&dump).write_all(bytes).unwrap(); }; - hash_string(&client_identification[..(client_identification.len() - 2)]); // V_C - hash_string(&SERVER_IDENTIFICATION[..(SERVER_IDENTIFICATION.len() - 2)]); // V_S - hash_string(client_kexinit); // I_C - hash_string(server_kexinit); // I_S - hash_string(&ssh_pubkey.to_bytes()); // K_S - let mut hash_mpint = hash_string; - hash_mpint(&dh.e.0); // e - hash_mpint(f.as_bytes()); // f - hash_mpint(k.as_bytes()); // K + let hash_string = |hash: &mut sha2::Sha256, bytes: &[u8]| { + add_hash(hash, &u32::to_be_bytes(bytes.len() as u32)); + add_hash(hash, bytes); + }; + let hash_mpint = |hash: &mut sha2::Sha256, mut bytes: &[u8]| { + while bytes[0] == 0 { + bytes = &bytes[1..]; + } + // If the first high bit is set, pad it with a zero. + let pad_zero = (bytes[0] & 0b10000000) > 1; + add_hash( + hash, + &u32::to_be_bytes((bytes.len() + (pad_zero as usize)) as u32), + ); + if pad_zero { + add_hash(hash, &[0]); + } + add_hash(hash, bytes); + }; + + hash_string( + &mut hash, + &client_identification[..(client_identification.len() - 2)], + ); // V_C + hash_string( + &mut hash, + &SERVER_IDENTIFICATION[..(SERVER_IDENTIFICATION.len() - 2)], + ); // V_S + hash_string(&mut hash, client_kexinit); // I_C + hash_string(&mut hash, server_kexinit); // I_S + add_hash(&mut hash, &pub_hostkey.to_bytes()); // K_S + + // While the RFC says that e and f are mpints, we need to *NOT* treat them as mpints here. + // Neither RFC4253 nor RFC8709 mention this. + hash_string(&mut hash, &client_public_key.0); // e + hash_string(&mut hash, server_public_key.as_bytes()); // f + hash_mpint(&mut hash, shared_secret.as_bytes()); // K let hash = hash.finalize(); let host_priv_key = ed25519_dalek::SigningKey::from_bytes(PRIVKEY_BYTES); - assert_eq!(PUBKEY_BYTES, host_priv_key.verifying_key().as_bytes()); + assert_eq!(PUB_HOSTKEY_BYTES, host_priv_key.verifying_key().as_bytes()); let signature = host_priv_key.sign(&hash); + // eprintln!("client_public_key: {:x?}", client_public_key.0); + // eprintln!("server_public_key: {:x?}", server_public_key.as_bytes()); + // eprintln!("shared_secret: {:x?}", shared_secret.as_bytes()); + // eprintln!("hash: {:x?}", hash); + let packet = DhKeyExchangeInitReplyPacket { - pubkey: ssh_pubkey, - f: MpInt(f.as_bytes()), + pubkey: pub_hostkey, + f: MpInt(server_public_key.as_bytes()), signature: SshSignature { format: b"ssh-ed25519", data: &signature.to_bytes(), @@ -540,7 +578,7 @@ impl PacketParser { const _PUBKEY: &str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOk5zfpvwNc3MztTTpE90zLI1Ref4AwwRVdSFyJLGbj2 testkey"; /// Manually extracted, even worse, , help -const PUBKEY_BYTES: &[u8; 32] = &[ +const PUB_HOSTKEY_BYTES: &[u8; 32] = &[ 0xe9, 0x39, 0xcd, 0xfa, 0x6f, 0xc0, 0xd7, 0x37, 0x33, 0x3b, 0x53, 0x4e, 0x91, 0x3d, 0xd3, 0x32, 0xc8, 0xd5, 0x17, 0x9f, 0xe0, 0x0c, 0x30, 0x45, 0x57, 0x52, 0x17, 0x22, 0x4b, 0x19, 0xb8, 0xf6, ]; diff --git a/src/parse.rs b/src/parse.rs index 9fd8634..b053a18 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -126,6 +126,8 @@ impl Debug for NameList<'_> { } } +// TODO: THIS IS A BRITTLE MESS BECAUSE THE RFC SUCKS HERE +// DO NOT TOUCH MPINT ENCODING ANYWHERE #[derive(Debug, Clone, Copy)] pub struct MpInt<'a>(pub(crate) &'a [u8]); diff --git a/test-openssh/default.nix b/test-openssh/default.nix new file mode 100644 index 0000000..f76bdc9 --- /dev/null +++ b/test-openssh/default.nix @@ -0,0 +1,19 @@ +{ pkgs ? import { }, ... }: +let + optimizeWithFlags = pkg: flags: + pkgs.lib.overrideDerivation pkg (old: + let + newflags = pkgs.lib.foldl' (acc: x: "${acc} ${x}") "" flags; + oldflags = + if (pkgs.lib.hasAttr "NIX_CFLAGS_COMPILE" old) + then "${old.NIX_CFLAGS_COMPILE}" + else ""; + in + { + CFLAGS = "-DDEBUG_KEXDH -DDEBUG_KEX -DDEBUG_KEXECDH"; + NIX_CFLAGS_COMPILE = "${oldflags} ${newflags}"; + checkPhase = ""; + doCheck = false; + }); +in +optimizeWithFlags pkgs.openssh [ "-DDEBUG_KEXDH" ]