fire_crypto/cipher/
key.rs

1use super::{Mac, MacNotEqual};
2use crate::xor;
3
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use std::fmt;
7
8use zeroize::Zeroize;
9
10use chacha20::cipher::typenum::U10;
11use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
12use chacha20::{hchacha, XChaCha20};
13
14use poly1305::Poly1305;
15use universal_hash::{KeyInit, UniversalHash};
16
17use generic_array::GenericArray;
18
19// KEY
20
21const BLOCK_SIZE: u64 = 64;
22
23/// A Key that allows to encrypt and decrypt messages.
24pub struct Key {
25	shared_secret: [u8; 32],
26	initial_nonce: [u8; 24],
27	count: u64,
28}
29
30impl Key {
31	/// Creates a new key.  
32	/// And modifying the shared_secret to be a uniformly random key.
33	pub(crate) fn new(
34		shared_secret: [u8; 32],
35		initial_nonce: [u8; 24],
36	) -> Self {
37		// is this really necessary See: https://github.com/RustCrypto/AEADs/pull/295
38		let shared_secret = hchacha::<U10>(
39			shared_secret.as_ref().into(),
40			&GenericArray::default(),
41		)
42		.into();
43
44		Self {
45			shared_secret,
46			initial_nonce,
47			count: 0,
48		}
49	}
50
51	/// Encrypts bytes generating returning the generated Mac-
52	pub fn encrypt(&mut self, msg: &mut [u8]) -> Mac {
53		self.new_cipher().encrypt(msg)
54	}
55
56	/// Decrypts data, returning an Error if the Mac's do not
57	/// match.
58	pub fn decrypt(
59		&mut self,
60		msg: &mut [u8],
61		recv_mac: &Mac,
62	) -> Result<(), MacNotEqual> {
63		self.new_cipher().decrypt(msg, recv_mac)
64	}
65
66	/// the cipher should only be used once
67	fn new_cipher(&mut self) -> Cipher {
68		self.count += 1;
69		Cipher::new(&self.shared_secret, &self.initial_nonce, self.count)
70	}
71
72	pub fn into_sync(self) -> SyncKey {
73		SyncKey::new(self.shared_secret, self.initial_nonce, self.count)
74	}
75
76	/// This should only be used in test.
77	///
78	/// Using the same key can lead to nonce reuse
79	/// which makes the encryption or decryption
80	/// unsecure.
81	pub fn dublicate(&self) -> Self {
82		Self {
83			shared_secret: self.shared_secret,
84			initial_nonce: self.initial_nonce,
85			count: self.count,
86		}
87	}
88}
89
90impl fmt::Debug for Key {
91	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92		f.write_str("Key")
93	}
94}
95
96impl Drop for Key {
97	fn drop(&mut self) {
98		self.shared_secret.zeroize();
99		self.initial_nonce.zeroize();
100	}
101}
102
103/// A Key that allows to encrypt and decrypt messages.  
104/// Without having to borrow mutably.
105pub struct SyncKey {
106	shared_secret: [u8; 32],
107	initial_nonce: [u8; 24],
108	count: AtomicU64,
109}
110
111impl SyncKey {
112	/// Creates a new key.  
113	/// And modifying the shared_secret to be a uniformly random key.
114	fn new(
115		shared_secret: [u8; 32],
116		initial_nonce: [u8; 24],
117		count: u64,
118	) -> Self {
119		Self {
120			shared_secret,
121			initial_nonce,
122			// + 1 since the values that will be used are before adding
123			count: AtomicU64::new(count + 1),
124		}
125	}
126
127	/// Encrypts bytes generating returning the generated Mac-
128	pub fn encrypt(&self, msg: &mut [u8]) -> Mac {
129		self.new_cipher().encrypt(msg)
130	}
131
132	/// Decrypts data, returning an Error if the Mac's do not
133	/// match.
134	pub fn decrypt(
135		&self,
136		msg: &mut [u8],
137		recv_mac: &Mac,
138	) -> Result<(), MacNotEqual> {
139		self.new_cipher().decrypt(msg, recv_mac)
140	}
141
142	/// the cipher should only be used once
143	fn new_cipher(&self) -> Cipher {
144		Cipher::new(
145			&self.shared_secret,
146			&self.initial_nonce,
147			// relaxed since we only need to guarantee a number get's used once.
148			self.count.fetch_add(1, Ordering::Relaxed),
149		)
150	}
151}
152
153impl fmt::Debug for SyncKey {
154	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155		f.write_str("SyncKey")
156	}
157}
158
159impl Drop for SyncKey {
160	fn drop(&mut self) {
161		self.shared_secret.zeroize();
162		self.initial_nonce.zeroize();
163	}
164}
165
166trait ToMac {
167	fn to_mac(self, msg_len: usize) -> Mac;
168}
169
170impl ToMac for Poly1305 {
171	fn to_mac(self, msg_len: usize) -> Mac {
172		// like https://docs.rs/crate/chacha20poly1305/0.5.1/source/src/cipher.rs
173		let bytes = (msg_len as u64).to_be_bytes();
174
175		// assuming no aad needs to be set
176		Mac::new(self.compute_unpadded(&bytes))
177	}
178}
179
180fn xor_nonce_with_u64(nonce: &mut [u8; 24], count: u64) {
181	let bytes = count.to_be_bytes();
182	xor(&mut nonce[..8], &bytes);
183	xor(&mut nonce[8..16], &bytes);
184	xor(&mut nonce[16..], &bytes);
185}
186
187struct Cipher {
188	cipher: XChaCha20,
189	poly: Poly1305,
190}
191
192impl Cipher {
193	fn new(
194		shared_secret: &[u8; 32],
195		initial_nonce: &[u8; 24],
196		count: u64,
197	) -> Self {
198		// new chacha
199		let mut iv = *initial_nonce;
200		xor_nonce_with_u64(&mut iv, count);
201
202		let mut cipher =
203			XChaCha20::new(shared_secret.into(), iv.as_ref().into());
204
205		// Derive Poly1305 key from the first 32-bytes of the ChaCha20 keystream
206		let mut mac_key = [0u8; 32];
207		cipher.apply_keystream(&mut mac_key);
208
209		let poly = Poly1305::new(mac_key.as_ref().into());
210
211		mac_key.zeroize();
212
213		// set ChaCha20 counter to 1
214		cipher.seek(BLOCK_SIZE);
215
216		Self { cipher, poly }
217	}
218
219	/// Encrypts bytes generating returning the generated Mac-
220	fn encrypt(mut self, msg: &mut [u8]) -> Mac {
221		self.cipher.apply_keystream(msg);
222		self.poly.update_padded(msg);
223		self.poly.to_mac(msg.len())
224	}
225
226	fn decrypt(
227		mut self,
228		msg: &mut [u8],
229		recv_mac: &Mac,
230	) -> Result<(), MacNotEqual> {
231		self.poly.update_padded(msg);
232		let mac = self.poly.to_mac(msg.len());
233
234		// This performs a constant-time comparison using the `subtle` crate
235		// via Poly1305 `Tag` Struct
236		if recv_mac == &mac {
237			self.cipher.apply_keystream(msg);
238
239			Ok(())
240		} else {
241			Err(MacNotEqual)
242		}
243	}
244}