aws_nitro_enclaves_cose/
encrypt.rs

1//! COSE Encryption
2use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer};
3use serde_bytes::ByteBuf;
4use serde_cbor::Error as CborError;
5use serde_cbor::Value as CborValue;
6
7use crate::crypto::{Decryption, Encryption, Entropy};
8use crate::error::CoseError;
9use crate::header_map::{map_to_empty_or_serialized, HeaderMap};
10
11const KTY: i8 = 1;
12const IV: i8 = 5;
13
14/// Holds the cipher configuration to be used
15pub enum CipherConfiguration {
16    /// AES-GCM mode, key length is derived from the key
17    Gcm,
18}
19
20impl CipherConfiguration {
21    fn cose_alg(&self, key: &[u8]) -> Option<CoseAlgorithm> {
22        Some(match self {
23            CipherConfiguration::Gcm => match key.len() {
24                16 => CoseAlgorithm::AesGcm96_128_128,
25                24 => CoseAlgorithm::AesGcm96_128_192,
26                32 => CoseAlgorithm::AesGcm96_128_256,
27                _ => return None,
28            },
29        })
30    }
31}
32
33pub(crate) enum CoseAlgorithm {
34    /// AES-GCM mode w/ 128-bit key, 128-bit tag
35    AesGcm96_128_128,
36    /// AES-GCM mode w/ 192-bit key, 128-bit tag
37    AesGcm96_128_192,
38    /// AES-GCM mode w/ 256-bit key, 128-bit tag
39    AesGcm96_128_256,
40}
41
42impl CoseAlgorithm {
43    fn value(&self) -> usize {
44        match self {
45            CoseAlgorithm::AesGcm96_128_128 => 1,
46            CoseAlgorithm::AesGcm96_128_192 => 2,
47            CoseAlgorithm::AesGcm96_128_256 => 3,
48        }
49    }
50
51    fn from_value(value: i8) -> Option<CoseAlgorithm> {
52        Some(match value {
53            1 => CoseAlgorithm::AesGcm96_128_128,
54            2 => CoseAlgorithm::AesGcm96_128_192,
55            3 => CoseAlgorithm::AesGcm96_128_256,
56            _ => return None,
57        })
58    }
59
60    // Returns the tag size for the given algorithm in bytes.
61    fn tag_size(&self) -> usize {
62        match self {
63            CoseAlgorithm::AesGcm96_128_128 => 16,
64            CoseAlgorithm::AesGcm96_128_192 => 16,
65            CoseAlgorithm::AesGcm96_128_256 => 16,
66        }
67    }
68
69    fn iv_len(&self) -> Option<usize> {
70        match self {
71            CoseAlgorithm::AesGcm96_128_128 => Some(12),
72            CoseAlgorithm::AesGcm96_128_192 => Some(12),
73            CoseAlgorithm::AesGcm96_128_256 => Some(12),
74        }
75    }
76}
77
78///  Implementation of the Enc_structure structure as defined in
79///  [RFC8152](https://tools.ietf.org/html/rfc8152#section-5.3).
80///
81///  The encryption algorithm for AEAD algorithms is fairly simple.  The
82///  first step is to create a consistent byte stream for the
83///  authenticated data structure.  For this purpose, we use an
84///  Enc_structure.  The Enc_structure is a CBOR array.  The fields of the
85///  Enc_structure in order are:
86///
87///  1.  A text string identifying the context of the authenticated data
88///      structure.  The context string is:
89///
90///         "Encrypt0" for the content encryption of a COSE_Encrypt0 data
91///         structure.
92///
93///         "Encrypt" for the first layer of a COSE_Encrypt data structure
94///         (i.e., for content encryption).
95///
96///         "Enc_Recipient" for a recipient encoding to be placed in an
97///         COSE_Encrypt data structure.
98///
99///         "Mac_Recipient" for a recipient encoding to be placed in a
100///         MACed message structure.
101///
102///         "Rec_Recipient" for a recipient encoding to be placed in a
103///         recipient structure.
104///
105///  2.  The protected attributes from the body structure encoded in a
106///      bstr type.  If there are no protected attributes, a bstr of
107///      length zero is used.
108///
109///  3.  The protected attributes from the application encoded in a bstr
110///      type.  If this field is not supplied, it defaults to a zero-
111///      length bstr.  (See Section 4.3 for application guidance on
112///      constructing this field.)
113///
114///  The CDDL fragment that describes the above text is:
115///
116///  Enc_structure = [
117///      context : "Encrypt" / "Encrypt0" / "Enc_Recipient" /
118///          "Mac_Recipient" / "Rec_Recipient",
119///      protected : empty_or_serialized_map,
120///      external_aad : bstr
121///  ]
122#[derive(Debug, Clone, Deserialize)]
123struct EncStructure {
124    /// context: "Encrypt0" / "Encrypt" / "Enc_Recipient" / "Mac_Recipient" / "Rec_Recipient"
125    context: String,
126    /// protected : empty_or_serialized_map,
127    protected: ByteBuf,
128    /// external_aad : bstr,
129    external_aad: ByteBuf,
130}
131
132impl Serialize for EncStructure {
133    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134    where
135        S: Serializer,
136    {
137        let mut seq = serializer.serialize_seq(Some(3))?;
138        seq.serialize_element(&self.context)?;
139        seq.serialize_element(&self.protected)?;
140        seq.serialize_element(&self.external_aad)?;
141        seq.end()
142    }
143}
144
145impl EncStructure {
146    fn new_encrypt0(protected: &[u8]) -> Result<Self, CborError> {
147        Ok(EncStructure {
148            context: String::from("Encrypt0"),
149            protected: ByteBuf::from(protected.to_vec()),
150            external_aad: ByteBuf::new(),
151        })
152    }
153
154    /// Serializes the EncStructure to . We don't care about deserialization, since
155    /// both sides are supposed to compute the EncStructure and compare.
156    fn as_bytes(&self) -> Result<Vec<u8>, CborError> {
157        serde_cbor::to_vec(self)
158    }
159}
160
161///  Implementation of the COSE_Encrypt0 structure as defined in
162///  [RFC8152](https://tools.ietf.org/html/rfc8152#section-5.2).
163///
164///  The COSE_Encrypt0 encrypted structure does not have the ability to
165///  specify recipients of the message.  The structure assumes that the
166///  recipient of the object will already know the identity of the key to
167///  be used in order to decrypt the message.  If a key needs to be
168///  identified to the recipient, the enveloped structure ought to be
169///  used.
170///
171///  The COSE_Encrypt0 structure can be encoded as either tagged or
172///  untagged depending on the context it will be used in.  A tagged
173///  COSE_Encrypt0 structure is identified by the CBOR tag 16.  The CDDL
174///  fragment that represents this is:
175///
176///  COSE_Encrypt0_Tagged = #6.16(COSE_Encrypt0)
177///
178///  The COSE_Encrypt0 structure is a CBOR array.  The fields of the array in
179///  order are:
180///
181///  protected:  This is as described in Section 3.
182///
183///  unprotected:  This is as described in Section 3.
184///
185///  ciphertext:  This is as described in Section 5.1.
186///
187///  The CDDL fragment that represents the above text for COSE_Encrypt0
188///  follows.
189///
190///  COSE_Encrypt0 = [
191///      Headers,
192///      ciphertext : bstr / nil,
193///  ]
194#[derive(Debug, Clone, Deserialize)]
195pub struct CoseEncrypt0 {
196    /// protected: empty_or_serialized_map,
197    protected: ByteBuf,
198    /// unprotected: HeaderMap
199    unprotected: HeaderMap,
200    /// ciphertext: bstr
201    /// The spec allows ciphertext to be nil and transported separately, but it's not useful at the
202    /// moment, so this is just a ByteBuf for simplicity.
203    ciphertext: ByteBuf,
204}
205
206impl Serialize for CoseEncrypt0 {
207    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
208    where
209        S: Serializer,
210    {
211        let mut seq = serializer.serialize_seq(Some(3))?;
212        seq.serialize_element(&self.protected)?;
213        seq.serialize_element(&self.unprotected)?;
214        seq.serialize_element(&self.ciphertext)?;
215        seq.end()
216    }
217}
218
219impl CoseEncrypt0 {
220    /// Creates a new instance of the COSE_Encrypt0 structure and encrypts the provided payload.
221    /// https://datatracker.ietf.org/doc/html/rfc8152#section-5.3
222    pub fn new<C: Encryption + Entropy>(
223        payload: &[u8],
224        cipher_config: CipherConfiguration,
225        key: &[u8],
226    ) -> Result<Self, CoseError> {
227        let cose_alg = match cipher_config.cose_alg(key) {
228            Some(v) => v,
229            None => {
230                return Err(CoseError::UnsupportedError(
231                    "Unsupported encryption algorithm".to_string(),
232                ))
233            }
234        };
235        let mut iv = vec![0; cose_alg.iv_len().unwrap()];
236        C::rand_bytes(&mut iv)?;
237
238        let cose_alg_value = cose_alg.value();
239        let mut protected = HeaderMap::new();
240        protected.insert(KTY.into(), CborValue::Integer(cose_alg_value as i128));
241        let mut unprotected = HeaderMap::new();
242        unprotected.insert(IV.into(), CborValue::Bytes(iv.to_owned()));
243
244        let protected_bytes =
245            map_to_empty_or_serialized(&protected).map_err(CoseError::SerializationError)?;
246
247        let enc_structure =
248            EncStructure::new_encrypt0(&protected_bytes).map_err(CoseError::SerializationError)?;
249
250        let mut tag = vec![0; cose_alg.tag_size()];
251        let mut ciphertext = C::encrypt_aead(
252            cose_alg.into(),
253            key,
254            Some(&iv[..]),
255            &enc_structure
256                .as_bytes()
257                .map_err(CoseError::SerializationError)?,
258            payload,
259            &mut tag,
260        )
261        .map_err(|e| CoseError::EncryptionError(Box::new(e)))?;
262
263        ciphertext.append(&mut tag);
264
265        Ok(CoseEncrypt0 {
266            protected: ByteBuf::from(protected_bytes),
267            unprotected,
268            ciphertext: ByteBuf::from(ciphertext),
269        })
270    }
271
272    /// Decrypt the ciphertext in the COSE_Encrypt0 structure and returns both
273    /// the protected and unprotected HeaderMap(s).
274    /// https://datatracker.ietf.org/doc/html/rfc8152#section-5.3
275    pub fn decrypt<C: Decryption>(
276        &self,
277        key: &[u8],
278    ) -> Result<(HeaderMap, &HeaderMap, Vec<u8>), CoseError> {
279        let protected: HeaderMap =
280            HeaderMap::from_bytes(&self.protected).map_err(CoseError::SerializationError)?;
281
282        let protected_enc_alg = match protected.get(&CborValue::Integer(1)) {
283            Some(CborValue::Integer(val)) => val,
284            _ => {
285                return Err(CoseError::SpecificationError(
286                    "Protected Header contains invalid Encryption Algorithm specification"
287                        .to_string(),
288                ))
289            }
290        };
291
292        let cose_alg = match CoseAlgorithm::from_value(*protected_enc_alg as i8) {
293            Some(v) => v,
294            None => {
295                return Err(CoseError::UnsupportedError(
296                    "Unsupported encryption algorithm".to_string(),
297                ))
298            }
299        };
300
301        let protected_bytes =
302            map_to_empty_or_serialized(&protected).map_err(CoseError::SerializationError)?;
303
304        let enc_structure =
305            EncStructure::new_encrypt0(&protected_bytes).map_err(CoseError::SerializationError)?;
306
307        let iv = match self.unprotected.get(&CborValue::Integer(5)) {
308            Some(CborValue::Bytes(val)) => val,
309            _ => {
310                return Err(CoseError::SpecificationError(
311                    "Unprotected Header contains invalid IV specification".to_string(),
312                ))
313            }
314        };
315
316        let (ciphertext, tag) = self
317            .ciphertext
318            .split_at(self.ciphertext.len() - cose_alg.tag_size());
319
320        let payload = C::decrypt_aead(
321            cose_alg.into(),
322            key,
323            Some(iv),
324            &enc_structure
325                .as_bytes()
326                .map_err(CoseError::SerializationError)?,
327            ciphertext,
328            tag,
329        )
330        .map_err(|e| CoseError::EncryptionError(Box::new(e)))?;
331
332        Ok((protected, &self.unprotected, payload))
333    }
334
335    /// Serializes the structure for transport / storage. If `tagged` is true, the optional #6.16
336    /// tag is added to the output.
337    pub fn as_bytes(&self, tagged: bool) -> Result<Vec<u8>, CoseError> {
338        let bytes = if tagged {
339            serde_cbor::to_vec(&serde_cbor::tags::Tagged::new(Some(16), &self))
340        } else {
341            serde_cbor::to_vec(&self)
342        };
343        bytes.map_err(CoseError::SerializationError)
344    }
345
346    /// This function deserializes the structure, but doesn't check the contents for correctness
347    /// at all. Accepts untagged structures or structures with tag 16.
348    pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoseError> {
349        let coseencrypt0: serde_cbor::tags::Tagged<Self> =
350            serde_cbor::from_slice(bytes).map_err(CoseError::SerializationError)?;
351
352        match coseencrypt0.tag {
353            None | Some(16) => (),
354            Some(tag) => return Err(CoseError::TagError(Some(tag))),
355        }
356        let protected = coseencrypt0.value.protected.as_slice();
357        let _: HeaderMap =
358            serde_cbor::from_slice(protected).map_err(CoseError::SerializationError)?;
359        Ok(coseencrypt0.value)
360    }
361}
362
363#[cfg(all(test, feature = "openssl"))]
364mod tests {
365    use super::*;
366    use crate::crypto::Openssl;
367
368    #[test]
369    fn test_encrypt_decrypt() {
370        let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
371        let plaintext = b"\x12\x34\x56\x78\x90\x12\x34\x56\x12\x34\x56\x78\x90\x12\x34\x56";
372        let cencrypt0 =
373            CoseEncrypt0::new::<Openssl>(plaintext, CipherConfiguration::Gcm, key).unwrap();
374        let (_, _, dec) = cencrypt0.decrypt::<Openssl>(key).unwrap();
375        assert_eq!(dec, plaintext);
376        assert_ne!(
377            plaintext.to_vec(),
378            serde_cbor::to_vec(&cencrypt0.ciphertext).unwrap()
379        );
380        let fromb = CoseEncrypt0::from_bytes(&cencrypt0.as_bytes(true).unwrap()[..]).unwrap();
381        let (_, _, dec) = fromb.decrypt::<Openssl>(key).unwrap();
382        assert_eq!(dec, plaintext);
383        assert_ne!(
384            plaintext.to_vec(),
385            serde_cbor::to_vec(&fromb.ciphertext).unwrap()
386        );
387    }
388
389    #[test]
390    fn test_encrypt_unsupported_alg() {
391        let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x56\x56";
392        let plaintext = b"\x12\x34\x56\x78\x90\x12\x34\x56\x12\x34\x56\x78\x90\x12\x34\x56";
393        let cencrypt0 = CoseEncrypt0::new::<Openssl>(plaintext, CipherConfiguration::Gcm, key);
394        match cencrypt0.unwrap_err() {
395            CoseError::UnsupportedError(_) => (),
396            _ => panic!(),
397        }
398    }
399
400    #[test]
401    fn test_decrypt_invalid_alg_spec() {
402        let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
403        let plaintext = b"\x12\x34\x56\x78\x90\x12\x34\x56\x12\x34\x56\x78\x90\x12\x34\x56";
404        let mut cencrypt0 =
405            CoseEncrypt0::new::<Openssl>(plaintext, CipherConfiguration::Gcm, key).unwrap();
406        let mut protected = HeaderMap::new();
407        protected.insert(KTY.into(), CborValue::Text("invalid".to_string()));
408        let protected_bytes = map_to_empty_or_serialized(&protected)
409            .map_err(CoseError::SerializationError)
410            .unwrap();
411        cencrypt0.protected = ByteBuf::from(protected_bytes);
412        match cencrypt0.decrypt::<Openssl>(key).unwrap_err() {
413            CoseError::SpecificationError(_) => (),
414            _ => panic!(),
415        }
416    }
417
418    #[test]
419    fn test_decrypt_unsupported_openssl_cipher() {
420        let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
421        let plaintext = b"\x12\x34\x56\x78\x90\x12\x34\x56\x12\x34\x56\x78\x90\x12\x34\x56";
422        let mut cencrypt0 =
423            CoseEncrypt0::new::<Openssl>(plaintext, CipherConfiguration::Gcm, key).unwrap();
424        let mut protected = HeaderMap::new();
425        protected.insert(KTY.into(), CborValue::Integer(42));
426        let protected_bytes = map_to_empty_or_serialized(&protected)
427            .map_err(CoseError::SerializationError)
428            .unwrap();
429        cencrypt0.protected = ByteBuf::from(protected_bytes);
430        match cencrypt0.decrypt::<Openssl>(key).unwrap_err() {
431            CoseError::UnsupportedError(_) => (),
432            _ => panic!(),
433        }
434    }
435
436    #[test]
437    fn test_decrypt_invalid_iv() {
438        let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
439        let plaintext = b"\x12\x34\x56\x78\x90\x12\x34\x56\x12\x34\x56\x78\x90\x12\x34\x56";
440        let mut cencrypt0 =
441            CoseEncrypt0::new::<Openssl>(plaintext, CipherConfiguration::Gcm, key).unwrap();
442        let mut unprotected = HeaderMap::new();
443        unprotected.insert(IV.into(), CborValue::Integer(42));
444        cencrypt0.unprotected = unprotected;
445        match cencrypt0.decrypt::<Openssl>(key).unwrap_err() {
446            CoseError::SpecificationError(_) => (),
447            _ => panic!(),
448        }
449    }
450}