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], _: crate::mac::MacKey) -> 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], _: crate::mac::MacKey) -> 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 length_block_size(&self) -> usize {
4
}
fn decrypt_packet_length(
&self,
_sequence_number: u32,
encrypted_packet_length: &[u8],
) -> u32 {
BigEndian::read_u32(&encrypted_packet_length[..4])
}
fn tag_len(&self) -> usize {
16
}
fn open<'a>(
&self,
_sequence_number: u32,
payload: &'a mut [u8],
tag: usize,
) -> Result<&'a [u8], Error> {
let (payload, tag) = payload.split_at_mut(tag);
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
}
fn seal(&self, _sequence_number: u32, payload: &mut [u8], tag_out: usize) {
let (payload, tag_out) = payload.split_at_mut(tag_out);
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()
}
}
use std::cell::RefCell;
thread_local! {
static BUF: RefCell<cryptovec::CryptoVec> = RefCell::new(cryptovec::CryptoVec::new());
}
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(())
})
}