1use std::borrow::Borrow;
18use std::collections::HashMap;
19use std::convert::TryFrom;
20use std::fmt::Debug;
21use std::marker::PhantomData;
22use std::num::Wrapping;
23use std::sync::LazyLock;
24
25use aes::{Aes128, Aes192, Aes256};
26#[cfg(feature = "aws-lc-rs")]
27use aws_lc_rs::aead::{AES_128_GCM as ALGORITHM_AES_128_GCM, AES_256_GCM as ALGORITHM_AES_256_GCM};
28use byteorder::{BigEndian, ByteOrder};
29use ctr::Ctr128BE;
30use delegate::delegate;
31use log::trace;
32#[cfg(all(not(feature = "aws-lc-rs"), feature = "ring"))]
33use ring::aead::{AES_128_GCM as ALGORITHM_AES_128_GCM, AES_256_GCM as ALGORITHM_AES_256_GCM};
34use ssh_encoding::Encode;
35use tokio::io::{AsyncRead, AsyncReadExt};
36
37use self::cbc::CbcWrapper;
38use crate::Error;
39use crate::mac::MacAlgorithm;
40use crate::sshbuffer::SSHBuffer;
41
42pub(crate) mod block;
43pub(crate) mod cbc;
44pub(crate) mod chacha20poly1305;
45pub(crate) mod clear;
46pub(crate) mod gcm;
47
48use block::SshBlockCipher;
49use chacha20poly1305::SshChacha20Poly1305Cipher;
50use clear::Clear;
51use gcm::GcmCipher;
52
53pub(crate) trait Cipher {
54 fn needs_mac(&self) -> bool {
55 false
56 }
57 fn key_len(&self) -> usize;
58 fn nonce_len(&self) -> usize {
59 0
60 }
61 fn make_opening_key(
62 &self,
63 key: &[u8],
64 nonce: &[u8],
65 mac_key: &[u8],
66 mac: &dyn MacAlgorithm,
67 ) -> Box<dyn OpeningKey + Send>;
68 fn make_sealing_key(
69 &self,
70 key: &[u8],
71 nonce: &[u8],
72 mac_key: &[u8],
73 mac: &dyn MacAlgorithm,
74 ) -> Box<dyn SealingKey + Send>;
75}
76
77pub const CLEAR: Name = Name("clear");
79#[cfg(feature = "des")]
81pub const TRIPLE_DES_CBC: Name = Name("3des-cbc");
82pub const AES_128_CTR: Name = Name("aes128-ctr");
84pub const AES_192_CTR: Name = Name("aes192-ctr");
86pub const AES_128_CBC: Name = Name("aes128-cbc");
88pub const AES_192_CBC: Name = Name("aes192-cbc");
90pub const AES_256_CBC: Name = Name("aes256-cbc");
92pub const AES_256_CTR: Name = Name("aes256-ctr");
94pub const AES_128_GCM: Name = Name("aes128-gcm@openssh.com");
96pub const AES_256_GCM: Name = Name("aes256-gcm@openssh.com");
98pub const CHACHA20_POLY1305: Name = Name("chacha20-poly1305@openssh.com");
100pub const NONE: Name = Name("none");
102
103pub(crate) static _CLEAR: Clear = Clear {};
104#[cfg(feature = "des")]
105static _3DES_CBC: SshBlockCipher<CbcWrapper<des::TdesEde3>> = SshBlockCipher(PhantomData);
106static _AES_128_CTR: SshBlockCipher<Ctr128BE<Aes128>> = SshBlockCipher(PhantomData);
107static _AES_192_CTR: SshBlockCipher<Ctr128BE<Aes192>> = SshBlockCipher(PhantomData);
108static _AES_256_CTR: SshBlockCipher<Ctr128BE<Aes256>> = SshBlockCipher(PhantomData);
109static _AES_128_GCM: GcmCipher = GcmCipher(&ALGORITHM_AES_128_GCM);
110static _AES_256_GCM: GcmCipher = GcmCipher(&ALGORITHM_AES_256_GCM);
111static _AES_128_CBC: SshBlockCipher<CbcWrapper<Aes128>> = SshBlockCipher(PhantomData);
112static _AES_192_CBC: SshBlockCipher<CbcWrapper<Aes192>> = SshBlockCipher(PhantomData);
113static _AES_256_CBC: SshBlockCipher<CbcWrapper<Aes256>> = SshBlockCipher(PhantomData);
114static _CHACHA20_POLY1305: SshChacha20Poly1305Cipher = SshChacha20Poly1305Cipher {};
115
116pub static ALL_CIPHERS: &[&Name] = &[
117 &CLEAR,
118 &NONE,
119 #[cfg(feature = "des")]
120 &TRIPLE_DES_CBC,
121 &AES_128_CTR,
122 &AES_192_CTR,
123 &AES_256_CTR,
124 &AES_128_GCM,
125 &AES_256_GCM,
126 &AES_128_CBC,
127 &AES_192_CBC,
128 &AES_256_CBC,
129 &CHACHA20_POLY1305,
130];
131
132pub(crate) static CIPHERS: LazyLock<HashMap<&'static Name, &(dyn Cipher + Send + Sync)>> =
133 LazyLock::new(|| {
134 let mut h: HashMap<&'static Name, &(dyn Cipher + Send + Sync)> = HashMap::new();
135 h.insert(&CLEAR, &_CLEAR);
136 h.insert(&NONE, &_CLEAR);
137 #[cfg(feature = "des")]
138 h.insert(&TRIPLE_DES_CBC, &_3DES_CBC);
139 h.insert(&AES_128_CTR, &_AES_128_CTR);
140 h.insert(&AES_192_CTR, &_AES_192_CTR);
141 h.insert(&AES_256_CTR, &_AES_256_CTR);
142 h.insert(&AES_128_GCM, &_AES_128_GCM);
143 h.insert(&AES_256_GCM, &_AES_256_GCM);
144 h.insert(&AES_128_CBC, &_AES_128_CBC);
145 h.insert(&AES_192_CBC, &_AES_192_CBC);
146 h.insert(&AES_256_CBC, &_AES_256_CBC);
147 h.insert(&CHACHA20_POLY1305, &_CHACHA20_POLY1305);
148 assert_eq!(h.len(), ALL_CIPHERS.len());
149 h
150 });
151
152#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
153pub struct Name(&'static str);
154impl AsRef<str> for Name {
155 fn as_ref(&self) -> &str {
156 self.0
157 }
158}
159
160impl Encode for Name {
161 delegate! { to self.as_ref() {
162 fn encoded_len(&self) -> Result<usize, ssh_encoding::Error>;
163 fn encode(&self, writer: &mut impl ssh_encoding::Writer) -> Result<(), ssh_encoding::Error>;
164 }}
165}
166
167impl Borrow<str> for &Name {
168 fn borrow(&self) -> &str {
169 self.0
170 }
171}
172
173impl TryFrom<&str> for Name {
174 type Error = ();
175 fn try_from(s: &str) -> Result<Name, ()> {
176 CIPHERS.keys().find(|x| x.0 == s).map(|x| **x).ok_or(())
177 }
178}
179
180pub(crate) struct CipherPair {
181 pub local_to_remote: Box<dyn SealingKey + Send>,
182 pub remote_to_local: Box<dyn OpeningKey + Send>,
183}
184
185impl Debug for CipherPair {
186 fn fmt(&self, _: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
187 Ok(())
188 }
189}
190
191pub(crate) trait OpeningKey {
192 fn packet_length_to_read_for_block_length(&self) -> usize {
193 4
194 }
195
196 fn decrypt_packet_length(&self, seqn: u32, encrypted_packet_length: &[u8]) -> [u8; 4];
197
198 fn tag_len(&self) -> usize;
199
200 fn open<'a>(&mut self, seqn: u32, ciphertext_and_tag: &'a mut [u8]) -> Result<&'a [u8], Error>;
201}
202
203pub(crate) trait SealingKey {
204 fn padding_length(&self, plaintext: &[u8]) -> usize;
205
206 fn fill_padding(&self, padding_out: &mut [u8]);
207
208 fn tag_len(&self) -> usize;
209
210 fn seal(&mut self, seqn: u32, plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8]);
211
212 fn write(&mut self, payload: &[u8], buffer: &mut SSHBuffer) {
213 trace!("writing, seqn = {:?}", buffer.seqn.0);
218
219 let padding_length = self.padding_length(payload);
220 trace!("padding length {padding_length:?}");
221 let packet_length = PADDING_LENGTH_LEN + payload.len() + padding_length;
222 trace!("packet_length {packet_length:?}");
223 let offset = buffer.buffer.len();
224
225 assert!(packet_length <= u32::MAX as usize);
228 #[allow(clippy::unwrap_used)] (packet_length as u32).encode(&mut buffer.buffer).unwrap();
230
231 assert!(padding_length <= u8::MAX as usize);
232 buffer.buffer.push(padding_length as u8);
233 buffer.buffer.extend(payload);
234 self.fill_padding(buffer.buffer.resize_mut(padding_length));
235 buffer.buffer.resize_mut(self.tag_len());
236
237 #[allow(clippy::indexing_slicing)] let (plaintext, tag) =
239 buffer.buffer[offset..].split_at_mut(PACKET_LENGTH_LEN + packet_length);
240
241 self.seal(buffer.seqn.0, plaintext, tag);
242
243 buffer.bytes += payload.len();
244 buffer.seqn += Wrapping(1);
247 }
248}
249
250pub(crate) async fn read<R: AsyncRead + Unpin>(
251 stream: &mut R,
252 buffer: &mut SSHBuffer,
253 cipher: &mut (dyn OpeningKey + Send),
254) -> Result<usize, Error> {
255 if buffer.len == 0 {
256 let mut len = vec![0; cipher.packet_length_to_read_for_block_length()];
257
258 stream.read_exact(&mut len).await?;
259 trace!("reading, len = {len:?}");
260 {
261 let seqn = buffer.seqn.0;
262 buffer.buffer.clear();
263 buffer.buffer.extend(&len);
264 trace!("reading, seqn = {seqn:?}");
265 let len = cipher.decrypt_packet_length(seqn, &len);
266 let len = BigEndian::read_u32(&len) as usize;
267
268 if len > MAXIMUM_PACKET_LEN {
269 return Err(Error::PacketSize(len));
270 }
271
272 buffer.len = len + cipher.tag_len();
273 trace!("reading, clear len = {:?}", buffer.len);
274 }
275 }
276
277 buffer.buffer.resize(buffer.len + 4);
278 trace!("read_exact {:?}", buffer.len + 4);
279
280 let l = cipher.packet_length_to_read_for_block_length();
281
282 #[allow(clippy::indexing_slicing)] stream.read_exact(&mut buffer.buffer[l..]).await?;
284
285 trace!("read_exact done");
286 let seqn = buffer.seqn.0;
287 let plaintext = cipher.open(seqn, &mut buffer.buffer)?;
288
289 let padding_length = *plaintext.first().to_owned().unwrap_or(&0) as usize;
290 trace!("reading, padding_length {padding_length:?}");
291 let plaintext_end = plaintext
292 .len()
293 .checked_sub(padding_length)
294 .ok_or(Error::IndexOutOfBounds)?;
295
296 buffer.seqn += Wrapping(1);
299 buffer.len = 0;
300
301 buffer.buffer.resize(plaintext_end + 4);
303
304 Ok(plaintext_end + 4)
305}
306
307pub(crate) const PACKET_LENGTH_LEN: usize = 4;
308
309const MINIMUM_PACKET_LEN: usize = 16;
310const MAXIMUM_PACKET_LEN: usize = 256 * 1024;
311
312const PADDING_LENGTH_LEN: usize = 1;
313
314#[cfg(feature = "_bench")]
315pub mod benchmark;