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