metalssh 0.0.1

Experimental SSH implementation
//! SSH key derivation function as described in [RFC 4253][1].
//!
//! Derives the key into the given output buffer, which is expected to be sized
//! to the proper length.
//!
//! [1]: https://datatracker.ietf.org/doc/html/rfc4253#autoid-20

use crate::crypto::hash::HashFn;
use crate::types::Vec;

// Maximum input size: shared_secret (128) + exchange_hash (64 for SHA-512) +
// key_type (1) + session_id (64)
const MAX_INPUT_SIZE: usize = 257;

#[rustfmt::skip]
pub mod key_type {
    pub const INITIAL_IV_CLIENT_TO_SERVER: u8     = b'A';
    pub const INITIAL_IV_SERVER_TO_CLIENT: u8     = b'B';
    pub const ENCRYPTION_KEY_CLIENT_TO_SERVER: u8 = b'C';
    pub const ENCRYPTION_KEY_SERVER_TO_CLIENT: u8 = b'D';
    pub const INTEGRITY_KEY_CLIENT_TO_SERVER: u8  = b'E';
    pub const INTEGRITY_KEY_SERVER_TO_CLIENT: u8  = b'F';
}

/// SSHKDF implementation.
pub fn sshkdf<H, const N: usize>(
    shared_secret: &[u8],
    exchange_hash: &[u8],
    key_type: u8,
    session_id: &[u8],
    output: &mut [u8],
) where
    H: HashFn<N>,
{
    if output.is_empty() {
        return;
    }

    let hash_len = N;

    let mut input: Vec<u8, MAX_INPUT_SIZE> = Vec::new();

    // Temporary buffer for hash output (sized for the hash function's output
    // length)
    let mut temp_hash = [0u8; 64];
    let temp_hash_slice = &mut temp_hash[..hash_len];

    // Build the initial input: K || H || key_type || session_id
    // Where K is the shared secret, H is the exchange hash
    input.extend_from_slice(shared_secret);
    input.extend_from_slice(exchange_hash);
    input.push(key_type);
    input.extend_from_slice(session_id);

    // Compute the first hash: HASH(K || H || key_type || session_id)
    let hash_result = H::hash(&[&input]);
    temp_hash_slice.copy_from_slice(&hash_result);

    // Copy as much as we can from the first hash
    let to_copy = output.len().min(hash_len);
    output[..to_copy].copy_from_slice(&temp_hash_slice[..to_copy]);

    let mut offset = to_copy;

    // If we need more key material, repeatedly hash: HASH(K || H ||
    // previous_output)
    while offset < output.len() {
        input.clear();
        input.extend_from_slice(shared_secret);
        input.extend_from_slice(exchange_hash);
        input.extend_from_slice(temp_hash_slice);

        let hash_result = H::hash(&[&input]);
        temp_hash_slice.copy_from_slice(&hash_result);

        let remaining = output.len() - offset;
        let to_copy = remaining.min(hash_len);
        output[offset..offset + to_copy].copy_from_slice(&temp_hash_slice[..to_copy]);

        offset += to_copy;
    }
}