ironcore_documents/v5/
attached.rs

1use bytes::{Buf, Bytes};
2use protobuf::Message;
3
4use crate::{Error, aes::IvAndCiphertext, icl_header_v4::V4DocumentHeader};
5
6use super::key_id_header::{self, KeyIdHeader};
7
8type Result<A> = std::result::Result<A, Error>;
9
10#[derive(Debug, PartialEq)]
11pub struct AttachedDocument {
12    pub key_id_header: KeyIdHeader,
13    pub edek: V4DocumentHeader,
14    pub edoc: IvAndCiphertext,
15}
16
17impl TryFrom<Vec<u8>> for AttachedDocument {
18    type Error = Error;
19
20    /// Breaks apart an attached edoc into its parts.
21    fn try_from(value: Vec<u8>) -> Result<Self> {
22        Bytes::from(value).try_into()
23    }
24}
25
26impl TryFrom<Bytes> for AttachedDocument {
27    type Error = Error;
28
29    /// Breaks apart an attached edoc into its parts.
30    fn try_from(value: Bytes) -> Result<Self> {
31        let (key_id_header, mut attached_document_with_header) =
32            key_id_header::decode_version_prefixed_value(value)?;
33        if attached_document_with_header.len() > 2 {
34            let edek_len = attached_document_with_header.get_u16();
35            if attached_document_with_header.len() > edek_len as usize {
36                let header_bytes = attached_document_with_header.split_to(edek_len as usize);
37                let edek = protobuf::Message::parse_from_bytes(&header_bytes[..])
38                    .map_err(|e| Error::HeaderParseErr(e.to_string()))?;
39                Ok(AttachedDocument {
40                    key_id_header,
41                    edek,
42                    edoc: IvAndCiphertext(attached_document_with_header),
43                })
44            } else {
45                Err(Error::HeaderParseErr(
46                    "The EDEK you passed in was too short based on the length bytes.".to_string(),
47                ))
48            }
49        } else {
50            Err(Error::HeaderParseErr("Header is too short.".to_string()))
51        }
52    }
53}
54
55impl AttachedDocument {
56    /// Write out the entire v5 attached documents to bytes.
57    pub fn write_to_bytes(&self) -> Result<Bytes> {
58        let AttachedDocument {
59            key_id_header,
60            edek,
61            edoc,
62        } = self;
63        let key_id_header_bytes = key_id_header.write_to_bytes();
64        let encoded_edek = edek.write_to_bytes().expect("Writing to bytes is safe");
65        if encoded_edek.len() > u16::MAX as usize {
66            Err(Error::HeaderLengthOverflow(encoded_edek.len() as u64))
67        } else {
68            let len = encoded_edek.len() as u16;
69
70            let result = [
71                key_id_header_bytes.as_ref(),
72                &len.to_be_bytes(),
73                &encoded_edek,
74                &edoc.0, // Note that the edoc is written without the leading OIRON since it's an attached document.
75            ]
76            .concat();
77            Ok(result.into())
78        }
79    }
80}
81
82#[cfg(test)]
83mod test {
84    use crate::{
85        Error,
86        aes::IvAndCiphertext,
87        icl_header_v4::{
88            V4DocumentHeader,
89            v4document_header::{
90                EdekWrapper, SignedPayload,
91                edek_wrapper::{Aes256GcmEncryptedDek, Edek},
92            },
93        },
94        v5::key_id_header::{EdekType, KeyId, KeyIdHeader, PayloadType},
95    };
96
97    use super::*;
98
99    #[test]
100    fn test_roundtrip() {
101        let key_id_header = KeyIdHeader::new(
102            EdekType::SaasShield,
103            PayloadType::StandardEdek,
104            KeyId(u32::MAX),
105        );
106
107        let edek_wrapper = EdekWrapper {
108            edek: Some(Edek::Aes256GcmEdek(Aes256GcmEncryptedDek {
109                ciphertext: [42u8; 255].as_ref().into(),
110                ..Default::default()
111            })),
112            ..Default::default()
113        };
114
115        let edek = V4DocumentHeader {
116            signed_payload: Some(SignedPayload {
117                edeks: vec![edek_wrapper],
118                ..Default::default()
119            })
120            .into(),
121            ..Default::default()
122        };
123
124        let edoc = IvAndCiphertext(vec![100, 200, 0].into());
125        let expected_result = AttachedDocument {
126            key_id_header,
127            edek,
128            edoc,
129        };
130        let encoded = expected_result.write_to_bytes().unwrap();
131        let result: AttachedDocument = encoded.try_into().unwrap();
132        assert_eq!(result, expected_result);
133    }
134
135    #[test]
136    fn test_len_too_long() {
137        // The `0,255` after the `0` is the u16 representing length. It is too large.
138        let encoded = vec![
139            255u8, 255, 255, 255, 2, 0, 0, 255, 18, 7, 18, 5, 26, 3, 18, 1, 42, 100, 200,
140        ];
141        let result = AttachedDocument::try_from(encoded).unwrap_err();
142        assert_eq!(
143            result,
144            Error::HeaderParseErr(
145                "The EDEK you passed in was too short based on the length bytes.".to_string()
146            )
147        );
148    }
149
150    #[test]
151    fn test_header_too_short() {
152        // This value is just a key_id header with 1 0 after.
153        let encoded = vec![255u8, 255, 255, 255, 2, 0, 0];
154        let result = AttachedDocument::try_from(encoded).unwrap_err();
155        assert_eq!(
156            result,
157            Error::HeaderParseErr("Header is too short.".to_string())
158        );
159    }
160}