Merge pull request #15 from TheBlueMatt/master
[rust-lightning] / src / util / chacha20poly1305rfc.rs
1 // ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305
2 // Instead, we steal rust-crypto's implementation and tweak it to match the RFC.
3
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 // This is a port of Andrew Moons poly1305-donna
11 // https://github.com/floodyberry/poly1305-donna
12
13 #[cfg(not(feature = "fuzztarget"))]
14 mod real_chachapoly {
15         use crypto::aead::{AeadEncryptor,AeadDecryptor};
16         use crypto::chacha20::ChaCha20;
17         use crypto::symmetriccipher::SynchronousStreamCipher;
18         use crypto::poly1305::Poly1305;
19         use crypto::mac::Mac;
20         use crypto::util::fixed_time_eq;
21
22         use util::byte_utils;
23
24         #[derive(Clone, Copy)]
25         pub struct ChaCha20Poly1305RFC {
26                 cipher  : ChaCha20,
27                 mac: Poly1305,
28                 finished: bool,
29                 data_len: usize,
30                 aad_len: u64,
31         }
32
33         impl ChaCha20Poly1305RFC {
34                 #[inline]
35                 fn pad_mac_16(mac: &mut Poly1305, len: usize) {
36                         if len % 16 != 0 {
37                                 mac.input(&[0; 16][0..16 - (len % 16)]);
38                         }
39                 }
40                 pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC {
41                         assert!(key.len() == 16 || key.len() == 32);
42                         assert!(nonce.len() == 12);
43
44                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
45                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
46
47                         let mut cipher = ChaCha20::new(key, &nonce[4..]);
48                         let mut mac_key = [0u8; 64];
49                         let zero_key = [0u8; 64];
50                         cipher.process(&zero_key, &mut mac_key);
51
52                         let mut mac = Poly1305::new(&mac_key[..32]);
53                         mac.input(aad);
54                         ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len());
55
56                         ChaCha20Poly1305RFC {
57                                 cipher: cipher,
58                                 mac: mac,
59                                 finished: false,
60                                 data_len: 0,
61                                 aad_len: aad.len() as u64,
62                         }
63                 }
64         }
65
66         impl AeadEncryptor for ChaCha20Poly1305RFC {
67                 fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
68                         assert!(input.len() == output.len());
69                         assert!(self.finished == false);
70                         self.cipher.process(input, output);
71                         self.data_len += input.len();
72                         self.mac.input(output);
73                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
74                         self.finished = true;
75                         self.mac.input(&byte_utils::le64_to_array(self.aad_len));
76                         self.mac.input(&byte_utils::le64_to_array(self.data_len as u64));
77                         self.mac.raw_result(out_tag);
78                 }
79         }
80
81         impl AeadDecryptor for ChaCha20Poly1305RFC {
82                 fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
83                         assert!(input.len() == output.len());
84                         assert!(self.finished == false);
85
86                         self.finished = true;
87
88                         self.mac.input(input);
89
90                         self.data_len += input.len();
91                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
92                         self.mac.input(&byte_utils::le64_to_array(self.aad_len));
93                         self.mac.input(&byte_utils::le64_to_array(self.data_len as u64));
94
95                         let mut calc_tag =  [0u8; 16];
96                         self.mac.raw_result(&mut calc_tag);
97                         if fixed_time_eq(&calc_tag, tag) {
98                                 self.cipher.process(input, output);
99                                 true
100                         } else {
101                                 false
102                         }
103                 }
104         }
105 }
106 #[cfg(not(feature = "fuzztarget"))]
107 pub use self::real_chachapoly::ChaCha20Poly1305RFC;
108
109 #[cfg(feature = "fuzztarget")]
110 mod fuzzy_chachapoly {
111         use crypto::aead::{AeadEncryptor,AeadDecryptor};
112
113         #[derive(Clone, Copy)]
114         pub struct ChaCha20Poly1305RFC {
115                 tag: [u8; 16],
116                 finished: bool,
117         }
118         impl ChaCha20Poly1305RFC {
119                 pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC {
120                         assert!(key.len() == 16 || key.len() == 32);
121                         assert!(nonce.len() == 12);
122
123                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
124                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
125
126                         let mut tag = [0; 16];
127                         tag.copy_from_slice(&key[0..16]);
128
129                         ChaCha20Poly1305RFC {
130                                 tag,
131                                 finished: false,
132                         }
133                 }
134         }
135
136         impl AeadEncryptor for ChaCha20Poly1305RFC {
137                 fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
138                         assert!(input.len() == output.len());
139                         assert!(self.finished == false);
140
141                         output.copy_from_slice(&input);
142                         out_tag.copy_from_slice(&self.tag);
143                         self.finished = true;
144                 }
145         }
146
147         impl AeadDecryptor for ChaCha20Poly1305RFC {
148                 fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
149                         assert!(input.len() == output.len());
150                         assert!(self.finished == false);
151
152                         if tag[..] != self.tag[..] { return false; }
153                         output.copy_from_slice(input);
154                         self.finished = true;
155                         true
156                 }
157         }
158 }
159 #[cfg(feature = "fuzztarget")]
160 pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC;