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 430 431 432 433 let Some(key) = public_key.get(..32) else {434 return false;435 };436 let Ok(key) = <[u8; 32]>::try_from(key) else {437 return false;438 };439 X25519_SMALL_ORDER_PUBKEYS440 .iter()441 .any(|low| low.ct_eq(&key).unwrap_u8() == 1)442}443444impl Dh for RejectZeroDh {445 fn name(&self) -> &'static str {446 self.inner.name()447 }448449 fn pub_len(&self) -> usize {450 self.inner.pub_len()451 }452453 fn priv_len(&self) -> usize {454 self.inner.priv_len()455 }456457 fn set(&mut self, private_key: &[u8]) {458 self.inner.set(private_key);459 }460461 fn generate(&mut self, rng: &mut dyn Random) -> Result<(), Error> {462 self.inner.generate(rng)463 }464465 fn pubkey(&self) -> &[u8] {466 self.inner.pubkey()467 }468469 fn privkey(&self) -> &[u8] {470 self.inner.privkey()471 }472473 fn dh(&self, public_key: &[u8], output: &mut [u8]) -> Result<(), Error> {474 if is_small_order_x25519_pubkey(public_key) {475 return Err(Error::Dh);476 }477 self.inner.dh(public_key, output)?;478 let used = self.inner.dh_len();479 if output[..used].ct_eq(&vec![0_u8; used]).unwrap_u8() == 1 {480 output[..used].zeroize();481 return Err(Error::Dh);482 }483 Ok(())484 }485486 fn dh_len(&self) -> usize {487 self.inner.dh_len()488 }489}490491#[cfg(target_arch = "wasm32")]492mod wasm {493 use super::CoreNoiseSession;494 use wasm_bindgen::prelude::*;495496 fn js_error(message: String) -> JsError {497 JsError::new(&message)498 }499500 #[wasm_bindgen]501 pub struct WasmNoiseSession {502 inner: CoreNoiseSession,503 }504505 #[wasm_bindgen]506 impl WasmNoiseSession {507 #[wasm_bindgen(js_name = nkInitiator)]508 pub fn nk_initiator(remote_public_key: &[u8]) -> Result<WasmNoiseSession, JsError> {509 Ok(Self {510 inner: CoreNoiseSession::nk_initiator(remote_public_key).map_err(js_error)?,511 })512 }513514 #[wasm_bindgen(js_name = nkResponder)]515 pub fn nk_responder(516 local_private_key: &[u8],517 local_public_key: &[u8],518 ) -> Result<WasmNoiseSession, JsError> {519 Ok(Self {520 inner: CoreNoiseSession::nk_responder(local_private_key, local_public_key)521 .map_err(js_error)?,522 })523 }524525 #[wasm_bindgen(js_name = nkInitiatorWithEphemeralForTest)]526 pub fn nk_initiator_with_ephemeral_for_test(527 remote_public_key: &[u8],528 ephemeral_private_key: &[u8],529 ) -> Result<WasmNoiseSession, JsError> {530 Ok(Self {531 inner: CoreNoiseSession::nk_initiator_with_ephemeral_for_test(532 remote_public_key,533 ephemeral_private_key,534 )535 .map_err(js_error)?,536 })537 }538539 #[wasm_bindgen(js_name = nkResponderWithEphemeralForTest)]540 pub fn nk_responder_with_ephemeral_for_test(541 local_private_key: &[u8],542 local_public_key: &[u8],543 ephemeral_private_key: &[u8],544 ) -> Result<WasmNoiseSession, JsError> {545 Ok(Self {546 inner: CoreNoiseSession::nk_responder_with_ephemeral_for_test(547 local_private_key,548 local_public_key,549 ephemeral_private_key,550 )551 .map_err(js_error)?,552 })553 }554555 #[wasm_bindgen(js_name = kkInitiator)]556 pub fn kk_initiator(557 local_private_key: &[u8],558 local_public_key: &[u8],559 remote_public_key: &[u8],560 ) -> Result<WasmNoiseSession, JsError> {561 Ok(Self {562 inner: CoreNoiseSession::kk_initiator(563 local_private_key,564 local_public_key,565 remote_public_key,566 )567 .map_err(js_error)?,568 })569 }570571 #[wasm_bindgen(js_name = kkResponder)]572 pub fn kk_responder(573 local_private_key: &[u8],574 local_public_key: &[u8],575 remote_public_key: &[u8],576 ) -> Result<WasmNoiseSession, JsError> {577 Ok(Self {578 inner: CoreNoiseSession::kk_responder(579 local_private_key,580 local_public_key,581 remote_public_key,582 )583 .map_err(js_error)?,584 })585 }586587 #[wasm_bindgen(js_name = kkInitiatorWithEphemeralForTest)]588 pub fn kk_initiator_with_ephemeral_for_test(589 local_private_key: &[u8],590 local_public_key: &[u8],591 remote_public_key: &[u8],592 ephemeral_private_key: &[u8],593 ) -> Result<WasmNoiseSession, JsError> {594 Ok(Self {595 inner: CoreNoiseSession::kk_initiator_with_ephemeral_for_test(596 local_private_key,597 local_public_key,598 remote_public_key,599 ephemeral_private_key,600 )601 .map_err(js_error)?,602 })603 }604605 #[wasm_bindgen(js_name = kkResponderWithEphemeralForTest)]606 pub fn kk_responder_with_ephemeral_for_test(607 local_private_key: &[u8],608 local_public_key: &[u8],609 remote_public_key: &[u8],610 ephemeral_private_key: &[u8],611 ) -> Result<WasmNoiseSession, JsError> {612 Ok(Self {613 inner: CoreNoiseSession::kk_responder_with_ephemeral_for_test(614 local_private_key,615 local_public_key,616 remote_public_key,617 ephemeral_private_key,618 )619 .map_err(js_error)?,620 })621 }622623 #[wasm_bindgen(js_name = writeHandshake)]624 pub fn write_handshake(&mut self, payload: &[u8]) -> Result<Vec<u8>, JsError> {625 self.inner.write_handshake(payload).map_err(js_error)626 }627628 #[wasm_bindgen(js_name = readHandshake)]629 pub fn read_handshake(&mut self, message: &[u8]) -> Result<Vec<u8>, JsError> {630 self.inner.read_handshake(message).map_err(js_error)631 }632633 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, JsError> {634 self.inner.encrypt(plaintext).map_err(js_error)635 }636637 pub fn decrypt(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>, JsError> {638 self.inner.decrypt(ciphertext).map_err(js_error)639 }640641 #[wasm_bindgen(getter, js_name = handshakeHash)]642 pub fn handshake_hash(&self) -> Result<Vec<u8>, JsError> {643 self.inner.handshake_hash().map_err(js_error)644 }645646 #[wasm_bindgen(js_name = rekeyOutgoing)]647 pub fn rekey_outgoing(&mut self) -> Result<(), JsError> {648 self.inner.rekey_outgoing().map_err(js_error)649 }650651 #[wasm_bindgen(js_name = rekeyIncoming)]652 pub fn rekey_incoming(&mut self) -> Result<(), JsError> {653 self.inner.rekey_incoming().map_err(js_error)654 }655 }656}657658#[cfg(not(target_arch = "wasm32"))]659mod jni_bridge;660661#[cfg(test)]662mod tests {663 use super::{664 CoreNoiseSession, KK, NK, X25519_SMALL_ORDER_PUBKEYS, core_builder,665 is_small_order_x25519_pubkey,666 };667 use blake2::{668 Blake2sMac256,669 digest::{KeyInit as BlakeKeyInit, Mac},670 };671 use chacha20poly1305::{672 ChaCha20Poly1305, Nonce,673 aead::{Aead, Payload},674 };675 use snow::params::NoiseParams;676 use x25519_dalek::{PublicKey, StaticSecret};677678 fn keypair(seed: u8) -> ([u8; 32], [u8; 32]) {679 let private = [seed; 32];680 let public = *PublicKey::from(&StaticSecret::from(private)).as_bytes();681 (private, public)682 }683684 fn hex(value: &str) -> Vec<u8> {685 assert!(value.len().is_multiple_of(2));686 value687 .as_bytes()688 .chunks_exact(2)689 .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap())690 .collect()691 }692693 fn official_pair(694 pattern: &str,695 initiator_static: Option<&str>,696 initiator_ephemeral: &str,697 initiator_remote_static: &str,698 responder_static: &str,699 responder_ephemeral: &str,700 responder_remote_static: Option<&str>,701 ) -> (CoreNoiseSession, CoreNoiseSession) {702 let prologue = hex("4a6f686e2047616c74");703 let responder_private = hex(responder_static);704 let responder_public = PublicKey::from(&StaticSecret::from(705 <[u8; 32]>::try_from(&responder_private[..]).unwrap(),706 ));707 assert_eq!(708 responder_public.as_bytes(),709 &hex(initiator_remote_static)[..]710 );711712 let params: NoiseParams = pattern.parse().unwrap();713 let initiator_ephemeral = hex(initiator_ephemeral);714 let initiator_static = initiator_static.map(hex);715 let responder_ephemeral = hex(responder_ephemeral);716 let responder_remote_static = responder_remote_static.map(hex);717 let mut initiator_builder = core_builder(params.clone())718 .prologue(&prologue)719 .unwrap()720 .remote_public_key(responder_public.as_bytes())721 .unwrap()722 .fixed_ephemeral_key_for_testing_only(&initiator_ephemeral);723 if let Some(private) = &initiator_static {724 initiator_builder = initiator_builder.local_private_key(private).unwrap();725 }726727 let mut responder_builder = core_builder(params)728 .prologue(&prologue)729 .unwrap()730 .local_private_key(&responder_private)731 .unwrap()732 .fixed_ephemeral_key_for_testing_only(&responder_ephemeral);733 if let Some(remote_public) = &responder_remote_static {734 responder_builder = responder_builder.remote_public_key(remote_public).unwrap();735 }736737 (738 CoreNoiseSession::handshake(initiator_builder.build_initiator().unwrap()),739 CoreNoiseSession::handshake(responder_builder.build_responder().unwrap()),740 )741 }742743 fn assert_cacophony_vector(744 mut initiator: CoreNoiseSession,745 mut responder: CoreNoiseSession,746 handshake_hash: &str,747 messages: &[(&str, &str)],748 ) {749 for (index, (payload, expected_ciphertext)) in messages.iter().enumerate() {750 let plaintext = hex(payload);751 let expected = hex(expected_ciphertext);752 if index < 2 {753 let (sender, receiver) = if index % 2 == 0 {754 (&mut initiator, &mut responder)755 } else {756 (&mut responder, &mut initiator)757 };758 let actual = sender.write_handshake(&plaintext).unwrap();759 assert_eq!(actual, expected, "handshake message {index}");760 assert_eq!(761 receiver.read_handshake(&actual).unwrap(),762 plaintext,763 "handshake payload {index}",764 );765 } else {766 let (sender, receiver) = if index % 2 == 0 {767 (&mut initiator, &mut responder)768 } else {769 (&mut responder, &mut initiator)770 };771 let actual = sender.encrypt(&plaintext).unwrap();772 assert_eq!(actual, expected, "transport message {index}");773 assert_eq!(774 receiver.decrypt(&actual).unwrap(),775 plaintext,776 "transport payload {index}",777 );778 }779 }780 let expected_hash = hex(handshake_hash);781 assert_eq!(initiator.handshake_hash().unwrap(), expected_hash);782 assert_eq!(responder.handshake_hash().unwrap(), expected_hash);783 }784785 fn complete(initiator: &mut CoreNoiseSession, responder: &mut CoreNoiseSession) {786 let message1 = initiator.write_handshake(&[]).unwrap();787 assert!(responder.read_handshake(&message1).unwrap().is_empty());788 let message2 = responder.write_handshake(&[]).unwrap();789 assert!(initiator.read_handshake(&message2).unwrap().is_empty());790 assert_eq!(791 initiator.handshake_hash().unwrap(),792 responder.handshake_hash().unwrap()793 );794 }795796 #[test]797 fn nk_round_trip_uses_one_state_machine() {798 let (responder_private, responder_public) = keypair(7);799 let mut initiator = CoreNoiseSession::nk_initiator(&responder_public).unwrap();800 let mut responder =801 CoreNoiseSession::nk_responder(&responder_private, &responder_public).unwrap();802 complete(&mut initiator, &mut responder);803804 let ciphertext = initiator.encrypt(b"peach").unwrap();805 assert_eq!(responder.decrypt(&ciphertext).unwrap(), b"peach");806 let response = responder.encrypt(b"sync").unwrap();807 assert_eq!(initiator.decrypt(&response).unwrap(), b"sync");808 }809810 811 812 813 814 815 #[test]816 fn small_order_remote_static_fails_handshake() {817 let (local_private, local_public) = keypair(11);818 for low in X25519_SMALL_ORDER_PUBKEYS.iter() {819 let mut initiator =820 CoreNoiseSession::kk_initiator(&local_private, &local_public, low).unwrap();821 assert!(822 initiator.write_handshake(&[]).is_err(),823 "handshake against small-order static must fail"824 );825 assert!(is_small_order_x25519_pubkey(low));826 }827 let (_, real_public) = keypair(9);828 assert!(!is_small_order_x25519_pubkey(&real_public));829 }830831 832 833 834 #[test]835 fn small_order_guard_handles_snow_padded_buffers() {836 for low in X25519_SMALL_ORDER_PUBKEYS.iter() {837 let mut padded = [0u8; 56];838 padded[..32].copy_from_slice(low);839 assert!(is_small_order_x25519_pubkey(&padded));840 }841 let (_, real_public) = keypair(13);842 let mut padded = [0u8; 56];843 padded[..32].copy_from_slice(&real_public);844 assert!(!is_small_order_x25519_pubkey(&padded));845 }846847 #[test]848 fn kk_round_trip_authenticates_both_static_keys() {849 let (initiator_private, initiator_public) = keypair(11);850 let (responder_private, responder_public) = keypair(19);851 let mut initiator = CoreNoiseSession::kk_initiator(852 &initiator_private,853 &initiator_public,854 &responder_public,855 )856 .unwrap();857 let mut responder = CoreNoiseSession::kk_responder(858 &responder_private,859 &responder_public,860 &initiator_public,861 )862 .unwrap();863 complete(&mut initiator, &mut responder);864 }865866 #[test]867 fn mismatched_local_keypair_is_rejected() {868 let (private, _) = keypair(2);869 let (_, wrong_public) = keypair(3);870 let result = CoreNoiseSession::nk_responder(&private, &wrong_public);871 assert!(matches!(result, Err(error) if error.contains("does not match")));872 }873874 #[test]875 fn tampered_transport_frame_is_rejected() {876 let (responder_private, responder_public) = keypair(23);877 let mut initiator = CoreNoiseSession::nk_initiator(&responder_public).unwrap();878 let mut responder =879 CoreNoiseSession::nk_responder(&responder_private, &responder_public).unwrap();880 complete(&mut initiator, &mut responder);881 let mut ciphertext = initiator.encrypt(b"vault").unwrap();882 ciphertext[0] ^= 0x80;883 assert!(responder.decrypt(&ciphertext).is_err());884 }885886 #[test]887 fn cacophony_nk_vector_matches_shared_core() {888 let pair = official_pair(889 NK,890 None,891 "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",892 "31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62",893 "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",894 "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",895 None,896 );897 assert_cacophony_vector(898 pair.0,899 pair.1,900 "d7244d974066aae2376f7ba5534f60a6e4e82cd7c9751e226cae3928e6b49f14",901 &[902 (903 "4c756477696720766f6e204d69736573",904 "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c794454ae7612d1724af42adb130160a9a94e67b5b169b4e00c189f6467cd17eb7cad",905 ),906 (907 "4d757272617920526f746862617264",908 "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f144808843986a5c929337e337ac8b4a074af12ab9f76318a5f18c8b599a443af07383ce",909 ),910 (911 "462e20412e20486179656b",912 "550027c7a5d450017bcb5e12b8253b1c53fd2213aeda84891d5f95",913 ),914 (915 "4361726c204d656e676572",916 "dfbce0c38210ccee35e830aca9dd8b8b3997b933e75bfc8864b759",917 ),918 ],919 );920 }921922 #[test]923 fn cacophony_kk_vector_matches_shared_core() {924 let pair = official_pair(925 KK,926 Some("e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1"),927 "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",928 "31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62",929 "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",930 "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",931 Some("6bc3822a2aa7f4e6981d6538692b3cdf3e6df9eea6ed269eb41d93c22757b75a"),932 );933 assert_cacophony_vector(934 pair.0,935 pair.1,936 "1362b8627a00907ce11e558aba8ce7cbca88e83f0e84ce7db5159b1c3e25ab59",937 &[938 (939 "4c756477696720766f6e204d69736573",940 "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c7944266a5f53784aa3becb0f7485c2759c328937867a4cbaafef07422b0725e098be",941 ),942 (943 "4d757272617920526f746862617264",944 "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f144808843008aeea5d76d6abcbab87a18502c8a8352d9933ac11e2a7d228038d721e31e",945 ),946 (947 "462e20412e20486179656b",948 "5f92113edf78c3e56e6d67201f5f9e0c8f2930c3e1ffb64ede0358",949 ),950 (951 "4361726c204d656e676572",952 "30ebbd9cdcef7f40d99c8cd11e880dac28f5c9e5032c1059b3b56a",953 ),954 ],955 );956 }957958 #[test]959 fn rfc_8439_chacha20_poly1305_aead_vector_matches_snow_cipher() {960 let key = hex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");961 let nonce = hex("070000004041424344454647");962 let aad = hex("50515253c0c1c2c3c4c5c6c7");963 let plaintext = hex(concat!(964 "4c616469657320616e642047656e746c656d656e206f662074686520636c6173",965 "73206f66202739393a204966204920636f756c64206f6666657220796f75206f",966 "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73",967 "637265656e20776f756c642062652069742e",968 ));969 let expected = hex(concat!(970 "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6",971 "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36",972 "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc",973 "3ff4def08e4b7a9de576d26586cec64b6116",974 "1ae10b594f09e26a7e902ecbd0600691",975 ));976 let cipher = ChaCha20Poly1305::new_from_slice(&key).unwrap();977 assert_eq!(978 cipher979 .encrypt(980 Nonce::from_slice(&nonce),981 Payload {982 msg: &plaintext,983 aad: &aad,984 },985 )986 .unwrap(),987 expected,988 );989 }990991 #[test]992 fn blake2_reference_keyed_vector_matches_snow_hash_dependency() {993 let key: Vec<u8> = (0_u8..32).collect();994 let mut mac = <Blake2sMac256 as BlakeKeyInit>::new_from_slice(&key).unwrap();995 Mac::update(&mut mac, &[]);996 assert_eq!(997 Mac::finalize(mac).into_bytes().as_slice(),998 &hex("48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49"),999 );1000 }1001}