use crate::Result; use aes_gcm::{aead::AeadMutInPlace, KeyInit}; use chacha20::cipher::{StreamCipher, StreamCipherSeek}; use subtle::ConstantTimeEq; use crate::packet::{EncryptedPacket, Packet, RawPacket}; use super::EncryptionAlgorithm; pub const CHACHA20POLY1305: EncryptionAlgorithm = EncryptionAlgorithm { name: "chacha20-poly1305@openssh.com", iv_size: 0, 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) }, decrypt_packet: |state, bytes, packet_number| { let alg = ChaCha20Poly1305OpenSsh::from_state(state); alg.decrypt_packet(bytes, packet_number) }, encrypt_packet: |state, packet, packet_number| { let alg = ChaCha20Poly1305OpenSsh::from_state(state); alg.encrypt_packet(packet, packet_number) }, }; pub const 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) }, }; /// RFC 4344 AES128 in counter mode. /// pub const ENC_AES128_CTR: EncryptionAlgorithm = EncryptionAlgorithm { name: "aes128-ctr", iv_size: 12, key_size: 32, decrypt_len: |state, bytes, packet_number| { let mut alg = Aes128Ctr::from_state(state); alg.decrypt_len(bytes, packet_number) }, decrypt_packet: |state, bytes, packet_number| { let mut state = Aes128Ctr::from_state(state); state.decrypt_packet(bytes, packet_number) }, encrypt_packet: |state, packet, packet_number| { let mut state = Aes128Ctr::from_state(state); state.encrypt_packet(packet, packet_number) }, }; /// `chacha20-poly1305@openssh.com` uses a 64-bit nonce, not the 96-bit one in the IETF version. type SshChaCha20 = chacha20::ChaCha20Legacy; /// struct ChaCha20Poly1305OpenSsh { header_key: chacha20::Key, main_key: chacha20::Key, } impl ChaCha20Poly1305OpenSsh { fn from_state(keys: &[u8]) -> Self { assert_eq!(keys.len(), 64); Self { main_key: <[u8; 32]>::try_from(&keys[..32]).unwrap().into(), header_key: <[u8; 32]>::try_from(&keys[32..]).unwrap().into(), } } fn decrypt_len(&self, bytes: &mut [u8], packet_number: u64) { // let mut cipher = ::new( &self.header_key, &packet_number.to_be_bytes().into(), ); cipher.apply_keystream(bytes); } fn decrypt_packet(&self, mut bytes: RawPacket, packet_number: u64) -> Result { // let mut cipher = ::new( &self.main_key, &packet_number.to_be_bytes().into(), ); let tag_offset = bytes.full_packet().len() - 16; let authenticated = &bytes.full_packet()[..tag_offset]; let mac = { let mut poly1305_key = [0; poly1305::KEY_SIZE]; cipher.apply_keystream(&mut poly1305_key); poly1305::Poly1305::new(&poly1305_key.into()).compute_unpadded(authenticated) }; let read_tag = poly1305::Tag::from_slice(&bytes.full_packet()[tag_offset..]); if !bool::from(mac.ct_eq(read_tag)) { return Err(crate::peer_error!( "failed to decrypt: invalid poly1305 MAC" )); } // Advance ChaCha's block counter to 1 cipher .seek(::block_size()); let encrypted_packet_content = bytes.content_mut(); cipher.apply_keystream(encrypted_packet_content); Packet::from_full(encrypted_packet_content) } fn encrypt_packet(&self, packet: Packet, packet_number: u64) -> EncryptedPacket { let mut bytes = packet.to_bytes(false, Packet::DEFAULT_BLOCK_SIZE); // Prepare the main cipher. let mut main_cipher = ::new( &self.main_key, &packet_number.to_be_bytes().into(), ); // Get the poly1305 key first, but don't use it yet! // We encrypt-then-mac. let mut poly1305_key = [0; poly1305::KEY_SIZE]; main_cipher.apply_keystream(&mut poly1305_key); // As the first act of encryption, encrypt the length. let mut len_cipher = ::new( &self.header_key, &packet_number.to_be_bytes().into(), ); len_cipher.apply_keystream(&mut bytes[..4]); // Advance ChaCha's block counter to 1 main_cipher .seek(::block_size()); // Encrypt the content of the packet, excluding the length and the MAC, which is not pushed yet. main_cipher.apply_keystream(&mut bytes[4..]); // Now, MAC the length || content, and push that to the end. let mac = poly1305::Poly1305::new(&poly1305_key.into()).compute_unpadded(&bytes); bytes.extend_from_slice(mac.as_slice()); EncryptedPacket::from_encrypted_full_bytes(bytes) } } /// /// struct Aes256GcmOpenSsh<'a> { key: aes_gcm::Key, 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. // } fn decrypt_packet(&mut self, mut bytes: RawPacket, _packet_number: u64) -> Result { 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::peer_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 mut bytes = packet.to_bytes( false, ::block_size() as u8, ); let mut cipher = aes_gcm::Aes256Gcm::new(&self.key); let (aad, plaintext) = bytes.split_at_mut(4); let tag = cipher .encrypt_in_place_detached((&*self.nonce).into(), aad, plaintext) .unwrap(); bytes.extend_from_slice(&tag); 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; } } } struct Aes128Ctr { _key: ctr::Ctr128BE, } impl Aes128Ctr { fn from_state(_keys: &mut [u8]) -> Self { todo!() } fn decrypt_len(&mut self, _: &mut [u8], _: u64) {} fn decrypt_packet(&mut self, _bytes: RawPacket, _packet_number: u64) -> Result { todo!() } fn encrypt_packet(&mut self, _packet: Packet, _packet_number: u64) -> EncryptedPacket { todo!() } }