mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
todo
This commit is contained in:
parent
0eb9001f08
commit
adff1f593b
5 changed files with 195 additions and 21 deletions
84
Cargo.lock
generated
84
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"
|
||||||
|
|
@ -92,6 +102,41 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"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]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
|
@ -124,6 +169,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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -270,6 +316,15 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -359,6 +414,12 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -404,6 +465,17 @@ dependencies = [
|
||||||
"spki",
|
"spki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly1305"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
|
|
@ -629,6 +701,7 @@ dependencies = [
|
||||||
name = "ssh-transport"
|
name = "ssh-transport"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chacha20poly1305",
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"eyre",
|
"eyre",
|
||||||
|
|
@ -636,7 +709,6 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
|
|
@ -771,6 +843,16 @@ version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chacha20poly1305 = "0.10.1"
|
||||||
crypto-bigint = "0.5.5"
|
crypto-bigint = "0.5.5"
|
||||||
ed25519-dalek = { version = "2.1.1" }
|
ed25519-dalek = { version = "2.1.1" }
|
||||||
eyre = "0.6.12"
|
eyre = "0.6.12"
|
||||||
|
|
|
||||||
71
ssh-transport/src/keys.rs
Normal file
71
ssh-transport/src/keys.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use chacha20poly1305::{
|
||||||
|
aead::{Aead, AeadCore},
|
||||||
|
ChaCha20Poly1305, KeyInit,
|
||||||
|
};
|
||||||
|
use sha2::Digest;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
pub(crate) struct Session {
|
||||||
|
session_id: [u8; 32],
|
||||||
|
client_to_server_iv: [u8; 32],
|
||||||
|
server_to_client_iv: [u8; 32],
|
||||||
|
encryption_key_client_to_server: ChaCha20Poly1305,
|
||||||
|
encryption_key_server_to_client: ChaCha20Poly1305,
|
||||||
|
integrity_key_server_to_client: [u8; 32],
|
||||||
|
integrity_key_client_to_server: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub(crate) fn new(h: [u8; 32], k: [u8; 32]) -> Self {
|
||||||
|
Self::from_keys(h, h, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn rekey(&mut self, h: [u8; 32], k: [u8; 32]) {
|
||||||
|
*self = Self::from_keys(self.session_id, h, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.2>
|
||||||
|
fn from_keys(session_id: [u8; 32], h: [u8; 32], k: [u8; 32]) -> Self {
|
||||||
|
let derive = |letter: &str| {
|
||||||
|
let mut hash = sha2::Sha256::new();
|
||||||
|
encode_mpint_for_hash(&k, |data| hash.update(data));
|
||||||
|
hash.update(h);
|
||||||
|
hash.update(letter.as_bytes());
|
||||||
|
hash.update(session_id);
|
||||||
|
hash.finalize()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encryption_key_client_to_server = ChaCha20Poly1305::new(&derive("C"));
|
||||||
|
let encryption_key_server_to_client = ChaCha20Poly1305::new(&derive("D"));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
session_id,
|
||||||
|
client_to_server_iv: derive("A").into(),
|
||||||
|
server_to_client_iv: derive("B").into(),
|
||||||
|
encryption_key_client_to_server,
|
||||||
|
encryption_key_server_to_client,
|
||||||
|
integrity_key_client_to_server: derive("E").into(),
|
||||||
|
integrity_key_server_to_client: derive("F").into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decrypt_bytes(&mut self, bytes: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
self.encryption_key_client_to_server
|
||||||
|
.decrypt(&[0; 12].into(), bytes)
|
||||||
|
.map_err(|_| crate::client_error!("failed to decrypt, invalid message"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn encode_mpint_for_hash(mut key: &[u8], mut add_to_hash: impl FnMut(&[u8])) {
|
||||||
|
while key[0] == 0 {
|
||||||
|
key = &key[1..];
|
||||||
|
}
|
||||||
|
// If the first high bit is set, pad it with a zero.
|
||||||
|
let pad_zero = (key[0] & 0b10000000) > 1;
|
||||||
|
add_to_hash(&u32::to_be_bytes((key.len() + (pad_zero as usize)) as u32));
|
||||||
|
if pad_zero {
|
||||||
|
add_to_hash(&[0]);
|
||||||
|
}
|
||||||
|
add_to_hash(key);
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod keys;
|
||||||
mod packet;
|
mod packet;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
|
||||||
|
|
@ -55,7 +56,10 @@ enum ServerState {
|
||||||
client_kexinit: Vec<u8>,
|
client_kexinit: Vec<u8>,
|
||||||
server_kexinit: Vec<u8>,
|
server_kexinit: Vec<u8>,
|
||||||
},
|
},
|
||||||
NewKeys,
|
NewKeys {
|
||||||
|
h: [u8; 32],
|
||||||
|
k: [u8; 32],
|
||||||
|
},
|
||||||
ServiceRequest {},
|
ServiceRequest {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,6 +216,7 @@ impl ServerConnection {
|
||||||
client_kexinit,
|
client_kexinit,
|
||||||
server_kexinit,
|
server_kexinit,
|
||||||
} => {
|
} => {
|
||||||
|
// TODO: move to keys.rs
|
||||||
let dh = DhKeyExchangeInitPacket::parse(&packet.payload)?;
|
let dh = DhKeyExchangeInitPacket::parse(&packet.payload)?;
|
||||||
|
|
||||||
let secret =
|
let secret =
|
||||||
|
|
@ -236,20 +241,8 @@ impl ServerConnection {
|
||||||
add_hash(hash, &u32::to_be_bytes(bytes.len() as u32));
|
add_hash(hash, &u32::to_be_bytes(bytes.len() as u32));
|
||||||
add_hash(hash, bytes);
|
add_hash(hash, bytes);
|
||||||
};
|
};
|
||||||
let hash_mpint = |hash: &mut sha2::Sha256, mut bytes: &[u8]| {
|
let hash_mpint = |hash: &mut sha2::Sha256, bytes: &[u8]| {
|
||||||
while bytes[0] == 0 {
|
keys::encode_mpint_for_hash(bytes, |data| add_hash(hash, data));
|
||||||
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(
|
hash_string(
|
||||||
|
|
@ -293,18 +286,23 @@ impl ServerConnection {
|
||||||
self.queue_msg(MsgKind::Packet(Packet {
|
self.queue_msg(MsgKind::Packet(Packet {
|
||||||
payload: packet.to_bytes(),
|
payload: packet.to_bytes(),
|
||||||
}));
|
}));
|
||||||
self.state = ServerState::NewKeys;
|
self.state = ServerState::NewKeys {
|
||||||
// TODO: set keys for transport
|
h: hash.into(),
|
||||||
|
k: shared_secret.to_bytes(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
ServerState::NewKeys => {
|
ServerState::NewKeys { h, k } => {
|
||||||
if packet.payload != &[Packet::SSH_MSG_NEWKEYS] {
|
if packet.payload != &[Packet::SSH_MSG_NEWKEYS] {
|
||||||
return Err(client_error!("did not send SSH_MSG_NEWKEYS"));
|
return Err(client_error!("did not send SSH_MSG_NEWKEYS"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (h, k) = (*h, *k);
|
||||||
|
|
||||||
self.queue_msg(MsgKind::Packet(Packet {
|
self.queue_msg(MsgKind::Packet(Packet {
|
||||||
payload: vec![Packet::SSH_MSG_NEWKEYS],
|
payload: vec![Packet::SSH_MSG_NEWKEYS],
|
||||||
}));
|
}));
|
||||||
self.state = ServerState::ServiceRequest {};
|
self.state = ServerState::ServiceRequest {};
|
||||||
|
self.packet_transport.set_key(h, k);
|
||||||
}
|
}
|
||||||
ServerState::ServiceRequest {} => {}
|
ServerState::ServiceRequest {} => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use crate::client_error;
|
use crate::client_error;
|
||||||
|
use crate::keys::Session;
|
||||||
use crate::parse::{MpInt, NameList, Parser, Writer};
|
use crate::parse::{MpInt, NameList, Parser, Writer};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
|
|
@ -8,11 +9,12 @@ use crate::Result;
|
||||||
pub(crate) struct PacketTransport {
|
pub(crate) struct PacketTransport {
|
||||||
state: PacketTransportState,
|
state: PacketTransportState,
|
||||||
packets: VecDeque<Packet>,
|
packets: VecDeque<Packet>,
|
||||||
|
next_recv_seq_nr: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PacketTransportState {
|
enum PacketTransportState {
|
||||||
Plaintext(PacketParser),
|
Plaintext(PacketParser),
|
||||||
Keyed { key: () },
|
Keyed { session: Session },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketTransport {
|
impl PacketTransport {
|
||||||
|
|
@ -20,6 +22,7 @@ impl PacketTransport {
|
||||||
PacketTransport {
|
PacketTransport {
|
||||||
state: PacketTransportState::Plaintext(PacketParser::new()),
|
state: PacketTransportState::Plaintext(PacketParser::new()),
|
||||||
packets: VecDeque::new(),
|
packets: VecDeque::new(),
|
||||||
|
next_recv_seq_nr: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn recv_bytes(&mut self, mut bytes: &[u8]) -> Result<()> {
|
pub(crate) fn recv_bytes(&mut self, mut bytes: &[u8]) -> Result<()> {
|
||||||
|
|
@ -34,6 +37,18 @@ impl PacketTransport {
|
||||||
pub(crate) fn next_packet(&mut self) -> Option<Packet> {
|
pub(crate) fn next_packet(&mut self) -> Option<Packet> {
|
||||||
self.packets.pop_front()
|
self.packets.pop_front()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_key(&mut self, h: [u8; 32], k: [u8; 32]) {
|
||||||
|
match &mut self.state {
|
||||||
|
PacketTransportState::Plaintext(_) => {
|
||||||
|
self.state = PacketTransportState::Keyed {
|
||||||
|
session: Session::new(h, k),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PacketTransportState::Keyed { session } => session.rekey(h, k),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn recv_bytes_step(&mut self, bytes: &[u8]) -> Result<Option<usize>> {
|
fn recv_bytes_step(&mut self, bytes: &[u8]) -> Result<Option<usize>> {
|
||||||
// TODO: This might not work if we buffer two packets where one changes keys in between?
|
// TODO: This might not work if we buffer two packets where one changes keys in between?
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
|
|
@ -41,11 +56,18 @@ impl PacketTransport {
|
||||||
let result = packet.recv_bytes(bytes, ())?;
|
let result = packet.recv_bytes(bytes, ())?;
|
||||||
if let Some((consumed, result)) = result {
|
if let Some((consumed, result)) = result {
|
||||||
self.packets.push_back(result);
|
self.packets.push_back(result);
|
||||||
|
self.next_recv_seq_nr = self.next_recv_seq_nr.wrapping_add(1);
|
||||||
*packet = PacketParser::new();
|
*packet = PacketParser::new();
|
||||||
return Ok(Some(consumed));
|
return Ok(Some(consumed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PacketTransportState::Keyed { key } => todo!(),
|
PacketTransportState::Keyed { session } => {
|
||||||
|
// TODO: don't yolo?...
|
||||||
|
let encrypted_len = &bytes[..4];
|
||||||
|
// TODO: all of this is nonsense. how does AEAD even work with these partial decryptions?
|
||||||
|
// should i just validate it by hand?? i will find out tomorrow!
|
||||||
|
let decrypted_len = session.decrypt_bytes(encrypted_len)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue