diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 2631a05f660..3a5541fa146 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -8,7 +8,7 @@ use crate::ln::onion_utils; use crate::onion_message::packet::ControlTlvs; use crate::prelude::*; use crate::sign::{NodeSigner, Recipient}; -use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; +use crate::crypto::streams::ChaChaPolyReadAdapter; use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer}; use core::mem; diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index 7ddb39c1b68..d4894a86aa1 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -19,7 +19,7 @@ use super::{BlindedHop, BlindedPath}; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use crate::onion_message::messenger::Destination; -use crate::util::chacha20poly1305rfc::ChaChaPolyWriteAdapter; +use crate::crypto::streams::ChaChaPolyWriteAdapter; use crate::util::ser::{Readable, Writeable}; use crate::io; diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index bcc324a5582..c81a48b78ac 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -4808,7 +4808,7 @@ mod tests { preimages_slice_to_htlcs!($preimages_slice).into_iter().map(|(htlc, _)| (htlc, None)).collect() } } - let dummy_sig = crate::util::crypto::sign(&secp_ctx, + let dummy_sig = crate::crypto::utils::sign(&secp_ctx, &bitcoin::secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap()); diff --git a/lightning/src/util/chacha20.rs b/lightning/src/crypto/chacha20.rs similarity index 98% rename from lightning/src/util/chacha20.rs rename to lightning/src/crypto/chacha20.rs index 865a09fa040..d6fd3a7dee0 100644 --- a/lightning/src/util/chacha20.rs +++ b/lightning/src/crypto/chacha20.rs @@ -9,8 +9,6 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::io; - #[cfg(not(fuzzing))] mod real_chacha { use core::cmp; @@ -335,27 +333,14 @@ mod fuzzy_chacha { #[cfg(fuzzing)] 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 crate::prelude::*; + use alloc::vec; + use alloc::vec::{Vec}; + use core::convert::TryInto; use core::iter::repeat; use super::ChaCha20; - use std::convert::TryInto; #[test] fn test_chacha20_256_tls_vectors() { diff --git a/lightning/src/crypto/chacha20poly1305rfc.rs b/lightning/src/crypto/chacha20poly1305rfc.rs new file mode 100644 index 00000000000..4ca974421c6 --- /dev/null +++ b/lightning/src/crypto/chacha20poly1305rfc.rs @@ -0,0 +1,237 @@ +// ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305 +// Instead, we steal rust-crypto's implementation and tweak it to match the RFC. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. +// +// This is a port of Andrew Moons poly1305-donna +// https://github.com/floodyberry/poly1305-donna + +#[cfg(not(fuzzing))] +mod real_chachapoly { + use super::super::chacha20::ChaCha20; + use super::super::poly1305::Poly1305; + use super::super::fixed_time_eq; + + #[derive(Clone, Copy)] + pub struct ChaCha20Poly1305RFC { + cipher: ChaCha20, + mac: Poly1305, + finished: bool, + data_len: usize, + aad_len: u64, + } + + impl ChaCha20Poly1305RFC { + #[inline] + fn pad_mac_16(mac: &mut Poly1305, len: usize) { + if len % 16 != 0 { + mac.input(&[0; 16][0..16 - (len % 16)]); + } + } + pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC { + assert!(key.len() == 16 || key.len() == 32); + assert!(nonce.len() == 12); + + // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant + assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); + + let mut cipher = ChaCha20::new(key, &nonce[4..]); + let mut mac_key = [0u8; 64]; + let zero_key = [0u8; 64]; + cipher.process(&zero_key, &mut mac_key); + + let mut mac = Poly1305::new(&mac_key[..32]); + mac.input(aad); + ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len()); + + ChaCha20Poly1305RFC { + cipher, + mac, + finished: false, + data_len: 0, + aad_len: aad.len() as u64, + } + } + + pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { + assert!(input.len() == output.len()); + assert!(self.finished == false); + self.cipher.process(input, output); + self.data_len += input.len(); + self.mac.input(output); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + self.mac.raw_result(out_tag); + } + + pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { + self.encrypt_in_place(input_output); + self.finish_and_get_tag(out_tag); + } + + // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag` + // below. + pub(in super::super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(self.finished == false); + self.cipher.process_in_place(input_output); + self.data_len += input_output.len(); + self.mac.input(input_output); + } + + // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish + // encrypting and calculate the tag. + pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { + debug_assert!(self.finished == false); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + self.mac.raw_result(out_tag); + } + + /// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents + /// into `output`. Note that, because `output` is not touched until the `tag` is checked, + /// this decryption is *variable time*. + pub fn variable_time_decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + assert!(input.len() == output.len()); + assert!(self.finished == false); + + self.finished = true; + + self.mac.input(input); + + self.data_len += input.len(); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + + let mut calc_tag = [0u8; 16]; + self.mac.raw_result(&mut calc_tag); + if fixed_time_eq(&calc_tag, tag) { + self.cipher.process(input, output); + Ok(()) + } else { + Err(()) + } + } + + pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + self.decrypt_in_place(input_output); + if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } + } + + /// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it + /// later when decryption finishes. + /// + /// Should never be `pub` because the public API should always enforce tag checking. + pub(in super::super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(self.finished == false); + self.mac.input(input_output); + self.data_len += input_output.len(); + self.cipher.process_in_place(input_output); + } + + /// If we were previously decrypting with `just_decrypt_in_place`, this method must be used + /// to check the tag. Returns whether or not the tag is valid. + pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { + debug_assert!(self.finished == false); + self.finished = true; + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + + let mut calc_tag = [0u8; 16]; + self.mac.raw_result(&mut calc_tag); + if fixed_time_eq(&calc_tag, tag) { + true + } else { + false + } + } + } +} +#[cfg(not(fuzzing))] +pub use self::real_chachapoly::ChaCha20Poly1305RFC; + +#[cfg(fuzzing)] +mod fuzzy_chachapoly { + #[derive(Clone, Copy)] + pub struct ChaCha20Poly1305RFC { + tag: [u8; 16], + finished: bool, + } + impl ChaCha20Poly1305RFC { + pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC { + assert!(key.len() == 16 || key.len() == 32); + assert!(nonce.len() == 12); + + // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant + assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); + + let mut tag = [0; 16]; + tag.copy_from_slice(&key[0..16]); + + ChaCha20Poly1305RFC { + tag, + finished: false, + } + } + + pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { + assert!(input.len() == output.len()); + assert!(self.finished == false); + + output.copy_from_slice(&input); + out_tag.copy_from_slice(&self.tag); + self.finished = true; + } + + pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { + self.encrypt_in_place(input_output); + self.finish_and_get_tag(out_tag); + } + + pub(in super::super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) { + assert!(self.finished == false); + } + + pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { + assert!(self.finished == false); + out_tag.copy_from_slice(&self.tag); + self.finished = true; + } + + pub fn variable_time_decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + assert!(input.len() == output.len()); + assert!(self.finished == false); + + if tag[..] != self.tag[..] { return Err(()); } + output.copy_from_slice(input); + self.finished = true; + Ok(()) + } + + pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + self.decrypt_in_place(input_output); + if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } + } + + pub(in super::super) fn decrypt_in_place(&mut self, _input: &mut [u8]) { + assert!(self.finished == false); + } + + pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { + if tag[..] != self.tag[..] { return false; } + self.finished = true; + true + } + } +} +#[cfg(fuzzing)] +pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC; diff --git a/lightning/src/crypto/mod.rs b/lightning/src/crypto/mod.rs new file mode 100644 index 00000000000..6efd120667c --- /dev/null +++ b/lightning/src/crypto/mod.rs @@ -0,0 +1,8 @@ +use bitcoin::hashes::cmp::fixed_time_eq; + +pub(crate) mod chacha20; +#[cfg(not(fuzzing))] +pub(crate) mod poly1305; +pub(crate) mod chacha20poly1305rfc; +pub(crate) mod streams; +pub(crate) mod utils; diff --git a/lightning/src/util/poly1305.rs b/lightning/src/crypto/poly1305.rs similarity index 99% rename from lightning/src/util/poly1305.rs rename to lightning/src/crypto/poly1305.rs index 1abda74b6c2..a1b9fbac516 100644 --- a/lightning/src/util/poly1305.rs +++ b/lightning/src/crypto/poly1305.rs @@ -205,10 +205,10 @@ impl Poly1305 { #[cfg(test)] mod test { - use crate::prelude::*; use core::iter::repeat; + use alloc::vec::Vec; - use crate::util::poly1305::Poly1305; + use super::Poly1305; fn poly1305(key: &[u8], msg: &[u8], mac: &mut [u8]) { let mut poly = Poly1305::new(key); diff --git a/lightning/src/crypto/streams.rs b/lightning/src/crypto/streams.rs new file mode 100644 index 00000000000..14921a38612 --- /dev/null +++ b/lightning/src/crypto/streams.rs @@ -0,0 +1,211 @@ +use crate::crypto::chacha20::ChaCha20; +use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; + +use crate::ln::msgs::DecodeError; +use crate::util::ser::{FixedLengthReader, LengthRead, LengthReadableArgs, Readable, Writeable, Writer}; +use crate::io::{self, Read, Write}; + +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) + } +} + +/// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and +/// serialized. This allows us to avoid an intermediate Vec allocation. +pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> { + pub rho: [u8; 32], + pub writeable: &'a W, +} + +impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> { + #[allow(unused)] // This will be used for onion messages soon + pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> { + Self { rho, writeable } + } +} + +impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> { + // Simultaneously write and encrypt Self::writeable. + fn write(&self, w: &mut W) -> Result<(), io::Error> { + let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]); + let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w }; + self.writeable.write(&mut chacha_stream)?; + let mut tag = [0 as u8; 16]; + chacha.finish_and_get_tag(&mut tag); + tag.write(w)?; + + Ok(()) + } +} + +/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and +/// deserialized. This allows us to avoid an intermediate Vec allocation. +pub(crate) struct ChaChaPolyReadAdapter { + pub readable: R, +} + +impl LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter { + // Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable. + // LengthRead must be used instead of std::io::Read because we need the total length to separate + // out the tag at the end. + fn read(mut r: &mut R, secret: [u8; 32]) -> Result { + if r.total_bytes() < 16 { return Err(DecodeError::InvalidValue) } + + let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]); + let decrypted_len = r.total_bytes() - 16; + let s = FixedLengthReader::new(&mut r, decrypted_len); + let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s }; + let readable: T = Readable::read(&mut chacha_stream)?; + chacha_stream.read.eat_remaining()?; + + let mut tag = [0 as u8; 16]; + r.read_exact(&mut tag)?; + if !chacha.finish_and_check_tag(&tag) { + return Err(DecodeError::InvalidValue) + } + + Ok(Self { readable }) + } +} + + +/// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read. +struct ChaChaPolyReader<'a, R: Read> { + pub chacha: &'a mut ChaCha20Poly1305RFC, + pub read: R, +} + +impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> { + // Decrypt bytes from Self::read into `dest`. + // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads + // complete. + fn read(&mut self, dest: &mut [u8]) -> Result { + let res = self.read.read(dest)?; + if res > 0 { + self.chacha.decrypt_in_place(&mut dest[0..res]); + } + Ok(res) + } +} + +/// Enables simultaneously writing and encrypting a byte stream into a Writer. +struct ChaChaPolyWriter<'a, W: Writer> { + pub chacha: &'a mut ChaCha20Poly1305RFC, + pub write: &'a mut W, +} + +impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> { + // Encrypt then write bytes from `src` into Self::write. + // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes + // complete. + fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> { + let mut src_idx = 0; + while src_idx < src.len() { + let mut write_buffer = [0; 8192]; + let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail"); + self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]); + self.write.write_all(&write_buffer[..bytes_written])?; + src_idx += bytes_written; + } + Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use crate::ln::msgs::DecodeError; + use super::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; + use crate::util::ser::{self, FixedLengthReader, LengthReadableArgs, Writeable}; + + // Used for for testing various lengths of serialization. + #[derive(Debug, PartialEq, Eq)] + struct TestWriteable { + field1: Vec, + field2: Vec, + field3: Vec, + } + impl_writeable_tlv_based!(TestWriteable, { + (1, field1, required_vec), + (2, field2, required_vec), + (3, field3, required_vec), + }); + + #[test] + fn test_chacha_stream_adapters() { + // Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an + // encrypted object. + macro_rules! check_object_read_write { + ($obj: expr) => { + // First, serialize the object, encrypted with ChaCha20Poly1305. + let rho = [42; 32]; + let writeable_len = $obj.serialized_length() as u64 + 16; + let write_adapter = ChaChaPolyWriteAdapter::new(rho, &$obj); + let encrypted_writeable_bytes = write_adapter.encode(); + let encrypted_writeable = &encrypted_writeable_bytes[..]; + + // Now deserialize the object back and make sure it matches the original. + let mut rd = FixedLengthReader::new(encrypted_writeable, writeable_len); + let read_adapter = >::read(&mut rd, rho).unwrap(); + assert_eq!($obj, read_adapter.readable); + }; + } + + // Try a big object that will require multiple write buffers. + let big_writeable = TestWriteable { + field1: vec![43], + field2: vec![44; 4192], + field3: vec![45; 4192 + 1], + }; + check_object_read_write!(big_writeable); + + // Try a small object that fits into one write buffer. + let small_writeable = TestWriteable { + field1: vec![43], + field2: vec![44], + field3: vec![45], + }; + check_object_read_write!(small_writeable); + } + + fn do_chacha_stream_adapters_ser_macros() -> Result<(), DecodeError> { + let writeable = TestWriteable { + field1: vec![43], + field2: vec![44; 4192], + field3: vec![45; 4192 + 1], + }; + + // First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305. + let rho = [42; 32]; + let write_adapter = ChaChaPolyWriteAdapter::new(rho, &writeable); + let mut writer = ser::VecWriter(Vec::new()); + encode_tlv_stream!(&mut writer, { + (1, write_adapter, required), + }); + + // Now deserialize the object back and make sure it matches the original. + let mut read_adapter: Option> = None; + decode_tlv_stream!(&writer.0[..], { + (1, read_adapter, (option: LengthReadableArgs, rho)), + }); + assert_eq!(writeable, read_adapter.unwrap().readable); + + Ok(()) + } + + #[test] + fn chacha_stream_adapters_ser_macros() { + // Test that our stream adapters work as expected with the TLV macros. + // This also serves to test the `option: $trait` variant of the `_decode_tlv` ser macro. + do_chacha_stream_adapters_ser_macros().unwrap() + } +} diff --git a/lightning/src/util/crypto.rs b/lightning/src/crypto/utils.rs similarity index 100% rename from lightning/src/util/crypto.rs rename to lightning/src/crypto/utils.rs diff --git a/lightning/src/lib.rs b/lightning/src/lib.rs index 6eefb3983cc..160f632415b 100644 --- a/lightning/src/lib.rs +++ b/lightning/src/lib.rs @@ -84,6 +84,8 @@ pub mod onion_message; pub mod blinded_path; pub mod events; +pub(crate) mod crypto; + #[cfg(feature = "std")] /// Re-export of either `core2::io` or `std::io`, depending on the `std` feature flag. pub use std::io; diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 672e3aa862e..18c4d83406c 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -45,7 +45,7 @@ use crate::ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI} use core::ops::Deref; use crate::chain; use crate::ln::features::ChannelTypeFeatures; -use crate::util::crypto::{sign, sign_with_aux_rand}; +use crate::crypto::utils::{sign, sign_with_aux_rand}; use super::channel_keys::{DelayedPaymentBasepoint, DelayedPaymentKey, HtlcKey, HtlcBasepoint, RevocationKey, RevocationBasepoint}; /// Maximum number of one-way in-flight HTLC (protocol-level value). diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 2ee93a6c46e..eeae514fdf3 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -18,8 +18,8 @@ use crate::sign::{KeyMaterial, EntropySource}; use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::msgs; use crate::ln::msgs::MAX_VALUE_MSAT; -use crate::util::chacha20::ChaCha20; -use crate::util::crypto::hkdf_extract_expand_5x; +use crate::crypto::chacha20::ChaCha20; +use crate::crypto::utils::hkdf_extract_expand_5x; use crate::util::errors::APIError; use crate::util::logger::Logger; diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 74740a6f227..b6270181439 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -19,7 +19,7 @@ use crate::ln::channel; use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, PaymentId, RecipientOnionFields}; use crate::ln::msgs::ChannelMessageHandler; use crate::util::config::UserConfig; -use crate::util::crypto::sign; +use crate::crypto::utils::sign; use crate::util::ser::Writeable; use crate::util::scid_utils::block_from_scid; use crate::util::test_utils; diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 1794aefe7ef..efd04d143d9 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -53,7 +53,7 @@ use crate::io::{self, Cursor, Read}; use crate::io_extras::read_to_end; use crate::events::{EventsProvider, MessageSendEventsProvider}; -use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; +use crate::crypto::streams::ChaChaPolyReadAdapter; use crate::util::logger; use crate::util::ser::{LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize}; use crate::util::base32; diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index ac0bb6189c6..d9455fc1de2 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -14,7 +14,8 @@ use crate::ln::wire::Encode; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop}; use crate::sign::NodeSigner; -use crate::util::chacha20::{ChaCha20, ChaChaReader}; +use crate::crypto::chacha20::ChaCha20; +use crate::crypto::streams::ChaChaReader; use crate::util::errors::{self, APIError}; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter}; use crate::util::logger::Logger; diff --git a/lightning/src/ln/peer_channel_encryptor.rs b/lightning/src/ln/peer_channel_encryptor.rs index c9e1ac47886..51e34bdb969 100644 --- a/lightning/src/ln/peer_channel_encryptor.rs +++ b/lightning/src/ln/peer_channel_encryptor.rs @@ -24,8 +24,8 @@ use bitcoin::secp256k1; use hex::DisplayHex; -use crate::util::chacha20poly1305rfc::ChaCha20Poly1305RFC; -use crate::util::crypto::hkdf_extract_expand_twice; +use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; +use crate::crypto::utils::hkdf_extract_expand_twice; use crate::util::ser::VecWriter; use core::ops::Deref; @@ -188,7 +188,7 @@ impl PeerChannelEncryptor { nonce[4..].copy_from_slice(&n.to_le_bytes()[..]); let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce, h); - if !chacha.decrypt(&cyphertext[0..cyphertext.len() - 16], res, &cyphertext[cyphertext.len() - 16..]) { + if chacha.variable_time_decrypt(&cyphertext[0..cyphertext.len() - 16], res, &cyphertext[cyphertext.len() - 16..]).is_err() { return Err(LightningError{err: "Bad MAC".to_owned(), action: msgs::ErrorAction::DisconnectPeer{ msg: None }}); } Ok(()) diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 5ce02c54d08..d9349fdadbf 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -19,7 +19,7 @@ use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use super::messenger::CustomOnionMessageHandler; use super::offers::OffersMessage; -use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; +use crate::crypto::streams::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; use crate::util::logger::Logger; use crate::util::ser::{BigSize, FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer}; diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 277661862e3..0b19369a4aa 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -26,7 +26,7 @@ use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp}; use crate::sign::EntropySource; use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer}; use crate::util::logger::{Level, Logger}; -use crate::util::chacha20::ChaCha20; +use crate::crypto::chacha20::ChaCha20; use crate::io; use crate::prelude::*; @@ -3195,7 +3195,7 @@ mod tests { use crate::offers::invoice::BlindedPayInfo; use crate::util::config::UserConfig; use crate::util::test_utils as ln_test_utils; - use crate::util::chacha20::ChaCha20; + use crate::crypto::chacha20::ChaCha20; use crate::util::ser::{Readable, Writeable}; #[cfg(c_bindings)] use crate::util::ser::Writer; diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 4e418f049bb..c4bb6fb8020 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -38,7 +38,7 @@ use bitcoin::secp256k1::schnorr; use bitcoin::{secp256k1, Sequence, Witness, Txid}; use crate::util::transaction_utils; -use crate::util::crypto::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; +use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::util::ser::{Writeable, Writer, Readable, ReadableArgs}; use crate::chain::transaction::OutPoint; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; @@ -65,7 +65,7 @@ use crate::sign::ecdsa::{EcdsaChannelSigner, WriteableEcdsaChannelSigner}; #[cfg(taproot)] use crate::sign::taproot::TaprootChannelSigner; use crate::util::atomic_counter::AtomicCounter; -use crate::util::chacha20::ChaCha20; +use crate::crypto::chacha20::ChaCha20; use crate::util::invoice::construct_invoice_preimage; pub(crate) mod type_resolver; diff --git a/lightning/src/util/chacha20poly1305rfc.rs b/lightning/src/util/chacha20poly1305rfc.rs deleted file mode 100644 index d5792e0ac2b..00000000000 --- a/lightning/src/util/chacha20poly1305rfc.rs +++ /dev/null @@ -1,427 +0,0 @@ -// ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305 -// Instead, we steal rust-crypto's implementation and tweak it to match the RFC. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. -// -// This is a port of Andrew Moons poly1305-donna -// https://github.com/floodyberry/poly1305-donna - -use crate::ln::msgs::DecodeError; -use crate::util::ser::{FixedLengthReader, LengthRead, LengthReadableArgs, Readable, Writeable, Writer}; -use crate::io::{self, Read, Write}; - -#[cfg(not(fuzzing))] -mod real_chachapoly { - use crate::util::chacha20::ChaCha20; - use crate::util::poly1305::Poly1305; - use bitcoin::hashes::cmp::fixed_time_eq; - - #[derive(Clone, Copy)] - pub struct ChaCha20Poly1305RFC { - cipher: ChaCha20, - mac: Poly1305, - finished: bool, - data_len: usize, - aad_len: u64, - } - - impl ChaCha20Poly1305RFC { - #[inline] - fn pad_mac_16(mac: &mut Poly1305, len: usize) { - if len % 16 != 0 { - mac.input(&[0; 16][0..16 - (len % 16)]); - } - } - pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC { - assert!(key.len() == 16 || key.len() == 32); - assert!(nonce.len() == 12); - - // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant - assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); - - let mut cipher = ChaCha20::new(key, &nonce[4..]); - let mut mac_key = [0u8; 64]; - let zero_key = [0u8; 64]; - cipher.process(&zero_key, &mut mac_key); - - let mut mac = Poly1305::new(&mac_key[..32]); - mac.input(aad); - ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len()); - - ChaCha20Poly1305RFC { - cipher, - mac, - finished: false, - data_len: 0, - aad_len: aad.len() as u64, - } - } - - pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { - assert!(input.len() == output.len()); - assert!(self.finished == false); - self.cipher.process(input, output); - self.data_len += input.len(); - self.mac.input(output); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.finished = true; - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - self.mac.raw_result(out_tag); - } - - pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { - self.encrypt_in_place(input_output); - self.finish_and_get_tag(out_tag); - } - - // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag` - // below. - pub(super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) { - debug_assert!(self.finished == false); - self.cipher.process_in_place(input_output); - self.data_len += input_output.len(); - self.mac.input(input_output); - } - - // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish - // encrypting and calculate the tag. - pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { - debug_assert!(self.finished == false); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.finished = true; - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - self.mac.raw_result(out_tag); - } - - pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool { - assert!(input.len() == output.len()); - assert!(self.finished == false); - - self.finished = true; - - self.mac.input(input); - - self.data_len += input.len(); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - - let mut calc_tag = [0u8; 16]; - self.mac.raw_result(&mut calc_tag); - if fixed_time_eq(&calc_tag, tag) { - self.cipher.process(input, output); - true - } else { - false - } - } - - pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { - self.decrypt_in_place(input_output); - if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } - } - - /// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it - /// later when decryption finishes. - /// - /// Should never be `pub` because the public API should always enforce tag checking. - pub(super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) { - debug_assert!(self.finished == false); - self.mac.input(input_output); - self.data_len += input_output.len(); - self.cipher.process_in_place(input_output); - } - - /// If we were previously decrypting with `just_decrypt_in_place`, this method must be used - /// to check the tag. Returns whether or not the tag is valid. - pub(super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { - debug_assert!(self.finished == false); - self.finished = true; - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - - let mut calc_tag = [0u8; 16]; - self.mac.raw_result(&mut calc_tag); - if fixed_time_eq(&calc_tag, tag) { - true - } else { - false - } - } - } -} -#[cfg(not(fuzzing))] -pub use self::real_chachapoly::ChaCha20Poly1305RFC; - -/// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read. -struct ChaChaPolyReader<'a, R: Read> { - pub chacha: &'a mut ChaCha20Poly1305RFC, - pub read: R, -} - -impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> { - // Decrypt bytes from Self::read into `dest`. - // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads - // complete. - fn read(&mut self, dest: &mut [u8]) -> Result { - let res = self.read.read(dest)?; - if res > 0 { - self.chacha.decrypt_in_place(&mut dest[0..res]); - } - Ok(res) - } -} - -/// Enables simultaneously writing and encrypting a byte stream into a Writer. -struct ChaChaPolyWriter<'a, W: Writer> { - pub chacha: &'a mut ChaCha20Poly1305RFC, - pub write: &'a mut W, -} - -impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> { - // Encrypt then write bytes from `src` into Self::write. - // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes - // complete. - fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> { - let mut src_idx = 0; - while src_idx < src.len() { - let mut write_buffer = [0; 8192]; - let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail"); - self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]); - self.write.write_all(&write_buffer[..bytes_written])?; - src_idx += bytes_written; - } - Ok(()) - } -} - -/// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and -/// serialized. This allows us to avoid an intermediate Vec allocation. -pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> { - pub rho: [u8; 32], - pub writeable: &'a W, -} - -impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> { - #[allow(unused)] // This will be used for onion messages soon - pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> { - Self { rho, writeable } - } -} - -impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> { - // Simultaneously write and encrypt Self::writeable. - fn write(&self, w: &mut W) -> Result<(), io::Error> { - let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]); - let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w }; - self.writeable.write(&mut chacha_stream)?; - let mut tag = [0 as u8; 16]; - chacha.finish_and_get_tag(&mut tag); - tag.write(w)?; - - Ok(()) - } -} - -/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and -/// deserialized. This allows us to avoid an intermediate Vec allocation. -pub(crate) struct ChaChaPolyReadAdapter { - pub readable: R, -} - -impl LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter { - // Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable. - // LengthRead must be used instead of std::io::Read because we need the total length to separate - // out the tag at the end. - fn read(mut r: &mut R, secret: [u8; 32]) -> Result { - if r.total_bytes() < 16 { return Err(DecodeError::InvalidValue) } - - let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]); - let decrypted_len = r.total_bytes() - 16; - let s = FixedLengthReader::new(&mut r, decrypted_len); - let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s }; - let readable: T = Readable::read(&mut chacha_stream)?; - chacha_stream.read.eat_remaining()?; - - let mut tag = [0 as u8; 16]; - r.read_exact(&mut tag)?; - if !chacha.finish_and_check_tag(&tag) { - return Err(DecodeError::InvalidValue) - } - - Ok(Self { readable }) - } -} - -#[cfg(fuzzing)] -mod fuzzy_chachapoly { - #[derive(Clone, Copy)] - pub struct ChaCha20Poly1305RFC { - tag: [u8; 16], - finished: bool, - } - impl ChaCha20Poly1305RFC { - pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC { - assert!(key.len() == 16 || key.len() == 32); - assert!(nonce.len() == 12); - - // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant - assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); - - let mut tag = [0; 16]; - tag.copy_from_slice(&key[0..16]); - - ChaCha20Poly1305RFC { - tag, - finished: false, - } - } - - pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { - assert!(input.len() == output.len()); - assert!(self.finished == false); - - output.copy_from_slice(&input); - out_tag.copy_from_slice(&self.tag); - self.finished = true; - } - - pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { - self.encrypt_in_place(input_output); - self.finish_and_get_tag(out_tag); - } - - pub(super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) { - assert!(self.finished == false); - } - - pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { - assert!(self.finished == false); - out_tag.copy_from_slice(&self.tag); - self.finished = true; - } - - pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool { - assert!(input.len() == output.len()); - assert!(self.finished == false); - - if tag[..] != self.tag[..] { return false; } - output.copy_from_slice(input); - self.finished = true; - true - } - - pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { - self.decrypt_in_place(input_output); - if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } - } - - pub(super) fn decrypt_in_place(&mut self, _input: &mut [u8]) { - assert!(self.finished == false); - } - - pub(super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { - if tag[..] != self.tag[..] { return false; } - self.finished = true; - true - } - } -} -#[cfg(fuzzing)] -pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC; - -#[cfg(test)] -mod tests { - use crate::ln::msgs::DecodeError; - use super::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; - use crate::util::ser::{self, FixedLengthReader, LengthReadableArgs, Writeable}; - - // Used for for testing various lengths of serialization. - #[derive(Debug, PartialEq, Eq)] - struct TestWriteable { - field1: Vec, - field2: Vec, - field3: Vec, - } - impl_writeable_tlv_based!(TestWriteable, { - (1, field1, required_vec), - (2, field2, required_vec), - (3, field3, required_vec), - }); - - #[test] - fn test_chacha_stream_adapters() { - // Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an - // encrypted object. - macro_rules! check_object_read_write { - ($obj: expr) => { - // First, serialize the object, encrypted with ChaCha20Poly1305. - let rho = [42; 32]; - let writeable_len = $obj.serialized_length() as u64 + 16; - let write_adapter = ChaChaPolyWriteAdapter::new(rho, &$obj); - let encrypted_writeable_bytes = write_adapter.encode(); - let encrypted_writeable = &encrypted_writeable_bytes[..]; - - // Now deserialize the object back and make sure it matches the original. - let mut rd = FixedLengthReader::new(encrypted_writeable, writeable_len); - let read_adapter = >::read(&mut rd, rho).unwrap(); - assert_eq!($obj, read_adapter.readable); - }; - } - - // Try a big object that will require multiple write buffers. - let big_writeable = TestWriteable { - field1: vec![43], - field2: vec![44; 4192], - field3: vec![45; 4192 + 1], - }; - check_object_read_write!(big_writeable); - - // Try a small object that fits into one write buffer. - let small_writeable = TestWriteable { - field1: vec![43], - field2: vec![44], - field3: vec![45], - }; - check_object_read_write!(small_writeable); - } - - fn do_chacha_stream_adapters_ser_macros() -> Result<(), DecodeError> { - let writeable = TestWriteable { - field1: vec![43], - field2: vec![44; 4192], - field3: vec![45; 4192 + 1], - }; - - // First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305. - let rho = [42; 32]; - let write_adapter = ChaChaPolyWriteAdapter::new(rho, &writeable); - let mut writer = ser::VecWriter(Vec::new()); - encode_tlv_stream!(&mut writer, { - (1, write_adapter, required), - }); - - // Now deserialize the object back and make sure it matches the original. - let mut read_adapter: Option> = None; - decode_tlv_stream!(&writer.0[..], { - (1, read_adapter, (option: LengthReadableArgs, rho)), - }); - assert_eq!(writeable, read_adapter.unwrap().readable); - - Ok(()) - } - - #[test] - fn chacha_stream_adapters_ser_macros() { - // Test that our stream adapters work as expected with the TLV macros. - // This also serves to test the `option: $trait` variant of the `_decode_tlv` ser macro. - do_chacha_stream_adapters_ser_macros().unwrap() - } -} diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index e86885a83db..f2116032786 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -29,10 +29,6 @@ pub(crate) mod base32; pub(crate) mod atomic_counter; pub(crate) mod byte_utils; -pub(crate) mod chacha20; -#[cfg(not(fuzzing))] -pub(crate) mod poly1305; -pub(crate) mod chacha20poly1305rfc; pub(crate) mod transaction_utils; pub(crate) mod scid_utils; pub(crate) mod time; @@ -43,9 +39,6 @@ pub mod indexed_map; #[macro_use] pub(crate) mod macro_logger; -/// Cryptography utilities. -pub(crate) mod crypto; - // These have to come after macro_logger to build pub mod logger; pub mod config; diff --git a/lightning/src/util/scid_utils.rs b/lightning/src/util/scid_utils.rs index fbbcc69a133..45b24fd14b0 100644 --- a/lightning/src/util/scid_utils.rs +++ b/lightning/src/util/scid_utils.rs @@ -69,7 +69,7 @@ pub(crate) mod fake_scid { use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use crate::sign::EntropySource; - use crate::util::chacha20::ChaCha20; + use crate::crypto::chacha20::ChaCha20; use crate::util::scid_utils; use core::convert::TryInto;