1 // Copyright 2021 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 //! COSE_Encrypt functionality.
18 
19 use crate::{
20     cbor,
21     cbor::value::Value,
22     common::AsCborValue,
23     iana,
24     util::{cbor_type_error, to_cbor_array, ValueTryAs},
25     CoseError, Header, ProtectedHeader, Result,
26 };
27 use alloc::{borrow::ToOwned, vec, vec::Vec};
28 
29 #[cfg(test)]
30 mod tests;
31 
32 /// Structure representing the recipient of encrypted data.
33 ///
34 /// ```cddl
35 ///  COSE_Recipient = [
36 ///      Headers,
37 ///      ciphertext : bstr / nil,
38 ///      ? recipients : [+COSE_recipient]
39 ///  ]
40 /// ```
41 #[derive(Clone, Debug, Default, PartialEq)]
42 pub struct CoseRecipient {
43     pub protected: ProtectedHeader,
44     pub unprotected: Header,
45     pub ciphertext: Option<Vec<u8>>,
46     pub recipients: Vec<CoseRecipient>,
47 }
48 
49 impl crate::CborSerializable for CoseRecipient {}
50 
51 impl AsCborValue for CoseRecipient {
from_cbor_value(value: Value) -> Result<Self>52     fn from_cbor_value(value: Value) -> Result<Self> {
53         let mut a = value.try_as_array()?;
54         if a.len() != 3 && a.len() != 4 {
55             return Err(CoseError::UnexpectedItem(
56                 "array",
57                 "array with 3 or 4 items",
58             ));
59         }
60 
61         // Remove array elements in reverse order to avoid shifts.
62         let recipients = if a.len() == 4 {
63             a.remove(3)
64                 .try_as_array_then_convert(CoseRecipient::from_cbor_value)?
65         } else {
66             Vec::new()
67         };
68 
69         Ok(Self {
70             recipients,
71             ciphertext: match a.remove(2) {
72                 Value::Bytes(b) => Some(b),
73                 Value::Null => None,
74                 v => return cbor_type_error(&v, "bstr / null"),
75             },
76             unprotected: Header::from_cbor_value(a.remove(1))?,
77             protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
78         })
79     }
80 
to_cbor_value(self) -> Result<Value>81     fn to_cbor_value(self) -> Result<Value> {
82         let mut v = vec![
83             self.protected.cbor_bstr()?,
84             self.unprotected.to_cbor_value()?,
85             match self.ciphertext {
86                 None => Value::Null,
87                 Some(b) => Value::Bytes(b),
88             },
89         ];
90         if !self.recipients.is_empty() {
91             v.push(to_cbor_array(self.recipients)?);
92         }
93         Ok(Value::Array(v))
94     }
95 }
96 
97 impl CoseRecipient {
98     /// Decrypt the `ciphertext` value with an AEAD, using `cipher` to decrypt the cipher text and
99     /// combined AAD as per RFC 8152 section 5.3.
100     ///
101     /// # Panics
102     ///
103     /// This function will panic if no `ciphertext` is available. It will also panic
104     /// if the `context` parameter does not refer to a recipient context.
decrypt<F, E>( &self, context: EncryptionContext, external_aad: &[u8], cipher: F, ) -> Result<Vec<u8>, E> where F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,105     pub fn decrypt<F, E>(
106         &self,
107         context: EncryptionContext,
108         external_aad: &[u8],
109         cipher: F,
110     ) -> Result<Vec<u8>, E>
111     where
112         F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,
113     {
114         let ct = self.ciphertext.as_ref().unwrap(/* safe: documented */);
115         match context {
116             EncryptionContext::EncRecipient
117             | EncryptionContext::MacRecipient
118             | EncryptionContext::RecRecipient => {}
119             _ => panic!("unsupported encryption context {:?}", context), // safe: documented
120         }
121         let aad = enc_structure_data(context, self.protected.clone(), external_aad);
122         cipher(ct, &aad)
123     }
124 }
125 
126 /// Builder for [`CoseRecipient`] objects.
127 #[derive(Debug, Default)]
128 pub struct CoseRecipientBuilder(CoseRecipient);
129 
130 impl CoseRecipientBuilder {
131     builder! {CoseRecipient}
132     builder_set_protected! {protected}
133     builder_set! {unprotected: Header}
134     builder_set_optional! {ciphertext: Vec<u8>}
135 
136     /// Add a [`CoseRecipient`].
137     #[must_use]
add_recipient(mut self, recipient: CoseRecipient) -> Self138     pub fn add_recipient(mut self, recipient: CoseRecipient) -> Self {
139         self.0.recipients.push(recipient);
140         self
141     }
142 
143     /// Calculate the ciphertext value with an AEAD, using `cipher` to generate the encrypted bytes
144     /// from the plaintext and combined AAD (in that order) as per RFC 8152 section 5.3.  Any
145     /// protected header values should be set before using this method.
146     ///
147     /// # Panics
148     ///
149     /// This function will panic if the `context` parameter does not refer to a recipient context.
150     #[must_use]
create_ciphertext<F>( self, context: EncryptionContext, plaintext: &[u8], external_aad: &[u8], cipher: F, ) -> Self where F: FnOnce(&[u8], &[u8]) -> Vec<u8>,151     pub fn create_ciphertext<F>(
152         self,
153         context: EncryptionContext,
154         plaintext: &[u8],
155         external_aad: &[u8],
156         cipher: F,
157     ) -> Self
158     where
159         F: FnOnce(&[u8], &[u8]) -> Vec<u8>,
160     {
161         let aad = self.aad(context, external_aad);
162         self.ciphertext(cipher(plaintext, &aad))
163     }
164 
165     /// Calculate the ciphertext value with an AEAD, using `cipher` to generate the encrypted bytes
166     /// from the plaintext and combined AAD (in that order) as per RFC 8152 section 5.3.  Any
167     /// protected header values should be set before using this method.
168     ///
169     /// # Panics
170     ///
171     /// This function will panic if the `context` parameter does not refer to a recipient context.
try_create_ciphertext<F, E>( self, context: EncryptionContext, plaintext: &[u8], external_aad: &[u8], cipher: F, ) -> Result<Self, E> where F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,172     pub fn try_create_ciphertext<F, E>(
173         self,
174         context: EncryptionContext,
175         plaintext: &[u8],
176         external_aad: &[u8],
177         cipher: F,
178     ) -> Result<Self, E>
179     where
180         F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,
181     {
182         let aad = self.aad(context, external_aad);
183         Ok(self.ciphertext(cipher(plaintext, &aad)?))
184     }
185 
186     /// Construct the combined AAD data needed for encryption with an AEAD. Any protected header
187     /// values should be set before using this method.
188     ///
189     /// # Panics
190     ///
191     /// This function will panic if the `context` parameter does not refer to a recipient context.
192     #[must_use]
aad(&self, context: EncryptionContext, external_aad: &[u8]) -> Vec<u8>193     fn aad(&self, context: EncryptionContext, external_aad: &[u8]) -> Vec<u8> {
194         match context {
195             EncryptionContext::EncRecipient
196             | EncryptionContext::MacRecipient
197             | EncryptionContext::RecRecipient => {}
198             _ => panic!("unsupported encryption context {:?}", context), // safe: documented
199         }
200         enc_structure_data(context, self.0.protected.clone(), external_aad)
201     }
202 }
203 
204 /// Structure representing an encrypted object.
205 ///
206 /// ```cddl
207 ///  COSE_Encrypt = [
208 ///      Headers,
209 ///      ciphertext : bstr / nil,
210 ///      recipients : [+COSE_recipient]
211 ///  ]
212 ///  ```
213 #[derive(Clone, Debug, Default, PartialEq)]
214 pub struct CoseEncrypt {
215     pub protected: ProtectedHeader,
216     pub unprotected: Header,
217     pub ciphertext: Option<Vec<u8>>,
218     pub recipients: Vec<CoseRecipient>,
219 }
220 
221 impl crate::CborSerializable for CoseEncrypt {}
222 
223 impl crate::TaggedCborSerializable for CoseEncrypt {
224     const TAG: u64 = iana::CborTag::CoseEncrypt as u64;
225 }
226 
227 impl AsCborValue for CoseEncrypt {
from_cbor_value(value: Value) -> Result<Self>228     fn from_cbor_value(value: Value) -> Result<Self> {
229         let mut a = value.try_as_array()?;
230         if a.len() != 4 {
231             return Err(CoseError::UnexpectedItem("array", "array with 4 items"));
232         }
233 
234         // Remove array elements in reverse order to avoid shifts.
235         let recipients = a
236             .remove(3)
237             .try_as_array_then_convert(CoseRecipient::from_cbor_value)?;
238         Ok(Self {
239             recipients,
240             ciphertext: match a.remove(2) {
241                 Value::Bytes(b) => Some(b),
242                 Value::Null => None,
243                 v => return cbor_type_error(&v, "bstr"),
244             },
245             unprotected: Header::from_cbor_value(a.remove(1))?,
246             protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
247         })
248     }
249 
to_cbor_value(self) -> Result<Value>250     fn to_cbor_value(self) -> Result<Value> {
251         Ok(Value::Array(vec![
252             self.protected.cbor_bstr()?,
253             self.unprotected.to_cbor_value()?,
254             match self.ciphertext {
255                 None => Value::Null,
256                 Some(b) => Value::Bytes(b),
257             },
258             to_cbor_array(self.recipients)?,
259         ]))
260     }
261 }
262 
263 impl CoseEncrypt {
264     /// Decrypt the `ciphertext` value with an AEAD, using `cipher` to decrypt the cipher text and
265     /// combined AAD.
266     ///
267     /// # Panics
268     ///
269     /// This function will panic if no `ciphertext` is available.
decrypt<F, E>(&self, external_aad: &[u8], cipher: F) -> Result<Vec<u8>, E> where F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,270     pub fn decrypt<F, E>(&self, external_aad: &[u8], cipher: F) -> Result<Vec<u8>, E>
271     where
272         F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,
273     {
274         let ct = self.ciphertext.as_ref().unwrap(/* safe: documented */);
275         let aad = enc_structure_data(
276             EncryptionContext::CoseEncrypt,
277             self.protected.clone(),
278             external_aad,
279         );
280         cipher(ct, &aad)
281     }
282 }
283 
284 /// Builder for [`CoseEncrypt`] objects.
285 #[derive(Debug, Default)]
286 pub struct CoseEncryptBuilder(CoseEncrypt);
287 
288 impl CoseEncryptBuilder {
289     builder! {CoseEncrypt}
290     builder_set_protected! {protected}
291     builder_set! {unprotected: Header}
292     builder_set_optional! {ciphertext: Vec<u8>}
293 
294     /// Calculate the ciphertext value with an AEAD, using `cipher` to generate the encrypted bytes
295     /// from the plaintext and combined AAD (in that order) as per RFC 8152 section 5.3.  Any
296     /// protected header values should be set before using this method.
297     #[must_use]
create_ciphertext<F>(self, plaintext: &[u8], external_aad: &[u8], cipher: F) -> Self where F: FnOnce(&[u8], &[u8]) -> Vec<u8>,298     pub fn create_ciphertext<F>(self, plaintext: &[u8], external_aad: &[u8], cipher: F) -> Self
299     where
300         F: FnOnce(&[u8], &[u8]) -> Vec<u8>,
301     {
302         let aad = enc_structure_data(
303             EncryptionContext::CoseEncrypt,
304             self.0.protected.clone(),
305             external_aad,
306         );
307         self.ciphertext(cipher(plaintext, &aad))
308     }
309 
310     /// Calculate the ciphertext value with an AEAD, using `cipher` to generate the encrypted bytes
311     /// from the plaintext and combined AAD (in that order) as per RFC 8152 section 5.3.  Any
312     /// protected header values should be set before using this method.
try_create_ciphertext<F, E>( self, plaintext: &[u8], external_aad: &[u8], cipher: F, ) -> Result<Self, E> where F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,313     pub fn try_create_ciphertext<F, E>(
314         self,
315         plaintext: &[u8],
316         external_aad: &[u8],
317         cipher: F,
318     ) -> Result<Self, E>
319     where
320         F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,
321     {
322         let aad = enc_structure_data(
323             EncryptionContext::CoseEncrypt,
324             self.0.protected.clone(),
325             external_aad,
326         );
327         Ok(self.ciphertext(cipher(plaintext, &aad)?))
328     }
329 
330     /// Add a [`CoseRecipient`].
331     #[must_use]
add_recipient(mut self, recipient: CoseRecipient) -> Self332     pub fn add_recipient(mut self, recipient: CoseRecipient) -> Self {
333         self.0.recipients.push(recipient);
334         self
335     }
336 }
337 
338 /// Structure representing an encrypted object.
339 ///
340 /// ```cddl
341 ///  COSE_Encrypt0 = [
342 ///      Headers,
343 ///      ciphertext : bstr / nil,
344 ///  ]
345 ///  ```
346 #[derive(Clone, Debug, Default, PartialEq)]
347 pub struct CoseEncrypt0 {
348     pub protected: ProtectedHeader,
349     pub unprotected: Header,
350     pub ciphertext: Option<Vec<u8>>,
351 }
352 
353 impl crate::CborSerializable for CoseEncrypt0 {}
354 
355 impl crate::TaggedCborSerializable for CoseEncrypt0 {
356     const TAG: u64 = iana::CborTag::CoseEncrypt0 as u64;
357 }
358 
359 impl AsCborValue for CoseEncrypt0 {
from_cbor_value(value: Value) -> Result<Self>360     fn from_cbor_value(value: Value) -> Result<Self> {
361         let mut a = value.try_as_array()?;
362         if a.len() != 3 {
363             return Err(CoseError::UnexpectedItem("array", "array with 3 items"));
364         }
365 
366         // Remove array elements in reverse order to avoid shifts.
367         Ok(Self {
368             ciphertext: match a.remove(2) {
369                 Value::Bytes(b) => Some(b),
370                 Value::Null => None,
371                 v => return cbor_type_error(&v, "bstr"),
372             },
373 
374             unprotected: Header::from_cbor_value(a.remove(1))?,
375             protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
376         })
377     }
378 
to_cbor_value(self) -> Result<Value>379     fn to_cbor_value(self) -> Result<Value> {
380         Ok(Value::Array(vec![
381             self.protected.cbor_bstr()?,
382             self.unprotected.to_cbor_value()?,
383             match self.ciphertext {
384                 None => Value::Null,
385                 Some(b) => Value::Bytes(b),
386             },
387         ]))
388     }
389 }
390 
391 impl CoseEncrypt0 {
392     /// Decrypt the `ciphertext` value with an AEAD, using `cipher` to decrypt the cipher text and
393     /// combined AAD.
394     ///
395     /// # Panics
396     ///
397     /// This function will panic if no `ciphertext` is available.
decrypt<F, E>(&self, external_aad: &[u8], cipher: F) -> Result<Vec<u8>, E> where F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,398     pub fn decrypt<F, E>(&self, external_aad: &[u8], cipher: F) -> Result<Vec<u8>, E>
399     where
400         F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,
401     {
402         let ct = self.ciphertext.as_ref().unwrap(/* safe: documented */);
403         let aad = enc_structure_data(
404             EncryptionContext::CoseEncrypt0,
405             self.protected.clone(),
406             external_aad,
407         );
408         cipher(ct, &aad)
409     }
410 }
411 
412 /// Builder for [`CoseEncrypt0`] objects.
413 #[derive(Debug, Default)]
414 pub struct CoseEncrypt0Builder(CoseEncrypt0);
415 
416 impl CoseEncrypt0Builder {
417     builder! {CoseEncrypt0}
418     builder_set_protected! {protected}
419     builder_set! {unprotected: Header}
420     builder_set_optional! {ciphertext: Vec<u8>}
421 
422     /// Calculate the ciphertext value with an AEAD, using `cipher` to generate the encrypted bytes
423     /// from the plaintext and combined AAD (in that order) as per RFC 8152 section 5.3.  Any
424     /// protected header values should be set before using this method.
425     #[must_use]
create_ciphertext<F>(self, plaintext: &[u8], external_aad: &[u8], cipher: F) -> Self where F: FnOnce(&[u8], &[u8]) -> Vec<u8>,426     pub fn create_ciphertext<F>(self, plaintext: &[u8], external_aad: &[u8], cipher: F) -> Self
427     where
428         F: FnOnce(&[u8], &[u8]) -> Vec<u8>,
429     {
430         let aad = enc_structure_data(
431             EncryptionContext::CoseEncrypt0,
432             self.0.protected.clone(),
433             external_aad,
434         );
435         self.ciphertext(cipher(plaintext, &aad))
436     }
437 
438     /// Calculate the ciphertext value with an AEAD, using `cipher` to generate the encrypted bytes
439     /// from the plaintext and combined AAD (in that order) as per RFC 8152 section 5.3.  Any
440     /// protected header values should be set before using this method.
try_create_ciphertext<F, E>( self, plaintext: &[u8], external_aad: &[u8], cipher: F, ) -> Result<Self, E> where F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,441     pub fn try_create_ciphertext<F, E>(
442         self,
443         plaintext: &[u8],
444         external_aad: &[u8],
445         cipher: F,
446     ) -> Result<Self, E>
447     where
448         F: FnOnce(&[u8], &[u8]) -> Result<Vec<u8>, E>,
449     {
450         let aad = enc_structure_data(
451             EncryptionContext::CoseEncrypt0,
452             self.0.protected.clone(),
453             external_aad,
454         );
455         Ok(self.ciphertext(cipher(plaintext, &aad)?))
456     }
457 }
458 
459 /// Possible encryption contexts.
460 #[derive(Clone, Copy, Debug)]
461 pub enum EncryptionContext {
462     CoseEncrypt,
463     CoseEncrypt0,
464     EncRecipient,
465     MacRecipient,
466     RecRecipient,
467 }
468 
469 impl EncryptionContext {
470     /// Return the context string as per RFC 8152 section 5.3.
text(&self) -> &'static str471     fn text(&self) -> &'static str {
472         match self {
473             EncryptionContext::CoseEncrypt => "Encrypt",
474             EncryptionContext::CoseEncrypt0 => "Encrypt0",
475             EncryptionContext::EncRecipient => "Enc_Recipient",
476             EncryptionContext::MacRecipient => "Mac_Recipient",
477             EncryptionContext::RecRecipient => "Rec_Recipient",
478         }
479     }
480 }
481 
482 /// Create a binary blob that will be signed.
483 //
484 /// ```cddl
485 ///  Enc_structure = [
486 ///      context : "Encrypt" / "Encrypt0" / "Enc_Recipient" /
487 ///          "Mac_Recipient" / "Rec_Recipient",
488 ///      protected : empty_or_serialized_map,
489 ///      external_aad : bstr
490 ///  ]
491 /// ```
enc_structure_data( context: EncryptionContext, protected: ProtectedHeader, external_aad: &[u8], ) -> Vec<u8>492 pub fn enc_structure_data(
493     context: EncryptionContext,
494     protected: ProtectedHeader,
495     external_aad: &[u8],
496 ) -> Vec<u8> {
497     let arr = vec![
498         Value::Text(context.text().to_owned()),
499         protected.cbor_bstr().expect("failed to serialize header"), // safe: always serializable
500         Value::Bytes(external_aad.to_vec()),
501     ];
502 
503     let mut data = Vec::new();
504     cbor::ser::into_writer(&Value::Array(arr), &mut data).unwrap(); // safe: always serializable
505     data
506 }
507