ironcore_documents/v5/
mod.rs1pub use crate::v4::aes;
3pub mod attached;
4pub mod key_id_header;
5use crate::{
6 Error, Result,
7 aes::{EncryptionKey, IvAndCiphertext, PlaintextDocument, aes_encrypt},
8 icl_header_v4::V4DocumentHeader,
9};
10use bytes::{Buf, Bytes};
11use key_id_header::KeyIdHeader;
12use rand::CryptoRng;
13
14const MAGIC: &[u8; 4] = crate::v4::MAGIC;
18pub(crate) const V0: u8 = 0u8;
19pub const VERSION_AND_MAGIC: [u8; 5] = [V0, MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3]];
21pub(crate) const DETACHED_HEADER_LEN: usize = 5;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct EncryptedPayload(IvAndCiphertext);
29
30impl Default for EncryptedPayload {
31 fn default() -> EncryptedPayload {
32 EncryptedPayload(Bytes::new().into())
33 }
34}
35
36impl TryFrom<Bytes> for EncryptedPayload {
37 type Error = Error;
38
39 fn try_from(mut value: Bytes) -> core::result::Result<Self, Self::Error> {
40 if value.len() < DETACHED_HEADER_LEN {
41 Err(Error::EdocTooShort(value.len()))
42 } else if value.get_u8() == V0 {
43 let maybe_magic = value.split_to(MAGIC.len());
44 if maybe_magic.as_ref() == MAGIC {
45 Ok(EncryptedPayload(value.into()))
46 } else {
47 Err(Error::NoIronCoreMagic)
48 }
49 } else {
50 Err(Error::HeaderParseErr(
51 "`0IRON` magic expected on the encrypted document.".to_string(),
52 ))
53 }
54 }
55}
56
57impl TryFrom<Vec<u8>> for EncryptedPayload {
58 type Error = Error;
59 fn try_from(value: Vec<u8>) -> core::result::Result<Self, Self::Error> {
60 Bytes::from(value).try_into()
61 }
62}
63
64impl From<IvAndCiphertext> for EncryptedPayload {
65 fn from(value: IvAndCiphertext) -> Self {
66 EncryptedPayload(value)
67 }
68}
69
70impl EncryptedPayload {
71 pub fn to_aes_value_with_attached_iv(self) -> IvAndCiphertext {
73 self.0
74 }
75
76 pub fn decrypt(self, key: &EncryptionKey) -> Result<PlaintextDocument> {
78 crate::aes::decrypt_document_with_attached_iv(key, &self.to_aes_value_with_attached_iv())
79 }
80
81 pub fn write_to_bytes(&self) -> Vec<u8> {
82 let mut result = Vec::with_capacity(self.0.len() + DETACHED_HEADER_LEN);
83 result.push(V0);
84 result.extend_from_slice(MAGIC);
85 result.extend_from_slice(self.0.as_ref());
86 result
87 }
88}
89
90pub fn encrypt_detached_document<R: CryptoRng>(
93 rng: &mut R,
94 key: EncryptionKey,
95 document: PlaintextDocument,
96) -> Result<EncryptedPayload> {
97 let (iv, enc_data) = aes_encrypt(key, &document.0, &[], rng)?;
98 Ok(EncryptedPayload(IvAndCiphertext(
99 iv.into_iter().chain(enc_data.0).collect(),
100 )))
101}
102
103pub fn parse_standard_edek(edek_bytes: Bytes) -> Result<(KeyIdHeader, V4DocumentHeader)> {
104 let (key_id_header, proto_bytes) = key_id_header::decode_version_prefixed_value(edek_bytes)?;
105 let pb = protobuf::Message::parse_from_bytes(&proto_bytes[..])
106 .map_err(|e| Error::HeaderParseErr(e.to_string()))?;
107 Ok((key_id_header, pb))
108}
109
110pub fn parse_standard_edoc(edoc: Bytes) -> Result<IvAndCiphertext> {
111 let encrypted_payload: EncryptedPayload = edoc.try_into()?;
112 Ok(encrypted_payload.to_aes_value_with_attached_iv())
113}
114
115#[cfg(test)]
116mod test {
117 use super::*;
118 use hex_literal::hex;
119 use rand::SeedableRng;
120 use rand_chacha::ChaCha20Rng;
121 #[test]
122 fn encrypt_decrypt_detached_document_roundtrips() {
123 let mut rng = ChaCha20Rng::seed_from_u64(172u64);
124 let key = EncryptionKey(hex!(
125 "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
126 ));
127 let plaintext = PlaintextDocument(vec![100u8, 200u8]);
128 let encrypted = encrypt_detached_document(&mut rng, key, plaintext.clone()).unwrap();
129 let result = encrypted.decrypt(&key).unwrap();
130 assert_eq!(result, plaintext);
131 }
132
133 #[test]
134 fn creation_fails_too_short() {
135 let encrypted_payload_or_error: Result<EncryptedPayload> =
136 hex!("00495241").to_vec().try_into();
137
138 let result = encrypted_payload_or_error.unwrap_err();
139 assert_eq!(result, Error::EdocTooShort(4));
140 }
141
142 #[test]
143 fn creation_fails_wrong_bytes() {
144 let encrypted_payload_or_error: Result<EncryptedPayload> =
146 hex!("0149524f4efa5111111111").to_vec().try_into();
147 let result = encrypted_payload_or_error.unwrap_err();
148 assert_eq!(
149 result,
150 Error::HeaderParseErr("`0IRON` magic expected on the encrypted document.".to_string())
151 );
152
153 let encrypted_payload_or_error: Result<EncryptedPayload> =
155 hex!("0000524f4efa5111111111").to_vec().try_into();
156 let result = encrypted_payload_or_error.unwrap_err();
157 assert_eq!(result, Error::NoIronCoreMagic);
158 }
159}