Merge pull request #2828 from TheBlueMatt/2024-01-crypto-module

Move cryptographic algorithms and utilities to a new `crypto` mod
This commit is contained in:
valentinewallace 2024-01-17 12:53:02 -05:00 committed by GitHub
commit a1759582ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 482 additions and 472 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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());

View file

@ -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<usize, io::Error> {
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() {

View file

@ -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 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;

View file

@ -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;

View file

@ -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);

View file

@ -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<usize, io::Error> {
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<W: Writer>(&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<R: Readable> {
pub readable: R,
}
impl<T: Readable> LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter<T> {
// 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<R: LengthRead>(mut r: &mut R, secret: [u8; 32]) -> Result<Self, DecodeError> {
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<usize, io::Error> {
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<u8>,
field2: Vec<u8>,
field3: Vec<u8>,
}
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 = <ChaChaPolyReadAdapter<TestWriteable>>::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<ChaChaPolyReadAdapter<TestWriteable>> = 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()
}
}

View file

@ -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;

View file

@ -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).

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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(())

View file

@ -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};

View file

@ -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::*;
@ -3190,7 +3190,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;

View file

@ -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;

View file

@ -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 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<usize, io::Error> {
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<W: Writer>(&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<R: Readable> {
pub readable: R,
}
impl<T: Readable> LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter<T> {
// 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<R: LengthRead>(mut r: &mut R, secret: [u8; 32]) -> Result<Self, DecodeError> {
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<u8>,
field2: Vec<u8>,
field3: Vec<u8>,
}
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 = <ChaChaPolyReadAdapter<TestWriteable>>::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<ChaChaPolyReadAdapter<TestWriteable>> = 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()
}
}

View file

@ -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;

View file

@ -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;