use crate::constants::SECURITY_PARAMETER;
use crate::header::keys::PayloadKey;
use crate::{Error, ErrorKind, Result};
use arrayref::array_ref;
use blake2::VarBlake2b;
use chacha::ChaCha; use lioness::Lioness;
pub const PAYLOAD_OVERHEAD_SIZE: usize = SECURITY_PARAMETER + 1;
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Payload(Vec<u8>);
#[allow(clippy::len_without_is_empty)]
impl Payload {
pub fn encapsulate_message(
plaintext_message: &[u8],
payload_keys: &[PayloadKey],
payload_size: usize,
) -> Result<Self> {
Self::validate_parameters(payload_size, plaintext_message.len())?;
let mut payload = Self::set_final_payload(plaintext_message, payload_size);
for payload_key in payload_keys.iter().rev() {
payload = payload.add_encryption_layer(payload_key)?;
}
Ok(payload)
}
fn validate_parameters(payload_size: usize, plaintext_len: usize) -> Result<()> {
if payload_size < PAYLOAD_OVERHEAD_SIZE {
return Err(Error::new(
ErrorKind::InvalidPayload,
"specified payload_size is smaller than the required overhead",
));
} else if payload_size < lioness::DIGEST_RESULT_SIZE {
return Err(Error::new(
ErrorKind::InvalidPayload,
"specified payload_size is smaller lioness block size",
));
}
let maximum_plaintext_length = payload_size - PAYLOAD_OVERHEAD_SIZE;
if plaintext_len > maximum_plaintext_length {
return Err(Error::new(
ErrorKind::InvalidPayload,
format!(
"too long message provided. Message was: {}B long, maximum_plaintext_length is: {}B",
plaintext_len,
maximum_plaintext_length
),
));
}
Ok(())
}
fn set_final_payload(plaintext_message: &[u8], payload_size: usize) -> Self {
let final_payload: Vec<u8> = std::iter::repeat(0u8)
.take(SECURITY_PARAMETER) .chain(plaintext_message.iter().cloned()) .chain(std::iter::repeat(1u8).take(1)) .chain(std::iter::repeat(0u8)) .take(payload_size) .collect();
Payload(final_payload)
}
fn add_encryption_layer(mut self, payload_enc_key: &PayloadKey) -> Result<Self> {
let lioness_cipher = Lioness::<VarBlake2b, ChaCha>::new_raw(array_ref!(
payload_enc_key,
0,
lioness::RAW_KEY_SIZE
));
if let Err(err) = lioness_cipher.encrypt(&mut self.0) {
return Err(Error::new(
ErrorKind::InvalidPayload,
format!("error while encrypting payload - {}", err),
));
};
Ok(self)
}
pub fn unwrap(mut self, payload_key: &PayloadKey) -> Result<Self> {
let lioness_cipher = Lioness::<VarBlake2b, ChaCha>::new_raw(array_ref!(
payload_key,
0,
lioness::RAW_KEY_SIZE
));
if let Err(err) = lioness_cipher.decrypt(&mut self.0) {
return Err(Error::new(
ErrorKind::InvalidPayload,
format!("error while unwrapping payload - {}", err),
));
};
Ok(self)
}
pub fn recover_plaintext(self) -> Result<Vec<u8>> {
debug_assert!(self.len() > PAYLOAD_OVERHEAD_SIZE);
if !self.0.iter().take(SECURITY_PARAMETER).all(|b| *b == 0) {
return Err(Error::new(
ErrorKind::InvalidPayload,
"malformed payload - no leading zero padding present",
));
}
let padded_plaintext = self
.into_inner()
.into_iter()
.skip(SECURITY_PARAMETER)
.collect::<Vec<_>>();
if let Some(i) = padded_plaintext.iter().rposition(|b| *b == 1) {
let plaintext = padded_plaintext.into_iter().take(i).collect();
return Ok(plaintext);
}
Err(Error::new(
ErrorKind::InvalidPayload,
"malformed payload - invalid trailing padding",
))
}
fn into_inner(self) -> Vec<u8> {
self.0
}
fn inner(&self) -> &[u8] {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn as_bytes(&self) -> &[u8] {
self.inner()
}
pub fn into_bytes(self) -> Vec<u8> {
self.into_inner()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < PAYLOAD_OVERHEAD_SIZE {
return Err(Error::new(
ErrorKind::InvalidPayload,
"too short payload provided",
));
}
Ok(Payload(bytes.to_vec()))
}
}
#[cfg(test)]
mod building_payload_from_bytes {
use super::*;
#[test]
fn from_bytes_returns_error_if_bytes_are_too_short() {
let bytes = [0u8; 1].to_vec();
let expected = ErrorKind::InvalidPayload;
match Payload::from_bytes(&bytes) {
Err(err) => assert_eq!(expected, err.kind()),
_ => panic!("Should have returned an error when packet bytes too short"),
};
}
}
#[cfg(test)]
mod parameter_verification {
use super::*;
#[test]
fn it_returns_an_error_if_payload_size_is_smaller_than_the_overhead() {
assert!(Payload::validate_parameters(PAYLOAD_OVERHEAD_SIZE - 1, 16).is_err());
}
#[test]
fn it_returns_an_error_if_payload_size_is_smaller_than_the_lioness_blocklen() {
assert!(Payload::validate_parameters(lioness::DIGEST_RESULT_SIZE - 1, 16).is_err());
}
#[test]
fn it_returns_an_error_if_message_is_longer_than_maximum_allowed_length() {
let payload_length = 100;
let max_allowed_length = payload_length - PAYLOAD_OVERHEAD_SIZE;
assert!(Payload::validate_parameters(payload_length, max_allowed_length + 1).is_err());
}
}
#[cfg(test)]
mod final_payload_setting {
use super::*;
#[test]
fn adds_correct_padding() {
let plaintext_lengths = vec![0, 1, 16, 128, 4096];
for plaintext_length in plaintext_lengths {
let payload_size = plaintext_length + lioness::DIGEST_RESULT_SIZE;
let final_payload =
Payload::set_final_payload(&vec![42u8; plaintext_length], payload_size);
let final_payload_inner = final_payload.into_inner();
assert!(final_payload_inner
.iter()
.take(SECURITY_PARAMETER)
.all(|&b| b == 0));
assert!(final_payload_inner
.iter()
.skip(SECURITY_PARAMETER)
.take(plaintext_length)
.all(|&b| b == 42));
assert_eq!(
final_payload_inner[SECURITY_PARAMETER + plaintext_length],
1
);
assert!(final_payload_inner
.iter()
.skip(SECURITY_PARAMETER + plaintext_length + 1)
.all(|&b| b == 0))
}
}
}
#[cfg(test)]
mod test_encapsulating_payload {
use super::*;
use crate::constants::PAYLOAD_KEY_SIZE;
#[test]
fn can_be_encapsulated_without_encryption() {
let message = vec![1u8, 16];
let payload_size = 512;
let unencrypted_message =
Payload::encapsulate_message(&message, &[], payload_size).unwrap();
assert_eq!(
unencrypted_message,
Payload::set_final_payload(&message, payload_size)
)
}
#[test]
fn works_with_single_encryption_layer() {
let message = vec![1u8, 16];
let payload_size = 512;
let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
assert!(Payload::encapsulate_message(&message, &[payload_key_1], payload_size).is_ok())
}
#[test]
fn works_with_five_encryption_layers() {
let message = vec![1u8, 16];
let payload_size = 512;
let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
let payload_key_4 = [6u8; PAYLOAD_KEY_SIZE];
let payload_key_5 = [7u8; PAYLOAD_KEY_SIZE];
assert!(Payload::encapsulate_message(
&message,
&[
payload_key_1,
payload_key_2,
payload_key_3,
payload_key_4,
payload_key_5
],
payload_size
)
.is_ok())
}
}
#[cfg(test)]
mod test_unwrapping_payload {
use super::*;
use crate::constants::{PAYLOAD_KEY_SIZE, SECURITY_PARAMETER};
use crate::packet::builder::DEFAULT_PAYLOAD_SIZE;
#[test]
fn unwrapping_results_in_original_payload_plaintext() {
let message = vec![42u8; 16];
let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
let encrypted_payload =
Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
let unwrapped_payload = payload_keys
.iter()
.fold(encrypted_payload, |current_layer, payload_key| {
current_layer.unwrap(payload_key).unwrap()
});
let zero_bytes = vec![0u8; SECURITY_PARAMETER];
let additional_padding =
vec![0u8; DEFAULT_PAYLOAD_SIZE - PAYLOAD_OVERHEAD_SIZE - message.len()];
let expected_payload = [zero_bytes, message, vec![1], additional_padding].concat();
assert_eq!(expected_payload, unwrapped_payload.into_inner());
}
}
#[cfg(test)]
mod plaintext_recovery {
use super::*;
use crate::constants::PAYLOAD_KEY_SIZE;
use crate::packet::builder::DEFAULT_PAYLOAD_SIZE;
#[test]
fn it_is_possible_to_recover_plaintext_from_valid_payload() {
let message = vec![42u8; 160];
let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
let encrypted_payload =
Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
let unwrapped_payload = payload_keys
.iter()
.fold(encrypted_payload, |current_layer, payload_key| {
current_layer.unwrap(payload_key).unwrap()
});
let recovered_plaintext = unwrapped_payload.recover_plaintext().unwrap();
assert_eq!(message, recovered_plaintext);
}
#[test]
fn it_is_possible_to_recover_plaintext_even_if_is_just_ones() {
let message = vec![1u8; 160];
let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
let encrypted_payload =
Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
let unwrapped_payload = payload_keys
.iter()
.fold(encrypted_payload, |current_layer, payload_key| {
current_layer.unwrap(payload_key).unwrap()
});
let recovered_plaintext = unwrapped_payload.recover_plaintext().unwrap();
assert_eq!(message, recovered_plaintext);
}
#[test]
fn it_is_possible_to_recover_plaintext_even_if_is_just_zeroes() {
let message = vec![0u8; 160];
let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
let encrypted_payload =
Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
let unwrapped_payload = payload_keys
.iter()
.fold(encrypted_payload, |current_layer, payload_key| {
current_layer.unwrap(payload_key).unwrap()
});
let recovered_plaintext = unwrapped_payload.recover_plaintext().unwrap();
assert_eq!(message, recovered_plaintext);
}
#[test]
fn it_fails_to_recover_plaintext_from_invalid_payload() {
let message = vec![42u8; 160];
let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
let encrypted_payload =
Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
let unwrapped_payload = payload_keys
.iter()
.skip(1) .fold(encrypted_payload, |current_layer, payload_key| {
current_layer.unwrap(payload_key).unwrap()
});
assert!(unwrapped_payload.recover_plaintext().is_err())
}
#[test]
fn it_fails_to_recover_plaintext_from_incorrectly_constructed_payload() {
let zero_payload = Payload(vec![0u8; DEFAULT_PAYLOAD_SIZE]);
assert!(zero_payload.recover_plaintext().is_err());
}
}