ssh_cipher/
chacha20poly1305.rs1pub use chacha20::ChaCha20Legacy as ChaCha20;
4
5use crate::Tag;
6use aead::{
7 AeadCore, AeadInOut, Error, KeyInit, KeySizeUser, Result, TagPosition,
8 array::typenum::{U8, U16, U32},
9 inout::InOutBuf,
10};
11use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
12use core::fmt::{self, Debug};
13use ctutils::CtEq;
14use poly1305::{Poly1305, universal_hash::UniversalHash};
15
16#[cfg(feature = "zeroize")]
17use zeroize::{Zeroize, ZeroizeOnDrop};
18
19pub type ChaChaKey = chacha20::Key;
21
22pub type ChaChaNonce = chacha20::LegacyNonce;
24
25#[derive(Clone)]
41pub struct ChaCha20Poly1305 {
42 key: ChaChaKey,
43}
44
45impl KeySizeUser for ChaCha20Poly1305 {
46 type KeySize = U32;
47}
48
49impl KeyInit for ChaCha20Poly1305 {
50 #[inline]
51 fn new(key: &ChaChaKey) -> Self {
52 Self { key: *key }
53 }
54}
55
56impl AeadCore for ChaCha20Poly1305 {
57 type NonceSize = U8;
58 type TagSize = U16;
59 const TAG_POSITION: TagPosition = TagPosition::Postfix;
60}
61
62impl AeadInOut for ChaCha20Poly1305 {
63 fn encrypt_inout_detached(
64 &self,
65 nonce: &ChaChaNonce,
66 associated_data: &[u8],
67 buffer: InOutBuf<'_, '_, u8>,
68 ) -> Result<Tag> {
69 Cipher::new(&self.key, *nonce).encrypt(associated_data, buffer)
70 }
71
72 fn decrypt_inout_detached(
73 &self,
74 nonce: &ChaChaNonce,
75 associated_data: &[u8],
76 buffer: InOutBuf<'_, '_, u8>,
77 tag: &Tag,
78 ) -> Result<()> {
79 Cipher::new(&self.key, *nonce).decrypt(associated_data, buffer, tag)
80 }
81}
82
83impl Debug for ChaCha20Poly1305 {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 f.debug_struct("ChaCha20Poly1305").finish_non_exhaustive()
86 }
87}
88
89impl Drop for ChaCha20Poly1305 {
90 fn drop(&mut self) {
91 #[cfg(feature = "zeroize")]
92 self.key.zeroize();
93 }
94}
95
96#[cfg(feature = "zeroize")]
97impl ZeroizeOnDrop for ChaCha20Poly1305 {}
98
99struct Cipher {
101 cipher: ChaCha20,
102 mac: Poly1305,
103}
104
105impl Cipher {
106 fn new(key: &ChaChaKey, nonce: ChaChaNonce) -> Self {
108 let mut cipher = ChaCha20::new(key, &nonce);
109 let mut poly1305_key = poly1305::Key::default();
110 cipher.apply_keystream(&mut poly1305_key);
111
112 let mac = Poly1305::new(&poly1305_key);
113
114 cipher.seek(64);
116
117 Self { cipher, mac }
118 }
119
120 #[inline]
122 fn encrypt(mut self, aad: &[u8], mut buffer: InOutBuf<'_, '_, u8>) -> Result<Tag> {
123 self.cipher.apply_keystream_inout(buffer.reborrow());
124 compute_mac(self.mac, aad, buffer.get_out())
125 }
126
127 #[inline]
130 fn decrypt(mut self, aad: &[u8], buffer: InOutBuf<'_, '_, u8>, tag: &Tag) -> Result<()> {
131 let expected_tag = compute_mac(self.mac, aad, buffer.get_in())?;
132
133 if expected_tag.ct_eq(tag).into() {
134 self.cipher.apply_keystream_inout(buffer);
135 Ok(())
136 } else {
137 Err(Error)
138 }
139 }
140}
141
142fn compute_mac(mut mac: Poly1305, aad: &[u8], buffer: &[u8]) -> Result<Tag> {
144 if aad.len() > poly1305::BLOCK_SIZE {
147 return Err(Error);
148 }
149
150 let mut block = poly1305::Block::default();
152 block[..aad.len()].copy_from_slice(aad);
153
154 let block_remaining = poly1305::BLOCK_SIZE.checked_sub(aad.len()).ok_or(Error)?;
155 let remaining = if buffer.len() <= block_remaining {
156 let msg_len = aad.len().checked_add(buffer.len()).ok_or(Error)?;
158 block[aad.len()..msg_len].copy_from_slice(buffer);
159 &block[..msg_len]
160 } else {
161 let (head, tail) = buffer.split_at(block_remaining);
163 block[aad.len()..].copy_from_slice(head);
164 mac.update(&[block]);
165 tail
166 };
167
168 Ok(mac.compute_unpadded(remaining))
170}
171
172#[cfg(test)]
173mod tests {
174 use super::{AeadInOut, ChaCha20Poly1305, KeyInit, Poly1305, compute_mac};
175 use hex_literal::hex;
176
177 #[test]
178 fn test_vector() {
179 const KEY: [u8; 32] =
180 hex!("379a8ca9e7e705763633213511e8d92eb148a46f1dd0045ec8164e5d23e456eb");
181 const NONCE: [u8; 8] = hex!("0000000000000003");
182 const AAD: [u8; 4] = hex!("5709db2d");
183 const PT: [u8; 24] = hex!("06050000000c7373682d7573657261757468de5949ab061f");
184 const CT: [u8; 24] = hex!("6dcfb03be8a55e7f0220465672edd921489ea0171198e8a7");
185 const TAG: [u8; 16] = hex!("3e82fe0a2db7128d58ef8d9047963ca3");
186
187 let cipher = ChaCha20Poly1305::new(&KEY.into());
188 let mut buffer = PT;
189 let actual_tag = cipher
190 .encrypt_inout_detached(&NONCE.into(), &AAD, buffer.as_mut_slice().into())
191 .unwrap();
192
193 assert_eq!(buffer, CT);
194 assert_eq!(actual_tag, TAG);
195
196 cipher
197 .decrypt_inout_detached(
198 &NONCE.into(),
199 &AAD,
200 buffer.as_mut_slice().into(),
201 &actual_tag,
202 )
203 .unwrap();
204
205 assert_eq!(buffer, PT);
206 }
207
208 #[test]
209 fn mac_computation_with_aad() {
210 const KEY: &[u8; poly1305::KEY_SIZE] = b"11112222333344445555666677778888";
211 const AAD: &[u8; poly1305::BLOCK_SIZE] = b"0123456789ABCDEF";
212 const PT: &[u8; poly1305::BLOCK_SIZE] = b"abcdefghijklmnop";
213
214 for aad_len in 0..=poly1305::BLOCK_SIZE {
215 for pt_len in 0..=poly1305::BLOCK_SIZE {
216 let mut buffer = [0; poly1305::BLOCK_SIZE * 2];
217 let aad = &AAD[..aad_len];
218 let pt = &PT[..pt_len];
219
220 let eob = aad_len + pt_len;
221 buffer[..aad_len].copy_from_slice(aad);
222 buffer[aad_len..eob].copy_from_slice(pt);
223
224 let poly = Poly1305::new(KEY.into());
225 let expected_mac = poly.clone().compute_unpadded(&buffer[..eob]);
226 let actual_mac = compute_mac(poly, aad, pt).unwrap();
227
228 assert_eq!(expected_mac, actual_mac);
229 }
230 }
231 }
232}