1234567use 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}375376377378379380const X25519_SMALL_ORDER_PUBKEYS: [[u8; 32]; 9] = [381 [382 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,383 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,384 0x00, 0x00,385 ],386 [387 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,388 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,389 0x00, 0x00,390 ],391 [392 0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4,393 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49,394 0xb8, 0x80,395 ],396 [397 0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef,398 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f,399 0x11, 0x57,400 ],401 [402 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,403 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,404 0xff, 0x7f,405 ],406 [407 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,408 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,409 0xff, 0x7f,410 ],411 [412 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,413 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,414 0xff, 0x7f,415 ],416 [417 0xcd, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4,418 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49,419 0xb8, 0x80,420 ],421 [422 0x4c, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef,423 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f,424 0x11, 0xd7,425 ],426];427428fn is_small_order_x25519_pubkey(public_key: &[u8]) -> bool {429 let Ok(key) = <[u8; 32]>::try_from(public_key) else {430 return false;431 };432 X25519_SMALL_ORDER_PUBKEYS433 .iter()434 .any(|low| low.ct_eq(&key).unwrap_u8() == 1)435}436437impl Dh for RejectZeroDh {438 fn name(&self) -> &'static str {439 self.inner.name()440 }441442 fn pub_len(&self) -> usize {443 self.inner.pub_len()444 }445446 fn priv_len(&self) -> usize {447 self.inner.priv_len()448 }449450 fn set(&mut self, private_key: &[u8]) {451 self.inner.set(private_key);452 }453454 fn generate(&mut self, rng: &mut dyn Random) -> Result<(), Error> {455 self.inner.generate(rng)456 }457458 fn pubkey(&self) -> &[u8] {459 self.inner.pubkey()460 }461462 fn privkey(&self) -> &[u8] {463 self.inner.privkey()464 }465466 fn dh(&self, public_key: &[u8], output: &mut [u8]) -> Result<(), Error> {467 if is_small_order_x25519_pubkey(public_key) {468 return Err(Error::Dh);469 }470 self.inner.dh(public_key, output)?;471 let used = self.inner.dh_len();472 if output[..used].ct_eq(&vec![0_u8; used]).unwrap_u8() == 1 {473 output[..used].zeroize();474 return Err(Error::Dh);475 }476 Ok(())477 }478479 fn dh_len(&self) -> usize {480 self.inner.dh_len()481 }482}483484#[cfg(target_arch = "wasm32")]485mod wasm {486 use super::CoreNoiseSession;487 use wasm_bindgen::prelude::*;488489 fn js_error(message: String) -> JsError {490 JsError::new(&message)491 }492493 #[wasm_bindgen]494 pub struct WasmNoiseSession {495 inner: CoreNoiseSession,496 }497498 #[wasm_bindgen]499 impl WasmNoiseSession {500 #[wasm_bindgen(js_name = nkInitiator)]501 pub fn nk_initiator(remote_public_key: &[u8]) -> Result<WasmNoiseSession, JsError> {502 Ok(Self {503 inner: CoreNoiseSession::nk_initiator(remote_public_key).map_err(js_error)?,504 })505 }506507 #[wasm_bindgen(js_name = nkResponder)]508 pub fn nk_responder(509 local_private_key: &[u8],510 local_public_key: &[u8],511 ) -> Result<WasmNoiseSession, JsError> {512 Ok(Self {513 inner: CoreNoiseSession::nk_responder(local_private_key, local_public_key)514 .map_err(js_error)?,515 })516 }517518 #[wasm_bindgen(js_name = nkInitiatorWithEphemeralForTest)]519 pub fn nk_initiator_with_ephemeral_for_test(520 remote_public_key: &[u8],521 ephemeral_private_key: &[u8],522 ) -> Result<WasmNoiseSession, JsError> {523 Ok(Self {524 inner: CoreNoiseSession::nk_initiator_with_ephemeral_for_test(525 remote_public_key,526 ephemeral_private_key,527 )528 .map_err(js_error)?,529 })530 }531532 #[wasm_bindgen(js_name = nkResponderWithEphemeralForTest)]533 pub fn nk_responder_with_ephemeral_for_test(534 local_private_key: &[u8],535 local_public_key: &[u8],536 ephemeral_private_key: &[u8],537 ) -> Result<WasmNoiseSession, JsError> {538 Ok(Self {539 inner: CoreNoiseSession::nk_responder_with_ephemeral_for_test(540 local_private_key,541 local_public_key,542 ephemeral_private_key,543 )544 .map_err(js_error)?,545 })546 }547548 #[wasm_bindgen(js_name = kkInitiator)]549 pub fn kk_initiator(550 local_private_key: &[u8],551 local_public_key: &[u8],552 remote_public_key: &[u8],553 ) -> Result<WasmNoiseSession, JsError> {554 Ok(Self {555 inner: CoreNoiseSession::kk_initiator(556 local_private_key,557 local_public_key,558 remote_public_key,559 )560 .map_err(js_error)?,561 })562 }563564 #[wasm_bindgen(js_name = kkResponder)]565 pub fn kk_responder(566 local_private_key: &[u8],567 local_public_key: &[u8],568 remote_public_key: &[u8],569 ) -> Result<WasmNoiseSession, JsError> {570 Ok(Self {571 inner: CoreNoiseSession::kk_responder(572 local_private_key,573 local_public_key,574 remote_public_key,575 )576 .map_err(js_error)?,577 })578 }579580 #[wasm_bindgen(js_name = kkInitiatorWithEphemeralForTest)]581 pub fn kk_initiator_with_ephemeral_for_test(582 local_private_key: &[u8],583 local_public_key: &[u8],584 remote_public_key: &[u8],585 ephemeral_private_key: &[u8],586 ) -> Result<WasmNoiseSession, JsError> {587 Ok(Self {588 inner: CoreNoiseSession::kk_initiator_with_ephemeral_for_test(589 local_private_key,590 local_public_key,591 remote_public_key,592 ephemeral_private_key,593 )594 .map_err(js_error)?,595 })596 }597598 #[wasm_bindgen(js_name = kkResponderWithEphemeralForTest)]599 pub fn kk_responder_with_ephemeral_for_test(600 local_private_key: &[u8],601 local_public_key: &[u8],602 remote_public_key: &[u8],603 ephemeral_private_key: &[u8],604 ) -> Result<WasmNoiseSession, JsError> {605 Ok(Self {606 inner: CoreNoiseSession::kk_responder_with_ephemeral_for_test(607 local_private_key,608 local_public_key,609 remote_public_key,610 ephemeral_private_key,611 )612 .map_err(js_error)?,613 })614 }615616 #[wasm_bindgen(js_name = writeHandshake)]617 pub fn write_handshake(&mut self, payload: &[u8]) -> Result<Vec<u8>, JsError> {618 self.inner.write_handshake(payload).map_err(js_error)619 }620621 #[wasm_bindgen(js_name = readHandshake)]622 pub fn read_handshake(&mut self, message: &[u8]) -> Result<Vec<u8>, JsError> {623 self.inner.read_handshake(message).map_err(js_error)624 }625626 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, JsError> {627 self.inner.encrypt(plaintext).map_err(js_error)628 }629630 pub fn decrypt(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>, JsError> {631 self.inner.decrypt(ciphertext).map_err(js_error)632 }633634 #[wasm_bindgen(getter, js_name = handshakeHash)]635 pub fn handshake_hash(&self) -> Result<Vec<u8>, JsError> {636 self.inner.handshake_hash().map_err(js_error)637 }638639 #[wasm_bindgen(js_name = rekeyOutgoing)]640 pub fn rekey_outgoing(&mut self) -> Result<(), JsError> {641 self.inner.rekey_outgoing().map_err(js_error)642 }643644 #[wasm_bindgen(js_name = rekeyIncoming)]645 pub fn rekey_incoming(&mut self) -> Result<(), JsError> {646 self.inner.rekey_incoming().map_err(js_error)647 }648 }649}650651#[cfg(not(target_arch = "wasm32"))]652mod jni_bridge;653654#[cfg(test)]655mod tests {656 use super::{657 CoreNoiseSession, KK, NK, X25519_SMALL_ORDER_PUBKEYS, core_builder,658 is_small_order_x25519_pubkey,659 };660 use blake2::{661 Blake2sMac256,662 digest::{KeyInit as BlakeKeyInit, Mac},663 };664 use chacha20poly1305::{665 ChaCha20Poly1305, Nonce,666 aead::{Aead, Payload},667 };668 use snow::params::NoiseParams;669 use x25519_dalek::{PublicKey, StaticSecret};670671 fn keypair(seed: u8) -> ([u8; 32], [u8; 32]) {672 let private = [seed; 32];673 let public = *PublicKey::from(&StaticSecret::from(private)).as_bytes();674 (private, public)675 }676677 fn hex(value: &str) -> Vec<u8> {678 assert!(value.len().is_multiple_of(2));679 value680 .as_bytes()681 .chunks_exact(2)682 .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap())683 .collect()684 }685686 fn official_pair(687 pattern: &str,688 initiator_static: Option<&str>,689 initiator_ephemeral: &str,690 initiator_remote_static: &str,691 responder_static: &str,692 responder_ephemeral: &str,693 responder_remote_static: Option<&str>,694 ) -> (CoreNoiseSession, CoreNoiseSession) {695 let prologue = hex("4a6f686e2047616c74");696 let responder_private = hex(responder_static);697 let responder_public = PublicKey::from(&StaticSecret::from(698 <[u8; 32]>::try_from(&responder_private[..]).unwrap(),699 ));700 assert_eq!(701 responder_public.as_bytes(),702 &hex(initiator_remote_static)[..]703 );704705 let params: NoiseParams = pattern.parse().unwrap();706 let initiator_ephemeral = hex(initiator_ephemeral);707 let initiator_static = initiator_static.map(hex);708 let responder_ephemeral = hex(responder_ephemeral);709 let responder_remote_static = responder_remote_static.map(hex);710 let mut initiator_builder = core_builder(params.clone())711 .prologue(&prologue)712 .unwrap()713 .remote_public_key(responder_public.as_bytes())714 .unwrap()715 .fixed_ephemeral_key_for_testing_only(&initiator_ephemeral);716 if let Some(private) = &initiator_static {717 initiator_builder = initiator_builder.local_private_key(private).unwrap();718 }719720 let mut responder_builder = core_builder(params)721 .prologue(&prologue)722 .unwrap()723 .local_private_key(&responder_private)724 .unwrap()725 .fixed_ephemeral_key_for_testing_only(&responder_ephemeral);726 if let Some(remote_public) = &responder_remote_static {727 responder_builder = responder_builder.remote_public_key(remote_public).unwrap();728 }729730 (731 CoreNoiseSession::handshake(initiator_builder.build_initiator().unwrap()),732 CoreNoiseSession::handshake(responder_builder.build_responder().unwrap()),733 )734 }735736 fn assert_cacophony_vector(737 mut initiator: CoreNoiseSession,738 mut responder: CoreNoiseSession,739 handshake_hash: &str,740 messages: &[(&str, &str)],741 ) {742 for (index, (payload, expected_ciphertext)) in messages.iter().enumerate() {743 let plaintext = hex(payload);744 let expected = hex(expected_ciphertext);745 if index < 2 {746 let (sender, receiver) = if index % 2 == 0 {747 (&mut initiator, &mut responder)748 } else {749 (&mut responder, &mut initiator)750 };751 let actual = sender.write_handshake(&plaintext).unwrap();752 assert_eq!(actual, expected, "handshake message {index}");753 assert_eq!(754 receiver.read_handshake(&actual).unwrap(),755 plaintext,756 "handshake payload {index}",757 );758 } else {759 let (sender, receiver) = if index % 2 == 0 {760 (&mut initiator, &mut responder)761 } else {762 (&mut responder, &mut initiator)763 };764 let actual = sender.encrypt(&plaintext).unwrap();765 assert_eq!(actual, expected, "transport message {index}");766 assert_eq!(767 receiver.decrypt(&actual).unwrap(),768 plaintext,769 "transport payload {index}",770 );771 }772 }773 let expected_hash = hex(handshake_hash);774 assert_eq!(initiator.handshake_hash().unwrap(), expected_hash);775 assert_eq!(responder.handshake_hash().unwrap(), expected_hash);776 }777778 fn complete(initiator: &mut CoreNoiseSession, responder: &mut CoreNoiseSession) {779 let message1 = initiator.write_handshake(&[]).unwrap();780 assert!(responder.read_handshake(&message1).unwrap().is_empty());781 let message2 = responder.write_handshake(&[]).unwrap();782 assert!(initiator.read_handshake(&message2).unwrap().is_empty());783 assert_eq!(784 initiator.handshake_hash().unwrap(),785 responder.handshake_hash().unwrap()786 );787 }788789 #[test]790 fn nk_round_trip_uses_one_state_machine() {791 let (responder_private, responder_public) = keypair(7);792 let mut initiator = CoreNoiseSession::nk_initiator(&responder_public).unwrap();793 let mut responder =794 CoreNoiseSession::nk_responder(&responder_private, &responder_public).unwrap();795 complete(&mut initiator, &mut responder);796797 let ciphertext = initiator.encrypt(b"peach").unwrap();798 assert_eq!(responder.decrypt(&ciphertext).unwrap(), b"peach");799 let response = responder.encrypt(b"sync").unwrap();800 assert_eq!(initiator.decrypt(&response).unwrap(), b"sync");801 }802803 804 805 806 #[test]807 fn small_order_remote_static_fails_handshake() {808 let (local_private, local_public) = keypair(11);809 let mut initiator = CoreNoiseSession::kk_initiator(810 &local_private,811 &local_public,812 &X25519_SMALL_ORDER_PUBKEYS[3],813 )814 .unwrap();815 let result = initiator.write_handshake(&[]);816 assert!(result.is_err());817 818 for low in X25519_SMALL_ORDER_PUBKEYS.iter() {819 assert!(is_small_order_x25519_pubkey(low));820 }821 let (_, real_public) = keypair(9);822 assert!(!is_small_order_x25519_pubkey(&real_public));823 }824825 #[test]826 fn kk_round_trip_authenticates_both_static_keys() {827 let (initiator_private, initiator_public) = keypair(11);828 let (responder_private, responder_public) = keypair(19);829 let mut initiator = CoreNoiseSession::kk_initiator(830 &initiator_private,831 &initiator_public,832 &responder_public,833 )834 .unwrap();835 let mut responder = CoreNoiseSession::kk_responder(836 &responder_private,837 &responder_public,838 &initiator_public,839 )840 .unwrap();841 complete(&mut initiator, &mut responder);842 }843844 #[test]845 fn mismatched_local_keypair_is_rejected() {846 let (private, _) = keypair(2);847 let (_, wrong_public) = keypair(3);848 let result = CoreNoiseSession::nk_responder(&private, &wrong_public);849 assert!(matches!(result, Err(error) if error.contains("does not match")));850 }851852 #[test]853 fn tampered_transport_frame_is_rejected() {854 let (responder_private, responder_public) = keypair(23);855 let mut initiator = CoreNoiseSession::nk_initiator(&responder_public).unwrap();856 let mut responder =857 CoreNoiseSession::nk_responder(&responder_private, &responder_public).unwrap();858 complete(&mut initiator, &mut responder);859 let mut ciphertext = initiator.encrypt(b"vault").unwrap();860 ciphertext[0] ^= 0x80;861 assert!(responder.decrypt(&ciphertext).is_err());862 }863864 #[test]865 fn cacophony_nk_vector_matches_shared_core() {866 let pair = official_pair(867 NK,868 None,869 "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",870 "31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62",871 "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",872 "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",873 None,874 );875 assert_cacophony_vector(876 pair.0,877 pair.1,878 "d7244d974066aae2376f7ba5534f60a6e4e82cd7c9751e226cae3928e6b49f14",879 &[880 (881 "4c756477696720766f6e204d69736573",882 "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c794454ae7612d1724af42adb130160a9a94e67b5b169b4e00c189f6467cd17eb7cad",883 ),884 (885 "4d757272617920526f746862617264",886 "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f144808843986a5c929337e337ac8b4a074af12ab9f76318a5f18c8b599a443af07383ce",887 ),888 (889 "462e20412e20486179656b",890 "550027c7a5d450017bcb5e12b8253b1c53fd2213aeda84891d5f95",891 ),892 (893 "4361726c204d656e676572",894 "dfbce0c38210ccee35e830aca9dd8b8b3997b933e75bfc8864b759",895 ),896 ],897 );898 }899900 #[test]901 fn cacophony_kk_vector_matches_shared_core() {902 let pair = official_pair(903 KK,904 Some("e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1"),905 "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",906 "31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62",907 "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",908 "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",909 Some("6bc3822a2aa7f4e6981d6538692b3cdf3e6df9eea6ed269eb41d93c22757b75a"),910 );911 assert_cacophony_vector(912 pair.0,913 pair.1,914 "1362b8627a00907ce11e558aba8ce7cbca88e83f0e84ce7db5159b1c3e25ab59",915 &[916 (917 "4c756477696720766f6e204d69736573",918 "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c7944266a5f53784aa3becb0f7485c2759c328937867a4cbaafef07422b0725e098be",919 ),920 (921 "4d757272617920526f746862617264",922 "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f144808843008aeea5d76d6abcbab87a18502c8a8352d9933ac11e2a7d228038d721e31e",923 ),924 (925 "462e20412e20486179656b",926 "5f92113edf78c3e56e6d67201f5f9e0c8f2930c3e1ffb64ede0358",927 ),928 (929 "4361726c204d656e676572",930 "30ebbd9cdcef7f40d99c8cd11e880dac28f5c9e5032c1059b3b56a",931 ),932 ],933 );934 }935936 #[test]937 fn rfc_8439_chacha20_poly1305_aead_vector_matches_snow_cipher() {938 let key = hex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");939 let nonce = hex("070000004041424344454647");940 let aad = hex("50515253c0c1c2c3c4c5c6c7");941 let plaintext = hex(concat!(942 "4c616469657320616e642047656e746c656d656e206f662074686520636c6173",943 "73206f66202739393a204966204920636f756c64206f6666657220796f75206f",944 "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73",945 "637265656e20776f756c642062652069742e",946 ));947 let expected = hex(concat!(948 "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6",949 "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36",950 "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc",951 "3ff4def08e4b7a9de576d26586cec64b6116",952 "1ae10b594f09e26a7e902ecbd0600691",953 ));954 let cipher = ChaCha20Poly1305::new_from_slice(&key).unwrap();955 assert_eq!(956 cipher957 .encrypt(958 Nonce::from_slice(&nonce),959 Payload {960 msg: &plaintext,961 aad: &aad,962 },963 )964 .unwrap(),965 expected,966 );967 }968969 #[test]970 fn blake2_reference_keyed_vector_matches_snow_hash_dependency() {971 let key: Vec<u8> = (0_u8..32).collect();972 let mut mac = <Blake2sMac256 as BlakeKeyInit>::new_from_slice(&key).unwrap();973 Mac::update(&mut mac, &[]);974 assert_eq!(975 Mac::finalize(mac).into_bytes().as_slice(),976 &hex("48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49"),977 );978 }979}