From: Matt Corallo Date: Fri, 27 Dec 2019 22:38:15 +0000 (-0500) Subject: Add a ChaChaReader adapter to read an encrypted stream & use it X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=8a4939173155c984eb6bdc32285567efb222cf02;p=rust-lightning Add a ChaChaReader adapter to read an encrypted stream & use it This prepares for variable-length per-hop-data by wrapping the full hop_data field in a decrypting stream, with a few minor optimizations and redundant allocations to boot. --- diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e42325746..420d8e92f 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -38,21 +38,19 @@ use chain::keysinterface::{ChannelKeys, KeysInterface, InMemoryChannelKeys}; use util::config::UserConfig; use util::{byte_utils, events}; use util::ser::{Readable, ReadableArgs, Writeable, Writer}; -use util::chacha20::ChaCha20; +use util::chacha20::{ChaCha20, ChaChaReader}; use util::logger::Logger; use util::errors::APIError; use std::{cmp, mem}; use std::collections::{HashMap, hash_map, HashSet}; -use std::io::Cursor; +use std::io::{Cursor, Read}; use std::sync::{Arc, Mutex, MutexGuard, RwLock}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; use std::marker::{Sync, Send}; use std::ops::Deref; -const SIXTY_FIVE_ZEROS: [u8; 65] = [0; 65]; - // We hold various information about HTLC relay in the HTLC objects in Channel itself: // // Upon receipt of an HTLC from a peer, we'll give it a PendingHTLCStatus indicating if it should @@ -906,12 +904,9 @@ impl ChannelManager where M::T } let mut chacha = ChaCha20::new(&rho, &[0u8; 8]); + let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&msg.onion_routing_packet.hop_data[..]) }; let (next_hop_data, next_hop_hmac) = { - let mut decoded = [0; 65]; - chacha.process(&msg.onion_routing_packet.hop_data[0..65], &mut decoded); - let mut hmac = [0; 32]; - hmac.copy_from_slice(&decoded[33..]); - match msgs::OnionHopData::read(&mut Cursor::new(&decoded[..33])) { + match msgs::OnionHopData::read(&mut chacha_stream) { Err(err) => { let error_code = match err { msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte @@ -919,7 +914,13 @@ impl ChannelManager where M::T }; return_err!("Unable to decode our hop data", error_code, &[0;0]); }, - Ok(msg) => (msg, hmac) + Ok(msg) => { + let mut hmac = [0; 32]; + if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) { + return_err!("Unable to decode hop data", 0x4000 | 1, &[0;0]); + } + (msg, hmac) + }, } }; @@ -933,10 +934,11 @@ impl ChannelManager where M::T // as-is (and were originally 0s). // Of course reverse path calculation is still pretty easy given naive routing // algorithms, but this fixes the most-obvious case. - let mut new_packet_data = [0; 19*65]; - chacha.process(&msg.onion_routing_packet.hop_data[65..], &mut new_packet_data[0..19*65]); - assert_ne!(new_packet_data[0..65], [0; 65][..]); - assert_ne!(new_packet_data[..], [0; 19*65][..]); + let mut next_bytes = [0; 32]; + chacha_stream.read_exact(&mut next_bytes).unwrap(); + assert_ne!(next_bytes[..], [0; 32][..]); + chacha_stream.read_exact(&mut next_bytes).unwrap(); + assert_ne!(next_bytes[..], [0; 32][..]); } // OUR PAYMENT! @@ -968,8 +970,10 @@ impl ChannelManager where M::T }) } else { let mut new_packet_data = [0; 20*65]; - chacha.process(&msg.onion_routing_packet.hop_data[65..], &mut new_packet_data[0..19*65]); - chacha.process(&SIXTY_FIVE_ZEROS[..], &mut new_packet_data[19*65..]); + let read_pos = chacha_stream.read(&mut new_packet_data).unwrap(); + // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we + // fill the onion hop data we'll forward to our next-hop peer. + chacha_stream.chacha.process_in_place(&mut new_packet_data[read_pos..]); let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap(); diff --git a/lightning/src/util/chacha20.rs b/lightning/src/util/chacha20.rs index c96577da0..09e9a8475 100644 --- a/lightning/src/util/chacha20.rs +++ b/lightning/src/util/chacha20.rs @@ -9,6 +9,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use std::io; + #[cfg(not(feature = "fuzztarget"))] mod real_chacha { use std::cmp; @@ -249,6 +251,29 @@ mod real_chacha { self.offset += count; } } + + pub fn process_in_place(&mut self, input_output: &mut [u8]) { + let len = input_output.len(); + let mut i = 0; + while i < len { + // If there is no keystream available in the output buffer, + // generate the next block. + if self.offset == 64 { + self.update(); + } + + // Process the min(available keystream, remaining input length). + let count = cmp::min(64 - self.offset, len - i); + // explicitly assert lengths to avoid bounds checks: + assert!(input_output.len() >= i + count); + assert!(self.output.len() >= self.offset + count); + for j in 0..count { + input_output[i + j] ^= self.output[self.offset + j]; + } + i += count; + self.offset += count; + } + } } } #[cfg(not(feature = "fuzztarget"))] @@ -268,11 +293,27 @@ mod fuzzy_chacha { pub fn process(&mut self, input: &[u8], output: &mut [u8]) { output.copy_from_slice(input); } + + pub fn process_in_place(&mut self, _input_output: &mut [u8]) {} } } #[cfg(feature = "fuzztarget")] pub use self::fuzzy_chacha::ChaCha20; +pub(crate) struct ChaChaReader<'a, R: io::Read> { + pub chacha: &'a mut ChaCha20, + pub read: R, +} +impl<'a, R: io::Read> io::Read for ChaChaReader<'a, R> { + fn read(&mut self, dest: &mut [u8]) -> Result { + let res = self.read.read(dest)?; + if res > 0 { + self.chacha.process_in_place(&mut dest[0..res]); + } + Ok(res) + } +} + #[cfg(test)] mod test { use std::iter::repeat;