mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
try more
This commit is contained in:
parent
0bd4f7d9ef
commit
f5561004f6
4 changed files with 122 additions and 42 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
|
@ -17,6 +17,16 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
|
@ -103,6 +113,19 @@ dependencies = [
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20poly1305"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"chacha20",
|
||||||
|
"cipher",
|
||||||
|
"poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
|
@ -111,6 +134,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
"inout",
|
"inout",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -135,6 +159,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -667,6 +692,7 @@ name = "ssh-transport"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chacha20",
|
"chacha20",
|
||||||
|
"chacha20poly1305",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"eyre",
|
"eyre",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chacha20 = "0.9.1"
|
chacha20 = "0.9.1"
|
||||||
|
chacha20poly1305 = "0.10.1"
|
||||||
ed25519-dalek = { version = "2.1.1" }
|
ed25519-dalek = { version = "2.1.1" }
|
||||||
eyre = "0.6.12"
|
eyre = "0.6.12"
|
||||||
poly1305 = "0.8.0"
|
poly1305 = "0.8.0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
use chacha20::cipher::{KeyInit, KeyIvInit, StreamCipher, StreamCipherSeek};
|
use chacha20::cipher::{KeyInit, KeyIvInit, StreamCipher, StreamCipherSeek};
|
||||||
|
use poly1305::universal_hash::UniversalHash;
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{
|
||||||
|
packet::{Packet, RawPacket},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) struct Session {
|
pub(crate) struct Session {
|
||||||
session_id: [u8; 32],
|
session_id: [u8; 32],
|
||||||
|
|
@ -11,14 +15,16 @@ pub(crate) struct Session {
|
||||||
|
|
||||||
pub(crate) trait Decryptor: Send + Sync + 'static {
|
pub(crate) trait Decryptor: Send + Sync + 'static {
|
||||||
fn decrypt_len(&mut self, bytes: &mut [u8; 4], packet_number: u64);
|
fn decrypt_len(&mut self, bytes: &mut [u8; 4], packet_number: u64);
|
||||||
fn decrypt_packet(&mut self, bytes: &mut [u8], packet_number: u64);
|
fn decrypt_packet(&mut self, raw_packet: RawPacket, packet_number: u64) -> Result<Packet>;
|
||||||
fn rekey(&mut self, h: [u8; 32], k: [u8; 32]) -> Result<(), ()>;
|
fn rekey(&mut self, h: [u8; 32], k: [u8; 32]) -> Result<(), ()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Plaintext;
|
pub(crate) struct Plaintext;
|
||||||
impl Decryptor for Plaintext {
|
impl Decryptor for Plaintext {
|
||||||
fn decrypt_len(&mut self, _: &mut [u8; 4], _: u64) {}
|
fn decrypt_len(&mut self, _: &mut [u8; 4], _: u64) {}
|
||||||
fn decrypt_packet(&mut self, _: &mut [u8], _: u64) {}
|
fn decrypt_packet(&mut self, raw: RawPacket, _: u64) -> Result<Packet> {
|
||||||
|
Packet::from_raw(&raw.rest())
|
||||||
|
}
|
||||||
fn rekey(&mut self, _: [u8; 32], _: [u8; 32]) -> Result<(), ()> {
|
fn rekey(&mut self, _: [u8; 32], _: [u8; 32]) -> Result<(), ()> {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
@ -54,9 +60,9 @@ impl Decryptor for Session {
|
||||||
.decrypt_len(bytes, packet_number);
|
.decrypt_len(bytes, packet_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_packet(&mut self, bytes: &mut [u8], packet_number: u64) {
|
fn decrypt_packet(&mut self, bytes: RawPacket, packet_number: u64) -> Result<Packet> {
|
||||||
self.encryption_key_client_to_server
|
self.encryption_key_client_to_server
|
||||||
.decrypt_packet(bytes, packet_number);
|
.decrypt_packet(bytes, packet_number)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rekey(&mut self, h: [u8; 32], k: [u8; 32]) -> Result<(), ()> {
|
fn rekey(&mut self, h: [u8; 32], k: [u8; 32]) -> Result<(), ()> {
|
||||||
|
|
@ -112,13 +118,18 @@ type SshChaCha20 = chacha20::ChaCha20Legacy;
|
||||||
|
|
||||||
struct SshChaCha20Poly1305 {
|
struct SshChaCha20Poly1305 {
|
||||||
header_key: chacha20::Key,
|
header_key: chacha20::Key,
|
||||||
main_key: chacha20::Key,
|
main_key2: chacha20::Key,
|
||||||
|
main_key:
|
||||||
|
chacha20poly1305::ChaChaPoly1305<chacha20::ChaCha20Legacy, chacha20::cipher::typenum::U8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshChaCha20Poly1305 {
|
impl SshChaCha20Poly1305 {
|
||||||
fn new(key: [u8; 64]) -> Self {
|
fn new(key: [u8; 64]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
main_key: <[u8; 32]>::try_from(&key[..32]).unwrap().into(),
|
main_key2: <[u8; 32]>::try_from(&key[..32]).unwrap().into(),
|
||||||
|
main_key: chacha20poly1305::ChaChaPoly1305::new(
|
||||||
|
&<[u8; 32]>::try_from(&key[..32]).unwrap().into(),
|
||||||
|
),
|
||||||
header_key: <[u8; 32]>::try_from(&key[32..]).unwrap().into(),
|
header_key: <[u8; 32]>::try_from(&key[32..]).unwrap().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,20 +140,39 @@ impl SshChaCha20Poly1305 {
|
||||||
cipher.apply_keystream(bytes);
|
cipher.apply_keystream(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_packet(&mut self, bytes: &mut [u8], packet_number: u64) {
|
fn decrypt_packet(&mut self, bytes: RawPacket, packet_number: u64) -> Result<Packet> {
|
||||||
// <https://github.com/openssh/openssh-portable/blob/1ec0a64c5dc57b8a2053a93b5ef0d02ff8598e5c/PROTOCOL.chacha20poly1305>
|
// <https://github.com/openssh/openssh-portable/blob/1ec0a64c5dc57b8a2053a93b5ef0d02ff8598e5c/PROTOCOL.chacha20poly1305>
|
||||||
let mut cipher = SshChaCha20::new(&self.main_key, &packet_number.to_be_bytes().into());
|
|
||||||
|
|
||||||
let mut poly1305_key = [0; poly1305::KEY_SIZE];
|
//let mut aead_payload = bytes.into_full_packet();
|
||||||
cipher.apply_keystream(&mut poly1305_key);
|
//let mut associated_data = [0; 4];
|
||||||
|
//associated_data.copy_from_slice(&aead_payload[0..4]);
|
||||||
|
//aead_payload.splice(0..4, []);
|
||||||
|
|
||||||
let total_len = bytes.len();
|
//chacha20poly1305::AeadInPlace::decrypt_in_place(
|
||||||
let payload = &mut bytes[..(total_len - 16)];
|
// &self.main_key,
|
||||||
|
// &packet_number.to_be_bytes().into(),
|
||||||
|
// &associated_data,
|
||||||
|
// &mut aead_payload,
|
||||||
|
//)
|
||||||
|
//.map_err(|err| crate::client_error!("failed to decrypt invalid poly1305 MAC: {err}"))?;
|
||||||
|
|
||||||
// TODO: Compute it over THE LENGTH AS WELL
|
let mut cipher = SshChaCha20::new(&self.main_key2, &packet_number.to_be_bytes().into());
|
||||||
let hash = poly1305::Poly1305::new(&poly1305_key.into()).compute_unpadded(payload);
|
|
||||||
|
|
||||||
cipher.seek(1_u32);
|
let mut mac = {
|
||||||
|
let mut poly1305_key = [0; poly1305::KEY_SIZE];
|
||||||
|
cipher.apply_keystream(&mut poly1305_key);
|
||||||
|
poly1305::Poly1305::new(&poly1305_key.into())
|
||||||
|
};
|
||||||
|
|
||||||
|
let tag_offset = bytes.full_packet().len() - 16;
|
||||||
|
let aead_payload = &bytes.full_packet()[..tag_offset];
|
||||||
|
mac.update_padded(aead_payload);
|
||||||
|
let read_tag = poly1305::Tag::from_slice(&bytes.full_packet()[tag_offset..]);
|
||||||
|
|
||||||
|
mac.verify(read_tag)
|
||||||
|
.map_err(|err| crate::client_error!("failed to decrypt invalid poly1305 MAC: {err}"))?;
|
||||||
|
|
||||||
|
cipher.seek(1);
|
||||||
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ impl Packet {
|
||||||
pub(crate) const SSH_MSG_KEXDH_INIT: u8 = 30;
|
pub(crate) const SSH_MSG_KEXDH_INIT: u8 = 30;
|
||||||
pub(crate) const SSH_MSG_KEXDH_REPLY: u8 = 31;
|
pub(crate) const SSH_MSG_KEXDH_REPLY: u8 = 31;
|
||||||
|
|
||||||
fn from_raw(bytes: &[u8]) -> Result<Self> {
|
pub(crate) fn from_raw(bytes: &[u8]) -> Result<Self> {
|
||||||
let Some(padding_length) = bytes.get(0) else {
|
let Some(padding_length) = bytes.get(0) else {
|
||||||
return Err(client_error!("empty packet"));
|
return Err(client_error!("empty packet"));
|
||||||
};
|
};
|
||||||
|
|
@ -256,18 +256,33 @@ impl<'a> DhKeyExchangeInitReplyPacket<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct RawPacket {
|
||||||
|
len: usize,
|
||||||
|
raw: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl RawPacket {
|
||||||
|
pub(crate) fn rest(&self) -> &[u8] {
|
||||||
|
&self.raw[4..]
|
||||||
|
}
|
||||||
|
pub(crate) fn full_packet(&self) -> &[u8] {
|
||||||
|
&self.raw
|
||||||
|
}
|
||||||
|
pub(crate) fn into_full_packet(self) -> Vec<u8> {
|
||||||
|
self.raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct PacketParser {
|
struct PacketParser {
|
||||||
// The length of the packet.
|
// The length of the packet.
|
||||||
packet_length: Option<usize>,
|
packet_length: Option<usize>,
|
||||||
// Before we've read the length fully, this stores the length.
|
// The raw data *encrypted*, including the length.
|
||||||
// Afterwards, this stores the packet data *after* the length.
|
raw_data: Vec<u8>,
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
impl PacketParser {
|
impl PacketParser {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
packet_length: None,
|
packet_length: None,
|
||||||
data: Vec::new(),
|
raw_data: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn recv_bytes(
|
fn recv_bytes(
|
||||||
|
|
@ -276,37 +291,39 @@ impl PacketParser {
|
||||||
decrytor: &mut dyn Decryptor,
|
decrytor: &mut dyn Decryptor,
|
||||||
next_seq_nr: u64,
|
next_seq_nr: u64,
|
||||||
) -> Result<Option<(usize, Packet)>> {
|
) -> Result<Option<(usize, Packet)>> {
|
||||||
let Some((consumed, mut data)) = self.recv_bytes_inner(bytes, decrytor, next_seq_nr)?
|
let Some((consumed, data)) = self.recv_bytes_inner(bytes, decrytor, next_seq_nr)? else {
|
||||||
else {
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
decrytor.decrypt_packet(&mut data, next_seq_nr);
|
let packet = decrytor.decrypt_packet(data, next_seq_nr)?;
|
||||||
Ok(Some((consumed, Packet::from_raw(&data)?)))
|
Ok(Some((consumed, packet)))
|
||||||
}
|
}
|
||||||
fn recv_bytes_inner(
|
fn recv_bytes_inner(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut bytes: &[u8],
|
mut bytes: &[u8],
|
||||||
decrytor: &mut dyn Decryptor,
|
decrytor: &mut dyn Decryptor,
|
||||||
next_seq_nr: u64,
|
next_seq_nr: u64,
|
||||||
) -> Result<Option<(usize, Vec<u8>)>> {
|
) -> Result<Option<(usize, RawPacket)>> {
|
||||||
let mut consumed = 0;
|
let mut consumed = 0;
|
||||||
let packet_length = match self.packet_length {
|
let packet_length = match self.packet_length {
|
||||||
Some(packet_length) => packet_length,
|
Some(packet_length) => {
|
||||||
|
assert!(self.raw_data.len() >= 4);
|
||||||
|
packet_length
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
let remaining_len = std::cmp::min(bytes.len(), 4 - self.data.len());
|
let remaining_len = std::cmp::min(bytes.len(), 4 - self.raw_data.len());
|
||||||
// Try to read the bytes of the length.
|
// Try to read the bytes of the length.
|
||||||
self.data.extend_from_slice(&bytes[..remaining_len]);
|
self.raw_data.extend_from_slice(&bytes[..remaining_len]);
|
||||||
if self.data.len() < 4 {
|
if self.raw_data.len() < 4 {
|
||||||
// Not enough data yet :(.
|
// Not enough data yet :(.
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut encrypted_len = self.data.as_slice().try_into().unwrap();
|
let mut len_to_decrypt = [0_u8; 4];
|
||||||
decrytor.decrypt_len(&mut encrypted_len, next_seq_nr);
|
len_to_decrypt.copy_from_slice(self.raw_data.as_slice());
|
||||||
|
|
||||||
let packet_length = u32::from_be_bytes(encrypted_len);
|
decrytor.decrypt_len(&mut len_to_decrypt, next_seq_nr);
|
||||||
|
let packet_length = u32::from_be_bytes(len_to_decrypt);
|
||||||
let packet_length = packet_length.try_into().unwrap();
|
let packet_length = packet_length.try_into().unwrap();
|
||||||
self.data.clear();
|
|
||||||
|
|
||||||
self.packet_length = Some(packet_length);
|
self.packet_length = Some(packet_length);
|
||||||
|
|
||||||
|
|
@ -318,19 +335,25 @@ impl PacketParser {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let remaining_len = std::cmp::min(bytes.len(), packet_length - self.data.len());
|
let remaining_len = std::cmp::min(bytes.len(), packet_length - (self.raw_data.len() - 4));
|
||||||
self.data.extend_from_slice(&bytes[..remaining_len]);
|
self.raw_data.extend_from_slice(&bytes[..remaining_len]);
|
||||||
consumed += remaining_len;
|
consumed += remaining_len;
|
||||||
|
|
||||||
if self.data.len() == packet_length {
|
if (self.raw_data.len() - 4) == packet_length {
|
||||||
// We have the full data.
|
// We have the full data.
|
||||||
Ok(Some((consumed, std::mem::take(&mut self.data))))
|
Ok(Some((
|
||||||
|
consumed,
|
||||||
|
RawPacket {
|
||||||
|
raw: std::mem::take(&mut self.raw_data),
|
||||||
|
len: packet_length,
|
||||||
|
},
|
||||||
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn test_recv_bytes(&mut self, bytes: &[u8]) -> Option<(usize, Vec<u8>)> {
|
fn test_recv_bytes(&mut self, bytes: &[u8]) -> Option<(usize, RawPacket)> {
|
||||||
self.recv_bytes_inner(bytes, &mut Plaintext, 0).unwrap()
|
self.recv_bytes_inner(bytes, &mut Plaintext, 0).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +379,7 @@ mod tests {
|
||||||
p.test_recv_bytes(&[1]).unwrap_none();
|
p.test_recv_bytes(&[1]).unwrap_none();
|
||||||
let (consumed, data) = p.test_recv_bytes(&[2]).unwrap();
|
let (consumed, data) = p.test_recv_bytes(&[2]).unwrap();
|
||||||
assert_eq!(consumed, 1);
|
assert_eq!(consumed, 1);
|
||||||
assert_eq!(data, &[1, 2]);
|
assert_eq!(data.rest(), &[1, 2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -369,7 +392,7 @@ mod tests {
|
||||||
p.test_recv_bytes(&[1]).unwrap_none();
|
p.test_recv_bytes(&[1]).unwrap_none();
|
||||||
let (consumed, data) = p.test_recv_bytes(&[2]).unwrap();
|
let (consumed, data) = p.test_recv_bytes(&[2]).unwrap();
|
||||||
assert_eq!(consumed, 1);
|
assert_eq!(consumed, 1);
|
||||||
assert_eq!(data, &[1, 2]);
|
assert_eq!(data.rest(), &[1, 2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -377,6 +400,6 @@ mod tests {
|
||||||
let mut p = PacketParser::new();
|
let mut p = PacketParser::new();
|
||||||
let (consumed, data) = p.test_recv_bytes(&[0, 0, 0, 2, 1, 2]).unwrap();
|
let (consumed, data) = p.test_recv_bytes(&[0, 0, 0, 2, 1, 2]).unwrap();
|
||||||
assert_eq!(consumed, 6);
|
assert_eq!(consumed, 6);
|
||||||
assert_eq!(data, &[1, 2]);
|
assert_eq!(data.rest(), &[1, 2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue