// 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(())
})
}