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 use super::*;
18 use crate::{
19 cbor::value::Value, iana, util::expect_err, CborSerializable, ContentType, CoseKeyBuilder,
20 CoseRecipientBuilder, CoseSignatureBuilder, HeaderBuilder, TaggedCborSerializable,
21 };
22 use alloc::{
23 string::{String, ToString},
24 vec,
25 vec::Vec,
26 };
27
28 #[test]
test_cose_recipient_decode()29 fn test_cose_recipient_decode() {
30 let tests: Vec<(CoseRecipient, &'static str)> = vec![
31 (
32 CoseRecipientBuilder::new().build(),
33 concat!(
34 "83", // 3-tuple
35 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
36 "a0", // 0-map
37 "f6", // null
38 ),
39 ),
40 (
41 CoseRecipientBuilder::new().ciphertext(vec![]).build(),
42 concat!(
43 "83", // 3-tuple
44 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
45 "a0", // 0-map
46 "40", // 0-bstr
47 ),
48 ),
49 (
50 CoseRecipientBuilder::new()
51 .ciphertext(vec![])
52 .add_recipient(CoseRecipientBuilder::new().build())
53 .build(),
54 concat!(
55 "84", // 4-tuple
56 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
57 "a0", // 0-map
58 "40", // 0-bstr
59 "81", // 1-tuple
60 "83", // 3-tuple
61 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
62 "a0", // 0-map
63 "f6", // null
64 ),
65 ),
66 ];
67
68 for (i, (recipient, recipient_data)) in tests.iter().enumerate() {
69 let got = recipient.clone().to_vec().unwrap();
70 assert_eq!(*recipient_data, hex::encode(&got), "case {}", i);
71
72 let mut got = CoseRecipient::from_slice(&got).unwrap();
73 got.protected.original_data = None;
74 for recip in &mut got.recipients {
75 recip.protected.original_data = None;
76 }
77 assert_eq!(*recipient, got);
78 }
79 }
80
81 #[test]
test_cose_recipient_decode_fail()82 fn test_cose_recipient_decode_fail() {
83 let tests = [
84 (
85 concat!(
86 "a2", // 2-map (should be tuple)
87 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
88 "a0", // 0-map
89 "4161", // 1-bstr
90 "40", // 0-bstr
91 ),
92 "expected array",
93 ),
94 (
95 concat!(
96 "82", // 2-tuple (should be 4-tuple)
97 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
98 "a0", // 0-map
99 ),
100 "expected array with 3 or 4 items",
101 ),
102 (
103 concat!(
104 "84", // 4-tuple
105 "80", // 0-tuple (should be bstr)
106 "a0", // 0-map
107 "40", // 0-bstr
108 "80", // 0-arr
109 ),
110 "expected bstr",
111 ),
112 (
113 concat!(
114 "84", // 4-tuple
115 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
116 "40", // 0-bstr (should be map)
117 "40", // 0-bstr
118 "80", // 0-arr
119 ),
120 "expected map",
121 ),
122 (
123 concat!(
124 "84", // 4-tuple
125 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
126 "a0", // 0-map
127 "60", // 0-tstr (should be bstr)
128 "80", // 0-arr
129 ),
130 "expected bstr",
131 ),
132 (
133 concat!(
134 "84", // 4-tuple
135 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
136 "a0", // 0-map
137 "40", // 0-bstr
138 "40", // 0-bstr
139 ),
140 "expected array",
141 ),
142 ];
143 for (recipient_data, err_msg) in tests.iter() {
144 let data = hex::decode(recipient_data).unwrap();
145 let result = CoseRecipient::from_slice(&data);
146 expect_err(result, err_msg);
147 }
148 }
149
150 #[test]
test_cose_encrypt_decode()151 fn test_cose_encrypt_decode() {
152 let tests: Vec<(CoseEncrypt, &'static str)> = vec![
153 (
154 CoseEncryptBuilder::new().build(),
155 concat!(
156 "84", // 4-tuple
157 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
158 "a0", // 0-map
159 "f6", // null
160 "80", // 0-tuple
161 ),
162 ),
163 (
164 CoseEncryptBuilder::new().ciphertext(vec![]).build(),
165 concat!(
166 "84", // 4-tuple
167 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
168 "a0", // 0-map
169 "40", // 0-bstr
170 "80", // 0-tuple
171 ),
172 ),
173 ];
174
175 for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
176 let got = encrypt.clone().to_vec().unwrap();
177 assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
178
179 let mut got = CoseEncrypt::from_slice(&got).unwrap();
180 got.protected.original_data = None;
181 assert_eq!(*encrypt, got);
182 }
183 }
184
185 #[test]
test_cose_encrypt_decode_fail()186 fn test_cose_encrypt_decode_fail() {
187 let tests = [
188 (
189 concat!(
190 "a2", // 2-map (should be tuple)
191 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
192 "a0", // 0-map
193 "4161", // 1-bstr
194 "40", // 0-bstr
195 ),
196 "expected array",
197 ),
198 (
199 concat!(
200 "83", // 3-tuple (should be 4-tuple)
201 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
202 "a0", // 0-map
203 "40", // 0-bstr
204 ),
205 "expected array with 4 items",
206 ),
207 (
208 concat!(
209 "84", // 4-tuple
210 "80", // 0-tuple (should be bstr)
211 "a0", // 0-map
212 "40", // 0-bstr
213 "80", // 0-arr
214 ),
215 "expected bstr",
216 ),
217 (
218 concat!(
219 "84", // 4-tuple
220 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
221 "40", // 0-bstr (should be map)
222 "40", // 0-bstr
223 "80", // 0-arr
224 ),
225 "expected map",
226 ),
227 (
228 concat!(
229 "84", // 4-tuple
230 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
231 "a0", // 0-map
232 "60", // 0-tstr (should be bstr)
233 "80", // 0-arr
234 ),
235 "expected bstr",
236 ),
237 (
238 concat!(
239 "84", // 4-tuple
240 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
241 "a0", // 0-map
242 "40", // 0-bstr
243 "40", // 0-bstr
244 ),
245 "expected array",
246 ),
247 ];
248 for (encrypt_data, err_msg) in tests.iter() {
249 let data = hex::decode(encrypt_data).unwrap();
250 let result = CoseEncrypt::from_slice(&data);
251 expect_err(result, err_msg);
252 }
253 }
254 #[test]
test_rfc8152_cose_encrypt_decode()255 fn test_rfc8152_cose_encrypt_decode() {
256 // COSE_Encrypt structures from RFC 8152 section C.3.
257 let tests: Vec<(CoseEncrypt, &'static str)> = vec![
258 (
259 CoseEncryptBuilder::new()
260 .protected(
261 HeaderBuilder::new()
262 .algorithm(iana::Algorithm::A128GCM)
263 .build(),
264 )
265 .unprotected(
266 HeaderBuilder::new()
267 .iv(hex::decode("c9cf4df2fe6c632bf7886413").unwrap())
268 .build(),
269 )
270 .ciphertext(
271 hex::decode(
272 "7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0",
273 )
274 .unwrap(),
275 )
276 .add_recipient(
277 CoseRecipientBuilder::new()
278 .protected(
279 HeaderBuilder::new()
280 .algorithm(iana::Algorithm::ECDH_ES_HKDF_256)
281 .build(),
282 )
283 .unprotected(
284 HeaderBuilder::new()
285 .value(iana::HeaderAlgorithmParameter::EphemeralKey as i64,
286 CoseKeyBuilder::new_ec2_pub_key_y_sign(iana::EllipticCurve::P_256,
287 hex::decode("98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280").unwrap(),
288 true)
289 .build().to_cbor_value().unwrap())
290 .key_id(b"[email protected]".to_vec())
291 .build(),
292 )
293 .ciphertext(vec![])
294 .build(),
295 )
296 .build(),
297 // Note: contents of maps have been re-ordered from the RFC to canonical ordering.
298 concat!(
299 "d860",
300 "84", "43", "a10101",
301 "a1", "05", "4c", "c9cf4df2fe6c632bf7886413",
302 "5824", "7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0",
303 "81",
304 "83",
305 "44", "a1013818",
306 "a2",
307 "04",
308 "5824", "6d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65",
309 "20",
310 "a4",
311 "01", "02",
312 "20", "01",
313 "21", "5820", "98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280",
314 "22", "f5",
315 "40",
316 ),
317 ),
318 (
319 CoseEncryptBuilder::new()
320 .protected(HeaderBuilder::new().algorithm(iana::Algorithm::AES_CCM_16_64_128).build())
321 .unprotected(HeaderBuilder::new().iv(hex::decode("89f52f65a1c580933b5261a76c").unwrap()).build())
322 .ciphertext(hex::decode("753548a19b1307084ca7b2056924ed95f2e3b17006dfe931b687b847").unwrap())
323 .add_recipient(CoseRecipientBuilder::new()
324 .protected(HeaderBuilder::new().algorithm(iana::Algorithm::Direct_HKDF_SHA_256).build())
325 .unprotected(
326 HeaderBuilder::new()
327 .key_id(b"our-secret".to_vec())
328 .value(iana::HeaderAlgorithmParameter::Salt as i64,
329 Value::Bytes(b"aabbccddeeffgghh".to_vec()))
330 .build())
331 .ciphertext(vec![])
332 .build())
333 .build(),
334 // Note: contents of maps have been re-ordered from the RFC to canonical ordering.
335 concat!(
336 "d860",
337 "84",
338 "43",
339 "a1010a",
340 "a1",
341 "05",
342 "4d", "89f52f65a1c580933b5261a76c",
343 "581c", "753548a19b1307084ca7b2056924ed95f2e3b17006dfe931b687b847",
344 "81",
345 "83",
346 "43",
347 "a10129",
348 "a2",
349 "04", "4a", "6f75722d736563726574",
350 "33", "50", "61616262636364646565666667676868",
351 "40",
352 ),
353 ),
354
355 (
356 CoseEncryptBuilder::new()
357 .protected(HeaderBuilder::new().algorithm(iana::Algorithm::A128GCM).build())
358 .unprotected(HeaderBuilder::new()
359 .iv(hex::decode("c9cf4df2fe6c632bf7886413").unwrap())
360 .add_counter_signature(CoseSignatureBuilder::new()
361 .protected(HeaderBuilder::new().algorithm(iana::Algorithm::ES512).build())
362 .unprotected(HeaderBuilder::new().key_id(b"[email protected]".to_vec()).build())
363 .signature(hex::decode("00929663c8789bb28177ae28467e66377da12302d7f9594d2999afa5dfa531294f8896f2b6cdf1740014f4c7f1a358e3a6cf57f4ed6fb02fcf8f7aa989f5dfd07f0700a3a7d8f3c604ba70fa9411bd10c2591b483e1d2c31de003183e434d8fba18f17a4c7e3dfa003ac1cf3d30d44d2533c4989d3ac38c38b71481cc3430c9d65e7ddff").unwrap())
364 .build())
365 .build())
366 .ciphertext(hex::decode("7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0").unwrap())
367 .add_recipient(CoseRecipientBuilder::new()
368 .protected(
369 HeaderBuilder::new()
370 .algorithm(iana::Algorithm::ECDH_ES_HKDF_256)
371 .build())
372 .unprotected(
373 HeaderBuilder::new()
374 .value(iana::HeaderAlgorithmParameter::EphemeralKey as i64,
375 CoseKeyBuilder::new_ec2_pub_key_y_sign(iana::EllipticCurve::P_256,
376 hex::decode("98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280").unwrap(),
377 true)
378 .build().to_cbor_value().unwrap())
379 .key_id(b"[email protected]".to_vec())
380 .build())
381 .ciphertext(vec![])
382 .build())
383 .build(),
384 // Note: contents of maps have been re-ordered from the RFC to canonical ordering.
385 concat!(
386 "d860",
387 "84",
388 "43",
389 "a10101",
390 "a2",
391 "05",
392 "4c", "c9cf4df2fe6c632bf7886413",
393 "07",
394 "83",
395 "44",
396 "a1013823",
397 "a1",
398 "04",
399 "581e", "62696c626f2e62616767696e7340686f626269746f6e2e6578616d706c65",
400 "5884",
401 "00929663c8789bb28177ae28467e66377da12302d7f9594d2999afa5dfa531294f8896f2b6cdf1740014f4c7f1a358e3a6cf57f4ed6fb02fcf8f7aa989f5dfd07f0700a3a7d8f3c604ba70fa9411bd10c2591b483e1d2c31de003183e434d8fba18f17a4c7e3dfa003ac1cf3d30d44d2533c4989d3ac38c38b71481cc3430c9d65e7ddff",
402 "5824",
403 "7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0",
404 "81",
405 "83",
406 "44", "a1013818",
407 "a2",
408 "04",
409 "5824", "6d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65",
410 "20",
411 "a4",
412 "01",
413 "02",
414 "20",
415 "01",
416 "21",
417 "5820", "98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280",
418 "22",
419 "f5",
420 "40",
421 ),
422 ),
423 (
424 CoseEncryptBuilder::new()
425 .protected(HeaderBuilder::new().algorithm(iana::Algorithm::A128GCM).build())
426 .unprotected(HeaderBuilder::new().iv(hex::decode("02d1f7e6f26c43d4868d87ce").unwrap()).build())
427 .ciphertext(hex::decode("64f84d913ba60a76070a9a48f26e97e863e28529d8f5335e5f0165eee976b4a5f6c6f09d").unwrap())
428 .add_recipient(CoseRecipientBuilder::new()
429 .protected(HeaderBuilder::new().algorithm(iana::Algorithm::ECDH_SS_A128KW).build())
430 .unprotected(HeaderBuilder::new()
431 .key_id(b"[email protected]".to_vec())
432 .value(
433 iana::HeaderAlgorithmParameter::StaticKeyId as i64,
434 Value::Bytes(b"[email protected]".to_vec())
435 )
436 .value(
437 iana::HeaderAlgorithmParameter::PartyUNonce as i64,
438 Value::Bytes(hex::decode("0101").unwrap())
439 )
440 .build())
441 .ciphertext(hex::decode("41e0d76f579dbd0d936a662d54d8582037de2e366fde1c62").unwrap())
442 .build())
443 .build(),
444 // Note: contents of maps have been re-ordered from the RFC to canonical ordering.
445 concat!(
446 "d860",
447 "84",
448 "43",
449 "a10101",
450 "a1",
451 "05",
452 "4c", "02d1f7e6f26c43d4868d87ce",
453 "5824", "64f84d913ba60a76070a9a48f26e97e863e28529d8f5335e5f0165eee976b4a5f6c6f09d",
454 "81",
455 "83",
456 "44", "a101381f",
457 "a3",
458 "04",
459 "5824", "6d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65",
460 "22",
461 "5821", "706572656772696e2e746f6f6b407475636b626f726f7567682e6578616d706c65",
462 "35",
463 "42",
464 "0101",
465 "5818", "41e0d76f579dbd0d936a662d54d8582037de2e366fde1c62",
466 ),
467 ),
468 ];
469
470 for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
471 let got = encrypt.clone().to_tagged_vec().unwrap();
472 assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
473
474 let mut got = CoseEncrypt::from_tagged_slice(&got).unwrap();
475 got.protected.original_data = None;
476 for recip in &mut got.recipients {
477 recip.protected.original_data = None;
478 }
479 for sig in &mut got.unprotected.counter_signatures {
480 sig.protected.original_data = None;
481 }
482 assert_eq!(*encrypt, got);
483 }
484 }
485
486 #[test]
test_cose_encrypt0_decode()487 fn test_cose_encrypt0_decode() {
488 let tests: Vec<(CoseEncrypt0, &'static str)> = vec![
489 (
490 CoseEncrypt0Builder::new().build(),
491 concat!(
492 "83", // 3-tuple
493 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
494 "a0", // 0-map
495 "f6", // null
496 ),
497 ),
498 (
499 CoseEncrypt0Builder::new().ciphertext(vec![]).build(),
500 concat!(
501 "83", // 3-tuple
502 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
503 "a0", // 0-map
504 "40", // 0-bstr
505 ),
506 ),
507 ];
508
509 for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
510 let got = encrypt.clone().to_vec().unwrap();
511 assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
512
513 let mut got = CoseEncrypt0::from_slice(&got).unwrap();
514 got.protected.original_data = None;
515 assert_eq!(*encrypt, got);
516 }
517 }
518
519 #[test]
test_cose_encrypt0_decode_fail()520 fn test_cose_encrypt0_decode_fail() {
521 let tests = [
522 (
523 concat!(
524 "a2", // 2-map (should be tuple)
525 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
526 "a0", // 0-map
527 "4100", // 1-bstr
528 "40", // 0-bstr
529 ),
530 "expected array",
531 ),
532 (
533 concat!(
534 "82", // 2-tuple (should be 3-tuple)
535 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
536 "a0", // 0-map
537 ),
538 "expected array with 3 items",
539 ),
540 (
541 concat!(
542 "83", // 3-tuple
543 "80", // 0-tuple (should be bstr)
544 "a0", // 0-map
545 "40", // 0-bstr
546 ),
547 "expected bstr",
548 ),
549 (
550 concat!(
551 "83", // 3-tuple
552 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
553 "40", // 0-bstr (should be map)
554 "40", // 0-bstr
555 ),
556 "expected map",
557 ),
558 (
559 concat!(
560 "83", // 3-tuple
561 "40", // 0-bstr (special case for empty protected headers, rather than 41a0)
562 "a0", // 0-map
563 "60", // 0-tstr (should be bstr)
564 ),
565 "expected bstr",
566 ),
567 ];
568 for (encrypt_data, err_msg) in tests.iter() {
569 let data = hex::decode(encrypt_data).unwrap();
570 let result = CoseEncrypt0::from_slice(&data);
571 expect_err(result, err_msg);
572 }
573 }
574
575 #[test]
test_rfc8152_cose_encrypt0_decode()576 fn test_rfc8152_cose_encrypt0_decode() {
577 // COSE_Encrypt0 structures from RFC 8152 section C.4.
578 let tests: Vec<(CoseEncrypt0, &'static str)> = vec![
579 (
580 CoseEncrypt0Builder::new()
581 .protected(
582 HeaderBuilder::new()
583 .algorithm(iana::Algorithm::AES_CCM_16_64_128)
584 .build(),
585 )
586 .unprotected(
587 HeaderBuilder::new()
588 .iv(hex::decode("89f52f65a1c580933b5261a78c").unwrap())
589 .build(),
590 )
591 .ciphertext(
592 hex::decode("5974e1b99a3a4cc09a659aa2e9e7fff161d38ce71cb45ce460ffb569")
593 .unwrap(),
594 )
595 .build(),
596 concat!(
597 "d0",
598 "83",
599 "43",
600 "a1010a",
601 "a1",
602 "05",
603 "4d",
604 "89f52f65a1c580933b5261a78c",
605 "581c",
606 "5974e1b99a3a4cc09a659aa2e9e7fff161d38ce71cb45ce460ffb569",
607 ),
608 ),
609 (
610 CoseEncrypt0Builder::new()
611 .protected(
612 HeaderBuilder::new()
613 .algorithm(iana::Algorithm::AES_CCM_16_64_128)
614 .build(),
615 )
616 .unprotected(
617 HeaderBuilder::new()
618 .partial_iv(hex::decode("61a7").unwrap())
619 .build(),
620 )
621 .ciphertext(
622 hex::decode("252a8911d465c125b6764739700f0141ed09192de139e053bd09abca")
623 .unwrap(),
624 )
625 .build(),
626 concat!(
627 "d0",
628 "83",
629 "43",
630 "a1010a",
631 "a1",
632 "06",
633 "42",
634 "61a7",
635 "581c",
636 "252a8911d465c125b6764739700f0141ed09192de139e053bd09abca",
637 ),
638 ),
639 ];
640
641 for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
642 let got = encrypt.clone().to_tagged_vec().unwrap();
643 assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
644
645 let mut got = CoseEncrypt0::from_tagged_slice(&got).unwrap();
646 got.protected.original_data = None;
647 assert_eq!(*encrypt, got);
648 }
649 }
650
651 struct FakeCipher {}
652
653 impl FakeCipher {
encrypt(&self, plaintext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, String>654 fn encrypt(&self, plaintext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, String> {
655 let mut result = vec![];
656 result.extend_from_slice(&(plaintext.len() as u32).to_be_bytes());
657 result.extend_from_slice(plaintext);
658 result.extend_from_slice(additional_data);
659 Ok(result)
660 }
661
decrypt(&self, ciphertext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, String>662 fn decrypt(&self, ciphertext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, String> {
663 if ciphertext.len() < 4 {
664 return Err("not long enough".to_owned());
665 }
666 let pt_len =
667 u32::from_be_bytes([ciphertext[0], ciphertext[1], ciphertext[2], ciphertext[3]])
668 as usize;
669 let pt = &ciphertext[4..4 + pt_len];
670 let recovered_aad = &ciphertext[4 + pt_len..];
671 if recovered_aad != additional_data {
672 return Err("aad doesn't match".to_owned());
673 }
674 Ok(pt.to_vec())
675 }
fail_encrypt(&self, _plaintext: &[u8], _additional_data: &[u8]) -> Result<Vec<u8>, String>676 fn fail_encrypt(&self, _plaintext: &[u8], _additional_data: &[u8]) -> Result<Vec<u8>, String> {
677 Err("failed".to_string())
678 }
679 }
680
681 #[test]
test_cose_recipient_roundtrip()682 fn test_cose_recipient_roundtrip() {
683 let pt = b"This is the plaintext";
684 let external_aad = b"This is the external aad";
685 let cipher = FakeCipher {};
686
687 for context in &[
688 EncryptionContext::EncRecipient,
689 EncryptionContext::MacRecipient,
690 EncryptionContext::RecRecipient,
691 ] {
692 let protected = HeaderBuilder::new()
693 .algorithm(iana::Algorithm::ES256)
694 .key_id(b"11".to_vec())
695 .build();
696
697 let mut recipient = CoseRecipientBuilder::new()
698 .protected(protected)
699 .create_ciphertext(*context, pt, external_aad, |pt, aad| {
700 cipher.encrypt(pt, aad).unwrap()
701 })
702 .build();
703
704 let recovered_pt = recipient
705 .decrypt(*context, external_aad, |ct, aad| cipher.decrypt(ct, aad))
706 .unwrap();
707 assert_eq!(&pt[..], recovered_pt);
708
709 // Changing an unprotected header leaves the ciphertext decipherable.
710 recipient.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned()));
711 assert!(recipient
712 .decrypt(*context, external_aad, |ct, aad| {
713 cipher.decrypt(ct, aad)
714 })
715 .is_ok());
716
717 // Providing a different `aad` means the ciphertext won't validate.
718 assert!(recipient
719 .decrypt(*context, b"not aad", |ct, aad| { cipher.decrypt(ct, aad) })
720 .is_err());
721
722 // Changing a protected header invalidates the ciphertext.
723 recipient.protected = ProtectedHeader::default();
724 assert!(recipient
725 .decrypt(*context, external_aad, |ct, aad| {
726 cipher.decrypt(ct, aad)
727 })
728 .is_err());
729 }
730 }
731
732 #[test]
test_cose_recipient_noncanonical()733 fn test_cose_recipient_noncanonical() {
734 let pt = b"aa";
735 let aad = b"bb";
736 let cipher = FakeCipher {};
737 let context = EncryptionContext::EncRecipient;
738
739 // Build an empty protected header from a non-canonical input of 41a0 rather than 40.
740 let protected = ProtectedHeader::from_cbor_bstr(Value::Bytes(vec![0xa0])).unwrap();
741 assert_eq!(protected.header, Header::default());
742 assert_eq!(protected.original_data, Some(vec![0xa0]));
743
744 let mut recipient = CoseRecipient {
745 protected: protected.clone(),
746 ..Default::default()
747 };
748 let internal_aad = crate::encrypt::enc_structure_data(context, protected, aad);
749 recipient.ciphertext = Some(cipher.encrypt(pt, &internal_aad).unwrap());
750
751 // Deciphering the ciphertext should still succeed, because the `ProtectedHeader`
752 // includes the wire data and uses it for building the decryption input.
753 let recovered_pt = recipient
754 .decrypt(context, aad, |ct, aad| cipher.decrypt(ct, aad))
755 .unwrap();
756 assert_eq!(&pt[..], recovered_pt);
757
758 // However, if we attempt to build the same decryption inputs by hand (thus not including the
759 // non-canonical wire data)...
760 let recreated_recipient = CoseRecipientBuilder::new()
761 .ciphertext(recipient.ciphertext.unwrap())
762 .build();
763
764 // ...then the transplanted cipher text will not decipher, because the re-building of the
765 // inputs will use the canonical encoding of the protected header, which is not what was
766 // originally used for the input.
767 assert!(recreated_recipient
768 .decrypt(context, aad, |ct, aad| cipher.decrypt(ct, aad))
769 .is_err());
770 }
771
772 #[test]
test_cose_recipient_result()773 fn test_cose_recipient_result() {
774 let pt = b"This is the plaintext";
775 let external_aad = b"This is the external aad";
776 let cipher = FakeCipher {};
777
778 let protected = HeaderBuilder::new()
779 .algorithm(iana::Algorithm::ES256)
780 .key_id(b"11".to_vec())
781 .build();
782 let _recipient = CoseRecipientBuilder::new()
783 .protected(protected.clone())
784 .try_create_ciphertext(
785 EncryptionContext::EncRecipient,
786 pt,
787 external_aad,
788 |pt, aad| cipher.encrypt(pt, aad),
789 )
790 .unwrap()
791 .build();
792 let status = CoseRecipientBuilder::new()
793 .protected(protected)
794 .try_create_ciphertext(
795 EncryptionContext::EncRecipient,
796 pt,
797 external_aad,
798 |pt, aad| cipher.fail_encrypt(pt, aad),
799 );
800 expect_err(status, "failed");
801 }
802
803 #[test]
804 #[should_panic]
test_cose_recipient_missing_ciphertext()805 fn test_cose_recipient_missing_ciphertext() {
806 let external_aad = b"This is the external aad";
807 let cipher = FakeCipher {};
808
809 let recipient = CoseRecipient::default();
810
811 // No ciphertext has been set, do decryption will panic.
812 let _result = recipient.decrypt(EncryptionContext::EncRecipient, external_aad, |ct, aad| {
813 cipher.decrypt(ct, aad)
814 });
815 }
816
817 #[test]
818 #[should_panic]
test_cose_recipient_builder_invalid_context()819 fn test_cose_recipient_builder_invalid_context() {
820 let pt = b"This is the plaintext";
821 let external_aad = b"This is the external aad";
822 let cipher = FakeCipher {};
823
824 // Can't use a non-recipient context.
825 let _recipient = CoseRecipientBuilder::new()
826 .create_ciphertext(
827 EncryptionContext::CoseEncrypt,
828 pt,
829 external_aad,
830 |pt, aad| cipher.encrypt(pt, aad).unwrap(),
831 )
832 .build();
833 }
834
835 #[test]
836 #[should_panic]
test_cose_recipient_decrypt_invalid_context()837 fn test_cose_recipient_decrypt_invalid_context() {
838 let pt = b"This is the plaintext";
839 let external_aad = b"This is the external aad";
840 let cipher = FakeCipher {};
841
842 let recipient = CoseRecipientBuilder::new()
843 .create_ciphertext(
844 EncryptionContext::EncRecipient,
845 pt,
846 external_aad,
847 |pt, aad| cipher.encrypt(pt, aad).unwrap(),
848 )
849 .build();
850
851 // Can't use a non-recipient context.
852 let _result = recipient.decrypt(EncryptionContext::CoseEncrypt, external_aad, |ct, aad| {
853 cipher.decrypt(ct, aad)
854 });
855 }
856
857 #[test]
test_cose_encrypt_roundtrip()858 fn test_cose_encrypt_roundtrip() {
859 let pt = b"This is the plaintext";
860 let external_aad = b"This is the external aad";
861 let cipher = FakeCipher {};
862
863 let protected = HeaderBuilder::new()
864 .algorithm(iana::Algorithm::ES256)
865 .key_id(b"11".to_vec())
866 .build();
867 let mut encrypt = CoseEncryptBuilder::new()
868 .protected(protected)
869 .create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad).unwrap())
870 .build();
871
872 let recovered_pt = encrypt
873 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
874 .unwrap();
875 assert_eq!(&pt[..], recovered_pt);
876
877 // Changing an unprotected header leaves the ciphertext decipherable.
878 encrypt.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned()));
879 assert!(encrypt
880 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
881 .is_ok());
882
883 // Providing a different `aad` means the signature won't validate.
884 assert!(encrypt
885 .decrypt(b"not aad", |ct, aad| cipher.decrypt(ct, aad))
886 .is_err());
887
888 // Changing a protected header invalidates the ciphertext.
889 encrypt.protected = ProtectedHeader::default();
890 assert!(encrypt
891 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
892 .is_err());
893 }
894
895 #[test]
test_cose_encrypt_noncanonical()896 fn test_cose_encrypt_noncanonical() {
897 let pt = b"aa";
898 let external_aad = b"bb";
899 let cipher = FakeCipher {};
900
901 // Build an empty protected header from a non-canonical input of 41a0 rather than 40.
902 let protected = ProtectedHeader::from_cbor_bstr(Value::Bytes(vec![0xa0])).unwrap();
903 assert_eq!(protected.header, Header::default());
904 assert_eq!(protected.original_data, Some(vec![0xa0]));
905
906 let mut encrypt = CoseEncrypt {
907 protected: protected.clone(),
908 ..Default::default()
909 };
910 let aad = enc_structure_data(
911 EncryptionContext::CoseEncrypt,
912 protected.clone(),
913 external_aad,
914 );
915 encrypt.ciphertext = Some(cipher.encrypt(pt, &aad).unwrap());
916
917 // Deciphering the ciphertext should still succeed, because the `ProtectedHeader`
918 // includes the wire data and uses it for building the decryption input.
919 let recovered_pt = encrypt
920 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
921 .unwrap();
922 assert_eq!(&pt[..], recovered_pt);
923
924 // However, if we attempt to build the same decryption inputs by hand (thus not including the
925 // non-canonical wire data)...
926 let recreated_encrypt = CoseEncryptBuilder::new()
927 .protected(protected.header)
928 .ciphertext(encrypt.ciphertext.unwrap())
929 .build();
930
931 // ...then the transplanted cipher text will not decipher, because the re-building of the
932 // inputs will use the canonical encoding of the protected header, which is not what was
933 // originally used for the input.
934 assert!(recreated_encrypt
935 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
936 .is_err());
937 }
938
939 #[test]
test_cose_encrypt_status()940 fn test_cose_encrypt_status() {
941 let pt = b"This is the plaintext";
942 let external_aad = b"This is the external aad";
943 let cipher = FakeCipher {};
944
945 let protected = HeaderBuilder::new()
946 .algorithm(iana::Algorithm::ES256)
947 .key_id(b"11".to_vec())
948 .build();
949 let _encrypt = CoseEncryptBuilder::new()
950 .protected(protected.clone())
951 .try_create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad))
952 .unwrap()
953 .build();
954 let status = CoseEncryptBuilder::new()
955 .protected(protected)
956 .try_create_ciphertext(pt, external_aad, |pt, aad| cipher.fail_encrypt(pt, aad));
957 expect_err(status, "failed");
958 }
959
960 #[test]
961 #[should_panic]
test_cose_encrypt_missing_ciphertext()962 fn test_cose_encrypt_missing_ciphertext() {
963 let external_aad = b"This is the external aad";
964 let cipher = FakeCipher {};
965
966 let encrypt = CoseEncrypt::default();
967
968 // No ciphertext has been set, do decryption will panic.
969 let _result = encrypt.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad));
970 }
971
972 #[test]
test_cose_encrypt0_roundtrip()973 fn test_cose_encrypt0_roundtrip() {
974 let pt = b"This is the plaintext";
975 let external_aad = b"This is the external aad";
976 let cipher = FakeCipher {};
977
978 let protected = HeaderBuilder::new()
979 .algorithm(iana::Algorithm::ES256)
980 .key_id(b"11".to_vec())
981 .build();
982 let mut encrypt = CoseEncrypt0Builder::new()
983 .protected(protected)
984 .create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad).unwrap())
985 .build();
986
987 let recovered_pt = encrypt
988 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
989 .unwrap();
990 assert_eq!(&pt[..], recovered_pt);
991
992 // Changing an unprotected header leaves the ciphertext decipherable.
993 encrypt.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned()));
994 assert!(encrypt
995 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
996 .is_ok());
997
998 // Providing a different `aad` means the ciphertext won't decrypt.
999 assert!(encrypt
1000 .decrypt(b"not aad", |ct, aad| cipher.decrypt(ct, aad))
1001 .is_err());
1002
1003 // Changing a protected header invalidates the ciphertext.
1004 encrypt.protected = ProtectedHeader::default();
1005 assert!(encrypt
1006 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
1007 .is_err());
1008 }
1009
1010 #[test]
test_cose_encrypt0_noncanonical()1011 fn test_cose_encrypt0_noncanonical() {
1012 let pt = b"aa";
1013 let external_aad = b"bb";
1014 let cipher = FakeCipher {};
1015
1016 // Build an empty protected header from a non-canonical input of 41a0 rather than 40.
1017 let protected = ProtectedHeader::from_cbor_bstr(Value::Bytes(vec![0xa0])).unwrap();
1018 assert_eq!(protected.header, Header::default());
1019 assert_eq!(protected.original_data, Some(vec![0xa0]));
1020
1021 let mut encrypt = CoseEncrypt0 {
1022 protected: protected.clone(),
1023 ..Default::default()
1024 };
1025 let aad = enc_structure_data(
1026 EncryptionContext::CoseEncrypt0,
1027 protected.clone(),
1028 external_aad,
1029 );
1030 encrypt.ciphertext = Some(cipher.encrypt(pt, &aad).unwrap());
1031
1032 // Deciphering the ciphertext should still succeed, because the `ProtectedHeader`
1033 // includes the wire data and uses it for building the decryption input.
1034 let recovered_pt = encrypt
1035 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
1036 .unwrap();
1037 assert_eq!(&pt[..], recovered_pt);
1038
1039 // However, if we attempt to build the same decryption inputs by hand (thus not including the
1040 // non-canonical wire data)...
1041 let recreated_encrypt = CoseEncrypt0Builder::new()
1042 .protected(protected.header)
1043 .ciphertext(encrypt.ciphertext.unwrap())
1044 .build();
1045
1046 // ...then the transplanted cipher text will not decipher, because the re-building of the
1047 // inputs will use the canonical encoding of the protected header, which is not what was
1048 // originally used for the input.
1049 assert!(recreated_encrypt
1050 .decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
1051 .is_err());
1052 }
1053 #[test]
test_cose_encrypt0_status()1054 fn test_cose_encrypt0_status() {
1055 let pt = b"This is the plaintext";
1056 let external_aad = b"This is the external aad";
1057 let cipher = FakeCipher {};
1058
1059 let protected = HeaderBuilder::new()
1060 .algorithm(iana::Algorithm::ES256)
1061 .key_id(b"11".to_vec())
1062 .build();
1063 let _encrypt = CoseEncrypt0Builder::new()
1064 .protected(protected.clone())
1065 .try_create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad))
1066 .unwrap()
1067 .build();
1068 let status = CoseEncrypt0Builder::new()
1069 .protected(protected)
1070 .try_create_ciphertext(pt, external_aad, |pt, aad| cipher.fail_encrypt(pt, aad));
1071 expect_err(status, "failed");
1072 }
1073
1074 #[test]
1075 #[should_panic]
test_cose_encrypt0_missing_ciphertext()1076 fn test_cose_encrypt0_missing_ciphertext() {
1077 let external_aad = b"This is the external aad";
1078 let cipher = FakeCipher {};
1079
1080 let encrypt = CoseEncrypt0::default();
1081
1082 // No ciphertext has been set, do decryption will panic.
1083 let _result = encrypt.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad));
1084 }
1085