try adding AES-GCM

This commit is contained in:
nora 2024-08-12 21:12:33 +02:00
parent 43c1696465
commit 4c3f0a97aa
5 changed files with 243 additions and 52 deletions

68
Cargo.lock generated
View file

@ -17,6 +17,41 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.3" version = "1.1.3"
@ -153,9 +188,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"rand_core",
"typenum", "typenum",
] ]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "4.1.3" version = "4.1.3"
@ -325,6 +370,16 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "ghash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
dependencies = [
"opaque-debug",
"polyval",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.29.0" version = "0.29.0"
@ -565,6 +620,18 @@ dependencies = [
"universal-hash", "universal-hash",
] ]
[[package]]
name = "polyval"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@ -859,6 +926,7 @@ dependencies = [
name = "ssh-transport" name = "ssh-transport"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"aes-gcm",
"chacha20", "chacha20",
"ed25519-dalek", "ed25519-dalek",
"eyre", "eyre",

View file

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
aes-gcm = "0.10.3"
chacha20 = "0.9.1" chacha20 = "0.9.1"
ed25519-dalek = { version = "2.1.1" } ed25519-dalek = { version = "2.1.1" }
eyre = "0.6.12" eyre = "0.6.12"

View file

@ -1,3 +1,4 @@
use aes_gcm::aead::{Aead, AeadMutInPlace};
use chacha20::cipher::{KeyInit, StreamCipher, StreamCipherSeek}; use chacha20::cipher::{KeyInit, StreamCipher, StreamCipherSeek};
use sha2::Digest; use sha2::Digest;
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
@ -81,9 +82,11 @@ pub const KEX_ECDH_SHA2_NISTP256: KexAlgorithm = KexAlgorithm {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct EncryptionAlgorithm { pub struct EncryptionAlgorithm {
name: &'static str, name: &'static str,
decrypt_len: fn(keys: &[u8], bytes: &mut [u8], packet_number: u64), iv_size: usize,
decrypt_packet: fn(keys: &[u8], bytes: RawPacket, packet_number: u64) -> Result<Packet>, key_size: usize,
encrypt_packet: fn(keys: &[u8], packet: Packet, packet_number: u64) -> EncryptedPacket, decrypt_len: fn(state: &mut [u8], bytes: &mut [u8], packet_number: u64),
decrypt_packet: fn(state: &mut [u8], bytes: RawPacket, packet_number: u64) -> Result<Packet>,
encrypt_packet: fn(state: &mut [u8], packet: Packet, packet_number: u64) -> EncryptedPacket,
} }
impl AlgorithmName for EncryptionAlgorithm { impl AlgorithmName for EncryptionAlgorithm {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -92,16 +95,35 @@ impl AlgorithmName for EncryptionAlgorithm {
} }
pub const ENC_CHACHA20POLY1305: EncryptionAlgorithm = EncryptionAlgorithm { pub const ENC_CHACHA20POLY1305: EncryptionAlgorithm = EncryptionAlgorithm {
name: "chacha20-poly1305@openssh.com", name: "chacha20-poly1305@openssh.com",
decrypt_len: |keys, bytes, packet_number| { iv_size: 0,
let alg = SshChaCha20Poly1305::from_keys(keys); key_size: 64, // 32 for header, 32 for main
decrypt_len: |state, bytes, packet_number| {
let alg = ChaCha20Poly1305OpenSsh::from_state(state);
alg.decrypt_len(bytes, packet_number) alg.decrypt_len(bytes, packet_number)
}, },
decrypt_packet: |keys, bytes, packet_number| { decrypt_packet: |state, bytes, packet_number| {
let alg = SshChaCha20Poly1305::from_keys(keys); let alg = ChaCha20Poly1305OpenSsh::from_state(state);
alg.decrypt_packet(bytes, packet_number) alg.decrypt_packet(bytes, packet_number)
}, },
encrypt_packet: |keys, packet, packet_number| { encrypt_packet: |state, packet, packet_number| {
let alg = SshChaCha20Poly1305::from_keys(keys); let alg = ChaCha20Poly1305OpenSsh::from_state(state);
alg.encrypt_packet(packet, packet_number)
},
};
pub const ENC_AES256_GCM: EncryptionAlgorithm = EncryptionAlgorithm {
name: "aes256-gcm@openssh.com",
iv_size: 12,
key_size: 32,
decrypt_len: |state, bytes, packet_number| {
let mut alg = Aes256GcmOpenSsh::from_state(state);
alg.decrypt_len(bytes, packet_number)
},
decrypt_packet: |state, bytes, packet_number| {
let mut alg = Aes256GcmOpenSsh::from_state(state);
alg.decrypt_packet(bytes, packet_number)
},
encrypt_packet: |state, packet, packet_number| {
let mut alg = Aes256GcmOpenSsh::from_state(state);
alg.encrypt_packet(packet, packet_number) alg.encrypt_packet(packet, packet_number)
}, },
}; };
@ -130,10 +152,14 @@ impl<T: AlgorithmName> AlgorithmNegotiation<T> {
pub(crate) struct Session { pub(crate) struct Session {
session_id: [u8; 32], session_id: [u8; 32],
encryption_key_client_to_server: [u8; 64], client_to_server: Tunnel,
encryption_client_to_server: EncryptionAlgorithm, server_to_client: Tunnel,
encryption_key_server_to_client: [u8; 64], }
encryption_server_to_client: EncryptionAlgorithm,
struct Tunnel {
/// `key || IV`
state: Vec<u8>,
algorithm: EncryptionAlgorithm,
} }
pub(crate) trait Keys: Send + Sync + 'static { pub(crate) trait Keys: Send + Sync + 'static {
@ -196,20 +222,27 @@ impl Session {
session_id: [u8; 32], session_id: [u8; 32],
h: [u8; 32], h: [u8; 32],
k: &[u8], k: &[u8],
encryption_client_to_server: EncryptionAlgorithm, alg_c2s: EncryptionAlgorithm,
encryption_server_to_client: EncryptionAlgorithm, alg_s2c: EncryptionAlgorithm,
) -> Self { ) -> Self {
let encryption_key_client_to_server = derive_key(k, h, "C", session_id);
let encryption_key_server_to_client = derive_key(k, h, "D", session_id);
Self { Self {
session_id, session_id,
// client_to_server_iv: derive("A").into(), client_to_server: Tunnel {
// server_to_client_iv: derive("B").into(), algorithm: alg_c2s,
encryption_key_client_to_server, state: {
encryption_client_to_server, let mut state = derive_key(k, h, "C", session_id, alg_c2s.key_size);
encryption_key_server_to_client, state.extend_from_slice(&derive_key(k, h, "A", session_id, alg_c2s.iv_size));
encryption_server_to_client, state
},
},
server_to_client: Tunnel {
algorithm: alg_s2c,
state: {
let mut state = derive_key(k, h, "D", session_id, alg_s2c.key_size);
state.extend_from_slice(&derive_key(k, h, "B", session_id, alg_s2c.iv_size));
state
},
},
// integrity_key_client_to_server: derive("E").into(), // integrity_key_client_to_server: derive("E").into(),
// integrity_key_server_to_client: derive("F").into(), // integrity_key_server_to_client: derive("F").into(),
} }
@ -218,24 +251,24 @@ impl Session {
impl Keys for Session { impl Keys for Session {
fn decrypt_len(&mut self, bytes: &mut [u8; 4], packet_number: u64) { fn decrypt_len(&mut self, bytes: &mut [u8; 4], packet_number: u64) {
(self.encryption_client_to_server.decrypt_len)( (self.client_to_server.algorithm.decrypt_len)(
&self.encryption_key_client_to_server, &mut self.client_to_server.state,
bytes, bytes,
packet_number, packet_number,
); );
} }
fn decrypt_packet(&mut self, bytes: RawPacket, packet_number: u64) -> Result<Packet> { fn decrypt_packet(&mut self, bytes: RawPacket, packet_number: u64) -> Result<Packet> {
(self.encryption_client_to_server.decrypt_packet)( (self.client_to_server.algorithm.decrypt_packet)(
&self.encryption_key_client_to_server, &mut self.client_to_server.state,
bytes, bytes,
packet_number, packet_number,
) )
} }
fn encrypt_packet_to_msg(&mut self, packet: Packet, packet_number: u64) -> Msg { fn encrypt_packet_to_msg(&mut self, packet: Packet, packet_number: u64) -> Msg {
let packet = (self.encryption_server_to_client.encrypt_packet)( let packet = (self.server_to_client.algorithm.encrypt_packet)(
&self.encryption_key_server_to_client, &mut self.server_to_client.state,
packet, packet,
packet_number, packet_number,
); );
@ -266,9 +299,15 @@ impl Keys for Session {
/// Derive a key from the shared secret K and exchange hash H. /// Derive a key from the shared secret K and exchange hash H.
/// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.2> /// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.2>
fn derive_key(k: &[u8], h: [u8; 32], letter: &str, session_id: [u8; 32]) -> [u8; 64] { fn derive_key(
k: &[u8],
h: [u8; 32],
letter: &str,
session_id: [u8; 32],
key_size: usize,
) -> Vec<u8> {
let sha2len = sha2::Sha256::output_size(); let sha2len = sha2::Sha256::output_size();
let mut output = [0; 64]; let mut output = vec![0; key_size];
//let mut hash = sha2::Sha256::new(); //let mut hash = sha2::Sha256::new();
//encode_mpint_for_hash(&k, |data| hash.update(data)); //encode_mpint_for_hash(&k, |data| hash.update(data));
@ -277,7 +316,7 @@ fn derive_key(k: &[u8], h: [u8; 32], letter: &str, session_id: [u8; 32]) -> [u8;
//hash.update(session_id); //hash.update(session_id);
//output[..sha2len].copy_from_slice(&hash.finalize()); //output[..sha2len].copy_from_slice(&hash.finalize());
for i in 0..(64 / sha2len) { for i in 0..(key_size / sha2len) {
let mut hash = <sha2::Sha256 as sha2::Digest>::new(); let mut hash = <sha2::Sha256 as sha2::Digest>::new();
encode_mpint_for_hash(&k, |data| hash.update(data)); encode_mpint_for_hash(&k, |data| hash.update(data));
hash.update(h); hash.update(h);
@ -311,13 +350,14 @@ pub(crate) fn encode_mpint_for_hash(mut key: &[u8], mut add_to_hash: impl FnMut(
/// `chacha20-poly1305@openssh.com` uses a 64-bit nonce, not the 96-bit one in the IETF version. /// `chacha20-poly1305@openssh.com` uses a 64-bit nonce, not the 96-bit one in the IETF version.
type SshChaCha20 = chacha20::ChaCha20Legacy; type SshChaCha20 = chacha20::ChaCha20Legacy;
struct SshChaCha20Poly1305 { /// <https://github.com/openssh/openssh-portable/blob/1ec0a64c5dc57b8a2053a93b5ef0d02ff8598e5c/PROTOCOL.chacha20poly1305>
struct ChaCha20Poly1305OpenSsh {
header_key: chacha20::Key, header_key: chacha20::Key,
main_key: chacha20::Key, main_key: chacha20::Key,
} }
impl SshChaCha20Poly1305 { impl ChaCha20Poly1305OpenSsh {
fn from_keys(keys: &[u8]) -> Self { fn from_state(keys: &[u8]) -> Self {
assert_eq!(keys.len(), 64); assert_eq!(keys.len(), 64);
Self { Self {
main_key: <[u8; 32]>::try_from(&keys[..32]).unwrap().into(), main_key: <[u8; 32]>::try_from(&keys[..32]).unwrap().into(),
@ -370,7 +410,7 @@ impl SshChaCha20Poly1305 {
} }
fn encrypt_packet(&self, packet: Packet, packet_number: u64) -> EncryptedPacket { fn encrypt_packet(&self, packet: Packet, packet_number: u64) -> EncryptedPacket {
let mut bytes = packet.to_bytes(false); let mut bytes = packet.to_bytes(false, Packet::DEFAULT_BLOCK_SIZE);
// Prepare the main cipher. // Prepare the main cipher.
let mut main_cipher = <SshChaCha20 as chacha20::cipher::KeyIvInit>::new( let mut main_cipher = <SshChaCha20 as chacha20::cipher::KeyIvInit>::new(
@ -404,3 +444,81 @@ impl SshChaCha20Poly1305 {
EncryptedPacket::from_encrypted_full_bytes(bytes) EncryptedPacket::from_encrypted_full_bytes(bytes)
} }
} }
/// <https://datatracker.ietf.org/doc/html/rfc5647>
/// <https://github.com/openssh/openssh-portable/blob/1ec0a64c5dc57b8a2053a93b5ef0d02ff8598e5c/PROTOCOL#L188C49-L188C64>
struct Aes256GcmOpenSsh<'a> {
key: aes_gcm::Key<aes_gcm::Aes256Gcm>,
nonce: &'a mut [u8; 12],
}
impl<'a> Aes256GcmOpenSsh<'a> {
fn from_state(keys: &'a mut [u8]) -> Self {
assert_eq!(keys.len(), 44);
Self {
key: <[u8; 32]>::try_from(&keys[..32]).unwrap().into(),
nonce: <&mut [u8; 12]>::try_from(&mut keys[32..]).unwrap(),
}
}
fn decrypt_len(&mut self, _: &mut [u8], _: u64) {
// AES-GCM does not encrypt the length.
// <https://datatracker.ietf.org/doc/html/rfc5647#section-7.3>
}
fn decrypt_packet(&mut self, mut bytes: RawPacket, _packet_number: u64) -> Result<Packet> {
let mut cipher = aes_gcm::Aes256Gcm::new(&self.key);
let mut len = [0; 4];
len.copy_from_slice(&bytes.full_packet()[..4]);
let tag_offset = bytes.full_packet().len() - 16;
let mut tag = [0; 16];
tag.copy_from_slice(&bytes.full_packet()[tag_offset..]);
let encrypted_packet_content = bytes.content_mut();
cipher
.decrypt_in_place_detached(
(&*self.nonce).into(),
&len,
encrypted_packet_content,
(&tag).into(),
)
.map_err(|_| crate::client_error!("failed to decrypt: invalid GCM MAC"))?;
self.inc_nonce();
Packet::from_full(encrypted_packet_content)
}
fn encrypt_packet(&mut self, packet: Packet, _packet_number: u64) -> EncryptedPacket {
let bytes = packet.to_bytes(
false,
<aes_gcm::aes::Aes256 as aes_gcm::aes::cipher::BlockSizeUser>::block_size() as u8,
);
let cipher = aes_gcm::Aes256Gcm::new(&self.key);
let bytes = cipher
.encrypt(
(&*self.nonce).into(),
aes_gcm::aead::Payload {
aad: &bytes[..4],
msg: &bytes[4..],
},
)
.unwrap();
self.inc_nonce();
EncryptedPacket::from_encrypted_full_bytes(bytes)
}
fn inc_nonce(&mut self) {
let mut carry = 1;
for i in (0..self.nonce.len()).rev() {
let n = self.nonce[i] as u16 + carry;
self.nonce[i] = n as u8;
carry = n >> 8;
}
}
}

View file

@ -1,4 +1,4 @@
mod keys; mod crypto;
pub mod packet; pub mod packet;
pub mod parse; pub mod parse;
@ -6,7 +6,7 @@ use core::str;
use std::{collections::VecDeque, mem::take}; use std::{collections::VecDeque, mem::take};
use ed25519_dalek::ed25519::signature::Signer; use ed25519_dalek::ed25519::signature::Signer;
use keys::{AlgorithmName, AlgorithmNegotiation, EncryptionAlgorithm}; use crypto::{AlgorithmName, AlgorithmNegotiation, EncryptionAlgorithm};
use packet::{ use packet::{
DhKeyExchangeInitReplyPacket, KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, DhKeyExchangeInitReplyPacket, KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet,
PacketTransport, SshPublicKey, SshSignature, PacketTransport, SshPublicKey, SshSignature,
@ -63,7 +63,7 @@ enum ServerState {
client_identification: Vec<u8>, client_identification: Vec<u8>,
client_kexinit: Vec<u8>, client_kexinit: Vec<u8>,
server_kexinit: Vec<u8>, server_kexinit: Vec<u8>,
kex_algorithm: keys::KexAlgorithm, kex_algorithm: crypto::KexAlgorithm,
encryption_client_to_server: EncryptionAlgorithm, encryption_client_to_server: EncryptionAlgorithm,
encryption_server_to_client: EncryptionAlgorithm, encryption_server_to_client: EncryptionAlgorithm,
}, },
@ -179,7 +179,7 @@ impl ServerConnection {
}; };
let kex_algorithms = AlgorithmNegotiation { let kex_algorithms = AlgorithmNegotiation {
supported: vec![keys::KEX_CURVE_25519_SHA256, keys::KEX_ECDH_SHA2_NISTP256], supported: vec![crypto::KEX_CURVE_25519_SHA256, crypto::KEX_ECDH_SHA2_NISTP256],
}; };
let kex_algorithm = kex_algorithms.find(kex.kex_algorithms.0)?; let kex_algorithm = kex_algorithms.find(kex.kex_algorithms.0)?;
@ -187,13 +187,12 @@ impl ServerConnection {
require_algorithm("ssh-ed25519", kex.server_host_key_algorithms)?; require_algorithm("ssh-ed25519", kex.server_host_key_algorithms)?;
let encryption_algorithms_client_to_server = AlgorithmNegotiation { let encryption_algorithms_client_to_server = AlgorithmNegotiation {
supported: vec![keys::ENC_CHACHA20POLY1305], supported: vec![crypto::ENC_CHACHA20POLY1305, crypto::ENC_AES256_GCM],
}; };
let encryption_algorithms_server_to_client = AlgorithmNegotiation { let encryption_algorithms_server_to_client = AlgorithmNegotiation {
supported: vec![keys::ENC_CHACHA20POLY1305], supported: vec![crypto::ENC_CHACHA20POLY1305, crypto::ENC_AES256_GCM],
}; };
// TODO: support aes256-gcm@openssh.com
let encryption_client_to_server = encryption_algorithms_client_to_server let encryption_client_to_server = encryption_algorithms_client_to_server
.find(kex.encryption_algorithms_client_to_server.0)?; .find(kex.encryption_algorithms_client_to_server.0)?;
let encryption_server_to_client = encryption_algorithms_server_to_client let encryption_server_to_client = encryption_algorithms_server_to_client
@ -270,7 +269,7 @@ impl ServerConnection {
let client_public_key = dh.qc; let client_public_key = dh.qc;
let keys::KexAlgorithmOutput { let crypto::KexAlgorithmOutput {
server_public_key, server_public_key,
shared_secret, shared_secret,
} = (kex_algorithm.exchange)(client_public_key, &mut *self.rng)?; } = (kex_algorithm.exchange)(client_public_key, &mut *self.rng)?;
@ -289,7 +288,7 @@ impl ServerConnection {
add_hash(hash, bytes); add_hash(hash, bytes);
}; };
let hash_mpint = |hash: &mut sha2::Sha256, bytes: &[u8]| { let hash_mpint = |hash: &mut sha2::Sha256, bytes: &[u8]| {
keys::encode_mpint_for_hash(bytes, |data| add_hash(hash, data)); crypto::encode_mpint_for_hash(bytes, |data| add_hash(hash, data));
}; };
hash_string( hash_string(

View file

@ -3,7 +3,7 @@ mod ctors;
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::client_error; use crate::client_error;
use crate::keys::{EncryptionAlgorithm, Keys, Plaintext, Session}; use crate::crypto::{EncryptionAlgorithm, Keys, Plaintext, Session};
use crate::parse::{NameList, Parser, Writer}; use crate::parse::{NameList, Parser, Writer};
use crate::Result; use crate::Result;
@ -33,7 +33,7 @@ impl Msg {
pub fn to_bytes(self) -> Vec<u8> { pub fn to_bytes(self) -> Vec<u8> {
match self.0 { match self.0 {
MsgKind::ServerProtocolInfo => crate::SERVER_IDENTIFICATION.to_vec(), MsgKind::ServerProtocolInfo => crate::SERVER_IDENTIFICATION.to_vec(),
MsgKind::PlaintextPacket(v) => v.to_bytes(true), MsgKind::PlaintextPacket(v) => v.to_bytes(true, Packet::DEFAULT_BLOCK_SIZE),
MsgKind::EncryptedPacket(v) => v.into_bytes(), MsgKind::EncryptedPacket(v) => v.into_bytes(),
} }
} }
@ -199,6 +199,8 @@ impl Packet {
pub const SSH_MSG_CHANNEL_SUCCESS: u8 = 99; pub const SSH_MSG_CHANNEL_SUCCESS: u8 = 99;
pub const SSH_MSG_CHANNEL_FAILURE: u8 = 100; pub const SSH_MSG_CHANNEL_FAILURE: u8 = 100;
pub const DEFAULT_BLOCK_SIZE: u8 = 8;
pub(crate) fn from_full(bytes: &[u8]) -> Result<Self> { pub(crate) fn from_full(bytes: &[u8]) -> Result<Self> {
let Some(padding_length) = bytes.first() else { let Some(padding_length) = bytes.first() else {
return Err(client_error!("empty packet")); return Err(client_error!("empty packet"));
@ -219,17 +221,20 @@ impl Packet {
}) })
} }
pub(crate) fn to_bytes(&self, respect_len_for_padding: bool) -> Vec<u8> { pub(crate) fn to_bytes(&self, respect_len_for_padding: bool, block_size: u8) -> Vec<u8> {
assert!(block_size.is_power_of_two());
let let_bytes = if respect_len_for_padding { 4 } else { 0 }; let let_bytes = if respect_len_for_padding { 4 } else { 0 };
// <https://datatracker.ietf.org/doc/html/rfc4253#section-6> // <https://datatracker.ietf.org/doc/html/rfc4253#section-6>
let min_full_length = self.payload.len() + let_bytes + 1; let min_full_length = self.payload.len() + let_bytes + 1;
// The padding must give a factor of 8. // The padding must give a factor of block_size.
let min_padding_len = (min_full_length.next_multiple_of(8) - min_full_length) as u8; let min_padding_len =
(min_full_length.next_multiple_of(block_size as usize) - min_full_length) as u8;
// > There MUST be at least four bytes of padding. // > There MUST be at least four bytes of padding.
let padding_len = if min_padding_len < 4 { let padding_len = if min_padding_len < 4 {
min_padding_len + 8 min_padding_len + block_size
} else { } else {
min_padding_len min_padding_len
}; };