ssh_cipher/
chacha20poly1305.rs

1//! OpenSSH variant of ChaCha20Poly1305: `chacha20-poly1305@openssh.com`
2//!
3//! Differences from ChaCha20Poly1305 as described in RFC8439:
4//!
5//! - Construction uses two separately keyed instances of ChaCha20: one for data, one for lengths
6//! - The input of Poly1305 is not padded
7//! - The lengths of ciphertext and AAD are not authenticated using Poly1305
8//!
9//! [PROTOCOL.chacha20poly1305]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD
10
11use crate::{Error, Nonce, Result, Tag};
12use chacha20::{ChaCha20, Key};
13use cipher::{KeyInit, KeyIvInit, StreamCipher, StreamCipherSeek};
14use poly1305::Poly1305;
15use subtle::ConstantTimeEq;
16
17const KEY_SIZE: usize = 32;
18
19pub(crate) struct ChaCha20Poly1305 {
20    cipher: ChaCha20,
21    mac: Poly1305,
22}
23
24impl ChaCha20Poly1305 {
25    /// Create a new [`ChaCha20Poly1305`] instance with a 64-byte key.
26    /// From [PROTOCOL.chacha20poly1305]:
27    ///
28    /// > The chacha20-poly1305@openssh.com cipher requires 512 bits of key
29    /// > material as output from the SSH key exchange. This forms two 256 bit
30    /// > keys (K_1 and K_2), used by two separate instances of chacha20.
31    /// > The first 256 bits constitute K_2 and the second 256 bits become
32    /// > K_1.
33    /// >
34    /// > The instance keyed by K_1 is a stream cipher that is used only
35    /// > to encrypt the 4 byte packet length field. The second instance,
36    /// > keyed by K_2, is used in conjunction with poly1305 to build an AEAD
37    /// > (Authenticated Encryption with Associated Data) that is used to encrypt
38    /// > and authenticate the entire packet.
39    ///
40    /// [PROTOCOL.chacha20poly1305]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD
41    pub fn new(key: &[u8], nonce: &[u8]) -> Result<Self> {
42        #[allow(clippy::arithmetic_side_effects)]
43        if key.len() != KEY_SIZE * 2 {
44            return Err(Error::KeySize);
45        }
46
47        // TODO(tarcieri): support for using both keys
48        let (k_2, _k_1) = key.split_at(KEY_SIZE);
49        let key = Key::from_slice(k_2);
50
51        let nonce = if nonce.is_empty() {
52            // For key encryption
53            Nonce::default()
54        } else {
55            Nonce::try_from(nonce).map_err(|_| Error::IvSize)?
56        };
57
58        let mut cipher = ChaCha20::new(key, &nonce.into());
59        let mut poly1305_key = poly1305::Key::default();
60        cipher.apply_keystream(&mut poly1305_key);
61
62        let mac = Poly1305::new(&poly1305_key);
63
64        // Seek to block 1
65        cipher.seek(64);
66
67        Ok(Self { cipher, mac })
68    }
69
70    #[inline]
71    pub fn encrypt(mut self, buffer: &mut [u8]) -> Tag {
72        self.cipher.apply_keystream(buffer);
73        self.mac.compute_unpadded(buffer).into()
74    }
75
76    #[inline]
77    pub fn decrypt(mut self, buffer: &mut [u8], tag: Tag) -> Result<()> {
78        let expected_tag = self.mac.compute_unpadded(buffer);
79
80        if expected_tag.ct_eq(&tag).into() {
81            self.cipher.apply_keystream(buffer);
82            Ok(())
83        } else {
84            Err(Error::Crypto)
85        }
86    }
87}