chromium/third_party/cloud_authenticator/handshake/src/noise.rs

// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate crypto;

use crate::error::Error;
use alloc::vec::Vec;

const SYMMETRIC_KEY_SIZE: usize = 32;
const NONCE_LEN: usize = 12;

#[derive(Debug, PartialEq)]
pub enum HandshakeType {
    Nk, // https://noiseexplorer.com/patterns/NK/
}

// Helper to generate 2 keys.
fn hkdf2(
    ck: &[u8; SYMMETRIC_KEY_SIZE],
    ikm: &[u8],
) -> ([u8; SYMMETRIC_KEY_SIZE], [u8; SYMMETRIC_KEY_SIZE]) {
    let mut output = [0; crypto::SHA256_OUTPUT_LEN * 2];
    // unwrap: only fails if the output size is too large, but the output
    // size is small and fixed here.
    crypto::hkdf_sha256(ikm, ck, &[], &mut output).unwrap();
    let key1: [u8; SYMMETRIC_KEY_SIZE] = output[..SYMMETRIC_KEY_SIZE].try_into().unwrap();
    let key2: [u8; SYMMETRIC_KEY_SIZE] = output[SYMMETRIC_KEY_SIZE..].try_into().unwrap();
    (key1, key2)
}

#[derive(Debug, PartialEq)]
pub struct Noise {
    chaining_key: [u8; SYMMETRIC_KEY_SIZE],
    h: [u8; SYMMETRIC_KEY_SIZE],
    symmetric_key: [u8; SYMMETRIC_KEY_SIZE],
    symmetric_nonce: u32,
}

impl Noise {
    pub fn new(handshake_type: HandshakeType) -> Self {
        assert_eq!(handshake_type, HandshakeType::Nk);
        let mut chaining_key_in = [0; SYMMETRIC_KEY_SIZE];
        let nk_protocol_name = b"Noise_NK_P256_AESGCM_SHA256";
        chaining_key_in[..nk_protocol_name.len()].copy_from_slice(nk_protocol_name);
        Noise {
            chaining_key: chaining_key_in,
            h: chaining_key_in,
            symmetric_key: [0; SYMMETRIC_KEY_SIZE],
            symmetric_nonce: 0,
        }
    }

    pub fn mix_hash(&mut self, in_data: &[u8]) {
        // See https://www.noiseprotocol.org/noise.html#the-symmetricstate-object
        self.h = crypto::sha256_two_part(&self.h, in_data);
    }

    pub fn mix_key(&mut self, ikm: &[u8]) {
        // See https://www.noiseprotocol.org/noise.html#the-symmetricstate-object
        let derived_keys = hkdf2(&self.chaining_key, ikm);
        self.chaining_key.copy_from_slice(&derived_keys.0);
        self.initialize_key(&derived_keys.1);
    }

    #[cfg(test)]
    pub fn mix_key_and_hash(&mut self, ikm: &[u8]) {
        // See https://www.noiseprotocol.org/noise.html#the-symmetricstate-object
        let mut output = [0; SYMMETRIC_KEY_SIZE * 3];
        // unwrap: only fails if the output size is too large, but the output
        // size is small and fixed here.
        crypto::hkdf_sha256(&ikm, &self.chaining_key, &[], &mut output).unwrap();
        self.chaining_key.copy_from_slice(&output[..SYMMETRIC_KEY_SIZE]);
        self.mix_hash(&output[SYMMETRIC_KEY_SIZE..SYMMETRIC_KEY_SIZE * 2]);
        self.initialize_key(&output[SYMMETRIC_KEY_SIZE * 2..].try_into().unwrap());
    }

    fn next_nonce(&mut self) -> [u8; NONCE_LEN] {
        let mut nonce_bytes = [0; NONCE_LEN];
        nonce_bytes[0] = (self.symmetric_nonce >> 24) as u8;
        nonce_bytes[1] = (self.symmetric_nonce >> 16) as u8;
        nonce_bytes[2] = (self.symmetric_nonce >> 8) as u8;
        nonce_bytes[3] = self.symmetric_nonce as u8;
        self.symmetric_nonce += 1;
        nonce_bytes
    }

    pub fn encrypt_and_hash(&mut self, plaintext: &[u8]) -> Vec<u8> {
        let mut encrypted_data = Vec::from(plaintext);
        let nonce = self.next_nonce();
        crypto::aes_256_gcm_seal_in_place(
            &self.symmetric_key,
            &nonce,
            &self.h,
            &mut encrypted_data,
        );
        self.mix_hash(&encrypted_data);
        encrypted_data
    }

    pub fn decrypt_and_hash(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
        let h = self.h;
        self.mix_hash(ciphertext);

        let ciphertext = Vec::from(ciphertext);
        let nonce = self.next_nonce();
        let plaintext =
            crypto::aes_256_gcm_open_in_place(&self.symmetric_key, &nonce, &h, ciphertext)
                .map_err(|_| Error::DecryptFailed)?;
        Ok(plaintext)
    }

    pub fn handshake_hash(&self) -> [u8; SYMMETRIC_KEY_SIZE] {
        self.h
    }

    // |point| is the contents from either agreement::PublicKey::as_ref() or
    // agreement::UnparsedPublicKey::bytes().
    pub fn mix_hash_point(&mut self, point: &[u8]) {
        self.mix_hash(point);
    }

    pub fn traffic_keys(&self) -> ([u8; SYMMETRIC_KEY_SIZE], [u8; SYMMETRIC_KEY_SIZE]) {
        hkdf2(&self.chaining_key, &[])
    }

    fn initialize_key(&mut self, key: &[u8; SYMMETRIC_KEY_SIZE]) {
        // See https://www.noiseprotocol.org/noise.html#the-cipherstate-object
        self.symmetric_key.copy_from_slice(key);
        self.symmetric_nonce = 0;
    }
}

#[cfg(test)]
mod tests {
    extern crate hex;

    use super::*;

    // The golden values embedded in these tests were generated by using the
    // Noise code from the reference implementation:
    // http://google3/experimental/users/agl/cablespec/noise.go;l=1;rcl=549756285
    #[test]
    fn mix_hash() {
        let mut noise = Noise::new(HandshakeType::Nk);
        noise.mix_hash("mixHash".as_bytes());
        assert_eq!(
            hex::encode(noise.handshake_hash()),
            "04d999779401b40a318f8729c99bec79cc15ec375f4a0bb3de1a00965b61d666"
        );
    }

    #[test]
    fn mix_hash_point() {
        let mut noise = Noise::new(HandshakeType::Nk);
        let x962_point: [u8; 65] = [
            0x04, 0x6f, 0x40, 0x4f, 0xbd, 0xa2, 0x1f, 0x6f, 0x26, 0x26, 0x11, 0xe2, 0x00, 0x5c,
            0x57, 0x14, 0x21, 0x72, 0x5c, 0xcb, 0xe8, 0xdd, 0x88, 0xfd, 0xd3, 0x63, 0xb8, 0x20,
            0xde, 0x29, 0x51, 0x67, 0xd0, 0x8d, 0x49, 0x88, 0x07, 0x7e, 0xc5, 0x21, 0x36, 0xd7,
            0x2f, 0x6c, 0xc0, 0x58, 0xee, 0x9a, 0x78, 0x5c, 0xf6, 0xb1, 0x91, 0xc3, 0xd2, 0xaa,
            0x1e, 0x3f, 0x5f, 0x20, 0xb0, 0xea, 0x9b, 0x2b, 0xa0,
        ];
        noise.mix_hash_point(&x962_point);
        assert_eq!(
            hex::encode(noise.handshake_hash()),
            "53741f8d5a69c11d8f6f0865193f15f6d756b0d209fd7116b400a4a8a39439b8"
        );
    }

    #[test]
    fn mix_key_then_encrypt_and_hash() {
        let mut noise = Noise::new(HandshakeType::Nk);
        noise.mix_key("encryptAndHash".as_bytes());
        let ciphertext = noise.encrypt_and_hash("plaintext".as_bytes());
        assert_eq!(hex::encode(ciphertext), "fdf1564256dcee03b5babe81df599ec4273c95e4269d747764");
        assert_eq!(
            hex::encode(noise.handshake_hash()),
            "1cf5b0af5f3365d715e9137382a5fd139a56e25bea8ecdbf3018f42af7432a75"
        );
    }

    #[test]
    fn mix_key_and_hash_then_encrypt_and_hash() {
        let mut noise = Noise::new(HandshakeType::Nk);
        noise.mix_key_and_hash("encryptAndHash".as_bytes());
        let ciphertext = noise.encrypt_and_hash("plaintext".as_bytes());
        assert_eq!(hex::encode(ciphertext), "2693c80637c7fd9e686949c46a4d189d41ba7cf74a53a85752");
        assert_eq!(
            hex::encode(noise.handshake_hash()),
            "b883ed0aa2303502e497c8609a979970bc6292cc316eafa7fc0c434f818b9e01"
        );
    }

    #[test]
    fn decrypt_and_hash() {
        let mut noise = Noise::new(HandshakeType::Nk);
        noise.mix_key("encryptAndHash".as_bytes());
        // This test uses the values from MixKeyThenEncryptAndHash, but in the
        // other direction.
        let plaintext = noise
            .decrypt_and_hash(
                &hex::decode("fdf1564256dcee03b5babe81df599ec4273c95e4269d747764").unwrap(),
            )
            .unwrap();
        assert_eq!(hex::encode(&plaintext), "706c61696e74657874");
        assert_eq!(
            hex::encode(noise.handshake_hash()),
            "1cf5b0af5f3365d715e9137382a5fd139a56e25bea8ecdbf3018f42af7432a75"
        );
    }

    #[test]
    fn split() {
        let mut noise = Noise::new(HandshakeType::Nk);
        noise.mix_key_and_hash("split".as_bytes());
        let keys = noise.traffic_keys();
        assert_eq!(
            hex::encode(keys.0),
            "1f73215029f964ce0a65ef92eaf97bd67c45feff8e49cca94fdf5050aaf25a58"
        );
        assert_eq!(
            hex::encode(keys.1),
            "0c7f90f88233858ecbd6492c273ed328acaef75ebcb31fd2f94033c5d296a623"
        );
    }

    #[test]
    fn combined() {
        let mut noise = Noise::new(HandshakeType::Nk);
        noise.mix_hash("mixHash".as_bytes());
        noise.mix_key("mixKey".as_bytes());
        noise.mix_key_and_hash("mixKeyAndHash".as_bytes());
        let ciphertext = noise.encrypt_and_hash("plaintext".as_bytes());
        assert_eq!(hex::encode(ciphertext), "b7235b3d77c9cf5ebb087793b399de2c2276edae52bba5199e");
        assert_eq!(
            hex::encode(noise.handshake_hash()),
            "db420fd8f9b072eacca4599019303f02b1938fb9096e3b4b063afebc687c9987"
        );
    }
}