metalssh 0.0.1

Experimental SSH implementation
//! Cryptographic hash functions.

use aws_lc_rs::digest::Algorithm;
use aws_lc_rs::digest::Context;
use aws_lc_rs::digest::SHA256;
use aws_lc_rs::digest::SHA256_OUTPUT_LEN;
use aws_lc_rs::digest::SHA512;
use aws_lc_rs::digest::SHA512_OUTPUT_LEN;

/// Abstraction for a hash algorithm.
pub trait HashFn<const N: usize> {
    /// Calculates the hash of the data.
    fn hash(data: &[&[u8]]) -> [u8; N];

    /// Calculates the SSH exchange hash.
    #[must_use]
    #[allow(clippy::too_many_arguments)]
    fn exchange_hash(
        banner_client: &[u8],
        banner_server: &[u8],
        kexinit_payload_client: &[u8],
        kexinit_payload_server: &[u8],
        public_host_key_server: &[u8],
        ephemeral_public_key_client: &[u8],
        ephemeral_public_key_server: &[u8],
        shared_secret: &[u8],
    ) -> [u8; N] {
        Self::hash(&[
            banner_client,
            banner_server,
            kexinit_payload_client,
            kexinit_payload_server,
            public_host_key_server,
            ephemeral_public_key_client,
            ephemeral_public_key_server,
            shared_secret,
        ])
    }
}

/// SHA-256 (SHA-2) hashing algorithm.
pub struct Sha256;

impl HashFn<SHA256_OUTPUT_LEN> for Sha256 {
    fn hash(data: &[&[u8]]) -> [u8; SHA256_OUTPUT_LEN] {
        hash_inner(&SHA256, data)
    }
}

/// SHA-512 (SHA-2) hashing algorithm.
pub struct Sha512;

impl HashFn<SHA512_OUTPUT_LEN> for Sha512 {
    fn hash(data: &[&[u8]]) -> [u8; SHA512_OUTPUT_LEN] {
        hash_inner(&SHA512, data)
    }
}

fn hash_inner<const N: usize>(algorithm: &'static Algorithm, data: &[&[u8]]) -> [u8; N] {
    let mut ctx = Context::new(algorithm);
    for d in data {
        ctx.update(d);
    }
    *ctx.finish()
        .as_ref()
        .as_array()
        .expect("Output will always be hash length")
}

#[cfg(test)]
mod tests {
    //! Test vectors taken from [here](https://di-mgt.com.au/sha_testvectors.html).

    use rstest::rstest;

    use super::*;

    #[rstest]
    #[case(
        b"abc",
        "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
    )]
    #[case(
        b"",
        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    )]
    #[case(
        b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
        "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"
    )]
    fn sha256_works(#[case] input: &[u8], #[case] hash_should: &str) {
        let hash_should = hex::decode(hash_should).unwrap();
        let hash_got = Sha256::hash(&[input]);
        assert_eq!(hash_got.as_slice(), &hash_should);
    }

    #[rstest]
    #[case(
        b"abc",
        "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
    )]
    #[case(
        b"",
        "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
    )]
    #[case(
        b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
        "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
    )]
    fn sha512_works(#[case] input: &[u8], #[case] hash_should: &str) {
        let hash_should = hex::decode(hash_should).unwrap();
        let hash_got = Sha512::hash(&[input]);
        assert_eq!(hash_got.as_slice(), &hash_should);
    }
}