Skip to main content

rustssh2/cipher/
mod.rs

1// Copyright 2016 Pierre-Étienne Meunier
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//!
16//! This module exports cipher names for use with [Preferred].
17use 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
77/// `clear`
78pub const CLEAR: Name = Name("clear");
79/// `3des-cbc`
80#[cfg(feature = "des")]
81pub const TRIPLE_DES_CBC: Name = Name("3des-cbc");
82/// `aes128-ctr`
83pub const AES_128_CTR: Name = Name("aes128-ctr");
84/// `aes192-ctr`
85pub const AES_192_CTR: Name = Name("aes192-ctr");
86/// `aes128-cbc`
87pub const AES_128_CBC: Name = Name("aes128-cbc");
88/// `aes192-cbc`
89pub const AES_192_CBC: Name = Name("aes192-cbc");
90/// `aes256-cbc`
91pub const AES_256_CBC: Name = Name("aes256-cbc");
92/// `aes256-ctr`
93pub const AES_256_CTR: Name = Name("aes256-ctr");
94/// `aes128-gcm@openssh.com`
95pub const AES_128_GCM: Name = Name("aes128-gcm@openssh.com");
96/// `aes256-gcm@openssh.com`
97pub const AES_256_GCM: Name = Name("aes256-gcm@openssh.com");
98/// `chacha20-poly1305@openssh.com`
99pub const CHACHA20_POLY1305: Name = Name("chacha20-poly1305@openssh.com");
100/// `none`
101pub 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        // https://tools.ietf.org/html/rfc4253#section-6
214        //
215        // The variables `payload`, `packet_length` and `padding_length` refer
216        // to the protocol fields of the same names.
217        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        // Maximum packet length:
226        // https://tools.ietf.org/html/rfc4253#section-6.1
227        assert!(packet_length <= u32::MAX as usize);
228        #[allow(clippy::unwrap_used)] // length checked
229        (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)] // length checked
238        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        // Sequence numbers are on 32 bits and wrap.
245        // https://tools.ietf.org/html/rfc4253#section-6.4
246        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)] // length checked
283    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    // Sequence numbers are on 32 bits and wrap.
297    // https://tools.ietf.org/html/rfc4253#section-6.4
298    buffer.seqn += Wrapping(1);
299    buffer.len = 0;
300
301    // Remove the padding
302    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;