mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
moves
This commit is contained in:
parent
3124e6a2ab
commit
8a627949a3
23 changed files with 102 additions and 77 deletions
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cluelessh-format = { path = "../cluelessh-format" }
|
||||
aes = "0.8.4"
|
||||
aes-gcm = "0.10.3"
|
||||
chacha20 = "0.9.1"
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ use crate::{
|
|||
},
|
||||
numbers,
|
||||
packet::{Packet, PacketTransport, ProtocolIdentParser},
|
||||
parse::{NameList, Parser, Writer},
|
||||
peer_error, Msg, Result, SshRng, SshStatus,
|
||||
};
|
||||
use cluelessh_format::{NameList, Reader, Writer};
|
||||
|
||||
pub struct ClientConnection {
|
||||
state: ClientState,
|
||||
|
|
@ -106,7 +106,7 @@ impl ClientConnection {
|
|||
match packet.payload.first().copied() {
|
||||
Some(numbers::SSH_MSG_DISCONNECT) => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4253#section-11.1>
|
||||
let mut p = Parser::new(&packet.payload[1..]);
|
||||
let mut p = Reader::new(&packet.payload[1..]);
|
||||
let reason = p.u32()?;
|
||||
let description = p.utf8_string()?;
|
||||
let _language_tag = p.utf8_string()?;
|
||||
|
|
@ -120,13 +120,13 @@ impl ClientConnection {
|
|||
}
|
||||
Some(numbers::SSH_MSG_IGNORE) => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4253#section-11.2>
|
||||
let mut p = Parser::new(&packet.payload[1..]);
|
||||
let mut p = Reader::new(&packet.payload[1..]);
|
||||
let _ = p.string()?;
|
||||
continue;
|
||||
}
|
||||
Some(numbers::SSH_MSG_DEBUG) => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4253#section-11.3>
|
||||
let mut p = Parser::new(&packet.payload[1..]);
|
||||
let mut p = Reader::new(&packet.payload[1..]);
|
||||
let always_display = p.bool()?;
|
||||
let msg = p.utf8_string()?;
|
||||
let _language_tag = p.utf8_string()?;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
pub mod encrypt;
|
||||
|
||||
use cluelessh_format::{Reader, Writer};
|
||||
use p256::ecdsa::signature::Signer;
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::{
|
||||
packet::{EncryptedPacket, MsgKind, Packet, RawPacket},
|
||||
parse::{self, Parser, Writer},
|
||||
peer_error, Msg, Result, SshRng,
|
||||
};
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ pub fn hostkey_ed25519(hostkey_private: Vec<u8>) -> HostKeySigningAlgorithm {
|
|||
},
|
||||
verify: |public_key, message, signature| {
|
||||
// Parse out public key
|
||||
let mut public_key = Parser::new(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"));
|
||||
|
|
@ -170,7 +170,7 @@ pub fn hostkey_ed25519(hostkey_private: Vec<u8>) -> HostKeySigningAlgorithm {
|
|||
.map_err(|err| peer_error!("incorrect public host key: {err}"))?;
|
||||
|
||||
// Parse out signature
|
||||
let mut signature = Parser::new(&signature.0);
|
||||
let mut signature = Reader::new(&signature.0);
|
||||
let alg = signature.string()?;
|
||||
if alg != b"ssh-ed25519" {
|
||||
return Err(peer_error!("incorrect algorithm for signature"));
|
||||
|
|
@ -473,7 +473,7 @@ fn derive_key(
|
|||
}
|
||||
|
||||
pub(crate) fn encode_mpint_for_hash(key: &[u8], mut add_to_hash: impl FnMut(&[u8])) {
|
||||
let (key, pad_zero) = parse::fixup_mpint(key);
|
||||
let (key, pad_zero) = cluelessh_format::fixup_mpint(key);
|
||||
add_to_hash(&u32::to_be_bytes((key.len() + (pad_zero as usize)) as u32));
|
||||
if pad_zero {
|
||||
add_to_hash(&[0]);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::fmt::Display;
|
|||
use base64::Engine;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::parse::{self, ParseError, Parser, Writer};
|
||||
use cluelessh_format::{ParseError, Reader, Writer};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PublicKey {
|
||||
|
|
@ -17,8 +17,8 @@ pub enum PublicKey {
|
|||
impl PublicKey {
|
||||
/// Parses an SSH public key from its wire encoding as specified in
|
||||
/// RFC4253, RFC5656, and RFC8709.
|
||||
pub fn from_wire_encoding(bytes: &[u8]) -> parse::Result<Self> {
|
||||
let mut p = Parser::new(bytes);
|
||||
pub fn from_wire_encoding(bytes: &[u8]) -> cluelessh_format::Result<Self> {
|
||||
let mut p = Reader::new(bytes);
|
||||
let alg = p.utf8_string()?;
|
||||
|
||||
let k = match alg {
|
||||
|
|
@ -55,7 +55,7 @@ impl PublicKey {
|
|||
pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool {
|
||||
match self {
|
||||
PublicKey::Ed25519 { public_key } => {
|
||||
let mut s = Parser::new(signature);
|
||||
let mut s = Reader::new(signature);
|
||||
let Ok(alg) = s.utf8_string() else {
|
||||
return false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ mod crypto;
|
|||
pub mod key;
|
||||
pub mod numbers;
|
||||
pub mod packet;
|
||||
pub mod parse;
|
||||
pub mod server;
|
||||
|
||||
use cluelessh_format::ParseError;
|
||||
pub use packet::Msg;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -21,6 +21,13 @@ pub enum SshStatus {
|
|||
|
||||
pub type Result<T, E = SshStatus> = std::result::Result<T, E>;
|
||||
|
||||
impl From<ParseError> for SshStatus {
|
||||
fn from(err: ParseError) -> Self {
|
||||
Self::PeerError(err.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait SshRng {
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::mem;
|
|||
use tracing::{debug, trace};
|
||||
|
||||
use crate::crypto::{self, EncryptionAlgorithm, Keys, Plaintext, Session};
|
||||
use crate::parse::{NameList, Parser, Writer};
|
||||
use cluelessh_format::{NameList, Reader, Writer};
|
||||
use crate::Result;
|
||||
use crate::{numbers, peer_error};
|
||||
|
||||
|
|
@ -214,8 +214,8 @@ impl Packet {
|
|||
new
|
||||
}
|
||||
|
||||
pub fn payload_parser(&self) -> Parser<'_> {
|
||||
Parser::new(&self.payload)
|
||||
pub fn payload_parser(&self) -> Reader<'_> {
|
||||
Reader::new(&self.payload)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +250,7 @@ pub(crate) struct KeyExchangeInitPacket<'a> {
|
|||
|
||||
impl<'a> KeyExchangeInitPacket<'a> {
|
||||
pub(crate) fn parse(payload: &'a [u8]) -> Result<KeyExchangeInitPacket<'_>> {
|
||||
let mut c = Parser::new(payload);
|
||||
let mut c = Reader::new(payload);
|
||||
|
||||
let kind = c.u8()?;
|
||||
if kind != numbers::SSH_MSG_KEXINIT {
|
||||
|
|
@ -317,7 +317,7 @@ pub(crate) struct KeyExchangeEcDhInitPacket<'a> {
|
|||
}
|
||||
impl<'a> KeyExchangeEcDhInitPacket<'a> {
|
||||
pub(crate) fn parse(payload: &'a [u8]) -> Result<KeyExchangeEcDhInitPacket<'_>> {
|
||||
let mut c = Parser::new(payload);
|
||||
let mut c = Reader::new(payload);
|
||||
|
||||
let kind = c.u8()?;
|
||||
if kind != numbers::SSH_MSG_KEX_ECDH_INIT {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::packet::Packet;
|
||||
use crate::parse::Writer;
|
||||
use cluelessh_format::Writer;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
mod ssh_type_to_rust {
|
||||
pub(super) use {bool, u32, u8};
|
||||
pub(super) type string<'a> = &'a [u8];
|
||||
pub(super) type name_list<'a> = crate::parse::NameList<'a>;
|
||||
pub(super) type name_list<'a> = cluelessh_format::NameList<'a>;
|
||||
}
|
||||
|
||||
macro_rules! ctors {
|
||||
|
|
|
|||
|
|
@ -1,205 +0,0 @@
|
|||
use core::str;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use crate::SshStatus;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseError(pub String);
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
impl From<ParseError> for SshStatus {
|
||||
fn from(err: ParseError) -> Self {
|
||||
Self::PeerError(err.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T, E = ParseError> = std::result::Result<T, E>;
|
||||
|
||||
/// A simplified `byteorder` clone that emits client errors when the data is too short.
|
||||
pub struct Parser<'a>(&'a [u8]);
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
Self(data)
|
||||
}
|
||||
|
||||
pub fn remaining(&self) -> &[u8] {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn has_data(&self) -> bool {
|
||||
!self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn u8(&mut self) -> Result<u8> {
|
||||
let arr = self.array::<1>()?;
|
||||
Ok(arr[0])
|
||||
}
|
||||
|
||||
pub fn u32(&mut self) -> Result<u32> {
|
||||
let arr = self.array()?;
|
||||
Ok(u32::from_be_bytes(arr))
|
||||
}
|
||||
|
||||
pub fn array<const N: usize>(&mut self) -> Result<[u8; N]> {
|
||||
assert!(N < 100_000);
|
||||
if self.0.len() < N {
|
||||
return Err(ParseError(format!(
|
||||
"packet too short, expected {N} but found {}",
|
||||
self.0.len()
|
||||
)));
|
||||
}
|
||||
let result = self.0[..N].try_into().unwrap();
|
||||
self.0 = &self.0[N..];
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn slice(&mut self, len: usize) -> Result<&'a [u8]> {
|
||||
if self.0.len() < len {
|
||||
return Err(ParseError(format!(
|
||||
"packet too short, expected {len} but found {}",
|
||||
self.0.len()
|
||||
)));
|
||||
}
|
||||
if len > 100_000 {
|
||||
return Err(ParseError(format!("bytes too long: {len}")));
|
||||
}
|
||||
let result = &self.0[..len];
|
||||
self.0 = &self.0[len..];
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn bool(&mut self) -> Result<bool> {
|
||||
let b = self.u8()?;
|
||||
match b {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => Err(ParseError(format!("invalid bool: {b}"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name_list(&mut self) -> Result<NameList<'a>> {
|
||||
let list = self.utf8_string()?;
|
||||
Ok(NameList(list))
|
||||
}
|
||||
|
||||
pub fn mpint(&mut self) -> Result<MpInt<'a>> {
|
||||
todo!("do correctly")
|
||||
}
|
||||
|
||||
pub fn string(&mut self) -> Result<&'a [u8]> {
|
||||
let len = self.u32()?;
|
||||
let data = self.slice(len.try_into().unwrap())?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn utf8_string(&mut self) -> Result<&'a str> {
|
||||
let s = self.string()?;
|
||||
let Ok(s) = str::from_utf8(s) else {
|
||||
return Err(ParseError(format!("name-list is invalid UTF-8")));
|
||||
};
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A writer for the SSH wire format.
|
||||
pub struct Writer(Vec<u8>);
|
||||
|
||||
impl Writer {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn u8(&mut self, v: u8) {
|
||||
self.raw(&[v]);
|
||||
}
|
||||
|
||||
pub fn u32(&mut self, v: u32) {
|
||||
self.raw(&u32::to_be_bytes(v));
|
||||
}
|
||||
|
||||
pub fn raw(&mut self, v: &[u8]) {
|
||||
self.0.extend_from_slice(v);
|
||||
}
|
||||
|
||||
pub fn array<const N: usize>(&mut self, arr: [u8; N]) {
|
||||
self.raw(&arr);
|
||||
}
|
||||
|
||||
pub fn name_list(&mut self, list: NameList<'_>) {
|
||||
self.string(list.0.as_bytes());
|
||||
}
|
||||
|
||||
pub fn mpint<const LIMBS: usize>(&mut self, uint: crypto_bigint::Uint<LIMBS>)
|
||||
where
|
||||
crypto_bigint::Uint<LIMBS>: crypto_bigint::ArrayEncoding,
|
||||
{
|
||||
let bytes = crypto_bigint::ArrayEncoding::to_be_byte_array(&uint);
|
||||
let (bytes, pad_zero) = fixup_mpint(&bytes);
|
||||
let len = bytes.len() + (pad_zero as usize);
|
||||
self.u32(len as u32);
|
||||
if pad_zero {
|
||||
self.u8(0);
|
||||
}
|
||||
self.raw(bytes);
|
||||
}
|
||||
|
||||
pub fn string(&mut self, data: impl AsRef<[u8]>) {
|
||||
let data = data.as_ref();
|
||||
self.u32(data.len() as u32);
|
||||
self.raw(data);
|
||||
}
|
||||
|
||||
pub fn bool(&mut self, v: bool) {
|
||||
self.u8(v as u8);
|
||||
}
|
||||
|
||||
pub fn finish(self) -> Vec<u8> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an array of significant bits for the mpint,
|
||||
/// and whether a leading 0 needs to be added for padding.
|
||||
pub fn fixup_mpint(mut int_encoded: &[u8]) -> (&[u8], bool) {
|
||||
while int_encoded[0] == 0 {
|
||||
int_encoded = &int_encoded[1..];
|
||||
}
|
||||
// If the first high bit is set, pad it with a zero.
|
||||
(int_encoded, (int_encoded[0] & 0b10000000) > 1)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NameList<'a>(pub &'a str);
|
||||
|
||||
impl<'a> NameList<'a> {
|
||||
pub fn one(item: &'a str) -> Self {
|
||||
if item.contains(',') {
|
||||
panic!("tried creating name list with comma in item: {item}");
|
||||
}
|
||||
Self(item)
|
||||
}
|
||||
pub fn multi(items: &'a str) -> Self {
|
||||
Self(items)
|
||||
}
|
||||
pub fn none() -> NameList<'static> {
|
||||
NameList("")
|
||||
}
|
||||
pub fn iter(&self) -> std::str::Split<'a, char> {
|
||||
self.0.split(',')
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NameList<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MpInt<'a>(pub &'a [u8]);
|
||||
|
|
@ -6,7 +6,7 @@ use crate::crypto::{
|
|||
use crate::packet::{
|
||||
KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport, ProtocolIdentParser,
|
||||
};
|
||||
use crate::parse::{NameList, Parser, Writer};
|
||||
use cluelessh_format::{NameList, Reader, Writer};
|
||||
use crate::{numbers, Result};
|
||||
use crate::{peer_error, Msg, SshRng, SshStatus};
|
||||
use tracing::{debug, info, trace};
|
||||
|
|
@ -91,7 +91,7 @@ impl ServerConnection {
|
|||
match packet.payload.first().copied() {
|
||||
Some(numbers::SSH_MSG_DISCONNECT) => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4253#section-11.1>
|
||||
let mut disconnect = Parser::new(&packet.payload[1..]);
|
||||
let mut disconnect = Reader::new(&packet.payload[1..]);
|
||||
let reason = disconnect.u32()?;
|
||||
let description = disconnect.utf8_string()?;
|
||||
let _language_tag = disconnect.utf8_string()?;
|
||||
|
|
@ -105,13 +105,13 @@ impl ServerConnection {
|
|||
}
|
||||
Some(numbers::SSH_MSG_IGNORE) => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4253#section-11.2>
|
||||
let mut p = Parser::new(&packet.payload[1..]);
|
||||
let mut p = Reader::new(&packet.payload[1..]);
|
||||
let _ = p.string()?;
|
||||
continue;
|
||||
}
|
||||
Some(numbers::SSH_MSG_DEBUG) => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4253#section-11.3>
|
||||
let mut p = Parser::new(&packet.payload[1..]);
|
||||
let mut p = Reader::new(&packet.payload[1..]);
|
||||
let always_display = p.bool()?;
|
||||
let msg = p.utf8_string()?;
|
||||
let _language_tag = p.utf8_string()?;
|
||||
|
|
@ -300,7 +300,7 @@ impl ServerConnection {
|
|||
if packet.payload.first() != Some(&numbers::SSH_MSG_SERVICE_REQUEST) {
|
||||
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
||||
}
|
||||
let mut p = Parser::new(&packet.payload[1..]);
|
||||
let mut p = Reader::new(&packet.payload[1..]);
|
||||
let service = p.utf8_string()?;
|
||||
debug!(%service, "Client requesting service");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue