Adding support for aes256-gcm

pmeunier
Aug 9, 2025, 4:59 PM
WX7UY5KKUXOHBGPNWJC6ZQDQ3LFGNTKGF6ZQFIPYNI6H5IMWGOYAC

Dependencies

  • [2] OBHPOIUH Modernising the API with async traits
  • [3] D6H7OWTT Fixing the terrapin attack mitigation
  • [4] TAOFQAII cargo fmt
  • [5] 2WEO7OZL Version updates: getting rid of anyhow + moving to Tokio 1.0
  • [6] 2B2UUFXG Fixing the doc tests
  • [7] WXZWQLGL Correct negotiation without OpenSSL
  • [8] UHAEQPZU Support ecdsa-sha2-nistp256 keys for authentication
  • [9] SMUTYV2C Fixing warnings
  • [10] VJIXIN4T Fixing CVE-2023-48795
  • [11] 7FRJYUI6 Reboot because of a bad change
  • [12] NHOSLQGG Thrussh: making OpenSSL optional
  • [13] 32GIIFWR Fixing strict mode

Change contents

  • edit in thrussh/src/negotiation.rs at line 57
    [3.270075][2.4472:4473]()
  • replacement in thrussh/src/negotiation.rs at line 74
    [3.391][3.391:442]()
    cipher: &[cipher::chacha20poly1305::NAME],
    [3.391]
    [3.442]
    cipher: &[cipher::chacha20poly1305::NAME, cipher::aes256_gcm::NAME],
  • replacement in thrussh/src/negotiation.rs at line 88
    [3.15559][3.15559:15610]()
    cipher: &[cipher::chacha20poly1305::NAME],
    [3.15559]
    [3.15610]
    cipher: &[cipher::chacha20poly1305::NAME, cipher::aes256_gcm::NAME],
  • replacement in thrussh/src/negotiation.rs at line 118
    [3.1325][3.1325:1376]()
    cipher: &[cipher::chacha20poly1305::NAME],
    [3.1325]
    [3.1376]
    cipher: &[cipher::chacha20poly1305::NAME, cipher::aes256_gcm::NAME],
  • replacement in thrussh/src/negotiation.rs at line 128
    [3.270223][3.33:84]()
    cipher: &[cipher::chacha20poly1305::NAME],
    [3.270223]
    [3.84]
    cipher: &[cipher::chacha20poly1305::NAME, cipher::aes256_gcm::NAME],
  • replacement in thrussh/src/negotiation.rs at line 346
    [3.275820][3.692:740]()
    rand::thread_rng().fill_bytes(&mut cookie);
    [3.275820]
    [3.275856]
    rand::rng().fill_bytes(&mut cookie);
  • replacement in thrussh/src/lib.rs at line 264
    [3.289921][3.289921:289933]()
    mod cipher;
    [3.289921]
    [3.289933]
    pub mod cipher;
  • edit in thrussh/src/kex.rs at line 55
    [3.308436]
    [3.308436]
    static IV_BUF: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
  • replacement in thrussh/src/kex.rs at line 83
    [3.309346][3.1015:1076]()
    rand::thread_rng().fill_bytes(&mut server_secret.0);
    [3.309346]
    [3.309389]
    rand::rng().fill_bytes(&mut server_secret.0);
  • replacement in thrussh/src/kex.rs at line 104
    [3.310050][3.1077:1138]()
    rand::thread_rng().fill_bytes(&mut client_secret.0);
    [3.310050]
    [3.310093]
    rand::rng().fill_bytes(&mut client_secret.0);
  • edit in thrussh/src/kex.rs at line 167
    [3.312336]
    [3.312336]
    #[cfg(feature = "openssl")]
    super::cipher::aes256_gcm::NAME => &super::cipher::aes256_gcm::CIPHER,
  • replacement in thrussh/src/kex.rs at line 175
    [3.312504][3.14969:15063](),[3.15063][3.312599:312876](),[3.312599][3.312599:312876]()
    let compute_key = |c, key: &mut CryptoVec, len| -> Result<(), crate::Error> {
    let mut buffer = buffer.borrow_mut();
    buffer.clear();
    key.clear();
    if let Some(ref shared) = self.shared_secret {
    buffer.extend_ssh_mpint(&shared.0);
    }
    [3.312504]
    [3.312876]
    IV_BUF.with(|iv| {
    let compute_key = |c, key: &mut CryptoVec, len| -> Result<(), crate::Error> {
    let mut buffer = buffer.borrow_mut();
    buffer.clear();
    key.clear();
  • edit in thrussh/src/kex.rs at line 181
    [3.312877][3.312877:313028](),[3.313028][3.1339:1372](),[3.1372][3.1463:1661](),[3.1463][3.1463:1661](),[3.1661][3.313273:313463](),[3.313273][3.313273:313463]()
    buffer.extend(exchange_hash.as_ref());
    buffer.push(c);
    buffer.extend(session_id.as_ref());
    let hash = {
    use sha2::Digest;
    let mut hasher = sha2::Sha256::new();
    hasher.update(&buffer[..]);
    hasher.finalize()
    };
    key.extend(hash.as_ref());
    while key.len() < len {
    // extend.
    buffer.clear();
  • edit in thrussh/src/kex.rs at line 184
    [3.313624]
    [3.313624]
  • replacement in thrussh/src/kex.rs at line 186
    [3.313687][3.313687:313731]()
    buffer.extend(key);
    [3.313687]
    [3.313731]
    buffer.push(c);
    buffer.extend(session_id.as_ref());
  • replacement in thrussh/src/kex.rs at line 194
    [3.313977][3.313977:314097]()
    key.extend(&hash.as_ref());
    }
    Ok(())
    };
    [3.313977]
    [3.314097]
    key.extend(hash.as_ref());
  • replacement in thrussh/src/kex.rs at line 196
    [3.314098][3.314098:314280]()
    let (local_to_remote, remote_to_local) = if is_server {
    (b'D', b'C')
    } else {
    (b'C', b'D')
    };
    [3.314098]
    [3.314280]
    while key.len() < len {
    // extend.
    buffer.clear();
    if let Some(ref shared) = self.shared_secret {
    buffer.extend_ssh_mpint(&shared.0);
    }
    buffer.extend(exchange_hash.as_ref());
    buffer.extend(key);
    let hash = {
    use sha2::Digest;
    let mut hasher = sha2::Sha256::new();
    hasher.update(&buffer[..]);
    hasher.finalize()
    };
    key.extend(&hash.as_ref());
    }
    key.resize(len);
    Ok(())
    };
    let (local_to_remote, local_to_remote_iv, remote_to_local, remote_to_local_iv) =
    if is_server {
    (b'D', b'B', b'C', b'A')
    } else {
    (b'C', b'A', b'D', b'B')
    };
  • replacement in thrussh/src/kex.rs at line 223
    [3.314281][3.314281:314476]()
    let mut key = key.borrow_mut();
    compute_key(local_to_remote, &mut key, cipher.key_len)?;
    let local_to_remote = (cipher.make_sealing_cipher)(&key);
    [3.314281]
    [3.314476]
    let mut key = key.borrow_mut();
    compute_key(local_to_remote, &mut key, cipher.key_len)?;
    let mut iv = iv.borrow_mut();
    if cipher.iv_len > 0 {
    compute_key(local_to_remote_iv, &mut iv, cipher.iv_len)?;
    }
    let local_to_remote = (cipher.make_sealing_cipher)(&key, &iv);
  • replacement in thrussh/src/kex.rs at line 231
    [3.314477][3.314477:314624]()
    compute_key(remote_to_local, &mut key, cipher.key_len)?;
    let remote_to_local = (cipher.make_opening_cipher)(&key);
    [3.314477]
    [3.314624]
    compute_key(remote_to_local, &mut key, cipher.key_len)?;
    if cipher.iv_len > 0 {
    compute_key(remote_to_local_iv, &mut iv, cipher.iv_len)?;
    }
    let remote_to_local = (cipher.make_opening_cipher)(&key, &iv);
  • replacement in thrussh/src/kex.rs at line 237
    [3.314625][3.314625:314780]()
    Ok(super::cipher::CipherPair {
    local_to_remote: local_to_remote,
    remote_to_local: remote_to_local,
    [3.314625]
    [3.314780]
    Ok(super::cipher::CipherPair {
    local_to_remote: local_to_remote,
    remote_to_local: remote_to_local,
    })
  • edit in thrussh/src/cipher/mod.rs at line 20
    [3.23187]
    [3.409826]
    #[cfg(feature = "openssl")]
    pub mod aes256_gcm;
  • replacement in thrussh/src/cipher/mod.rs at line 28
    [3.409955][3.409955:410079]()
    pub make_opening_cipher: fn(key: &[u8]) -> OpeningCipher,
    pub make_sealing_cipher: fn(key: &[u8]) -> SealingCipher,
    [3.409955]
    [3.410079]
    pub iv_len: usize,
    pub make_opening_cipher: fn(key: &[u8], iv: &[u8]) -> OpeningCipher,
    pub make_sealing_cipher: fn(key: &[u8], iv: &[u8]) -> SealingCipher,
  • edit in thrussh/src/cipher/mod.rs at line 36
    [3.410182]
    [3.410182]
    #[cfg(feature = "openssl")]
    Aes256Gcm(aes256_gcm::Key),
  • edit in thrussh/src/cipher/mod.rs at line 45
    [3.410393]
    [3.410393]
    #[cfg(feature = "openssl")]
    OpeningCipher::Aes256Gcm(ref key) => key,
  • edit in thrussh/src/cipher/mod.rs at line 54
    [3.410512]
    [3.410512]
    #[cfg(feature = "openssl")]
    Aes256Gcm(aes256_gcm::Key),
  • edit in thrussh/src/cipher/mod.rs at line 63
    [3.410729]
    [3.410729]
    #[cfg(feature = "openssl")]
    SealingCipher::Aes256Gcm(ref key) => key,
  • edit in thrussh/src/cipher/chacha20poly1305.rs at line 36
    [3.418029]
    [3.418029]
    iv_len: 0,
  • replacement in thrussh/src/cipher/chacha20poly1305.rs at line 43
    [3.418160][3.418160:418219]()
    fn make_sealing_cipher(k: &[u8]) -> super::SealingCipher {
    [3.418160]
    [3.418219]
    fn make_sealing_cipher(k: &[u8], _: &[u8]) -> super::SealingCipher {
  • replacement in thrussh/src/cipher/chacha20poly1305.rs at line 51
    [3.418452][3.418452:418511]()
    fn make_opening_cipher(k: &[u8]) -> super::OpeningCipher {
    [3.418452]
    [3.418511]
    fn make_opening_cipher(k: &[u8], _: &[u8]) -> super::OpeningCipher {
  • file addition: aes256_gcm.rs (----------)
    [3.409076]
    // Copyright 2016 Pierre-Étienne Meunier
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    // http://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.
    //
    use super::super::Error;
    use byteorder::{BigEndian, ByteOrder};
    use openssl::symm::Cipher;
    use rand::Rng;
    use std::sync::atomic::{AtomicU64, Ordering};
    pub const NAME: super::Name = super::Name("aes256-gcm@openssh.com");
    pub struct Key {
    cipher: openssl::symm::Cipher,
    key: [u8; 32],
    iv: (u32, AtomicU64),
    }
    pub static CIPHER: super::Cipher = super::Cipher {
    _name: NAME,
    key_len: 32,
    iv_len: 12,
    make_sealing_cipher,
    make_opening_cipher,
    };
    fn make_sealing_cipher(k: &[u8], i: &[u8]) -> super::SealingCipher {
    let mut key = [0; 32];
    key.clone_from_slice(k);
    let (a, b) = i.split_at(4);
    let a = BigEndian::read_u32(&a);
    let b = BigEndian::read_u64(&b);
    super::SealingCipher::Aes256Gcm(Key {
    key,
    iv: (a, b.into()),
    cipher: Cipher::aes_256_gcm(),
    })
    }
    fn make_opening_cipher(k: &[u8], i: &[u8]) -> super::OpeningCipher {
    let mut key = [0; 32];
    key.clone_from_slice(k);
    let (a, b) = i.split_at(4);
    let a = BigEndian::read_u32(&a);
    let b = BigEndian::read_u64(&b);
    super::OpeningCipher::Aes256Gcm(Key {
    key,
    iv: (a, b.into()),
    cipher: Cipher::aes_256_gcm(),
    })
    }
    impl super::OpeningKey for Key {
    fn decrypt_packet_length(
    &self,
    _sequence_number: u32,
    encrypted_packet_length: [u8; 4],
    ) -> [u8; 4] {
    encrypted_packet_length
    }
    fn tag_len(&self) -> usize {
    16
    }
    fn open<'a>(
    &self,
    _sequence_number: u32,
    payload: &'a mut [u8],
    tag: &[u8],
    ) -> Result<&'a [u8], Error> {
    let (aad, data) = payload.split_at_mut(4);
    let mut iv = [0; 12];
    {
    let (a, b) = iv.split_at_mut(4);
    BigEndian::write_u32(a, self.iv.0);
    let counter = self.iv.1.fetch_add(1, Ordering::Relaxed);
    BigEndian::write_u64(b, counter);
    }
    let mut tag_ = [0; 16];
    tag_.clone_from_slice(tag);
    crypt_aead(
    self.cipher,
    openssl::symm::Mode::Decrypt,
    &self.key,
    Some(&iv),
    aad,
    data,
    &mut tag_,
    )
    .unwrap();
    Ok(payload)
    }
    }
    impl super::SealingKey for Key {
    fn padding_length(&self, payload: &[u8]) -> usize {
    let encrypted_len = payload.len() + 1;
    let padding = 16 - (encrypted_len % 16);
    let min_padding = if padding < 4 { padding + 16 } else { padding };
    let mut rng = rand::rng();
    (rng.random::<u8>() & 0xf0 - 16) as usize + min_padding
    }
    fn fill_padding(&self, padding_out: &mut [u8]) {
    openssl::rand::rand_bytes(padding_out).unwrap()
    }
    fn tag_len(&self) -> usize {
    16
    }
    /// Append an encrypted packet with contents `packet_content` at
    /// the end of `buffer`.
    fn seal(&self, _sequence_number: u32, payload: &mut [u8], tag_out: &mut [u8]) {
    let (aad, data) = payload.split_at_mut(4);
    let mut iv = [0; 12];
    {
    let (a, b) = iv.split_at_mut(4);
    BigEndian::write_u32(a, self.iv.0);
    let counter = self.iv.1.fetch_add(1, Ordering::Relaxed);
    BigEndian::write_u64(b, counter);
    }
    crypt_aead(
    self.cipher,
    openssl::symm::Mode::Encrypt,
    &self.key,
    Some(&iv),
    aad,
    data,
    tag_out,
    )
    .unwrap()
    }
    }
    // The following is copied from the OpenSSL crate, with the addition
    // of a thread-local buffer and less genericity on encryption modes.
    use std::cell::RefCell;
    thread_local! {
    static BUF: RefCell<cryptovec::CryptoVec> = RefCell::new(cryptovec::CryptoVec::new());
    }
    pub fn crypt_aead(
    t: openssl::symm::Cipher,
    mode: openssl::symm::Mode,
    key: &[u8],
    iv: Option<&[u8]>,
    aad: &[u8],
    data: &mut [u8],
    tag: &mut [u8],
    ) -> Result<(), openssl::error::ErrorStack> {
    let mut c = openssl::symm::Crypter::new(t, mode, key, iv)?;
    BUF.with(|buffer| {
    let mut buffer = buffer.borrow_mut();
    buffer.resize(data.len() + t.block_size());
    c.aad_update(aad)?;
    let count = c.update(data, &mut buffer)?;
    let rest = if let openssl::symm::Mode::Encrypt = mode {
    let rest = c.finalize(&mut buffer[count..])?;
    c.get_tag(tag)?;
    rest
    } else {
    c.set_tag(tag)?;
    c.finalize(&mut buffer[count..])?
    };
    data.clone_from_slice(&buffer[..count + rest]);
    buffer.clear();
    Ok(())
    })
    }
  • replacement in thrussh/Cargo.toml at line 58
    [3.427375][3.1926:1939]()
    rand = "0.8"
    [3.427375]
    [3.1939]
    rand = "0.9"