Move cryptographic algorithms and utilities to a new `crypto` mod
[rust-lightning] / lightning / src / crypto / streams.rs
1 use crate::crypto::chacha20::ChaCha20;
2 use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC;
3
4 use crate::ln::msgs::DecodeError;
5 use crate::util::ser::{FixedLengthReader, LengthRead, LengthReadableArgs, Readable, Writeable, Writer};
6 use crate::io::{self, Read, Write};
7
8 pub(crate) struct ChaChaReader<'a, R: io::Read> {
9         pub chacha: &'a mut ChaCha20,
10         pub read: R,
11 }
12 impl<'a, R: io::Read> io::Read for ChaChaReader<'a, R> {
13         fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
14                 let res = self.read.read(dest)?;
15                 if res > 0 {
16                         self.chacha.process_in_place(&mut dest[0..res]);
17                 }
18                 Ok(res)
19         }
20 }
21
22 /// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and
23 /// serialized. This allows us to avoid an intermediate Vec allocation.
24 pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> {
25         pub rho: [u8; 32],
26         pub writeable: &'a W,
27 }
28
29 impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> {
30         #[allow(unused)] // This will be used for onion messages soon
31         pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> {
32                 Self { rho, writeable }
33         }
34 }
35
36 impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
37         // Simultaneously write and encrypt Self::writeable.
38         fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
39                 let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]);
40                 let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w };
41                 self.writeable.write(&mut chacha_stream)?;
42                 let mut tag = [0 as u8; 16];
43                 chacha.finish_and_get_tag(&mut tag);
44                 tag.write(w)?;
45
46                 Ok(())
47         }
48 }
49
50 /// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and
51 /// deserialized. This allows us to avoid an intermediate Vec allocation.
52 pub(crate) struct ChaChaPolyReadAdapter<R: Readable> {
53         pub readable: R,
54 }
55
56 impl<T: Readable> LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter<T> {
57         // Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable.
58         // LengthRead must be used instead of std::io::Read because we need the total length to separate
59         // out the tag at the end.
60         fn read<R: LengthRead>(mut r: &mut R, secret: [u8; 32]) -> Result<Self, DecodeError> {
61                 if r.total_bytes() < 16 { return Err(DecodeError::InvalidValue) }
62
63                 let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]);
64                 let decrypted_len = r.total_bytes() - 16;
65                 let s = FixedLengthReader::new(&mut r, decrypted_len);
66                 let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s };
67                 let readable: T = Readable::read(&mut chacha_stream)?;
68                 chacha_stream.read.eat_remaining()?;
69
70                 let mut tag = [0 as u8; 16];
71                 r.read_exact(&mut tag)?;
72                 if !chacha.finish_and_check_tag(&tag) {
73                         return Err(DecodeError::InvalidValue)
74                 }
75
76                 Ok(Self { readable })
77         }
78 }
79
80
81 /// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read.
82 struct ChaChaPolyReader<'a, R: Read> {
83         pub chacha: &'a mut ChaCha20Poly1305RFC,
84         pub read: R,
85 }
86
87 impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> {
88         // Decrypt bytes from Self::read into `dest`.
89         // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads
90         // complete.
91         fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
92                 let res = self.read.read(dest)?;
93                 if res > 0 {
94                         self.chacha.decrypt_in_place(&mut dest[0..res]);
95                 }
96                 Ok(res)
97         }
98 }
99
100 /// Enables simultaneously writing and encrypting a byte stream into a Writer.
101 struct ChaChaPolyWriter<'a, W: Writer> {
102         pub chacha: &'a mut ChaCha20Poly1305RFC,
103         pub write: &'a mut W,
104 }
105
106 impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
107         // Encrypt then write bytes from `src` into Self::write.
108         // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
109         // complete.
110         fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> {
111                 let mut src_idx = 0;
112                 while src_idx < src.len() {
113                         let mut write_buffer = [0; 8192];
114                         let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail");
115                         self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]);
116                         self.write.write_all(&write_buffer[..bytes_written])?;
117                         src_idx += bytes_written;
118                 }
119                 Ok(())
120         }
121 }
122
123
124 #[cfg(test)]
125 mod tests {
126         use crate::ln::msgs::DecodeError;
127         use super::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
128         use crate::util::ser::{self, FixedLengthReader, LengthReadableArgs, Writeable};
129
130         // Used for for testing various lengths of serialization.
131         #[derive(Debug, PartialEq, Eq)]
132         struct TestWriteable {
133                 field1: Vec<u8>,
134                 field2: Vec<u8>,
135                 field3: Vec<u8>,
136         }
137         impl_writeable_tlv_based!(TestWriteable, {
138                 (1, field1, required_vec),
139                 (2, field2, required_vec),
140                 (3, field3, required_vec),
141         });
142
143         #[test]
144         fn test_chacha_stream_adapters() {
145                 // Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an
146                 // encrypted object.
147                 macro_rules! check_object_read_write {
148                         ($obj: expr) => {
149                                 // First, serialize the object, encrypted with ChaCha20Poly1305.
150                                 let rho = [42; 32];
151                                 let writeable_len = $obj.serialized_length() as u64 + 16;
152                                 let write_adapter = ChaChaPolyWriteAdapter::new(rho, &$obj);
153                                 let encrypted_writeable_bytes = write_adapter.encode();
154                                 let encrypted_writeable = &encrypted_writeable_bytes[..];
155
156                                 // Now deserialize the object back and make sure it matches the original.
157                                 let mut rd = FixedLengthReader::new(encrypted_writeable, writeable_len);
158                                 let read_adapter = <ChaChaPolyReadAdapter<TestWriteable>>::read(&mut rd, rho).unwrap();
159                                 assert_eq!($obj, read_adapter.readable);
160                         };
161                 }
162
163                 // Try a big object that will require multiple write buffers.
164                 let big_writeable = TestWriteable {
165                         field1: vec![43],
166                         field2: vec![44; 4192],
167                         field3: vec![45; 4192 + 1],
168                 };
169                 check_object_read_write!(big_writeable);
170
171                 // Try a small object that fits into one write buffer.
172                 let small_writeable = TestWriteable {
173                         field1: vec![43],
174                         field2: vec![44],
175                         field3: vec![45],
176                 };
177                 check_object_read_write!(small_writeable);
178         }
179
180         fn do_chacha_stream_adapters_ser_macros() -> Result<(), DecodeError> {
181                 let writeable = TestWriteable {
182                         field1: vec![43],
183                         field2: vec![44; 4192],
184                         field3: vec![45; 4192 + 1],
185                 };
186
187                 // First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305.
188                 let rho = [42; 32];
189                 let write_adapter = ChaChaPolyWriteAdapter::new(rho, &writeable);
190                 let mut writer = ser::VecWriter(Vec::new());
191                 encode_tlv_stream!(&mut writer, {
192                         (1, write_adapter, required),
193                 });
194
195                 // Now deserialize the object back and make sure it matches the original.
196                 let mut read_adapter: Option<ChaChaPolyReadAdapter<TestWriteable>> = None;
197                 decode_tlv_stream!(&writer.0[..], {
198                         (1, read_adapter, (option: LengthReadableArgs, rho)),
199                 });
200                 assert_eq!(writeable, read_adapter.unwrap().readable);
201
202                 Ok(())
203         }
204
205         #[test]
206         fn chacha_stream_adapters_ser_macros() {
207                 // Test that our stream adapters work as expected with the TLV macros.
208                 // This also serves to test the `option: $trait` variant of the `_decode_tlv` ser macro.
209                 do_chacha_stream_adapters_ser_macros().unwrap()
210         }
211 }