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