| // Copyright 2021 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| use super::*; |
| use crate::{cbor::value::Value, iana, util::expect_err, CborSerializable, Label}; |
| use alloc::{borrow::ToOwned, vec}; |
| |
| #[test] |
| fn test_header_encode() { |
| let tests = vec![ |
| ( |
| Header { |
| alg: Some(Algorithm::Assigned(iana::Algorithm::A128GCM)), |
| key_id: vec![1, 2, 3], |
| partial_iv: vec![1, 2, 3], |
| ..Default::default() |
| }, |
| concat!( |
| "a3", // 3-map |
| "01", "01", // 1 (alg) => A128GCM |
| "04", "43", "010203", // 4 (kid) => 3-bstr |
| "06", "43", "010203", // 6 (partial-iv) => 3-bstr |
| ), |
| ), |
| ( |
| Header { |
| alg: Some(Algorithm::PrivateUse(i64::MIN)), |
| ..Default::default() |
| }, |
| concat!( |
| "a1", // 1-map |
| "01", |
| "3b7fffffffffffffff", // 1 (alg) => -lots |
| ), |
| ), |
| ( |
| Header { |
| alg: Some(Algorithm::Assigned(iana::Algorithm::A128GCM)), |
| crit: vec![RegisteredLabel::Assigned(iana::HeaderParameter::Alg)], |
| content_type: Some(ContentType::Assigned(iana::CoapContentFormat::CoseEncrypt0)), |
| key_id: vec![1, 2, 3], |
| iv: vec![1, 2, 3], |
| rest: vec![ |
| (Label::Int(0x46), Value::from(0x47)), |
| (Label::Int(0x66), Value::from(0x67)), |
| ], |
| ..Default::default() |
| }, |
| concat!( |
| "a7", // 7-map |
| "01", "01", // 1 (alg) => A128GCM |
| "02", "81", "01", // 2 (crit) => 1-arr [x01] |
| "03", "10", // 3 (content-type) => 16 |
| "04", "43", "010203", // 4 (kid) => 3-bstr |
| "05", "43", "010203", // 5 (iv) => 3-bstr |
| "1846", "1847", // 46 => 47 (note canonical ordering) |
| "1866", "1867", // 66 => 67 |
| ), |
| ), |
| ( |
| Header { |
| alg: Some(Algorithm::Text("abc".to_owned())), |
| crit: vec![RegisteredLabel::Text("d".to_owned())], |
| content_type: Some(ContentType::Text("a/b".to_owned())), |
| key_id: vec![1, 2, 3], |
| iv: vec![1, 2, 3], |
| rest: vec![ |
| (Label::Int(0x46), Value::from(0x47)), |
| (Label::Text("a".to_owned()), Value::from(0x47)), |
| ], |
| counter_signatures: vec![CoseSignature { |
| signature: vec![1, 2, 3], |
| ..Default::default() |
| }], |
| ..Default::default() |
| }, |
| concat!( |
| "a8", // 8-map |
| "01", "63616263", // 1 (alg) => "abc" |
| "02", "81", "6164", // 2 (crit) => 1-arr ["d"] |
| "03", "63612f62", // 3 (content-type) => "a/b" |
| "04", "43", "010203", // 4 (kid) => 3-bstr |
| "05", "43", "010203", // 5 (iv) => 3-bstr |
| "07", "83", // 7 (sig) => [3-arr for COSE_Signature |
| "40", "a0", "43010203", // ] |
| "1846", "1847", // 46 => 47 (note canonical ordering) |
| "6161", "1847", // "a" => 47 |
| ), |
| ), |
| ( |
| Header { |
| alg: Some(Algorithm::Text("abc".to_owned())), |
| crit: vec![RegisteredLabel::Text("d".to_owned())], |
| content_type: Some(ContentType::Text("a/b".to_owned())), |
| key_id: vec![1, 2, 3], |
| iv: vec![1, 2, 3], |
| rest: vec![ |
| (Label::Int(0x46), Value::from(0x47)), |
| (Label::Text("a".to_owned()), Value::from(0x47)), |
| ], |
| counter_signatures: vec![ |
| CoseSignature { |
| signature: vec![1, 2, 3], |
| ..Default::default() |
| }, |
| CoseSignature { |
| signature: vec![3, 4, 5], |
| ..Default::default() |
| }, |
| ], |
| ..Default::default() |
| }, |
| concat!( |
| "a8", // 8-map |
| "01", "63616263", // 1 (alg) => "abc" |
| "02", "81", "6164", // 2 (crit) => 1-arr ["d"] |
| "03", "63612f62", // 3 (content-type) => "a/b" |
| "04", "43", "010203", // 4 (kid) => 3-bstr |
| "05", "43", "010203", // 5 (iv) => 3-bstr |
| "07", "82", // 7 (sig) => 2-array |
| "83", "40", "a0", "43010203", // [3-arr for COSE_Signature] |
| "83", "40", "a0", "43030405", // [3-arr for COSE_Signature] |
| "1846", "1847", // 46 => 47 (note canonical ordering) |
| "6161", "1847", // "a" => 47 |
| ), |
| ), |
| ( |
| HeaderBuilder::new() |
| .add_critical(iana::HeaderParameter::Alg) |
| .add_critical(iana::HeaderParameter::Alg) |
| .build(), |
| concat!( |
| "a1", // 1-map |
| "02", "820101", // crit => 2-arr [1, 1] |
| ), |
| ), |
| ]; |
| for (i, (header, header_data)) in tests.iter().enumerate() { |
| let got = header.clone().to_vec().unwrap(); |
| assert_eq!(*header_data, hex::encode(&got), "case {}", i); |
| |
| let mut got = Header::from_slice(&got).unwrap(); |
| for sig in &mut got.counter_signatures { |
| sig.protected.original_data = None; |
| } |
| assert_eq!(*header, got); |
| assert!(!got.is_empty()); |
| |
| // The same data also parses as a `ProtectedHeader` |
| let protected = ProtectedHeader { |
| original_data: None, |
| header: header.clone(), |
| }; |
| let protected_data = protected.clone().to_vec().unwrap(); |
| assert_eq!(*header_data, hex::encode(&protected_data), "case {}", i); |
| |
| let mut got = ProtectedHeader::from_slice(&protected_data).unwrap(); |
| for sig in &mut got.header.counter_signatures { |
| sig.protected.original_data = None; |
| } |
| assert!(!got.is_empty()); |
| assert_eq!(*header, got.header); |
| |
| // Also try parsing as a protected header inside a `bstr` |
| let prot_bstr_val = protected.cbor_bstr().unwrap(); |
| let mut got = ProtectedHeader::from_cbor_bstr(prot_bstr_val).unwrap(); |
| for sig in &mut got.header.counter_signatures { |
| sig.protected.original_data = None; |
| } |
| assert!(!got.is_empty()); |
| assert_eq!(*header, got.header); |
| assert_eq!( |
| *header_data, |
| hex::encode(&got.original_data.expect("missing original data")) |
| ); |
| } |
| } |
| |
| #[test] |
| fn test_header_decode_fail() { |
| let tests = vec![ |
| ( |
| concat!( |
| "a1", // 1-map |
| "01", "01", // 1 (alg) => 01 |
| "01", // extraneous data |
| ), |
| "extraneous data in CBOR input", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "01", "08", // 1 (alg) => invalid value |
| ), |
| "expected value in IANA or private use range", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "01", "4101", // 1 (alg) => bstr (invalid value type) |
| ), |
| "expected int/tstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "02", "4101", // 2 (crit) => bstr (invalid value type) |
| ), |
| "expected array", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "02", "81", "4101", // 2 (crit) => [bstr] (invalid value type) |
| ), |
| "expected int/tstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "02", "80", // 2 (crit) => [] |
| ), |
| "expected non-empty array", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "03", "81", "4101", // 3 (content-type) => [bstr] (invalid value type) |
| ), |
| "expected int/tstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "03", "19", "0606", // 3 (content-type) => invalid value 1542 |
| ), |
| "expected recognized IANA value", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "03", "64", "20612f62" // 3 (content-type) => invalid value " a/b" |
| ), |
| "expected no leading/trailing whitespace", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "03", "64", "612f6220" // 3 (content-type) => invalid value "a/b " |
| ), |
| "expected no leading/trailing whitespace", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "03", "62", "6162" // 3 (content-type) => invalid value "ab" |
| ), |
| "expected text of form type/subtype", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "03", "60", // 3 (content-type) => invalid value "" |
| ), |
| "expected non-empty tstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "04", "40", // 4 (key-id) => 0-bstr |
| ), |
| "expected non-empty bstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "04", "01", // 4 (key-id) => invalid value type |
| ), |
| "expected bstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "05", "40", // 5 (iv) => 0-bstr |
| ), |
| "expected non-empty bstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "05", "01", // 5 (iv) => invalid value type |
| ), |
| "expected bstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "06", "40", // 6 (partial-iv) => 0-bstr |
| ), |
| "expected non-empty bstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "06", "01", // 6 (partial-iv) => invalid value type |
| ), |
| "expected bstr", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "07", "01", // 7 (counter-sig) => invalid value type |
| ), |
| "expected array", |
| ), |
| ( |
| concat!( |
| "a1", // 1-map |
| "07", "80", // 7 (counter-sig) => 0-arr |
| ), |
| "expected non-empty sig array", |
| ), |
| ( |
| concat!( |
| "a2", // 1-map |
| "05", "4101", // 5 (iv) => 1-bstr |
| "06", "4101", // 6 (partial-iv) => 1-bstr |
| ), |
| "expected only one of IV and partial IV", |
| ), |
| ( |
| concat!( |
| "a2", // 2-map |
| "01", "63616263", // 1 (alg) => "abc" |
| "07", "82", // 7 (sig) => 2-array |
| "63616263", // tstr (invalid) |
| "83", "40", "a0", "43010203", // [3-arr for COSE_Signature] |
| ), |
| "array or bstr value", |
| ), |
| ]; |
| for (header_data, err_msg) in tests.iter() { |
| let data = hex::decode(header_data).unwrap(); |
| let result = Header::from_slice(&data); |
| expect_err(result, err_msg); |
| } |
| } |
| |
| #[test] |
| fn test_header_decode_dup_fail() { |
| let tests = [ |
| ( |
| concat!( |
| "a3", // 3-map |
| "01", "01", // 1 (alg) => A128GCM |
| "1866", "1867", // 66 => 67 |
| "1866", "1847", // 66 => 47 |
| ), |
| "duplicate map key", |
| ), |
| ( |
| concat!( |
| "a3", // 3-map |
| "01", "01", // 1 (alg) => A128GCM |
| "1866", "1867", // 66 => 67 |
| "01", "01", // 1 (alg) => A128GCM (duplicate label) |
| ), |
| "duplicate map key", |
| ), |
| ]; |
| for (header_data, err_msg) in tests.iter() { |
| let data = hex::decode(header_data).unwrap(); |
| let result = Header::from_slice(&data); |
| expect_err(result, err_msg); |
| } |
| } |
| |
| #[test] |
| fn test_header_encode_dup_fail() { |
| let tests = vec![ |
| Header { |
| alg: Some(Algorithm::Assigned(iana::Algorithm::A128GCM)), |
| crit: vec![RegisteredLabel::Assigned(iana::HeaderParameter::Alg)], |
| content_type: Some(ContentType::Assigned(iana::CoapContentFormat::CoseEncrypt0)), |
| key_id: vec![1, 2, 3], |
| iv: vec![1, 2, 3], |
| rest: vec![ |
| (Label::Int(0x46), Value::from(0x47)), |
| (Label::Int(0x46), Value::from(0x67)), |
| ], |
| ..Default::default() |
| }, |
| HeaderBuilder::new() |
| .text_value("doop".to_owned(), Value::from(1)) |
| .text_value("doop".to_owned(), Value::from(2)) |
| .build(), |
| ]; |
| for header in tests { |
| let result = header.clone().to_vec(); |
| expect_err(result, "duplicate map key"); |
| } |
| } |
| |
| #[test] |
| fn test_header_builder() { |
| let tests = vec![ |
| ( |
| HeaderBuilder::new().build(), |
| Header { |
| ..Default::default() |
| }, |
| ), |
| ( |
| HeaderBuilder::new() |
| .algorithm(iana::Algorithm::A128GCM) |
| .add_critical(iana::HeaderParameter::Alg) |
| .add_critical_label(RegisteredLabel::Text("abc".to_owned())) |
| .content_format(iana::CoapContentFormat::CoseEncrypt0) |
| .key_id(vec![1, 2, 3]) |
| .partial_iv(vec![4, 5, 6]) // removed by .iv() call |
| .iv(vec![1, 2, 3]) |
| .value(0x46, Value::from(0x47)) |
| .value(0x66, Value::from(0x67)) |
| .build(), |
| Header { |
| alg: Some(Algorithm::Assigned(iana::Algorithm::A128GCM)), |
| crit: vec![ |
| RegisteredLabel::Assigned(iana::HeaderParameter::Alg), |
| RegisteredLabel::Text("abc".to_owned()), |
| ], |
| content_type: Some(ContentType::Assigned(iana::CoapContentFormat::CoseEncrypt0)), |
| key_id: vec![1, 2, 3], |
| iv: vec![1, 2, 3], |
| rest: vec![ |
| (Label::Int(0x46), Value::from(0x47)), |
| (Label::Int(0x66), Value::from(0x67)), |
| ], |
| ..Default::default() |
| }, |
| ), |
| ( |
| HeaderBuilder::new() |
| .algorithm(iana::Algorithm::A128GCM) |
| .add_critical(iana::HeaderParameter::Alg) |
| .add_critical_label(RegisteredLabel::Text("abc".to_owned())) |
| .content_type("type/subtype".to_owned()) |
| .key_id(vec![1, 2, 3]) |
| .iv(vec![1, 2, 3]) // removed by .partial_iv() call |
| .partial_iv(vec![4, 5, 6]) |
| .build(), |
| Header { |
| alg: Some(Algorithm::Assigned(iana::Algorithm::A128GCM)), |
| crit: vec![ |
| RegisteredLabel::Assigned(iana::HeaderParameter::Alg), |
| RegisteredLabel::Text("abc".to_owned()), |
| ], |
| content_type: Some(ContentType::Text("type/subtype".to_owned())), |
| key_id: vec![1, 2, 3], |
| partial_iv: vec![4, 5, 6], |
| ..Default::default() |
| }, |
| ), |
| ]; |
| for (got, want) in tests { |
| assert_eq!(got, want); |
| } |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_header_builder_core_param_panic() { |
| // Attempting to set a core header parameter (in range [1,7]) via `.param()` panics. |
| let _hdr = HeaderBuilder::new().value(1, Value::Null).build(); |
| } |