coolssh 1.2.0

SSH 2.0 Client
Documentation
use super::{
    Cipher, Hmac, VERSION_HEADER, Keypair, Rng, ed25519_blob_len, Error,
    TcpStream, BufReader, BufWriter, BufRead, Result, Write, sha256,
};
use super::{KeyIvInit, Verifier};
use super::userauth::sign_userauth;
use super::messages::{
    UnsignedMpInt, ServiceRequest, ServiceAccept, UserauthRequest, Blob,
    Kexinit, KexdhInit, KexdhReply, ExchangeHash, Newkeys, Message,
};
use super::parsedump::ParseDump;
use super::keygen::decode_hex;
use super::packets::{PacketReader, PacketWriter};

pub enum Auth<'a> {
    Password {
        username: &'a str,
        password: &'a str,
    },
    Ed25519 {
        username: &'a str,
        /// 128-character hex-encoded keypair
        hex_keypair: &'a str,
    }
}

pub struct Connection {
    pub(crate) reader: PacketReader<TcpStream>,
    pub(crate) writer: PacketWriter<TcpStream>,
    pub(crate) next_client_channel: u32,
}

impl Connection {
    pub fn new(stream: TcpStream, auth: Auth) -> Result<Self> {
        let mut reader = BufReader::new(stream.try_clone()?);
        let mut writer = BufWriter::new(stream);

        writer.write(VERSION_HEADER)?;
        writer.write(b"\r\n")?;
        writer.flush()?;

        let peer_version = {
            let mut peer_version = String::new();

            loop {
                reader.read_line(&mut peer_version)?;
                let sw = |prefix| peer_version.starts_with(prefix);
                match sw("SSH-2.0-") || sw("SSH-1.99-") {
                    true => break,
                    _    => continue,
                }
            }

            let lf = peer_version.pop();
            let cr = peer_version.pop();

            if (cr, lf) != (Some('\r'), Some('\n')) {
                log::error!("Invalid Version Header: {}", peer_version);
                return Err(Error::InvalidData);
            }

            peer_version
        };

        log::info!("peer_version: {}", peer_version);

        let mut reader = PacketReader::new(reader);
        let mut writer = PacketWriter::new(writer);

        let client_kexinit = Kexinit {
            cookie: [0; 16],
            kex_algorithms: "curve25519-sha256",
            server_host_key_algorithms: "ssh-ed25519",
            encryption_algorithms_client_to_server: "aes256-ctr",
            encryption_algorithms_server_to_client: "aes256-ctr",
            mac_algorithms_client_to_server: "hmac-sha2-256",
            mac_algorithms_server_to_client: "hmac-sha2-256",
            compression_algorithms_client_to_server: "none",
            compression_algorithms_server_to_client: "none",
            languages_client_to_server: "",
            languages_server_to_client: "",
            first_kex_packet_follows: false,
            nop: 0,
        };

        let mut client_kexinit_payload = Vec::new();
        client_kexinit.dump(&mut client_kexinit_payload)?;
        let client_kexinit_payload = &client_kexinit_payload.into_boxed_slice();

        writer.send(&client_kexinit)?;

        let server_kexinit_payload = reader.recv_raw()?.to_vec();
        let server_kexinit_payload = &server_kexinit_payload.into_boxed_slice();
        let (server_kexinit, _) = Kexinit::parse(server_kexinit_payload)?;
        server_kexinit.check_compat(&client_kexinit)?;

        let secret_key = x25519_dalek::EphemeralSecret::new(Rng);
        let public_key = x25519_dalek::PublicKey::from(&secret_key);
        let client_ephemeral_pubkey = public_key.as_bytes().as_slice();

        writer.send(&KexdhInit {
            client_ephemeral_pubkey,
        })?;

        let shared_secret_array;
        let (exchange_hash, shared_secret) = {
            let KexdhReply {
                server_public_host_key,
                server_ephemeral_pubkey,
                exchange_hash_signature: Blob {
                    blob_len: _,
                    header: _,
                    content: signature,
                },
            } = reader.recv()?;

            let Blob {
                blob_len: _,
                header: _,
                content: host_pubkey_bytes,
            } = server_public_host_key;

            if server_ephemeral_pubkey.len() != 32 || signature.len() != 64 || host_pubkey_bytes.len() != 32 {
                log::error!("Invalid Server KexdhReply (wrong field length)");
                return Err(Error::InvalidData);
            }

            shared_secret_array = {
                let mut sep_array = [0; 32];
                sep_array.copy_from_slice(server_ephemeral_pubkey);
                secret_key.diffie_hellman(&sep_array.into())
            };

            let host_pubkey = ed25519_dalek::PublicKey::from_bytes(host_pubkey_bytes).map_err(|e| {
                log::error!("Couldn't reconstruct server public key: {}", e);
                Error::InvalidData
            })?;

            let signature = {
                let mut sig_array = [0; 64];
                sig_array.copy_from_slice(signature);
                ed25519_dalek::Signature::from(sig_array)
            };

            let shared_secret = UnsignedMpInt(shared_secret_array.as_bytes());

            let exchange_hash = sha256(&ExchangeHash {
                client_header: VERSION_HEADER,
                server_header: peer_version.as_bytes(),
                client_kexinit_payload,
                server_kexinit_payload,
                server_public_host_key,
                client_ephemeral_pubkey,
                server_ephemeral_pubkey,
                shared_secret,
            })?;

            host_pubkey.verify(&exchange_hash, &signature).map_err(|e| {
                log::error!("Exchange hash couldn't be verified: {}", e);
                Error::InvalidData
            })?;

            (exchange_hash, shared_secret)
        };

        let session_id = exchange_hash;

        writer.send(&Newkeys {})?;
        let _: Newkeys = reader.recv()?;

        log::trace!("Got server Newkeys");

        let kex = KeyExchangeOutput::new(shared_secret, &exchange_hash, &session_id)?;
        writer.set_encryptor(Cipher::new(&kex.c2s_key.into(), &kex.c2s_iv.into()), Hmac::new(&kex.c2s_hmac), 32);
        reader.set_decryptor(Cipher::new(&kex.s2c_key.into(), &kex.s2c_iv.into()), Hmac::new(&kex.s2c_hmac), 32, 32);

        log::trace!("Sending ServiceRequest");

        writer.send(&ServiceRequest {
            service_name: "ssh-userauth",
        })?;

        log::trace!("Awaiting ServiceAccept");
        let _: ServiceAccept = reader.recv()?;
        log::trace!("Got ServiceAccept");

        let service_name = "ssh-connection";
        match auth {
            Auth::Password {
                username,
                password,
            } => {
                writer.send(&UserauthRequest::Password {
                    username,
                    service_name,
                    password,
                    new_password: None,
                })?;
            },
            Auth::Ed25519 {
                username,
                hex_keypair,
            } => {
                let algorithm = "ssh-ed25519";
                let keypair = {
                    let bytes: [u8; 64] = decode_hex(hex_keypair).ok_or(Error::InvalidKeypair)?;
                    Keypair::from_bytes(&bytes).ok().ok_or(Error::InvalidKeypair)?
                };

                let ed25519_pub = Blob {
                    blob_len: ed25519_blob_len(32),
                    header: algorithm,
                    content: keypair.public.as_bytes().as_slice(),
                };

                writer.send(&UserauthRequest::PublicKey {
                    username,
                    service_name,
                    algorithm,
                    blob: ed25519_pub,
                    signature: None,
                })?;

                log::trace!("Awaiting UserauthPkOk");
                match reader.recv()? {
                    Message::UserauthPkOk(_) => Ok((/* nice */)),
                    Message::UserauthFailure(_) => Err(Error::AuthenticationFailure),
                    msg => {
                        log::error!("Expected UserauthPkOk, got {:?}", msg);
                        Err(Error::UnexpectedMessageType(msg.typ()))
                    },
                }?;
                log::trace!("Got UserauthPkOk");

                let signature = sign_userauth(&keypair, &session_id, username, service_name, &ed25519_pub)?;

                writer.send(&UserauthRequest::PublicKey {
                    username,
                    service_name,
                    algorithm,
                    blob: ed25519_pub,
                    signature: Some(Blob {
                        blob_len: ed25519_blob_len(64),
                        header: algorithm,
                        content: &signature,
                    }),
                })?;
            },
        }

        log::trace!("Awaiting UserauthSuccess");
        match reader.recv()? {
            Message::UserauthSuccess(_) => Ok((/* nice */)),
            Message::UserauthFailure(_) => Err(Error::AuthenticationFailure),
            msg => {
                log::error!("Expected UserauthSuccess, got {:?}", msg);
                Err(Error::UnexpectedMessageType(msg.typ()))
            },
        }?;
        log::trace!("Got UserauthSuccess");

        Ok(Self {
            reader,
            writer,
            next_client_channel: 0,
        })
    }

    /// Gives access to the internal stream, allowing to change
    /// its parameters
    pub fn mutate_stream<F: Fn(&mut TcpStream)>(&mut self, func: F) {
        func(self.reader.inner.get_mut())
    }
}

pub struct KeyExchangeOutput {
    c2s_iv:   [u8; 16],
    s2c_iv:   [u8; 16],
    c2s_key:  [u8; 32],
    s2c_key:  [u8; 32],
    c2s_hmac: [u8; 32],
    s2c_hmac: [u8; 32],
}

impl KeyExchangeOutput {
    fn fill_array<const N: usize>(
        dumped_shared_secret: &[u8],
        exchange_hash: &[u8],
        session_id: &[u8],
        magic_byte: u8,
    ) -> Result<[u8; N]> {
        let mut out_key = [0u8; N];
        let mut progress = 0;

        let mut appendage = sha256(&[
            dumped_shared_secret,
            exchange_hash,
            &[magic_byte],
            session_id,
        ].as_slice())?;

        loop {
            let len = appendage.len().min(N - progress);
            out_key[progress..][..len].copy_from_slice(&appendage[..len]);
            progress += len;

            if progress != N {
                appendage = sha256(&[
                    dumped_shared_secret,
                    exchange_hash,
                    &out_key[..progress],
                ].as_slice())?;
            } else {
                break;
            }
        }

        Ok(out_key)
    }

    pub fn new(shared_secret: UnsignedMpInt, exchange_hash: &[u8], session_id: &[u8]) -> Result<Self> {
        let mut dumped_shared_secret = Vec::new();
        shared_secret.dump(&mut dumped_shared_secret)?;
        let dumped_shared_secret = dumped_shared_secret.as_slice();

        let kex_output_16 = |magic_byte| Self::fill_array(dumped_shared_secret, exchange_hash, session_id, magic_byte);
        let c2s_iv:   [u8; 16] = kex_output_16(b'A')?;
        let s2c_iv:   [u8; 16] = kex_output_16(b'B')?;

        let kex_output_32 = |magic_byte| Self::fill_array(dumped_shared_secret, exchange_hash, session_id, magic_byte);
        let c2s_key:  [u8; 32] = kex_output_32(b'C')?;
        let s2c_key:  [u8; 32] = kex_output_32(b'D')?;
        let c2s_hmac: [u8; 32] = kex_output_32(b'E')?;
        let s2c_hmac: [u8; 32] = kex_output_32(b'F')?;

        Ok(Self {
            c2s_iv,
            s2c_iv,
            c2s_key,
            s2c_key,
            c2s_hmac,
            s2c_hmac,
        })
    }
}

impl<'a> From<(&'a str, &'a str)> for Auth<'a> {
    fn from(tuple: (&'a str, &'a str)) -> Auth<'a> {
        let (username, hex_keypair) = tuple;
        Self::Ed25519 {
            username,
            hex_keypair,
        }
    }
}

impl core::fmt::Debug for Connection {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Connection").finish()
    }
}