lib.rs

31.5 KB · raw

1//! Canonical Peach Noise state machine.2//!3//! The browser and Android adapters are deliberately thin: all NK/KK4//! transcript processing, DH, AEAD, nonce handling, and transport rekeying5//! happen here through `snow`'s unmodified named patterns.67use snow::{8    Builder, Error, HandshakeState, TransportState,9    params::{CipherChoice, DHChoice, HashChoice, NoiseParams},10    resolvers::{CryptoResolver, DefaultResolver},11    types::{Cipher, Dh, Hash, Random},12};13use subtle::ConstantTimeEq;14use x25519_dalek::{PublicKey, StaticSecret};15use zeroize::{Zeroize, Zeroizing};1617const NK: &str = "Noise_NK_25519_ChaChaPoly_BLAKE2s";18const KK: &str = "Noise_KK_25519_ChaChaPoly_BLAKE2s";19const HASH_LEN: usize = 32;20const TAG_LEN: usize = 16;21const MAX_NOISE_MESSAGE: usize = 65_535;2223type CoreResult<T> = Result<T, String>;2425enum SessionState {26    Handshake(Option<HandshakeState>),27    Transport(TransportState),28    Closed,29}3031pub struct CoreNoiseSession {32    state: SessionState,33    handshake_hash: Option<[u8; HASH_LEN]>,34}3536impl CoreNoiseSession {37    pub fn nk_initiator(remote_public_key: &[u8]) -> CoreResult<Self> {38        Self::nk_initiator_inner(remote_public_key, None)39    }4041    #[doc(hidden)]42    pub fn nk_initiator_with_ephemeral_for_test(43        remote_public_key: &[u8],44        ephemeral_private_key: &[u8],45    ) -> CoreResult<Self> {46        Self::nk_initiator_inner(remote_public_key, Some(ephemeral_private_key))47    }4849    fn nk_initiator_inner(50        remote_public_key: &[u8],51        ephemeral_private_key: Option<&[u8]>,52    ) -> CoreResult<Self> {53        require_key(remote_public_key, "responder public key")?;54        let params: NoiseParams = NK.parse().map_err(display_error)?;55        let mut builder = core_builder(params)56            .remote_public_key(remote_public_key)57            .map_err(display_error)?;58        if let Some(ephemeral) = ephemeral_private_key {59            require_key(ephemeral, "initiator ephemeral private key")?;60            builder = builder.fixed_ephemeral_key_for_testing_only(ephemeral);61        }62        let handshake = builder.build_initiator().map_err(display_error)?;63        Ok(Self::handshake(handshake))64    }6566    pub fn nk_responder(local_private_key: &[u8], local_public_key: &[u8]) -> CoreResult<Self> {67        Self::nk_responder_inner(local_private_key, local_public_key, None)68    }6970    #[doc(hidden)]71    pub fn nk_responder_with_ephemeral_for_test(72        local_private_key: &[u8],73        local_public_key: &[u8],74        ephemeral_private_key: &[u8],75    ) -> CoreResult<Self> {76        Self::nk_responder_inner(77            local_private_key,78            local_public_key,79            Some(ephemeral_private_key),80        )81    }8283    fn nk_responder_inner(84        local_private_key: &[u8],85        local_public_key: &[u8],86        ephemeral_private_key: Option<&[u8]>,87    ) -> CoreResult<Self> {88        require_matching_keypair(local_private_key, local_public_key)?;89        let private = Zeroizing::new(local_private_key.to_vec());90        let params: NoiseParams = NK.parse().map_err(display_error)?;91        let mut builder = core_builder(params)92            .local_private_key(private.as_slice())93            .map_err(display_error)?;94        if let Some(ephemeral) = ephemeral_private_key {95            require_key(ephemeral, "responder ephemeral private key")?;96            builder = builder.fixed_ephemeral_key_for_testing_only(ephemeral);97        }98        let handshake = builder.build_responder().map_err(display_error)?;99        Ok(Self::handshake(handshake))100    }101102    pub fn kk_initiator(103        local_private_key: &[u8],104        local_public_key: &[u8],105        remote_public_key: &[u8],106    ) -> CoreResult<Self> {107        Self::kk_initiator_inner(local_private_key, local_public_key, remote_public_key, None)108    }109110    #[doc(hidden)]111    pub fn kk_initiator_with_ephemeral_for_test(112        local_private_key: &[u8],113        local_public_key: &[u8],114        remote_public_key: &[u8],115        ephemeral_private_key: &[u8],116    ) -> CoreResult<Self> {117        Self::kk_initiator_inner(118            local_private_key,119            local_public_key,120            remote_public_key,121            Some(ephemeral_private_key),122        )123    }124125    fn kk_initiator_inner(126        local_private_key: &[u8],127        local_public_key: &[u8],128        remote_public_key: &[u8],129        ephemeral_private_key: Option<&[u8]>,130    ) -> CoreResult<Self> {131        require_matching_keypair(local_private_key, local_public_key)?;132        require_key(remote_public_key, "responder public key")?;133        let private = Zeroizing::new(local_private_key.to_vec());134        let params: NoiseParams = KK.parse().map_err(display_error)?;135        let mut builder = core_builder(params)136            .local_private_key(private.as_slice())137            .map_err(display_error)?138            .remote_public_key(remote_public_key)139            .map_err(display_error)?;140        if let Some(ephemeral) = ephemeral_private_key {141            require_key(ephemeral, "initiator ephemeral private key")?;142            builder = builder.fixed_ephemeral_key_for_testing_only(ephemeral);143        }144        let handshake = builder.build_initiator().map_err(display_error)?;145        Ok(Self::handshake(handshake))146    }147148    pub fn kk_responder(149        local_private_key: &[u8],150        local_public_key: &[u8],151        remote_public_key: &[u8],152    ) -> CoreResult<Self> {153        Self::kk_responder_inner(local_private_key, local_public_key, remote_public_key, None)154    }155156    #[doc(hidden)]157    pub fn kk_responder_with_ephemeral_for_test(158        local_private_key: &[u8],159        local_public_key: &[u8],160        remote_public_key: &[u8],161        ephemeral_private_key: &[u8],162    ) -> CoreResult<Self> {163        Self::kk_responder_inner(164            local_private_key,165            local_public_key,166            remote_public_key,167            Some(ephemeral_private_key),168        )169    }170171    fn kk_responder_inner(172        local_private_key: &[u8],173        local_public_key: &[u8],174        remote_public_key: &[u8],175        ephemeral_private_key: Option<&[u8]>,176    ) -> CoreResult<Self> {177        require_matching_keypair(local_private_key, local_public_key)?;178        require_key(remote_public_key, "initiator public key")?;179        let private = Zeroizing::new(local_private_key.to_vec());180        let params: NoiseParams = KK.parse().map_err(display_error)?;181        let mut builder = core_builder(params)182            .local_private_key(private.as_slice())183            .map_err(display_error)?184            .remote_public_key(remote_public_key)185            .map_err(display_error)?;186        if let Some(ephemeral) = ephemeral_private_key {187            require_key(ephemeral, "responder ephemeral private key")?;188            builder = builder.fixed_ephemeral_key_for_testing_only(ephemeral);189        }190        let handshake = builder.build_responder().map_err(display_error)?;191        Ok(Self::handshake(handshake))192    }193194    fn handshake(handshake: HandshakeState) -> Self {195        Self {196            state: SessionState::Handshake(Some(handshake)),197            handshake_hash: None,198        }199    }200201    pub fn write_handshake(&mut self, payload: &[u8]) -> CoreResult<Vec<u8>> {202        let handshake = self.handshake_mut()?;203        if !handshake.is_my_turn() {204            return Err("Noise handshake write attempted out of turn".into());205        }206        let mut output = vec![0_u8; MAX_NOISE_MESSAGE];207        let size = handshake208            .write_message(payload, &mut output)209            .map_err(display_error)?;210        output.truncate(size);211        self.finish_if_ready()?;212        Ok(output)213    }214215    pub fn read_handshake(&mut self, message: &[u8]) -> CoreResult<Vec<u8>> {216        if message.len() > MAX_NOISE_MESSAGE {217            return Err("Noise handshake message exceeds 65535 bytes".into());218        }219        let handshake = self.handshake_mut()?;220        if handshake.is_my_turn() {221            return Err("Noise handshake read attempted out of turn".into());222        }223        let mut payload = vec![0_u8; MAX_NOISE_MESSAGE];224        let size = handshake225            .read_message(message, &mut payload)226            .map_err(display_error)?;227        payload.truncate(size);228        self.finish_if_ready()?;229        Ok(payload)230    }231232    pub fn encrypt(&mut self, plaintext: &[u8]) -> CoreResult<Vec<u8>> {233        if plaintext.len() > MAX_NOISE_MESSAGE - TAG_LEN {234            return Err("Noise plaintext exceeds transport limit".into());235        }236        let transport = self.transport_mut()?;237        let mut output = vec![0_u8; plaintext.len() + TAG_LEN];238        let size = transport239            .write_message(plaintext, &mut output)240            .map_err(display_error)?;241        output.truncate(size);242        Ok(output)243    }244245    pub fn decrypt(&mut self, ciphertext: &[u8]) -> CoreResult<Vec<u8>> {246        if ciphertext.len() > MAX_NOISE_MESSAGE {247            return Err("Noise ciphertext exceeds transport limit".into());248        }249        let transport = self.transport_mut()?;250        let mut output = vec![0_u8; ciphertext.len()];251        let size = transport252            .read_message(ciphertext, &mut output)253            .map_err(display_error)?;254        output.truncate(size);255        Ok(output)256    }257258    pub fn handshake_hash(&self) -> CoreResult<Vec<u8>> {259        self.handshake_hash260            .map(|hash| hash.to_vec())261            .ok_or_else(|| "Noise handshake is not complete".into())262    }263264    pub fn rekey_outgoing(&mut self) -> CoreResult<()> {265        self.transport_mut()?.rekey_outgoing();266        Ok(())267    }268269    pub fn rekey_incoming(&mut self) -> CoreResult<()> {270        self.transport_mut()?.rekey_incoming();271        Ok(())272    }273274    fn handshake_mut(&mut self) -> CoreResult<&mut HandshakeState> {275        match &mut self.state {276            SessionState::Handshake(Some(handshake)) => Ok(handshake),277            SessionState::Handshake(None) => Err("Noise handshake is transitioning".into()),278            SessionState::Transport(_) => Err("Noise handshake is already complete".into()),279            SessionState::Closed => Err("Noise session is closed".into()),280        }281    }282283    fn transport_mut(&mut self) -> CoreResult<&mut TransportState> {284        match &mut self.state {285            SessionState::Transport(transport) => Ok(transport),286            SessionState::Handshake(_) => Err("Noise handshake is not complete".into()),287            SessionState::Closed => Err("Noise session is closed".into()),288        }289    }290291    fn finish_if_ready(&mut self) -> CoreResult<()> {292        let ready = matches!(293            &self.state,294            SessionState::Handshake(Some(handshake)) if handshake.is_handshake_finished()295        );296        if !ready {297            return Ok(());298        }299300        let handshake = match std::mem::replace(&mut self.state, SessionState::Closed) {301            SessionState::Handshake(Some(handshake)) => handshake,302            _ => return Err("Noise session transition failed".into()),303        };304        let hash: [u8; HASH_LEN] = handshake305            .get_handshake_hash()306            .try_into()307            .map_err(|_| "Noise handshake hash must be 32 bytes".to_string())?;308        let transport = handshake.into_transport_mode().map_err(display_error)?;309        self.handshake_hash = Some(hash);310        self.state = SessionState::Transport(transport);311        Ok(())312    }313}314315impl Drop for CoreNoiseSession {316    fn drop(&mut self) {317        if let Some(hash) = &mut self.handshake_hash {318            hash.zeroize();319        }320        self.state = SessionState::Closed;321    }322}323324fn require_key(key: &[u8], label: &str) -> CoreResult<()> {325    if key.len() != 32 {326        return Err(format!("{label} must be 32 bytes"));327    }328    Ok(())329}330331fn require_matching_keypair(private_key: &[u8], public_key: &[u8]) -> CoreResult<()> {332    require_key(private_key, "local private key")?;333    require_key(public_key, "local public key")?;334    let private: [u8; 32] = private_key.try_into().expect("length checked");335    let derived = PublicKey::from(&StaticSecret::from(private));336    if derived.as_bytes().ct_eq(public_key).unwrap_u8() != 1 {337        return Err("local public key does not match local private key".into());338    }339    Ok(())340}341342fn display_error(error: impl std::fmt::Display) -> String {343    error.to_string()344}345346fn core_builder(params: NoiseParams) -> Builder<'static> {347    Builder::with_resolver(params, Box::new(RejectZeroResolver))348}349350struct RejectZeroResolver;351352impl CryptoResolver for RejectZeroResolver {353    fn resolve_rng(&self) -> Option<Box<dyn Random>> {354        DefaultResolver.resolve_rng()355    }356357    fn resolve_dh(&self, choice: &DHChoice) -> Option<Box<dyn Dh>> {358        DefaultResolver359            .resolve_dh(choice)360            .map(|inner| Box::new(RejectZeroDh { inner }) as Box<dyn Dh>)361    }362363    fn resolve_hash(&self, choice: &HashChoice) -> Option<Box<dyn Hash>> {364        DefaultResolver.resolve_hash(choice)365    }366367    fn resolve_cipher(&self, choice: &CipherChoice) -> Option<Box<dyn Cipher>> {368        DefaultResolver.resolve_cipher(choice)369    }370}371372struct RejectZeroDh {373    inner: Box<dyn Dh>,374}375376impl Dh for RejectZeroDh {377    fn name(&self) -> &'static str {378        self.inner.name()379    }380381    fn pub_len(&self) -> usize {382        self.inner.pub_len()383    }384385    fn priv_len(&self) -> usize {386        self.inner.priv_len()387    }388389    fn set(&mut self, private_key: &[u8]) {390        self.inner.set(private_key);391    }392393    fn generate(&mut self, rng: &mut dyn Random) -> Result<(), Error> {394        self.inner.generate(rng)395    }396397    fn pubkey(&self) -> &[u8] {398        self.inner.pubkey()399    }400401    fn privkey(&self) -> &[u8] {402        self.inner.privkey()403    }404405    fn dh(&self, public_key: &[u8], output: &mut [u8]) -> Result<(), Error> {406        self.inner.dh(public_key, output)?;407        let used = self.inner.dh_len();408        if output[..used].ct_eq(&vec![0_u8; used]).unwrap_u8() == 1 {409            output[..used].zeroize();410            return Err(Error::Dh);411        }412        Ok(())413    }414415    fn dh_len(&self) -> usize {416        self.inner.dh_len()417    }418}419420#[cfg(target_arch = "wasm32")]421mod wasm {422    use super::CoreNoiseSession;423    use wasm_bindgen::prelude::*;424425    fn js_error(message: String) -> JsError {426        JsError::new(&message)427    }428429    #[wasm_bindgen]430    pub struct WasmNoiseSession {431        inner: CoreNoiseSession,432    }433434    #[wasm_bindgen]435    impl WasmNoiseSession {436        #[wasm_bindgen(js_name = nkInitiator)]437        pub fn nk_initiator(remote_public_key: &[u8]) -> Result<WasmNoiseSession, JsError> {438            Ok(Self {439                inner: CoreNoiseSession::nk_initiator(remote_public_key).map_err(js_error)?,440            })441        }442443        #[wasm_bindgen(js_name = nkResponder)]444        pub fn nk_responder(445            local_private_key: &[u8],446            local_public_key: &[u8],447        ) -> Result<WasmNoiseSession, JsError> {448            Ok(Self {449                inner: CoreNoiseSession::nk_responder(local_private_key, local_public_key)450                    .map_err(js_error)?,451            })452        }453454        #[wasm_bindgen(js_name = nkInitiatorWithEphemeralForTest)]455        pub fn nk_initiator_with_ephemeral_for_test(456            remote_public_key: &[u8],457            ephemeral_private_key: &[u8],458        ) -> Result<WasmNoiseSession, JsError> {459            Ok(Self {460                inner: CoreNoiseSession::nk_initiator_with_ephemeral_for_test(461                    remote_public_key,462                    ephemeral_private_key,463                )464                .map_err(js_error)?,465            })466        }467468        #[wasm_bindgen(js_name = nkResponderWithEphemeralForTest)]469        pub fn nk_responder_with_ephemeral_for_test(470            local_private_key: &[u8],471            local_public_key: &[u8],472            ephemeral_private_key: &[u8],473        ) -> Result<WasmNoiseSession, JsError> {474            Ok(Self {475                inner: CoreNoiseSession::nk_responder_with_ephemeral_for_test(476                    local_private_key,477                    local_public_key,478                    ephemeral_private_key,479                )480                .map_err(js_error)?,481            })482        }483484        #[wasm_bindgen(js_name = kkInitiator)]485        pub fn kk_initiator(486            local_private_key: &[u8],487            local_public_key: &[u8],488            remote_public_key: &[u8],489        ) -> Result<WasmNoiseSession, JsError> {490            Ok(Self {491                inner: CoreNoiseSession::kk_initiator(492                    local_private_key,493                    local_public_key,494                    remote_public_key,495                )496                .map_err(js_error)?,497            })498        }499500        #[wasm_bindgen(js_name = kkResponder)]501        pub fn kk_responder(502            local_private_key: &[u8],503            local_public_key: &[u8],504            remote_public_key: &[u8],505        ) -> Result<WasmNoiseSession, JsError> {506            Ok(Self {507                inner: CoreNoiseSession::kk_responder(508                    local_private_key,509                    local_public_key,510                    remote_public_key,511                )512                .map_err(js_error)?,513            })514        }515516        #[wasm_bindgen(js_name = kkInitiatorWithEphemeralForTest)]517        pub fn kk_initiator_with_ephemeral_for_test(518            local_private_key: &[u8],519            local_public_key: &[u8],520            remote_public_key: &[u8],521            ephemeral_private_key: &[u8],522        ) -> Result<WasmNoiseSession, JsError> {523            Ok(Self {524                inner: CoreNoiseSession::kk_initiator_with_ephemeral_for_test(525                    local_private_key,526                    local_public_key,527                    remote_public_key,528                    ephemeral_private_key,529                )530                .map_err(js_error)?,531            })532        }533534        #[wasm_bindgen(js_name = kkResponderWithEphemeralForTest)]535        pub fn kk_responder_with_ephemeral_for_test(536            local_private_key: &[u8],537            local_public_key: &[u8],538            remote_public_key: &[u8],539            ephemeral_private_key: &[u8],540        ) -> Result<WasmNoiseSession, JsError> {541            Ok(Self {542                inner: CoreNoiseSession::kk_responder_with_ephemeral_for_test(543                    local_private_key,544                    local_public_key,545                    remote_public_key,546                    ephemeral_private_key,547                )548                .map_err(js_error)?,549            })550        }551552        #[wasm_bindgen(js_name = writeHandshake)]553        pub fn write_handshake(&mut self, payload: &[u8]) -> Result<Vec<u8>, JsError> {554            self.inner.write_handshake(payload).map_err(js_error)555        }556557        #[wasm_bindgen(js_name = readHandshake)]558        pub fn read_handshake(&mut self, message: &[u8]) -> Result<Vec<u8>, JsError> {559            self.inner.read_handshake(message).map_err(js_error)560        }561562        pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, JsError> {563            self.inner.encrypt(plaintext).map_err(js_error)564        }565566        pub fn decrypt(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>, JsError> {567            self.inner.decrypt(ciphertext).map_err(js_error)568        }569570        #[wasm_bindgen(getter, js_name = handshakeHash)]571        pub fn handshake_hash(&self) -> Result<Vec<u8>, JsError> {572            self.inner.handshake_hash().map_err(js_error)573        }574575        #[wasm_bindgen(js_name = rekeyOutgoing)]576        pub fn rekey_outgoing(&mut self) -> Result<(), JsError> {577            self.inner.rekey_outgoing().map_err(js_error)578        }579580        #[wasm_bindgen(js_name = rekeyIncoming)]581        pub fn rekey_incoming(&mut self) -> Result<(), JsError> {582            self.inner.rekey_incoming().map_err(js_error)583        }584    }585}586587#[cfg(not(target_arch = "wasm32"))]588mod jni_bridge;589590#[cfg(test)]591mod tests {592    use super::{CoreNoiseSession, KK, NK, core_builder};593    use blake2::{594        Blake2sMac256,595        digest::{KeyInit as BlakeKeyInit, Mac},596    };597    use chacha20poly1305::{598        ChaCha20Poly1305, Nonce,599        aead::{Aead, Payload},600    };601    use snow::params::NoiseParams;602    use x25519_dalek::{PublicKey, StaticSecret};603604    fn keypair(seed: u8) -> ([u8; 32], [u8; 32]) {605        let private = [seed; 32];606        let public = *PublicKey::from(&StaticSecret::from(private)).as_bytes();607        (private, public)608    }609610    fn hex(value: &str) -> Vec<u8> {611        assert!(value.len().is_multiple_of(2));612        value613            .as_bytes()614            .chunks_exact(2)615            .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap())616            .collect()617    }618619    fn official_pair(620        pattern: &str,621        initiator_static: Option<&str>,622        initiator_ephemeral: &str,623        initiator_remote_static: &str,624        responder_static: &str,625        responder_ephemeral: &str,626        responder_remote_static: Option<&str>,627    ) -> (CoreNoiseSession, CoreNoiseSession) {628        let prologue = hex("4a6f686e2047616c74");629        let responder_private = hex(responder_static);630        let responder_public = PublicKey::from(&StaticSecret::from(631            <[u8; 32]>::try_from(&responder_private[..]).unwrap(),632        ));633        assert_eq!(634            responder_public.as_bytes(),635            &hex(initiator_remote_static)[..]636        );637638        let params: NoiseParams = pattern.parse().unwrap();639        let initiator_ephemeral = hex(initiator_ephemeral);640        let initiator_static = initiator_static.map(hex);641        let responder_ephemeral = hex(responder_ephemeral);642        let responder_remote_static = responder_remote_static.map(hex);643        let mut initiator_builder = core_builder(params.clone())644            .prologue(&prologue)645            .unwrap()646            .remote_public_key(responder_public.as_bytes())647            .unwrap()648            .fixed_ephemeral_key_for_testing_only(&initiator_ephemeral);649        if let Some(private) = &initiator_static {650            initiator_builder = initiator_builder.local_private_key(private).unwrap();651        }652653        let mut responder_builder = core_builder(params)654            .prologue(&prologue)655            .unwrap()656            .local_private_key(&responder_private)657            .unwrap()658            .fixed_ephemeral_key_for_testing_only(&responder_ephemeral);659        if let Some(remote_public) = &responder_remote_static {660            responder_builder = responder_builder.remote_public_key(remote_public).unwrap();661        }662663        (664            CoreNoiseSession::handshake(initiator_builder.build_initiator().unwrap()),665            CoreNoiseSession::handshake(responder_builder.build_responder().unwrap()),666        )667    }668669    fn assert_cacophony_vector(670        mut initiator: CoreNoiseSession,671        mut responder: CoreNoiseSession,672        handshake_hash: &str,673        messages: &[(&str, &str)],674    ) {675        for (index, (payload, expected_ciphertext)) in messages.iter().enumerate() {676            let plaintext = hex(payload);677            let expected = hex(expected_ciphertext);678            if index < 2 {679                let (sender, receiver) = if index % 2 == 0 {680                    (&mut initiator, &mut responder)681                } else {682                    (&mut responder, &mut initiator)683                };684                let actual = sender.write_handshake(&plaintext).unwrap();685                assert_eq!(actual, expected, "handshake message {index}");686                assert_eq!(687                    receiver.read_handshake(&actual).unwrap(),688                    plaintext,689                    "handshake payload {index}",690                );691            } else {692                let (sender, receiver) = if index % 2 == 0 {693                    (&mut initiator, &mut responder)694                } else {695                    (&mut responder, &mut initiator)696                };697                let actual = sender.encrypt(&plaintext).unwrap();698                assert_eq!(actual, expected, "transport message {index}");699                assert_eq!(700                    receiver.decrypt(&actual).unwrap(),701                    plaintext,702                    "transport payload {index}",703                );704            }705        }706        let expected_hash = hex(handshake_hash);707        assert_eq!(initiator.handshake_hash().unwrap(), expected_hash);708        assert_eq!(responder.handshake_hash().unwrap(), expected_hash);709    }710711    fn complete(initiator: &mut CoreNoiseSession, responder: &mut CoreNoiseSession) {712        let message1 = initiator.write_handshake(&[]).unwrap();713        assert!(responder.read_handshake(&message1).unwrap().is_empty());714        let message2 = responder.write_handshake(&[]).unwrap();715        assert!(initiator.read_handshake(&message2).unwrap().is_empty());716        assert_eq!(717            initiator.handshake_hash().unwrap(),718            responder.handshake_hash().unwrap()719        );720    }721722    #[test]723    fn nk_round_trip_uses_one_state_machine() {724        let (responder_private, responder_public) = keypair(7);725        let mut initiator = CoreNoiseSession::nk_initiator(&responder_public).unwrap();726        let mut responder =727            CoreNoiseSession::nk_responder(&responder_private, &responder_public).unwrap();728        complete(&mut initiator, &mut responder);729730        let ciphertext = initiator.encrypt(b"peach").unwrap();731        assert_eq!(responder.decrypt(&ciphertext).unwrap(), b"peach");732        let response = responder.encrypt(b"sync").unwrap();733        assert_eq!(initiator.decrypt(&response).unwrap(), b"sync");734    }735736    #[test]737    fn kk_round_trip_authenticates_both_static_keys() {738        let (initiator_private, initiator_public) = keypair(11);739        let (responder_private, responder_public) = keypair(19);740        let mut initiator = CoreNoiseSession::kk_initiator(741            &initiator_private,742            &initiator_public,743            &responder_public,744        )745        .unwrap();746        let mut responder = CoreNoiseSession::kk_responder(747            &responder_private,748            &responder_public,749            &initiator_public,750        )751        .unwrap();752        complete(&mut initiator, &mut responder);753    }754755    #[test]756    fn mismatched_local_keypair_is_rejected() {757        let (private, _) = keypair(2);758        let (_, wrong_public) = keypair(3);759        let result = CoreNoiseSession::nk_responder(&private, &wrong_public);760        assert!(matches!(result, Err(error) if error.contains("does not match")));761    }762763    #[test]764    fn tampered_transport_frame_is_rejected() {765        let (responder_private, responder_public) = keypair(23);766        let mut initiator = CoreNoiseSession::nk_initiator(&responder_public).unwrap();767        let mut responder =768            CoreNoiseSession::nk_responder(&responder_private, &responder_public).unwrap();769        complete(&mut initiator, &mut responder);770        let mut ciphertext = initiator.encrypt(b"vault").unwrap();771        ciphertext[0] ^= 0x80;772        assert!(responder.decrypt(&ciphertext).is_err());773    }774775    #[test]776    fn cacophony_nk_vector_matches_shared_core() {777        let pair = official_pair(778            NK,779            None,780            "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",781            "31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62",782            "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",783            "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",784            None,785        );786        assert_cacophony_vector(787            pair.0,788            pair.1,789            "d7244d974066aae2376f7ba5534f60a6e4e82cd7c9751e226cae3928e6b49f14",790            &[791                (792                    "4c756477696720766f6e204d69736573",793                    "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c794454ae7612d1724af42adb130160a9a94e67b5b169b4e00c189f6467cd17eb7cad",794                ),795                (796                    "4d757272617920526f746862617264",797                    "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f144808843986a5c929337e337ac8b4a074af12ab9f76318a5f18c8b599a443af07383ce",798                ),799                (800                    "462e20412e20486179656b",801                    "550027c7a5d450017bcb5e12b8253b1c53fd2213aeda84891d5f95",802                ),803                (804                    "4361726c204d656e676572",805                    "dfbce0c38210ccee35e830aca9dd8b8b3997b933e75bfc8864b759",806                ),807            ],808        );809    }810811    #[test]812    fn cacophony_kk_vector_matches_shared_core() {813        let pair = official_pair(814            KK,815            Some("e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1"),816            "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",817            "31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62",818            "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",819            "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",820            Some("6bc3822a2aa7f4e6981d6538692b3cdf3e6df9eea6ed269eb41d93c22757b75a"),821        );822        assert_cacophony_vector(823            pair.0,824            pair.1,825            "1362b8627a00907ce11e558aba8ce7cbca88e83f0e84ce7db5159b1c3e25ab59",826            &[827                (828                    "4c756477696720766f6e204d69736573",829                    "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c7944266a5f53784aa3becb0f7485c2759c328937867a4cbaafef07422b0725e098be",830                ),831                (832                    "4d757272617920526f746862617264",833                    "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f144808843008aeea5d76d6abcbab87a18502c8a8352d9933ac11e2a7d228038d721e31e",834                ),835                (836                    "462e20412e20486179656b",837                    "5f92113edf78c3e56e6d67201f5f9e0c8f2930c3e1ffb64ede0358",838                ),839                (840                    "4361726c204d656e676572",841                    "30ebbd9cdcef7f40d99c8cd11e880dac28f5c9e5032c1059b3b56a",842                ),843            ],844        );845    }846847    #[test]848    fn rfc_8439_chacha20_poly1305_aead_vector_matches_snow_cipher() {849        let key = hex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");850        let nonce = hex("070000004041424344454647");851        let aad = hex("50515253c0c1c2c3c4c5c6c7");852        let plaintext = hex(concat!(853            "4c616469657320616e642047656e746c656d656e206f662074686520636c6173",854            "73206f66202739393a204966204920636f756c64206f6666657220796f75206f",855            "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73",856            "637265656e20776f756c642062652069742e",857        ));858        let expected = hex(concat!(859            "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6",860            "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36",861            "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc",862            "3ff4def08e4b7a9de576d26586cec64b6116",863            "1ae10b594f09e26a7e902ecbd0600691",864        ));865        let cipher = ChaCha20Poly1305::new_from_slice(&key).unwrap();866        assert_eq!(867            cipher868                .encrypt(869                    Nonce::from_slice(&nonce),870                    Payload {871                        msg: &plaintext,872                        aad: &aad,873                    },874                )875                .unwrap(),876            expected,877        );878    }879880    #[test]881    fn blake2_reference_keyed_vector_matches_snow_hash_dependency() {882        let key: Vec<u8> = (0_u8..32).collect();883        let mut mac = <Blake2sMac256 as BlakeKeyInit>::new_from_slice(&key).unwrap();884        Mac::update(&mut mac, &[]);885        assert_eq!(886            Mac::finalize(mac).into_bytes().as_slice(),887            &hex("48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49"),888        );889    }890}