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 poly1305::{Poly1305, universal_hash::UniversalHash};
13use subtle::ConstantTimeEq;
14
15#[cfg(feature = "zeroize")]
16use zeroize::{Zeroize, ZeroizeOnDrop};
17
18pub type ChaChaKey = chacha20::Key;
20
21pub type ChaChaNonce = chacha20::LegacyNonce;
23
24#[derive(Clone)]
40pub struct ChaCha20Poly1305 {
41 key: ChaChaKey,
42}
43
44impl KeySizeUser for ChaCha20Poly1305 {
45 type KeySize = U32;
46}
47
48impl KeyInit for ChaCha20Poly1305 {
49 #[inline]
50 fn new(key: &ChaChaKey) -> Self {
51 Self { key: *key }
52 }
53}
54
55impl AeadCore for ChaCha20Poly1305 {
56 type NonceSize = U8;
57 type TagSize = U16;
58 const TAG_POSITION: TagPosition = TagPosition::Postfix;
59}
60
61impl AeadInOut for ChaCha20Poly1305 {
62 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 Drop for ChaCha20Poly1305 {
84 fn drop(&mut self) {
85 #[cfg(feature = "zeroize")]
86 self.key.zeroize();
87 }
88}
89
90#[cfg(feature = "zeroize")]
91impl ZeroizeOnDrop for ChaCha20Poly1305 {}
92
93struct Cipher {
95 cipher: ChaCha20,
96 mac: Poly1305,
97}
98
99impl Cipher {
100 pub fn new(key: &ChaChaKey, nonce: &ChaChaNonce) -> Self {
102 let mut cipher = ChaCha20::new(key, nonce);
103 let mut poly1305_key = poly1305::Key::default();
104 cipher.apply_keystream(&mut poly1305_key);
105
106 let mac = Poly1305::new(&poly1305_key);
107
108 cipher.seek(64);
110
111 Self { cipher, mac }
112 }
113
114 #[inline]
116 pub fn encrypt(mut self, aad: &[u8], mut buffer: InOutBuf<'_, '_, u8>) -> Result<Tag> {
117 self.cipher.apply_keystream_inout(buffer.reborrow());
118 compute_mac(self.mac, aad, buffer.get_out())
119 }
120
121 #[inline]
124 pub fn decrypt(mut self, aad: &[u8], buffer: InOutBuf<'_, '_, u8>, tag: &Tag) -> Result<()> {
125 let expected_tag = compute_mac(self.mac, aad, buffer.get_in())?;
126
127 if expected_tag.ct_eq(tag).into() {
128 self.cipher.apply_keystream_inout(buffer);
129 Ok(())
130 } else {
131 Err(Error)
132 }
133 }
134}
135
136fn compute_mac(mut mac: Poly1305, aad: &[u8], buffer: &[u8]) -> Result<Tag> {
138 match aad.len() {
139 0 => Ok(mac.compute_unpadded(buffer)),
140 1..poly1305::BLOCK_SIZE => {
141 let mut block = poly1305::Block::default();
142 block[..aad.len()].copy_from_slice(aad);
143
144 let block_remaining = poly1305::BLOCK_SIZE.checked_sub(aad.len()).ok_or(Error)?;
145 if buffer.len() > block_remaining {
146 let (head, tail) = buffer.split_at(block_remaining);
147 block[aad.len()..].copy_from_slice(head);
148 mac.update(&[block]);
149 Ok(mac.compute_unpadded(tail))
150 } else {
151 let msg_len = aad.len().checked_add(buffer.len()).ok_or(Error)?;
152 block[aad.len()..msg_len].copy_from_slice(buffer);
153 Ok(mac.compute_unpadded(&block[..msg_len]))
154 }
155 }
156 _ => Err(Error),
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::{AeadInOut, ChaCha20Poly1305, KeyInit};
163 use hex_literal::hex;
164
165 #[test]
166 fn test_vector() {
167 let key = hex!("379a8ca9e7e705763633213511e8d92eb148a46f1dd0045ec8164e5d23e456eb");
168 let nonce = hex!("0000000000000003");
169 let aad = hex!("5709db2d");
170 let plaintext = hex!("06050000000c7373682d7573657261757468de5949ab061f");
171 let ciphertext = hex!("6dcfb03be8a55e7f0220465672edd921489ea0171198e8a7");
172 let tag = hex!("3e82fe0a2db7128d58ef8d9047963ca3");
173
174 let cipher = ChaCha20Poly1305::new(key.as_ref());
175 let mut buffer = plaintext.clone();
176 let actual_tag = cipher
177 .encrypt_inout_detached(nonce.as_ref(), &aad, buffer.as_mut_slice().into())
178 .unwrap();
179
180 assert_eq!(buffer, ciphertext);
181 assert_eq!(actual_tag, tag);
182
183 cipher
184 .decrypt_inout_detached(
185 nonce.as_ref(),
186 &aad,
187 buffer.as_mut_slice().into(),
188 &actual_tag,
189 )
190 .unwrap();
191
192 assert_eq!(buffer, plaintext);
193 }
194}