use bytes::{Buf, Bytes};
use protobuf::Message;
use crate::{Error, aes::IvAndCiphertext, icl_header_v4::V4DocumentHeader};
use super::key_id_header::{self, KeyIdHeader};
type Result<A> = std::result::Result<A, Error>;
#[derive(Debug, PartialEq)]
pub struct AttachedDocument {
pub key_id_header: KeyIdHeader,
pub edek: V4DocumentHeader,
pub edoc: IvAndCiphertext,
}
impl TryFrom<Vec<u8>> for AttachedDocument {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self> {
Bytes::from(value).try_into()
}
}
impl TryFrom<Bytes> for AttachedDocument {
type Error = Error;
fn try_from(value: Bytes) -> Result<Self> {
let (key_id_header, mut attached_document_with_header) =
key_id_header::decode_version_prefixed_value(value)?;
if attached_document_with_header.len() > 2 {
let edek_len = attached_document_with_header.get_u16();
if attached_document_with_header.len() > edek_len as usize {
let header_bytes = attached_document_with_header.split_to(edek_len as usize);
let edek = protobuf::Message::parse_from_bytes(&header_bytes[..])
.map_err(|e| Error::HeaderParseErr(e.to_string()))?;
Ok(AttachedDocument {
key_id_header,
edek,
edoc: IvAndCiphertext(attached_document_with_header),
})
} else {
Err(Error::HeaderParseErr(
"The EDEK you passed in was too short based on the length bytes.".to_string(),
))
}
} else {
Err(Error::HeaderParseErr("Header is too short.".to_string()))
}
}
}
impl AttachedDocument {
pub fn write_to_bytes(&self) -> Result<Bytes> {
let AttachedDocument {
key_id_header,
edek,
edoc,
} = self;
let key_id_header_bytes = key_id_header.write_to_bytes();
let encoded_edek = edek.write_to_bytes().expect("Writing to bytes is safe");
if encoded_edek.len() > u16::MAX as usize {
Err(Error::HeaderLengthOverflow(encoded_edek.len() as u64))
} else {
let len = encoded_edek.len() as u16;
let result = [
key_id_header_bytes.as_ref(),
&len.to_be_bytes(),
&encoded_edek,
&edoc.0, ]
.concat();
Ok(result.into())
}
}
}
#[cfg(test)]
mod test {
use crate::{
Error,
aes::IvAndCiphertext,
icl_header_v4::{
V4DocumentHeader,
v4document_header::{
EdekWrapper, SignedPayload,
edek_wrapper::{Aes256GcmEncryptedDek, Edek},
},
},
v5::key_id_header::{EdekType, KeyId, KeyIdHeader, PayloadType},
};
use super::*;
#[test]
fn test_roundtrip() {
let key_id_header = KeyIdHeader::new(
EdekType::SaasShield,
PayloadType::StandardEdek,
KeyId(u32::MAX),
);
let edek_wrapper = EdekWrapper {
edek: Some(Edek::Aes256GcmEdek(Aes256GcmEncryptedDek {
ciphertext: [42u8; 255].as_ref().into(),
..Default::default()
})),
..Default::default()
};
let edek = V4DocumentHeader {
signed_payload: Some(SignedPayload {
edeks: vec![edek_wrapper],
..Default::default()
})
.into(),
..Default::default()
};
let edoc = IvAndCiphertext(vec![100, 200, 0].into());
let expected_result = AttachedDocument {
key_id_header,
edek,
edoc,
};
let encoded = expected_result.write_to_bytes().unwrap();
let result: AttachedDocument = encoded.try_into().unwrap();
assert_eq!(result, expected_result);
}
#[test]
fn test_len_too_long() {
let encoded = vec![
255u8, 255, 255, 255, 2, 0, 0, 255, 18, 7, 18, 5, 26, 3, 18, 1, 42, 100, 200,
];
let result = AttachedDocument::try_from(encoded).unwrap_err();
assert_eq!(
result,
Error::HeaderParseErr(
"The EDEK you passed in was too short based on the length bytes.".to_string()
)
);
}
#[test]
fn test_header_too_short() {
let encoded = vec![255u8, 255, 255, 255, 2, 0, 0];
let result = AttachedDocument::try_from(encoded).unwrap_err();
assert_eq!(
result,
Error::HeaderParseErr("Header is too short.".to_string())
);
}
}