From 52b9b12bd685528d5d420f6fd7e66c9bb19c186c Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Tue, 25 May 2021 10:49:42 +0100 Subject: [PATCH] Use sk-cbor library instead of serde_cbor - Bump crate version to 0.2.0 - Move to be no_std; drop use of maplit crate in tests. - Change encoding methods to consume `self`. - Change encoding methods to be fallible. --- Cargo.lock | 83 +--------- Cargo.toml | 6 +- README.md | 6 +- examples/signature.rs | 3 +- fuzz/Cargo.lock | 85 +---------- src/common/mod.rs | 343 +++++++++++++++++++++++++----------------- src/common/tests.rs | 157 +++++++++++-------- src/context/mod.rs | 142 ++++++++--------- src/context/tests.rs | 6 +- src/encrypt/mod.rs | 166 ++++++++++---------- src/encrypt/tests.rs | 57 +++---- src/header/mod.rs | 217 ++++++++++++-------------- src/header/tests.rs | 128 +++++++++++----- src/iana/mod.rs | 6 +- src/key/mod.rs | 207 +++++++++++++------------ src/key/tests.rs | 180 ++++++++++------------ src/lib.rs | 9 +- src/mac/mod.rs | 121 ++++++++------- src/mac/tests.rs | 44 +++--- src/sign/mod.rs | 165 ++++++++++---------- src/sign/tests.rs | 61 ++++---- src/util/mod.rs | 70 ++++----- src/util/tests.rs | 52 +++---- 23 files changed, 1130 insertions(+), 1184 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b30c007..40020d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,20 +4,12 @@ version = 3 [[package]] name = "coset" -version = "0.1.2" +version = "0.2.0" dependencies = [ "hex", - "maplit", - "serde", - "serde_cbor", + "sk-cbor", ] -[[package]] -name = "half" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" - [[package]] name = "hex" version = "0.4.3" @@ -25,72 +17,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "proc-macro2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_cbor" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" -dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "unicode-xid" -version = "0.2.1" +name = "sk-cbor" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "9981cc3d57ed04d0ea5779729fcd49f36322207d515f88010d7823944bf85974" diff --git a/Cargo.toml b/Cargo.toml index ed17e9e..dfb5364 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coset" -version = "0.1.2" +version = "0.2.0" authors = ["David Drysdale "] edition = "2018" license = "Apache-2.0" @@ -10,9 +10,7 @@ keywords = ["cryptography", "cose"] categories = ["cryptography"] [dependencies] -maplit = "^1.0" -serde_cbor = { version = "^0.11.1", features = ["std", "tags"] } -serde = { version = "^1.0.127", features = ["derive"] } +sk-cbor = "^0.1.0" [dev-dependencies] hex = "^0.4.2" diff --git a/README.md b/README.md index dd661f2..c345757 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,17 @@ This crate holds a set of Rust types for working with CBOR Object Signing and Encryption (COSE) objects, as defined in [RFC 8152](https://tools.ietf.org/html/rfc8152). It builds on the core [CBOR](https://tools.ietf.org/html/rfc7049) -parsing functionality from the [`serde_cbor` crate](https://docs.rs/serde_cbor). +parsing functionality from the [`sk-cbor` crate](https://docs.rs/sk-cbor). See [crate docs](https://google.github.io/coset/rust/coset/index.html), or the [signature example](examples/signature.rs) for documentation on how to use the code. **This repo is under construction** and so details of the API and the code may change without warning. +## `no_std` Support + +This crate supports `no_std`, but uses the `alloc` crate. + ## Working on the Code Local coding conventions are enforced by the [continuous integration jobs](.github/workflows) and include: diff --git a/examples/signature.rs b/examples/signature.rs index c732b9f..5061f84 100644 --- a/examples/signature.rs +++ b/examples/signature.rs @@ -15,7 +15,6 @@ //////////////////////////////////////////////////////////////////////////////// //! Example program demonstrating signature creation. - use coset::{iana, CborSerializable}; #[derive(Copy, Clone)] @@ -62,7 +61,7 @@ fn main() { "'{}' + '{}' => {}", String::from_utf8_lossy(pt), String::from_utf8_lossy(aad), - hex::encode(sign1.to_vec().unwrap()) + hex::encode(&sign1_data) ); // At the receiving end, deserialize the bytes back to a `CoseSign1` object. diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 34b6ea9..443dab6 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "arbitrary" version = "1.0.0" @@ -14,11 +16,9 @@ checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" [[package]] name = "coset" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "maplit", - "serde", - "serde_cbor", + "sk-cbor", ] [[package]] @@ -29,12 +29,6 @@ dependencies = [ "libfuzzer-sys", ] -[[package]] -name = "half" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" - [[package]] name = "libfuzzer-sys" version = "0.4.0" @@ -46,72 +40,7 @@ dependencies = [ ] [[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "proc-macro2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_cbor" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" -dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "1.0.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "unicode-xid" -version = "0.2.1" +name = "sk-cbor" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "7e94879a793aba6e65d691f345cfd172c4cc924a78259d5f9612a2cbfb78847a" diff --git a/src/common/mod.rs b/src/common/mod.rs index 8b9d0de..32b9d12 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -17,37 +17,70 @@ //! Common types. use crate::{ + cbor::{reader::DecoderError, values::Value, writer::EncoderError}, iana, iana::{EnumI128, WithPrivateRange}, util::{cbor_type_error, AsCborValue}, }; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; -use serde_cbor as cbor; -use std::cmp::Ordering; +use alloc::{boxed::Box, string::String, vec::Vec}; +use core::{cmp::Ordering, convert::TryInto}; #[cfg(test)] mod tests; -/// Extension trait that adds serialization/deserialization methods. -pub trait CborSerializable: Serialize + DeserializeOwned { - /// Create an object instance by reading serialized CBOR data from [`std::io::Read`] instance. - fn from_reader(reader: R) -> cbor::Result { - cbor::from_reader::(reader) +/// Error type for failures in encoding or decoding COSE types. +pub enum CoseError { + /// CBOR decoding failure. + DecodeFailed(DecoderError), + /// CBOR encoding failure. + EncodeFailed(EncoderError), + /// Unexpected CBOR type encountered (got, want). + UnexpectedType(&'static str, &'static str), + /// Unrecognized value in IANA-controlled range (with no private range). + UnregisteredIanaValue, + /// Unrecognized value in neither IANA-controlled range nor private range. + UnregisteredIanaNonPrivateValue, +} + +impl core::convert::From for CoseError { + fn from(e: DecoderError) -> Self { + CoseError::DecodeFailed(e) } +} - /// Create an object instance from serialized CBOR data in a slice. - fn from_slice(slice: &[u8]) -> cbor::Result { - cbor::from_slice::(slice) +impl core::convert::From for CoseError { + fn from(e: EncoderError) -> Self { + CoseError::EncodeFailed(e) + } +} + +impl core::fmt::Debug for CoseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + CoseError::DecodeFailed(de) => write!(f, "decode CBOR failure: {:?}", de), + CoseError::EncodeFailed(ee) => write!(f, "encode CBOR failure: {:?}", ee), + CoseError::UnexpectedType(got, want) => write!(f, "got {}, expected {}", got, want), + CoseError::UnregisteredIanaValue => write!(f, "expected recognized IANA value"), + CoseError::UnregisteredIanaNonPrivateValue => { + write!(f, "expected value in IANA or private use range") + } + } } +} - /// Serialize this object to a vector. - fn to_vec(&self) -> cbor::Result> { - cbor::to_vec(self) +/// Extension trait that adds serialization/deserialization methods. +pub trait CborSerializable: AsCborValue { + /// Create an object instance from serialized CBOR data in a slice. + fn from_slice(slice: &[u8]) -> Result { + let value = sk_cbor::reader::read(slice).map_err(CoseError::DecodeFailed)?; + Self::from_cbor_value(value) } - /// Serialize this object to a [`std::io::Write`] instance. - fn to_writer(&self, writer: W) -> cbor::Result<()> { - cbor::to_writer(writer, self) + /// Serialize this object to a vector, consuming it along the way. + fn to_vec(self) -> Result, CoseError> { + let mut data = Vec::new(); + sk_cbor::writer::write(self.to_cbor_value()?, &mut data)?; + Ok(data) } } @@ -56,35 +89,24 @@ pub trait TaggedCborSerializable: AsCborValue { /// The associated tag value. const TAG: u64; - /// Create an object instance by reading serialized CBOR data from [`std::io::Read`] instance, - /// expecting an initial tag value. - fn from_tagged_reader(reader: R) -> cbor::Result { - match cbor::from_reader::(reader)? { - cbor::Value::Tag(t, v) if t == Self::TAG => Self::from_cbor_value(*v), - v => cbor_type_error(&v, &"tag"), - } - } - /// Create an object instance from serialized CBOR data in a slice, expecting an initial /// tag value. - fn from_tagged_slice(slice: &[u8]) -> cbor::Result { - match cbor::from_slice::(slice)? { - cbor::Value::Tag(t, v) if t == Self::TAG => Self::from_cbor_value(*v), - v => cbor_type_error(&v, &"tag"), + fn from_tagged_slice(slice: &[u8]) -> Result { + match sk_cbor::reader::read(slice)? { + Value::Tag(t, v) if t == Self::TAG => Self::from_cbor_value(*v), + v => cbor_type_error(&v, "tag"), } } - /// Serialize this object to a vector, including initial tag. - fn to_tagged_vec(&self) -> cbor::Result> { - cbor::to_vec(&cbor::Value::Tag(Self::TAG, Box::new(self.to_cbor_value()))) - } - - /// Serialize this object to a [`std::io::Write`] instance, including initial tag. - fn to_tagged_writer(&self, writer: W) -> cbor::Result<()> { - cbor::to_writer( - writer, - &cbor::Value::Tag(Self::TAG, Box::new(self.to_cbor_value())), - ) + /// Serialize this object to a vector, including initial tag, consuming the object along the + /// way. + fn to_tagged_vec(self) -> Result, CoseError> { + let mut data = Vec::new(); + sk_cbor::writer::write( + Value::Tag(Self::TAG, Box::new(self.to_cbor_value()?)), + &mut data, + )?; + Ok(data) } } @@ -113,9 +135,26 @@ impl CborSerializable for Label {} /// (where the primary sorting criterion is the length of the encoded form) impl Ord for Label { fn cmp(&self, other: &Self) -> Ordering { - self.to_cbor_value().cmp(&other.to_cbor_value()) + match (self, other) { + (Label::Int(i1), Label::Int(i2)) => match (i1.signum(), i2.signum()) { + (-1, -1) => (-i1).cmp(&(-i2)), + (-1, 0) => Ordering::Greater, + (-1, 1) => Ordering::Greater, + (0, -1) => Ordering::Less, + (0, 0) => Ordering::Equal, + (0, 1) => Ordering::Less, + (1, -1) => Ordering::Less, + (1, 0) => Ordering::Greater, + (1, 1) => i1.cmp(i2), + (_, _) => unreachable!(), // safe: all possibilies covered + }, + (Label::Int(_i1), Label::Text(_t2)) => Ordering::Less, + (Label::Text(_t1), Label::Int(_i2)) => Ordering::Greater, + (Label::Text(t1), Label::Text(t2)) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)), + } } } + impl PartialOrd for Label { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -123,33 +162,26 @@ impl PartialOrd for Label { } impl AsCborValue for Label { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { match value { - cbor::Value::Integer(i) => Ok(Label::Int(i)), - cbor::Value::Text(t) => Ok(Label::Text(t)), - v => cbor_type_error(&v, &"int/tstr"), + Value::Unsigned(i) => Ok(Label::Int(i as i128)), + Value::Negative(i) => Ok(Label::Int(i as i128)), + Value::TextString(t) => Ok(Label::Text(t)), + v => cbor_type_error(&v, "int/tstr"), } } - fn to_cbor_value(&self) -> cbor::Value { - match self { - Label::Int(i) => cbor::Value::Integer(*i as i128), - Label::Text(t) => cbor::Value::Text(t.clone()), - } - } -} - -impl Serialize for Label { - fn serialize(&self, serializer: S) -> Result { - match self { - Label::Int(i) => serializer.serialize_i128(*i as i128), - Label::Text(t) => serializer.serialize_str(t), - } - } -} - -impl<'de> Deserialize<'de> for Label { - fn deserialize>(deserializer: D) -> Result { - Self::from_cbor_value(cbor::Value::deserialize(deserializer)?) + fn to_cbor_value(self) -> Result { + Ok(match self { + Label::Int(i) if i < 0 => Value::Negative( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("-i128", "i64"))?, + ), + Label::Int(i) => Value::Unsigned( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("i128", "u64"))?, + ), + Label::Text(t) => Value::TextString(t), + }) } } @@ -164,58 +196,66 @@ pub enum RegisteredLabel { impl CborSerializable for RegisteredLabel {} /// Manual implementation of [`Ord`] to ensure that CBOR canonical ordering is respected. -/// -/// Note that this uses the ordering given by RFC 8949 section 4.2.1 (lexicographic ordering of -/// encoded form), which is *different* from the canonical ordering defined in RFC 7049 section 3.9 -/// (where the primary sorting criterion is the length of the encoded form) -impl Ord for RegisteredLabel { +impl Ord for RegisteredLabel { fn cmp(&self, other: &Self) -> Ordering { - self.to_cbor_value().cmp(&other.to_cbor_value()) + match (self, other) { + (RegisteredLabel::Assigned(i1), RegisteredLabel::Assigned(i2)) => { + Label::Int(i1.to_i128()).cmp(&Label::Int(i2.to_i128())) + } + (RegisteredLabel::Assigned(_i1), RegisteredLabel::Text(_t2)) => Ordering::Less, + (RegisteredLabel::Text(_t1), RegisteredLabel::Assigned(_i2)) => Ordering::Greater, + (RegisteredLabel::Text(t1), RegisteredLabel::Text(t2)) => { + t1.len().cmp(&t2.len()).then(t1.cmp(t2)) + } + } } } -impl PartialOrd for RegisteredLabel { + +impl PartialOrd for RegisteredLabel { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl AsCborValue for RegisteredLabel { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { match value { - cbor::Value::Integer(i) => { - if let Some(a) = T::from_i128(i) { + Value::Unsigned(i) => { + if let Some(a) = T::from_i128(i as i128) { Ok(RegisteredLabel::Assigned(a)) } else { - Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Signed(i as i64), - &"recognized IANA value", - )) + Err(CoseError::UnregisteredIanaValue) } } - cbor::Value::Text(t) => Ok(RegisteredLabel::Text(t)), - v => cbor_type_error(&v, &"int/tstr"), - } - } - fn to_cbor_value(&self) -> cbor::Value { - match self { - RegisteredLabel::Assigned(e) => cbor::Value::Integer(e.to_i128()), - RegisteredLabel::Text(t) => cbor::Value::Text(t.clone()), - } - } -} - -impl Serialize for RegisteredLabel { - fn serialize(&self, serializer: S) -> Result { - match self { - RegisteredLabel::Assigned(i) => serializer.serialize_i128(i.to_i128()), - RegisteredLabel::Text(t) => serializer.serialize_str(t), + Value::Negative(i) => { + if let Some(a) = T::from_i128(i as i128) { + Ok(RegisteredLabel::Assigned(a)) + } else { + Err(CoseError::UnregisteredIanaValue) + } + } + Value::TextString(t) => Ok(RegisteredLabel::Text(t)), + v => cbor_type_error(&v, "int/tstr"), } } -} - -impl<'de, T: EnumI128> Deserialize<'de> for RegisteredLabel { - fn deserialize>(deserializer: D) -> Result { - Self::from_cbor_value(cbor::Value::deserialize(deserializer)?) + fn to_cbor_value(self) -> Result { + Ok(match self { + RegisteredLabel::Assigned(e) => { + let e128 = e.to_i128(); + if e128 >= 0 { + Value::Unsigned( + e128.try_into() + .map_err(|_e| CoseError::UnexpectedType("i128", "u64"))?, + ) + } else { + Value::Negative( + e128.try_into() + .map_err(|_e| CoseError::UnexpectedType("-i128", "i64"))?, + ) + } + } + RegisteredLabel::Text(t) => Value::TextString(t), + }) } } @@ -232,61 +272,86 @@ pub enum RegisteredLabelWithPrivate { impl CborSerializable for RegisteredLabelWithPrivate {} /// Manual implementation of [`Ord`] to ensure that CBOR canonical ordering is respected. -/// -/// Note that this uses the ordering given by RFC 8949 section 4.2.1 (lexicographic ordering of -/// encoded form), which is *different* from the canonical ordering defined in RFC 7049 section 3.9 -/// (where the primary sorting criterion is the length of the encoded form) -impl Ord for RegisteredLabelWithPrivate { +impl Ord for RegisteredLabelWithPrivate { fn cmp(&self, other: &Self) -> Ordering { - self.to_cbor_value().cmp(&other.to_cbor_value()) + use RegisteredLabelWithPrivate::{Assigned, PrivateUse, Text}; + match (self, other) { + (Assigned(i1), Assigned(i2)) => Label::Int(i1.to_i128()).cmp(&Label::Int(i2.to_i128())), + (Assigned(i1), PrivateUse(i2)) => Label::Int(i1.to_i128()).cmp(&Label::Int(*i2)), + (PrivateUse(i1), Assigned(i2)) => Label::Int(*i1).cmp(&Label::Int(i2.to_i128())), + (PrivateUse(i1), PrivateUse(i2)) => Label::Int(*i1).cmp(&Label::Int(*i2)), + (Assigned(_i1), Text(_t2)) => Ordering::Less, + (PrivateUse(_i1), Text(_t2)) => Ordering::Less, + (Text(_t1), Assigned(_i2)) => Ordering::Greater, + (Text(_t1), PrivateUse(_i2)) => Ordering::Greater, + (Text(t1), Text(t2)) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)), + } } } -impl PartialOrd for RegisteredLabelWithPrivate { + +impl PartialOrd for RegisteredLabelWithPrivate { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl AsCborValue for RegisteredLabelWithPrivate { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { match value { - cbor::Value::Integer(i) => { + Value::Unsigned(i) => { + let i = i as i128; if let Some(a) = T::from_i128(i) { Ok(RegisteredLabelWithPrivate::Assigned(a)) } else if T::is_private(i) { Ok(RegisteredLabelWithPrivate::PrivateUse(i)) } else { - Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Signed(i as i64), - &"value in IANA or private use range", - )) + Err(CoseError::UnregisteredIanaNonPrivateValue) } } - cbor::Value::Text(t) => Ok(RegisteredLabelWithPrivate::Text(t)), - v => cbor_type_error(&v, &"int/tstr"), - } - } - fn to_cbor_value(&self) -> cbor::Value { - match self { - RegisteredLabelWithPrivate::PrivateUse(i) => cbor::Value::Integer(*i), - RegisteredLabelWithPrivate::Assigned(e) => cbor::Value::Integer(e.to_i128()), - RegisteredLabelWithPrivate::Text(t) => cbor::Value::Text(t.clone()), - } - } -} - -impl Serialize for RegisteredLabelWithPrivate { - fn serialize(&self, serializer: S) -> Result { - match self { - RegisteredLabelWithPrivate::PrivateUse(i) => serializer.serialize_i128(*i), - RegisteredLabelWithPrivate::Assigned(i) => serializer.serialize_i128(i.to_i128()), - RegisteredLabelWithPrivate::Text(t) => serializer.serialize_str(t), + Value::Negative(i) => { + let i = i as i128; + if let Some(a) = T::from_i128(i) { + Ok(RegisteredLabelWithPrivate::Assigned(a)) + } else if T::is_private(i) { + Ok(RegisteredLabelWithPrivate::PrivateUse(i)) + } else { + Err(CoseError::UnregisteredIanaNonPrivateValue) + } + } + Value::TextString(t) => Ok(RegisteredLabelWithPrivate::Text(t)), + v => cbor_type_error(&v, "int/tstr"), } } -} - -impl<'de, T: EnumI128 + WithPrivateRange> Deserialize<'de> for RegisteredLabelWithPrivate { - fn deserialize>(deserializer: D) -> Result { - Self::from_cbor_value(cbor::Value::deserialize(deserializer)?) + fn to_cbor_value(self) -> Result { + Ok(match self { + RegisteredLabelWithPrivate::PrivateUse(i) => { + if i >= 0 { + Value::Unsigned( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("i128", "u64"))?, + ) + } else { + Value::Negative( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("-i128", "i64"))?, + ) + } + } + RegisteredLabelWithPrivate::Assigned(i) => { + let i = i.to_i128(); + if i >= 0 { + Value::Unsigned( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("i128", "u64"))?, + ) + } else { + Value::Negative( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("-i128", "i64"))?, + ) + } + } + RegisteredLabelWithPrivate::Text(t) => Value::TextString(t), + }) } } diff --git a/src/common/tests.rs b/src/common/tests.rs index eccaa12..1149a6d 100644 --- a/src/common/tests.rs +++ b/src/common/tests.rs @@ -16,6 +16,8 @@ use super::*; use crate::util::expect_err; +use alloc::{borrow::ToOwned, vec}; +use core::cmp::Ordering; #[test] fn test_label_encode() { @@ -26,19 +28,11 @@ fn test_label_encode() { ]; for (i, (label, label_data)) in tests.iter().enumerate() { - let got = label.to_vec().unwrap(); + let got = label.clone().to_vec().unwrap(); assert_eq!(*label_data, hex::encode(&got), "case {}", i); let got = Label::from_slice(&got).unwrap(); assert_eq!(*label, got); - - // Also exercise the `Read` / `Write` versions. - let mut got = vec![]; - label.to_writer(&mut got).unwrap(); - assert_eq!(*label_data, hex::encode(&got), "case {}", i); - - let got = Label::from_reader(std::io::Cursor::new(&got)).unwrap(); - assert_eq!(*label, got); } } @@ -48,27 +42,55 @@ fn test_label_sort() { let pairs = vec![ (Label::Int(0x1234), Label::Text("a".to_owned())), (Label::Int(0x1234), Label::Text("ab".to_owned())), + (Label::Int(0), Label::Text("ab".to_owned())), + (Label::Int(-1), Label::Text("ab".to_owned())), + (Label::Int(0), Label::Int(10)), + (Label::Int(0), Label::Int(-10)), (Label::Int(10), Label::Int(-1)), + (Label::Int(-1), Label::Int(-2)), (Label::Int(0x12), Label::Int(0x1234)), (Label::Int(0x99), Label::Int(0x1234)), (Label::Int(0x1234), Label::Int(0x1235)), + (Label::Text("a".to_owned()), Label::Text("ab".to_owned())), + (Label::Text("aa".to_owned()), Label::Text("ab".to_owned())), ]; for (left, right) in pairs.into_iter() { let value_cmp = left.cmp(&right); let value_partial_cmp = left.partial_cmp(&right); - let left_data = cbor::to_vec(&left).unwrap(); - let right_data = cbor::to_vec(&right).unwrap(); + let left_data = left.clone().to_vec().unwrap(); + let right_data = right.clone().to_vec().unwrap(); let data_cmp = left_data.cmp(&right_data); + let reverse_cmp = right.cmp(&left); + let equal_cmp = left.cmp(&left); - assert_eq!(value_cmp, std::cmp::Ordering::Less); - assert_eq!(value_partial_cmp, Some(std::cmp::Ordering::Less)); - assert_eq!(data_cmp, std::cmp::Ordering::Less); + assert_eq!(value_cmp, Ordering::Less, "{:?} < {:?}", left, right); + assert_eq!( + value_partial_cmp, + Some(Ordering::Less), + "{:?} < {:?}", + left, + right + ); + assert_eq!( + data_cmp, + Ordering::Less, + "{:?}={} < {:?}={}", + left, + hex::encode(&left_data), + right, + hex::encode(&right_data) + ); + assert_eq!(reverse_cmp, Ordering::Greater, "{:?} > {:?}", right, left); + assert_eq!(equal_cmp, Ordering::Equal, "{:?} = {:?}", left, left); } } #[test] fn test_label_decode_fail() { - let tests = vec![("43010203", "expected int/tstr"), ("", "EofWhileParsing")]; + let tests = vec![ + ("43010203", "expected int/tstr"), + ("", "IncompleteCborData"), + ]; for (label_data, err_msg) in tests.iter() { let data = hex::decode(label_data).unwrap(); let result = Label::from_slice(&data); @@ -84,19 +106,11 @@ fn test_registered_label_encode() { ]; for (i, (label, label_data)) in tests.iter().enumerate() { - let got = label.to_vec().unwrap(); + let got = label.clone().to_vec().unwrap(); assert_eq!(*label_data, hex::encode(&got), "case {}", i); let got = RegisteredLabel::from_slice(&got).unwrap(); assert_eq!(*label, got); - - // Also exercise the `Read` / `Write` versions. - let mut got = vec![]; - label.to_writer(&mut got).unwrap(); - assert_eq!(*label_data, hex::encode(&got), "case {}", i); - - let got = RegisteredLabel::from_reader(std::io::Cursor::new(&got)).unwrap(); - assert_eq!(*label, got); } } @@ -124,13 +138,31 @@ fn test_registered_label_sort() { for (left, right) in pairs.into_iter() { let value_cmp = left.cmp(&right); let value_partial_cmp = left.partial_cmp(&right); - let left_data = cbor::to_vec(&left).unwrap(); - let right_data = cbor::to_vec(&right).unwrap(); + let left_data = left.clone().to_vec().unwrap(); + let right_data = right.clone().to_vec().unwrap(); let data_cmp = left_data.cmp(&right_data); + let reverse_cmp = right.cmp(&left); + let equal_cmp = left.cmp(&left); - assert_eq!(value_cmp, std::cmp::Ordering::Less); - assert_eq!(value_partial_cmp, Some(std::cmp::Ordering::Less)); - assert_eq!(data_cmp, std::cmp::Ordering::Less); + assert_eq!(value_cmp, Ordering::Less, "{:?} < {:?}", left, right); + assert_eq!( + value_partial_cmp, + Some(Ordering::Less), + "{:?} < {:?}", + left, + right + ); + assert_eq!( + data_cmp, + Ordering::Less, + "{:?}={} < {:?}={}", + left, + hex::encode(&left_data), + right, + hex::encode(&right_data) + ); + assert_eq!(reverse_cmp, Ordering::Greater, "{:?} > {:?}", right, left); + assert_eq!(equal_cmp, Ordering::Equal, "{:?} = {:?}", left, left); } } @@ -138,7 +170,7 @@ fn test_registered_label_sort() { fn test_registered_label_decode_fail() { let tests = vec![ ("43010203", "expected int/tstr"), - ("", "EofWhileParsing"), + ("", "IncompleteCborData"), ("09", "expected recognized IANA value"), ]; for (label_data, err_msg) in tests.iter() { @@ -166,57 +198,64 @@ fn test_registered_label_with_private_encode() { ]; for (i, (label, label_data)) in tests.iter().enumerate() { - let got = label.to_vec().unwrap(); + let got = label.clone().to_vec().unwrap(); assert_eq!(*label_data, hex::encode(&got), "case {}", i); let got = RegisteredLabelWithPrivate::from_slice(&got).unwrap(); assert_eq!(*label, got); - - // Also exercise the `Read` / `Write` versions. - let mut got = vec![]; - label.to_writer(&mut got).unwrap(); - assert_eq!(*label_data, hex::encode(&got), "case {}", i); - - let got = RegisteredLabelWithPrivate::from_reader(std::io::Cursor::new(&got)).unwrap(); - assert_eq!(*label, got); } } #[test] fn test_registered_label_with_private_sort() { + use RegisteredLabelWithPrivate::{Assigned, PrivateUse, Text}; // Pairs of `RegisteredLabelWithPrivate`s with the "smaller" first. let pairs = vec![ + (Assigned(iana::Algorithm::A192GCM), Text("a".to_owned())), + (Assigned(iana::Algorithm::WalnutDSA), Text("ab".to_owned())), ( - RegisteredLabelWithPrivate::Assigned(iana::Algorithm::A192GCM), - RegisteredLabelWithPrivate::Text("a".to_owned()), + Assigned(iana::Algorithm::AES_CCM_16_64_128), + Assigned(iana::Algorithm::A128KW), ), ( - RegisteredLabelWithPrivate::Assigned(iana::Algorithm::WalnutDSA), - RegisteredLabelWithPrivate::Text("ab".to_owned()), + Assigned(iana::Algorithm::A192GCM), + Assigned(iana::Algorithm::AES_CCM_16_64_128), ), ( - RegisteredLabelWithPrivate::Assigned(iana::Algorithm::AES_CCM_16_64_128), - RegisteredLabelWithPrivate::Assigned(iana::Algorithm::A128KW), - ), - ( - RegisteredLabelWithPrivate::Assigned(iana::Algorithm::A192GCM), - RegisteredLabelWithPrivate::Assigned(iana::Algorithm::AES_CCM_16_64_128), - ), - ( - RegisteredLabelWithPrivate::Assigned(iana::Algorithm::AES_CCM_16_64_128), - RegisteredLabelWithPrivate::PrivateUse(-70_000), + Assigned(iana::Algorithm::AES_CCM_16_64_128), + PrivateUse(-70_000), ), + (PrivateUse(-70_000), PrivateUse(-70_001)), + (PrivateUse(-70_000), Text("a".to_owned())), ]; for (left, right) in pairs.into_iter() { let value_cmp = left.cmp(&right); let value_partial_cmp = left.partial_cmp(&right); - let left_data = cbor::to_vec(&left).unwrap(); - let right_data = cbor::to_vec(&right).unwrap(); + let left_data = left.clone().to_vec().unwrap(); + let right_data = right.clone().to_vec().unwrap(); let data_cmp = left_data.cmp(&right_data); + let reverse_cmp = right.cmp(&left); + let equal_cmp = left.cmp(&left); - assert_eq!(value_cmp, std::cmp::Ordering::Less); - assert_eq!(value_partial_cmp, Some(std::cmp::Ordering::Less)); - assert_eq!(data_cmp, std::cmp::Ordering::Less); + assert_eq!(value_cmp, Ordering::Less, "{:?} < {:?}", left, right); + assert_eq!( + value_partial_cmp, + Some(Ordering::Less), + "{:?} < {:?}", + left, + right + ); + assert_eq!( + data_cmp, + Ordering::Less, + "{:?}={} < {:?}={}", + left, + hex::encode(&left_data), + right, + hex::encode(&right_data) + ); + assert_eq!(reverse_cmp, Ordering::Greater, "{:?} > {:?}", right, left); + assert_eq!(equal_cmp, Ordering::Equal, "{:?} = {:?}", left, left); } } @@ -224,7 +263,7 @@ fn test_registered_label_with_private_sort() { fn test_registered_label_with_private_decode_fail() { let tests = vec![ ("43010203", "expected int/tstr"), - ("", "EofWhileParsing"), + ("", "IncompleteCborData"), ("09", "expected value in IANA or private use range"), ]; for (label_data, err_msg) in tests.iter() { diff --git a/src/context/mod.rs b/src/context/mod.rs index 3290507..50445f9 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -17,12 +17,13 @@ //! COSE_KDF_Context functionality. use crate::{ + cbor::{SimpleValue, Value}, iana, util::{cbor_type_error, AsCborValue}, - Algorithm, Header, + Algorithm, CoseError, Header, }; -use serde::de::Unexpected; -use serde_cbor as cbor; +use alloc::{vec, vec::Vec}; +use core::convert::TryInto; #[cfg(test)] mod tests; @@ -53,60 +54,63 @@ pub struct PartyInfo { impl crate::CborSerializable for PartyInfo {} impl AsCborValue for PartyInfo { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 3 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 3 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 3 items")); } // Remove array elements in reverse order to avoid shifts. Ok(Self { other: match a.remove(2) { - cbor::Value::Null => None, - cbor::Value::Bytes(b) => Some(b), - v => return cbor_type_error(&v, &"bstr / nil"), + Value::Simple(SimpleValue::NullValue) => None, + Value::ByteString(b) => Some(b), + v => return cbor_type_error(&v, "bstr / nil"), }, nonce: match a.remove(1) { - cbor::Value::Null => None, - cbor::Value::Bytes(b) => Some(Nonce::Bytes(b)), - cbor::Value::Integer(i) => Some(Nonce::Integer(i)), - v => return cbor_type_error(&v, &"bstr / int / nil"), + Value::Simple(SimpleValue::NullValue) => None, + Value::ByteString(b) => Some(Nonce::Bytes(b)), + Value::Unsigned(i) => Some(Nonce::Integer(i.into())), + Value::Negative(i) => Some(Nonce::Integer(i.into())), + v => return cbor_type_error(&v, "bstr / int / nil"), }, identity: match a.remove(0) { - cbor::Value::Null => None, - cbor::Value::Bytes(b) => Some(b), - v => return cbor_type_error(&v, &"bstr / nil"), + Value::Simple(SimpleValue::NullValue) => None, + Value::ByteString(b) => Some(b), + v => return cbor_type_error(&v, "bstr / nil"), }, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - match &self.identity { - None => cbor::Value::Null, - Some(b) => cbor::Value::Bytes(b.clone()), + fn to_cbor_value(self) -> Result { + Ok(Value::Array(vec![ + match self.identity { + None => Value::Simple(SimpleValue::NullValue), + Some(b) => Value::ByteString(b), }, - match &self.nonce { - None => cbor::Value::Null, - Some(Nonce::Bytes(b)) => cbor::Value::Bytes(b.clone()), - Some(Nonce::Integer(i)) => cbor::Value::Integer(*i), + match self.nonce { + None => Value::Simple(SimpleValue::NullValue), + Some(Nonce::Bytes(b)) => Value::ByteString(b), + Some(Nonce::Integer(i)) if i < 0 => Value::Negative( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("i128", "u64"))?, + ), + Some(Nonce::Integer(i)) => Value::Unsigned( + i.try_into() + .map_err(|_e| CoseError::UnexpectedType("-i128", "i64"))?, + ), }, - match &self.other { - None => cbor::Value::Null, - Some(b) => cbor::Value::Bytes(b.clone()), + match self.other { + None => Value::Simple(SimpleValue::NullValue), + Some(b) => Value::ByteString(b), }, - ]) + ])) } } -cbor_serialize!(PartyInfo); - /// Builder for [`PartyInfo`] objects. #[derive(Default)] pub struct PartyInfoBuilder(PartyInfo); @@ -137,15 +141,15 @@ pub struct SuppPubInfo { impl crate::CborSerializable for SuppPubInfo {} impl AsCborValue for SuppPubInfo { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 2 && a.len() != 3 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 2 or 3 items", + return Err(CoseError::UnexpectedType( + "array", + "array with 2 or 3 items", )); } @@ -154,8 +158,8 @@ impl AsCborValue for SuppPubInfo { other: { if a.len() == 3 { match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => Some(b), + v => return cbor_type_error(&v, "bstr"), } } else { None @@ -163,26 +167,24 @@ impl AsCborValue for SuppPubInfo { }, protected: Header::from_cbor_bstr(a.remove(1))?, key_data_length: match a.remove(0) { - cbor::Value::Integer(i) if i >= 0 && i <= u64::MAX.into() => i as u64, - v => return cbor_type_error(&v, &"uint"), + Value::Unsigned(i) => i, + v => return cbor_type_error(&v, "uint"), }, }) } - fn to_cbor_value(&self) -> cbor::Value { + fn to_cbor_value(self) -> Result { let mut v = vec![ - cbor::Value::Integer(self.key_data_length as i128), - self.protected.to_cbor_bstr(), + Value::Unsigned(self.key_data_length), + self.protected.cbor_bstr()?, ]; - if let Some(other) = &self.other { - v.push(cbor::Value::Bytes(other.clone())); + if let Some(other) = self.other { + v.push(Value::ByteString(other)); } - cbor::Value::Array(v) + Ok(Value::Array(v)) } } -cbor_serialize!(SuppPubInfo); - /// Builder for [`SuppPubInfo`] objects. #[derive(Default)] pub struct SuppPubInfoBuilder(SuppPubInfo); @@ -220,15 +222,15 @@ pub struct CoseKdfContext { impl crate::CborSerializable for CoseKdfContext {} impl AsCborValue for CoseKdfContext { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() < 4 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with at least 4 items", + return Err(CoseError::UnexpectedType( + "array", + "array with at least 4 items", )); } @@ -236,8 +238,8 @@ impl AsCborValue for CoseKdfContext { let mut supp_priv_info = Vec::with_capacity(a.len() - 4); for i in (4..a.len()).rev() { let b = match a.remove(i) { - cbor::Value::Bytes(b) => b, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => b, + v => return cbor_type_error(&v, "bstr"), }; supp_priv_info.push(b); } @@ -252,22 +254,20 @@ impl AsCborValue for CoseKdfContext { }) } - fn to_cbor_value(&self) -> cbor::Value { + fn to_cbor_value(self) -> Result { let mut v = vec![ - self.algorithm_id.to_cbor_value(), - self.party_u_info.to_cbor_value(), - self.party_v_info.to_cbor_value(), - self.supp_pub_info.to_cbor_value(), + self.algorithm_id.to_cbor_value()?, + self.party_u_info.to_cbor_value()?, + self.party_v_info.to_cbor_value()?, + self.supp_pub_info.to_cbor_value()?, ]; - for supp_priv_info in &self.supp_priv_info { - v.push(cbor::Value::Bytes(supp_priv_info.clone())); + for supp_priv_info in self.supp_priv_info { + v.push(Value::ByteString(supp_priv_info)); } - cbor::Value::Array(v) + Ok(Value::Array(v)) } } -cbor_serialize!(CoseKdfContext); - /// Builder for [`CoseKdfContext`] objects. #[derive(Default)] pub struct CoseKdfContextBuilder(CoseKdfContext); diff --git a/src/context/tests.rs b/src/context/tests.rs index 254f678..1f41cb8 100644 --- a/src/context/tests.rs +++ b/src/context/tests.rs @@ -16,7 +16,7 @@ use super::*; use crate::{iana, util::expect_err, CborSerializable, HeaderBuilder}; -use serde_cbor as cbor; +use alloc::vec; #[test] fn test_context_encode() { @@ -180,7 +180,7 @@ fn test_context_encode() { ), ]; for (i, (key, key_data)) in tests.iter().enumerate() { - let got = cbor::ser::to_vec(&key).unwrap(); + let got = key.clone().to_vec().unwrap(); assert_eq!(*key_data, hex::encode(&got), "case {}", i); let got = CoseKdfContext::from_slice(&got).unwrap(); @@ -217,7 +217,7 @@ fn test_context_decode_fail() { "83", "f6f6f6", // 3-tuple: [nil, nil, nil] "83", "f6f6f6", // 3-tuple: [nil, nil, nil] ), - "EofWhileParsingValue", + "IncompleteCborData", ), ( concat!( diff --git a/src/encrypt/mod.rs b/src/encrypt/mod.rs index 847a39e..b6bb406 100644 --- a/src/encrypt/mod.rs +++ b/src/encrypt/mod.rs @@ -17,13 +17,14 @@ //! COSE_Encrypt functionality. use crate::{ + cbor, + cbor::values::{SimpleValue, Value}, common::CborSerializable, iana, util::{cbor_type_error, AsCborValue}, - Header, + CoseError, Header, }; -use serde::de::Unexpected; -use serde_cbor as cbor; +use alloc::{borrow::ToOwned, vec, vec::Vec}; #[cfg(test)] mod tests; @@ -48,15 +49,15 @@ pub struct CoseRecipient { impl crate::CborSerializable for CoseRecipient {} impl AsCborValue for CoseRecipient { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 3 && a.len() != 4 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 3 or 4 items", + return Err(CoseError::UnexpectedType( + "array", + "array with 3 or 4 items", )); } @@ -64,47 +65,47 @@ impl AsCborValue for CoseRecipient { let mut recipients = Vec::new(); if a.len() == 4 { match a.remove(3) { - cbor::Value::Array(a) => { + Value::Array(a) => { for val in a { recipients.push(CoseRecipient::from_cbor_value(val)?); } } - v => return cbor_type_error(&v, &"array"), + v => return cbor_type_error(&v, "array"), } } Ok(Self { recipients, ciphertext: match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - cbor::Value::Null => None, - v => return cbor_type_error(&v, &"bstr / null"), + Value::ByteString(b) => Some(b), + Value::Simple(SimpleValue::NullValue) => None, + v => return cbor_type_error(&v, "bstr / null"), }, unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { + fn to_cbor_value(self) -> Result { let mut v = vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - match &self.ciphertext { - None => cbor::Value::Null, - Some(b) => cbor::Value::Bytes(b.clone()), + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + match self.ciphertext { + None => Value::Simple(SimpleValue::NullValue), + Some(b) => Value::ByteString(b), }, ]; if !self.recipients.is_empty() { - v.push(cbor::Value::Array( - self.recipients.iter().map(|r| r.to_cbor_value()).collect(), - )); + let mut arr = Vec::new(); + for r in self.recipients { + arr.push(r.to_cbor_value()?); + } + v.push(Value::Array(arr)); } - cbor::Value::Array(v) + Ok(Value::Array(v)) } } -cbor_serialize!(CoseRecipient); - impl CoseRecipient { /// Decrypt the `ciphertext` value, using `cipher` to decrypt the cipher text and /// combined AAD. @@ -129,7 +130,7 @@ impl CoseRecipient { | EncryptionContext::RecRecipient => {} _ => panic!("unsupported encryption context {:?}", context), // safe: documented } - let aad = enc_structure_data(context, &self.protected, external_aad); + let aad = enc_structure_data(context, self.protected.clone(), external_aad); cipher(ct, &aad) } } @@ -205,7 +206,7 @@ impl CoseRecipientBuilder { | EncryptionContext::RecRecipient => {} _ => panic!("unsupported encryption context {:?}", context), // safe: documented } - enc_structure_data(context, &self.0.protected, external_aad) + enc_structure_data(context, self.0.protected.clone(), external_aad) } } @@ -233,56 +234,54 @@ impl crate::TaggedCborSerializable for CoseEncrypt { } impl AsCborValue for CoseEncrypt { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 4 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 4 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 4 items")); } // Remove array elements in reverse order to avoid shifts. let mut recipients = Vec::new(); match a.remove(3) { - cbor::Value::Array(a) => { + Value::Array(a) => { for val in a { recipients.push(CoseRecipient::from_cbor_value(val)?); } } - v => return cbor_type_error(&v, &"array"), + v => return cbor_type_error(&v, "array"), } - Ok(Self { recipients, ciphertext: match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - cbor::Value::Null => None, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => Some(b), + Value::Simple(SimpleValue::NullValue) => None, + v => return cbor_type_error(&v, "bstr"), }, unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - match &self.ciphertext { - None => cbor::Value::Null, - Some(b) => cbor::Value::Bytes(b.clone()), + fn to_cbor_value(self) -> Result { + let mut arr = Vec::new(); + for r in self.recipients { + arr.push(r.to_cbor_value()?); + } + Ok(Value::Array(vec![ + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + match self.ciphertext { + None => Value::Simple(SimpleValue::NullValue), + Some(b) => Value::ByteString(b), }, - cbor::Value::Array(self.recipients.iter().map(|r| r.to_cbor_value()).collect()), - ]) + Value::Array(arr), + ])) } } -cbor_serialize!(CoseEncrypt); - impl CoseEncrypt { /// Decrypt the `ciphertext` value, using `cipher` to decrypt the cipher text and /// combined AAD. @@ -297,7 +296,7 @@ impl CoseEncrypt { let ct = self.ciphertext.as_ref().unwrap(/* safe: documented */); let aad = enc_structure_data( EncryptionContext::CoseEncrypt, - &self.protected, + self.protected.clone(), external_aad, ); cipher(ct, &aad) @@ -323,7 +322,7 @@ impl CoseEncryptBuilder { { let aad = enc_structure_data( EncryptionContext::CoseEncrypt, - &self.0.protected, + self.0.protected.clone(), external_aad, ); self.ciphertext(cipher(plaintext, &aad)) @@ -343,7 +342,7 @@ impl CoseEncryptBuilder { { let aad = enc_structure_data( EncryptionContext::CoseEncrypt, - &self.0.protected, + self.0.protected.clone(), external_aad, ); Ok(self.ciphertext(cipher(plaintext, &aad)?)) @@ -378,44 +377,40 @@ impl crate::TaggedCborSerializable for CoseEncrypt0 { } impl AsCborValue for CoseEncrypt0 { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 3 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 3 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 3 items")); } // Remove array elements in reverse order to avoid shifts. Ok(Self { ciphertext: match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - cbor::Value::Null => None, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => Some(b), + Value::Simple(SimpleValue::NullValue) => None, + v => return cbor_type_error(&v, "bstr"), }, + unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - match &self.ciphertext { - None => cbor::Value::Null, - Some(b) => cbor::Value::Bytes(b.clone()), + fn to_cbor_value(self) -> Result { + Ok(Value::Array(vec![ + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + match self.ciphertext { + None => Value::Simple(SimpleValue::NullValue), + Some(b) => Value::ByteString(b), }, - ]) + ])) } } -cbor_serialize!(CoseEncrypt0); - impl CoseEncrypt0 { /// Decrypt the `ciphertext` value, using `cipher` to decrypt the cipher text and /// combined AAD. @@ -430,7 +425,7 @@ impl CoseEncrypt0 { let ct = self.ciphertext.as_ref().unwrap(/* safe: documented */); let aad = enc_structure_data( EncryptionContext::CoseEncrypt0, - &self.protected, + self.protected.clone(), external_aad, ); cipher(ct, &aad) @@ -456,7 +451,7 @@ impl CoseEncrypt0Builder { { let aad = enc_structure_data( EncryptionContext::CoseEncrypt0, - &self.0.protected, + self.0.protected.clone(), external_aad, ); self.ciphertext(cipher(plaintext, &aad)) @@ -476,7 +471,7 @@ impl CoseEncrypt0Builder { { let aad = enc_structure_data( EncryptionContext::CoseEncrypt0, - &self.0.protected, + self.0.protected.clone(), external_aad, ); Ok(self.ciphertext(cipher(plaintext, &aad)?)) @@ -518,20 +513,23 @@ impl EncryptionContext { /// ``` pub fn enc_structure_data( context: EncryptionContext, - protected: &Header, + protected: Header, external_aad: &[u8], ) -> Vec { let arr = vec![ - cbor::Value::Text(context.text().to_owned()), + Value::TextString(context.text().to_owned()), if protected.is_empty() { - cbor::Value::Bytes(vec![]) + Value::ByteString(vec![]) } else { - cbor::Value::Bytes( + Value::ByteString( protected.to_vec().expect("failed to serialize header"), /* safe: always * serializable */ ) }, - cbor::Value::Bytes(external_aad.to_vec()), + Value::ByteString(external_aad.to_vec()), ]; - cbor::to_vec(&cbor::Value::Array(arr)).expect("failed to serialize Enc_structure") // safe: always serializable + + let mut data = Vec::new(); + cbor::writer::write(Value::Array(arr), &mut data).unwrap(); // safe: always serializable + data } diff --git a/src/encrypt/tests.rs b/src/encrypt/tests.rs index fe3a7e7..4c23973 100644 --- a/src/encrypt/tests.rs +++ b/src/encrypt/tests.rs @@ -16,9 +16,14 @@ use super::*; use crate::{ - iana, util::expect_err, ContentType, CoseKeyBuilder, CoseRecipientBuilder, + cbor::Value, iana, util::expect_err, ContentType, CoseKeyBuilder, CoseRecipientBuilder, CoseSignatureBuilder, HeaderBuilder, TaggedCborSerializable, }; +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; #[test] fn test_cose_recipient_decode() { @@ -61,7 +66,7 @@ fn test_cose_recipient_decode() { ]; for (i, (recipient, recipient_data)) in tests.iter().enumerate() { - let got = recipient.to_vec().unwrap(); + let got = recipient.clone().to_vec().unwrap(); assert_eq!(*recipient_data, hex::encode(&got), "case {}", i); let got = CoseRecipient::from_slice(&got).unwrap(); @@ -74,11 +79,11 @@ fn test_cose_recipient_decode_fail() { let tests = vec![ ( concat!( - "a2", // 2-map (should be tuple) - "40", // 0-bstr (special case for empty protected headers, rather than 41a0) - "a0", // 0-map - "40", // 0-bstr - "40", // 0-bstr + "a2", // 2-map (should be tuple) + "40", // 0-bstr (special case for empty protected headers, rather than 41a0) + "a0", // 0-map + "4161", // 1-bstr + "40", // 0-bstr ), "expected array", ), @@ -164,7 +169,7 @@ fn test_cose_encrypt_decode() { ]; for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() { - let got = encrypt.to_vec().unwrap(); + let got = encrypt.clone().to_vec().unwrap(); assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i); let got = CoseEncrypt::from_slice(&got).unwrap(); @@ -177,11 +182,11 @@ fn test_cose_encrypt_decode_fail() { let tests = vec![ ( concat!( - "a2", // 2-map (should be tuple) - "40", // 0-bstr (special case for empty protected headers, rather than 41a0) - "a0", // 0-map - "40", // 0-bstr - "40", // 0-bstr + "a2", // 2-map (should be tuple) + "40", // 0-bstr (special case for empty protected headers, rather than 41a0) + "a0", // 0-map + "4161", // 1-bstr + "40", // 0-bstr ), "expected array", ), @@ -276,7 +281,7 @@ fn test_rfc8152_cose_encrypt_decode() { CoseKeyBuilder::new_ec2_pub_key_y_sign(iana::EllipticCurve::P_256, hex::decode("98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280").unwrap(), true) - .build().to_cbor_value()) + .build().to_cbor_value().unwrap()) .key_id(b"meriadoc.brandybuck@buckland.example".to_vec()) .build(), ) @@ -316,7 +321,7 @@ fn test_rfc8152_cose_encrypt_decode() { HeaderBuilder::new() .key_id(b"our-secret".to_vec()) .value(iana::HeaderAlgorithmParameter::Salt as i128, - cbor::Value::Bytes(b"aabbccddeeffgghh".to_vec())) + Value::ByteString(b"aabbccddeeffgghh".to_vec())) .build()) .ciphertext(vec![]) .build()) @@ -365,7 +370,7 @@ fn test_rfc8152_cose_encrypt_decode() { CoseKeyBuilder::new_ec2_pub_key_y_sign(iana::EllipticCurve::P_256, hex::decode("98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280").unwrap(), true) - .build().to_cbor_value()) + .build().to_cbor_value().unwrap()) .key_id(b"meriadoc.brandybuck@buckland.example".to_vec()) .build()) .ciphertext(vec![]) @@ -421,11 +426,11 @@ fn test_rfc8152_cose_encrypt_decode() { .key_id(b"meriadoc.brandybuck@buckland.example".to_vec()) .value( iana::HeaderAlgorithmParameter::StaticKeyId as i128, - cbor::Value::Bytes(b"peregrin.took@tuckborough.example".to_vec()) + Value::ByteString(b"peregrin.took@tuckborough.example".to_vec()) ) .value( iana::HeaderAlgorithmParameter::PartyUNonce as i128, - cbor::Value::Bytes(hex::decode("0101").unwrap()) + Value::ByteString(hex::decode("0101").unwrap()) ) .build()) .ciphertext(hex::decode("41e0d76f579dbd0d936a662d54d8582037de2e366fde1c62").unwrap()) @@ -458,7 +463,7 @@ fn test_rfc8152_cose_encrypt_decode() { ]; for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() { - let got = encrypt.to_tagged_vec().unwrap(); + let got = encrypt.clone().to_tagged_vec().unwrap(); assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i); let got = CoseEncrypt::from_tagged_slice(&got).unwrap(); @@ -490,7 +495,7 @@ fn test_cose_encrypt0_decode() { ]; for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() { - let got = encrypt.to_vec().unwrap(); + let got = encrypt.clone().to_vec().unwrap(); assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i); let got = CoseEncrypt0::from_slice(&got).unwrap(); @@ -503,11 +508,11 @@ fn test_cose_encrypt0_decode_fail() { let tests = vec![ ( concat!( - "a2", // 2-map (should be tuple) - "40", // 0-bstr (special case for empty protected headers, rather than 41a0) - "a0", // 0-map - "40", // 0-bstr - "40", // 0-bstr + "a2", // 2-map (should be tuple) + "40", // 0-bstr (special case for empty protected headers, rather than 41a0) + "a0", // 0-map + "4100", // 1-bstr + "40", // 0-bstr ), "expected array", ), @@ -621,7 +626,7 @@ fn test_rfc8152_cose_encrypt0_decode() { ]; for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() { - let got = encrypt.to_tagged_vec().unwrap(); + let got = encrypt.clone().to_tagged_vec().unwrap(); assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i); let got = CoseEncrypt0::from_tagged_slice(&got).unwrap(); diff --git a/src/header/mod.rs b/src/header/mod.rs index 1459298..ef921c9 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -17,14 +17,13 @@ //! COSE Headers functionality. use crate::{ + cbor::values::Value, iana, iana::EnumI128, util::{cbor_type_error, AsCborValue}, - Algorithm, CborSerializable, CoseSignature, Label, RegisteredLabel, + Algorithm, CborSerializable, CoseError, CoseSignature, Label, RegisteredLabel, }; -use serde::de::Unexpected; -use serde_cbor as cbor; -use std::collections::{btree_map::Entry, BTreeMap}; +use alloc::{string::String, vec, vec::Vec}; #[cfg(test)] mod tests; @@ -66,34 +65,34 @@ pub struct Header { pub partial_iv: Vec, /// Counter signature pub counter_signatures: Vec, - /// Any additional header values. - pub rest: BTreeMap, + /// Any additional header (label,value) pairs. If duplicate labels are present, CBOR-encoding + /// will fail. + pub rest: Vec<(Label, Value)>, } impl Header { - /// Constructor from a [`cbor::Value`] that holds a `bstr` encoded header. + /// Constructor from a [`Value`] that holds a `bstr` encoded header. #[inline] - pub fn from_cbor_bstr(val: cbor::Value) -> Result { + pub fn from_cbor_bstr(val: Value) -> Result { let data = match val { - cbor::Value::Bytes(b) => b, - v => return cbor_type_error(&v, &"bstr encoded map"), + Value::ByteString(b) => b, + v => return cbor_type_error(&v, "bstr encoded map"), }; if data.is_empty() { return Ok(Self::default()); } - Header::from_slice(&data).map_err(|_e| { - serde::de::Error::invalid_value(Unexpected::StructVariant, &"header struct") - }) + Header::from_slice(&data) } - /// Convert this header to a `bstr` encoded map, as a [`cbor::Value`]. + /// Convert this header to a `bstr` encoded map, as a [`Value`], consuming the object along the + /// way. #[inline] - pub fn to_cbor_bstr(&self) -> cbor::Value { - cbor::Value::Bytes(if self.is_empty() { + pub fn cbor_bstr(self) -> Result { + Ok(Value::ByteString(if self.is_empty() { vec![] } else { - self.to_vec().expect("failed to serialize header") // safe: Header always serializable - }) + self.to_vec()? + })) } /// Indicate whether the `Header` is empty. @@ -111,20 +110,19 @@ impl Header { impl crate::CborSerializable for Header {} -const ALG: cbor::Value = cbor::Value::Integer(iana::HeaderParameter::Alg as i128); -const CRIT: cbor::Value = cbor::Value::Integer(iana::HeaderParameter::Crit as i128); -const CONTENT_TYPE: cbor::Value = cbor::Value::Integer(iana::HeaderParameter::ContentType as i128); -const KID: cbor::Value = cbor::Value::Integer(iana::HeaderParameter::Kid as i128); -const IV: cbor::Value = cbor::Value::Integer(iana::HeaderParameter::Iv as i128); -const PARTIAL_IV: cbor::Value = cbor::Value::Integer(iana::HeaderParameter::PartialIv as i128); -const COUNTER_SIG: cbor::Value = - cbor::Value::Integer(iana::HeaderParameter::CounterSignature as i128); +const ALG: Value = Value::Unsigned(iana::HeaderParameter::Alg as u64); +const CRIT: Value = Value::Unsigned(iana::HeaderParameter::Crit as u64); +const CONTENT_TYPE: Value = Value::Unsigned(iana::HeaderParameter::ContentType as u64); +const KID: Value = Value::Unsigned(iana::HeaderParameter::Kid as u64); +const IV: Value = Value::Unsigned(iana::HeaderParameter::Iv as u64); +const PARTIAL_IV: Value = Value::Unsigned(iana::HeaderParameter::PartialIv as u64); +const COUNTER_SIG: Value = Value::Unsigned(iana::HeaderParameter::CounterSignature as u64); impl AsCborValue for Header { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let m = match value { - cbor::Value::Map(m) => m, - v => return cbor_type_error(&v, &"map"), + Value::Map(m) => m, + v => return cbor_type_error(&v, "map"), }; let mut headers = Self::default(); @@ -133,11 +131,11 @@ impl AsCborValue for Header { x if x == ALG => headers.alg = Some(Algorithm::from_cbor_value(value)?), x if x == CRIT => match value { - cbor::Value::Array(a) => { + Value::Array(a) => { if a.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"non-empty array", + return Err(CoseError::UnexpectedType( + "empty array", + "non-empty array", )); } for v in a { @@ -146,79 +144,67 @@ impl AsCborValue for Header { ); } } - v => return cbor_type_error(&v, &"array value"), + v => return cbor_type_error(&v, "array value"), }, x if x == CONTENT_TYPE => { headers.content_type = Some(ContentType::from_cbor_value(value)?); if let Some(ContentType::Text(text)) = &headers.content_type { if text.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::Str(text), - &"non-empty string", - )); + return Err(CoseError::UnexpectedType("empty tstr", "non-empty tstr")); } if text.trim() != text { - return Err(serde::de::Error::invalid_value( - Unexpected::Str(text), - &"no leading/trailing whitespace", + return Err(CoseError::UnexpectedType( + "leading/trailing whitespace", + "no leading/trailing whitespace", )); } // Basic check that the content type is of form type/subtype. // We don't check the precise definition though (RFC 6838 s4.2) if text.matches('/').count() != 1 { - return Err(serde::de::Error::invalid_value( - Unexpected::Str(text), - &"text of form type/subtype", + return Err(CoseError::UnexpectedType( + "arbitrary text", + "text of form type/subtype", )); } } } x if x == KID => match value { - cbor::Value::Bytes(v) => { + Value::ByteString(v) => { if v.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::Bytes(&v), - &"non-empty bstr", - )); + return Err(CoseError::UnexpectedType("empty bstr", "non-empty bstr")); } headers.key_id = v; } - v => return cbor_type_error(&v, &"bstr value"), + v => return cbor_type_error(&v, "bstr value"), }, x if x == IV => match value { - cbor::Value::Bytes(v) => { + Value::ByteString(v) => { if v.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::Bytes(&v), - &"non-empty bstr", - )); + return Err(CoseError::UnexpectedType("empty bstr", "non-empty bstr")); } headers.iv = v; } - v => return cbor_type_error(&v, &"bstr value"), + v => return cbor_type_error(&v, "bstr value"), }, x if x == PARTIAL_IV => match value { - cbor::Value::Bytes(v) => { + Value::ByteString(v) => { if v.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::Bytes(&v), - &"non-empty bstr", - )); + return Err(CoseError::UnexpectedType("empty bstr", "non-empty bstr")); } headers.partial_iv = v; } - v => return cbor_type_error(&v, &"bstr value"), + v => return cbor_type_error(&v, "bstr value"), }, x if x == COUNTER_SIG => match value { - cbor::Value::Array(sig_or_sigs) => { + Value::Array(sig_or_sigs) => { if sig_or_sigs.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"non-empty array", + return Err(CoseError::UnexpectedType( + "empty sig array", + "non-empty sig array", )); } // The encoding of counter signature[s] is pesky: @@ -229,101 +215,85 @@ impl AsCborValue for Header { // - If it's a bstr, sig_or_sigs is a single signature. // - If it's an array, sig_or_sigs is an array of signatures match &sig_or_sigs[0] { - cbor::Value::Bytes(_) => { - headers - .counter_signatures - .push(CoseSignature::from_cbor_value(cbor::Value::Array( - sig_or_sigs, - ))?) - } - cbor::Value::Array(_) => { + Value::ByteString(_) => headers + .counter_signatures + .push(CoseSignature::from_cbor_value(Value::Array(sig_or_sigs))?), + Value::Array(_) => { for sig in sig_or_sigs.into_iter() { headers .counter_signatures .push(CoseSignature::from_cbor_value(sig)?); } } - v => return cbor_type_error(v, &"array or bstr value"), + v => return cbor_type_error(v, "array or bstr value"), } } - v => return cbor_type_error(&v, &"array value"), + v => return cbor_type_error(&v, "array value"), }, l => { let label = Label::from_cbor_value(l)?; - match headers.rest.entry(label) { - Entry::Occupied(_) => { - return Err(serde::de::Error::invalid_value( - Unexpected::StructVariant, - &"unique map label", - )); - } - Entry::Vacant(ve) => { - ve.insert(value); - } - } + headers.rest.push((label, value)); } } // RFC 8152 section 3.1: "The 'Initialization Vector' and 'Partial Initialization // Vector' parameters MUST NOT both be present in the same security layer." if !headers.iv.is_empty() && !headers.partial_iv.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::StructVariant, - &"only one of IV and partial IV", + return Err(CoseError::UnexpectedType( + "IV and partial-IV specified", + "only one of IV and partial IV", )); } } Ok(headers) } - fn to_cbor_value(&self) -> cbor::Value { - let mut map = BTreeMap::::new(); - if let Some(alg) = &self.alg { - map.insert(ALG, alg.to_cbor_value()); + fn to_cbor_value(mut self) -> Result { + let mut map = Vec::<(Value, Value)>::new(); + if let Some(alg) = self.alg { + map.push((ALG, alg.to_cbor_value()?)); } if !self.crit.is_empty() { - map.insert( - CRIT, - cbor::Value::Array(self.crit.iter().map(|c| c.to_cbor_value()).collect()), - ); + let mut arr = Vec::new(); + for c in self.crit { + arr.push(c.to_cbor_value()?); + } + map.push((CRIT, Value::Array(arr))); } - if let Some(content_type) = &self.content_type { - map.insert(CONTENT_TYPE, content_type.to_cbor_value()); + if let Some(content_type) = self.content_type { + map.push((CONTENT_TYPE, content_type.to_cbor_value()?)); } if !self.key_id.is_empty() { - map.insert(KID, cbor::Value::Bytes(self.key_id.to_vec())); + map.push((KID, Value::ByteString(self.key_id))); } if !self.iv.is_empty() { - map.insert(IV, cbor::Value::Bytes(self.iv.to_vec())); + map.push((IV, Value::ByteString(self.iv))); } if !self.partial_iv.is_empty() { - map.insert(PARTIAL_IV, cbor::Value::Bytes(self.partial_iv.to_vec())); + map.push((PARTIAL_IV, Value::ByteString(self.partial_iv))); } if !self.counter_signatures.is_empty() { if self.counter_signatures.len() == 1 { // A single counter signature is encoded differently. - map.insert(COUNTER_SIG, self.counter_signatures[0].to_cbor_value()); - } else { - map.insert( + map.push(( COUNTER_SIG, - cbor::Value::Array( - self.counter_signatures - .iter() - .map(|s| s.to_cbor_value()) - .collect(), - ), - ); + self.counter_signatures.remove(0).to_cbor_value()?, + )); + } else { + let mut arr = Vec::new(); + for cs in self.counter_signatures { + arr.push(cs.to_cbor_value()?); + } + map.push((COUNTER_SIG, Value::Array(arr))); } } - for (label, value) in &self.rest { - map.insert(label.to_cbor_value(), value.clone()); + for (label, value) in self.rest.into_iter() { + map.push((label.to_cbor_value()?, value)); } - cbor::Value::Map(map) + Ok(Value::Map(map)) } } -cbor_serialize!(Header); - /// Builder for [`Header`] objects. #[derive(Default)] pub struct HeaderBuilder(Header); @@ -382,24 +352,25 @@ impl HeaderBuilder { self } - /// Set a header label:value pair. + /// Set a header label:value pair. If duplicate labels are added to a [`Header`], + /// subsequent attempts to CBOR-encode the header will fail. /// /// # Panics /// /// This function will panic if it used to set a header label from the range [1, 6]. - pub fn value(mut self, label: i128, value: cbor::Value) -> Self { + pub fn value(mut self, label: i128, value: Value) -> Self { if label >= iana::HeaderParameter::Alg.to_i128() && label <= iana::HeaderParameter::CounterSignature.to_i128() { panic!("value() method used to set core header parameter"); // safe: invalid input } - self.0.rest.insert(Label::Int(label), value); + self.0.rest.push((Label::Int(label), value)); self } /// Set a header label:value pair where the `label` is text. - pub fn text_value(mut self, label: String, value: cbor::Value) -> Self { - self.0.rest.insert(Label::Text(label), value); + pub fn text_value(mut self, label: String, value: Value) -> Self { + self.0.rest.push((Label::Text(label), value)); self } } diff --git a/src/header/tests.rs b/src/header/tests.rs index a840992..19fa289 100644 --- a/src/header/tests.rs +++ b/src/header/tests.rs @@ -15,14 +15,25 @@ //////////////////////////////////////////////////////////////////////////////// use super::*; -use crate::{iana, util::expect_err, CborSerializable, Label}; -use maplit::btreemap; -use serde_cbor as cbor; +use crate::{ + cbor::values::{SimpleValue, Value}, + iana, + util::expect_err, + CborSerializable, Label, +}; +use alloc::{borrow::ToOwned, vec}; // The most negative integer value that can be encoded in CBOR is: // 0x3B (0b001_11011) 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF // which is -18_446_744_073_709_551_616 (-1 - 18_446_744_073_709_551_615). -const MOST_NEGATIVE_NINT: i128 = -18_446_744_073_709_551_616i128; +// +// However, the underlying sk-cbor crate encodes negative integers as: +// Value::Negative(i64) +// meaning that the most negative integer available in practice is +// i64::MIN = -9_223_372_036_854_775_808 +// which is 0x8000000000000000 in hex, and which CBOR-encodes as: +// 0x3b 0x7fffffffffffffff +const MOST_NEGATIVE_NINT: i128 = i64::MIN as i128; #[test] fn test_header_encode() { @@ -49,7 +60,7 @@ fn test_header_encode() { concat!( "a1", // 1-map "01", - "3bffffffffffffffff", // 1 (alg) => -lots + "3b7fffffffffffffff", // 1 (alg) => -lots ), ), ( @@ -59,10 +70,10 @@ fn test_header_encode() { content_type: Some(ContentType::Assigned(iana::CoapContentFormat::CoseEncrypt0)), key_id: vec![1, 2, 3], iv: vec![1, 2, 3], - rest: btreemap! { - Label::Int(0x46) => cbor::Value::Integer(0x47), - Label::Int(0x66) => cbor::Value::Integer(0x67), - }, + rest: vec![ + (Label::Int(0x46), Value::Unsigned(0x47)), + (Label::Int(0x66), Value::Unsigned(0x67)), + ], ..Default::default() }, concat!( @@ -83,10 +94,10 @@ fn test_header_encode() { content_type: Some(ContentType::Text("a/b".to_owned())), key_id: vec![1, 2, 3], iv: vec![1, 2, 3], - rest: btreemap! { - Label::Int(0x46) => cbor::Value::Integer(0x47), - Label::Text("a".to_owned()) => cbor::Value::Integer(0x47), - }, + rest: vec![ + (Label::Int(0x46), Value::Unsigned(0x47)), + (Label::Text("a".to_owned()), Value::Unsigned(0x47)), + ], counter_signatures: vec![CoseSignature { signature: vec![1, 2, 3], ..Default::default() @@ -113,10 +124,10 @@ fn test_header_encode() { content_type: Some(ContentType::Text("a/b".to_owned())), key_id: vec![1, 2, 3], iv: vec![1, 2, 3], - rest: btreemap! { - Label::Int(0x46) => cbor::Value::Integer(0x47), - Label::Text("a".to_owned()) => cbor::Value::Integer(0x47), - }, + rest: vec![ + (Label::Int(0x46), Value::Unsigned(0x47)), + (Label::Text("a".to_owned()), Value::Unsigned(0x47)), + ], counter_signatures: vec![ CoseSignature { signature: vec![1, 2, 3], @@ -143,9 +154,19 @@ fn test_header_encode() { "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 = cbor::ser::to_vec(&header).unwrap(); + let got = header.clone().to_vec().unwrap(); assert_eq!(*header_data, hex::encode(&got), "case {}", i); let got = Header::from_slice(&got).unwrap(); @@ -176,7 +197,7 @@ fn test_header_decode_fail() { "a1", // 1-map "02", "4101", // 2 (crit) => bstr (invalid value type) ), - "expected array value", + "expected array", ), ( concat!( @@ -232,7 +253,7 @@ fn test_header_decode_fail() { "a1", // 1-map "03", "60", // 3 (content-type) => invalid value "" ), - "expected non-empty string", + "expected non-empty tstr", ), ( concat!( @@ -288,7 +309,7 @@ fn test_header_decode_fail() { "a1", // 1-map "07", "80", // 7 (counter-sig) => 0-arr ), - "expected non-empty array", + "expected non-empty sig array", ), ( concat!( @@ -316,9 +337,7 @@ fn test_header_decode_fail() { } } -// TODO(#1): get serde_cbor to generate an error on duplicate keys in map #[test] -#[ignore] fn test_header_decode_dup_fail() { let tests = vec![ ( @@ -328,7 +347,7 @@ fn test_header_decode_dup_fail() { "1866", "1867", // 66 => 67 "1866", "1847", // 66 => 47 ), - "expected unique map label", + "OutOfOrderKey", ), ( concat!( @@ -337,7 +356,7 @@ fn test_header_decode_dup_fail() { "1866", "1867", // 66 => 67 "01", "01", // 1 (alg) => A128GCM (duplicate label) ), - "expected unique map label", + "OutOfOrderKey", ), ]; for (header_data, err_msg) in tests.iter() { @@ -347,27 +366,54 @@ fn test_header_decode_dup_fail() { } } +#[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::Unsigned(0x47)), + (Label::Int(0x46), Value::Unsigned(0x67)), + ], + ..Default::default() + }, + HeaderBuilder::new() + .text_value("doop".to_owned(), Value::Unsigned(1)) + .text_value("doop".to_owned(), Value::Unsigned(2)) + .build(), + ]; + for header in tests { + let result = header.clone().to_vec(); + expect_err(result, "encode CBOR failure"); + } +} + #[test] fn test_header_encode_int_out_of_range_fail() { - // Unfortunately, it is possible to build Rust COSE structures that hold values that - // cannot be serialized to CBOR -- serde_cbor uses i128 for integer values for convenience, - // but the serializable range is more like an i65 (!). + // Unfortunately, it is possible to build Rust COSE structures that hold values that cannot be + // serialized to CBOR -- we use i128 for integer values for convenience, but the + // serializable range is more like an i65 (!) in theory and [i64::MIN, u64::MAX] in + // practice. let header = HeaderBuilder::new() .value( 0x10000000000000000i128, - cbor::Value::Text("boom!".to_owned()), + Value::TextString("boom!".to_owned()), ) .build(); - let result = cbor::ser::to_vec(&header); - expect_err(result, "stored in CBOR"); + let result = header.to_vec(); + expect_err(result, "expected u64"); let header = Header { // "What we do, is if we need that extra push over the cliff, you know what we do?" alg: Some(Algorithm::PrivateUse(MOST_NEGATIVE_NINT - 1)), ..Default::default() }; - let result = cbor::ser::to_vec(&header); - expect_err(result, "stored in CBOR"); + let result = header.to_vec(); + expect_err(result, "expected i64"); } #[test] @@ -388,8 +434,8 @@ fn test_header_builder() { .key_id(vec![1, 2, 3]) .partial_iv(vec![4, 5, 6]) // removed by .iv() call .iv(vec![1, 2, 3]) - .value(0x46, cbor::Value::Integer(0x47)) - .value(0x66, cbor::Value::Integer(0x67)) + .value(0x46, Value::Unsigned(0x47)) + .value(0x66, Value::Unsigned(0x67)) .build(), Header { alg: Some(Algorithm::Assigned(iana::Algorithm::A128GCM)), @@ -400,10 +446,10 @@ fn test_header_builder() { content_type: Some(ContentType::Assigned(iana::CoapContentFormat::CoseEncrypt0)), key_id: vec![1, 2, 3], iv: vec![1, 2, 3], - rest: btreemap! { - Label::Int(0x46) => cbor::Value::Integer(0x47), - Label::Int(0x66) => cbor::Value::Integer(0x67), - }, + rest: vec![ + (Label::Int(0x46), Value::Unsigned(0x47)), + (Label::Int(0x66), Value::Unsigned(0x67)), + ], ..Default::default() }, ), @@ -439,5 +485,7 @@ fn test_header_builder() { #[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, cbor::Value::Null).build(); + let _hdr = HeaderBuilder::new() + .value(1, Value::Simple(SimpleValue::NullValue)) + .build(); } diff --git a/src/iana/mod.rs b/src/iana/mod.rs index 0450cb9..466391f 100644 --- a/src/iana/mod.rs +++ b/src/iana/mod.rs @@ -21,13 +21,11 @@ //! - //! - -use serde::{Deserialize, Serialize}; - #[cfg(test)] mod tests; /// Trait indicating an enum that can be constructed from `i128` values. -pub trait EnumI128: Sized { +pub trait EnumI128: Sized + Eq { fn from_i128(i: i128) -> Option; fn to_i128(&self) -> i128; } @@ -43,7 +41,7 @@ macro_rules! iana_registry { #[allow(non_camel_case_types)] $(#[$attr])* #[non_exhaustive] - #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum $enum_name { $($(#[$fattr])* $name = $val,)* } diff --git a/src/key/mod.rs b/src/key/mod.rs index 48a5e35..01aef7c 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -17,15 +17,13 @@ //! COSE_Key functionality. use crate::{ + cbor::values::{SimpleValue, Value}, iana, iana::EnumI128, util::{cbor_type_error, AsCborValue}, - Algorithm, Label, + Algorithm, CoseError, Label, }; -use maplit::btreemap; -use serde::de::Unexpected; -use serde_cbor as cbor; -use std::collections::{btree_map::Entry, BTreeMap, BTreeSet}; +use alloc::{collections::BTreeSet, vec, vec::Vec}; #[cfg(test)] mod tests; @@ -42,6 +40,33 @@ impl Default for KeyType { /// Key operation. pub type KeyOperation = crate::RegisteredLabel; +/// A collection of [`CoseKey`] objects. +pub struct CoseKeySet(pub Vec); + +impl crate::CborSerializable for CoseKeySet {} + +impl AsCborValue for CoseKeySet { + fn from_cbor_value(value: Value) -> Result { + let a = match value { + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), + }; + let mut keys = Vec::new(); + for v in a { + keys.push(CoseKey::from_cbor_value(v)?); + } + Ok(Self(keys)) + } + + fn to_cbor_value(self) -> Result { + let mut arr = Vec::new(); + for k in self.0 { + arr.push(k.to_cbor_value()?); + } + Ok(Value::Array(arr)) + } +} + /// Structure representing a cryptographic key. /// /// ```cddl @@ -66,26 +91,24 @@ pub struct CoseKey { pub key_ops: BTreeSet, /// Base IV to be xor-ed with partial IVs. pub base_iv: Vec, - /// Any additional parameter values. - pub params: BTreeMap, + /// Any additional parameter (label,value) pairs. If duplicate labels are present, + /// CBOR-encoding will fail. + pub params: Vec<(Label, Value)>, } -/// A collection of [`CoseKey`] objects. -pub type CoseKeySet = Vec; - impl crate::CborSerializable for CoseKey {} -const KTY: cbor::Value = cbor::Value::Integer(iana::KeyParameter::Kty as i128); -const KID: cbor::Value = cbor::Value::Integer(iana::KeyParameter::Kid as i128); -const ALG: cbor::Value = cbor::Value::Integer(iana::KeyParameter::Alg as i128); -const KEY_OPS: cbor::Value = cbor::Value::Integer(iana::KeyParameter::KeyOps as i128); -const BASE_IV: cbor::Value = cbor::Value::Integer(iana::KeyParameter::BaseIv as i128); +const KTY: Value = Value::Unsigned(iana::KeyParameter::Kty as u64); +const KID: Value = Value::Unsigned(iana::KeyParameter::Kid as u64); +const ALG: Value = Value::Unsigned(iana::KeyParameter::Alg as u64); +const KEY_OPS: Value = Value::Unsigned(iana::KeyParameter::KeyOps as u64); +const BASE_IV: Value = Value::Unsigned(iana::KeyParameter::BaseIv as u64); impl AsCborValue for CoseKey { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let m = match value { - cbor::Value::Map(m) => m, - v => return cbor_type_error(&v, &"map"), + Value::Map(m) => m, + v => return cbor_type_error(&v, "map"), }; let mut key = Self::default(); @@ -94,115 +117,89 @@ impl AsCborValue for CoseKey { x if x == KTY => key.kty = KeyType::from_cbor_value(value)?, x if x == KID => match value { - cbor::Value::Bytes(v) => { + Value::ByteString(v) => { if v.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::Bytes(&v), - &"non-empty bstr", - )); + return Err(CoseError::UnexpectedType("empty bstr", "non-empty bstr")); } key.key_id = v; } - v => return cbor_type_error(&v, &"bstr value"), + v => return cbor_type_error(&v, "bstr value"), }, x if x == ALG => key.alg = Some(Algorithm::from_cbor_value(value)?), x if x == KEY_OPS => match value { - cbor::Value::Array(key_ops) => { + Value::Array(key_ops) => { for key_op in key_ops.into_iter() { if !key.key_ops.insert(KeyOperation::from_cbor_value(key_op)?) { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"unique array label", + return Err(CoseError::UnexpectedType( + "repeated array entry", + "unique array label", )); } } if key.key_ops.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"non-empty array", + return Err(CoseError::UnexpectedType( + "empty array", + "non-empty array", )); } } - v => return cbor_type_error(&v, &"array value"), + v => return cbor_type_error(&v, "array value"), }, x if x == BASE_IV => match value { - cbor::Value::Bytes(v) => { + Value::ByteString(v) => { if v.is_empty() { - return Err(serde::de::Error::invalid_value( - Unexpected::Bytes(&v), - &"non-empty bstr", - )); + return Err(CoseError::UnexpectedType("empty bstr", "non-empty bstr")); } key.base_iv = v; } - v => return cbor_type_error(&v, &"bstr value"), + v => return cbor_type_error(&v, "bstr value"), }, l => { let label = Label::from_cbor_value(l)?; - match key.params.entry(label) { - Entry::Occupied(_) => { - return Err(serde::de::Error::invalid_value( - Unexpected::StructVariant, - &"unique map label", - )); - } - Entry::Vacant(ve) => { - ve.insert(value); - } - } + key.params.push((label, value)); } } } // Check that key type has been set. if key.kty == KeyType::Assigned(iana::KeyType::Reserved) { - return Err(serde::de::Error::invalid_value( - Unexpected::StructVariant, - &"mandatory kty label", + return Err(CoseError::UnexpectedType( + "no kty label", + "mandatory kty label", )); } Ok(key) } - fn to_cbor_value(&self) -> cbor::Value { - let mut map = BTreeMap::::new(); - map.insert(KTY, self.kty.to_cbor_value()); + fn to_cbor_value(self) -> Result { + let mut map: Vec<(Value, Value)> = vec![(KTY, self.kty.to_cbor_value()?)]; if !self.key_id.is_empty() { - map.insert(KID, cbor::Value::Bytes(self.key_id.to_vec())); + map.push((KID, Value::ByteString(self.key_id))); } - if let Some(alg) = &self.alg { - map.insert(ALG, alg.to_cbor_value()); + if let Some(alg) = self.alg { + map.push((ALG, alg.to_cbor_value()?)); } if !self.key_ops.is_empty() { - map.insert( - KEY_OPS, - cbor::Value::Array( - self.key_ops - .iter() - .map(|op| match op { - KeyOperation::Assigned(i) => cbor::Value::Integer(*i as i128), - KeyOperation::Text(t) => cbor::Value::Text(t.to_owned()), - }) - .collect(), - ), - ); + let mut arr = Vec::new(); + for op in self.key_ops { + arr.push(op.to_cbor_value()?); + } + map.push((KEY_OPS, Value::Array(arr))); } if !self.base_iv.is_empty() { - map.insert(BASE_IV, cbor::Value::Bytes(self.base_iv.to_vec())); + map.push((BASE_IV, Value::ByteString(self.base_iv))); } - for (label, value) in &self.params { - map.insert(label.to_cbor_value(), value.clone()); + for (label, value) in self.params { + map.push((label.to_cbor_value()?, value)); } - cbor::Value::Map(map) + Ok(Value::Map(map)) } } -cbor_serialize!(CoseKey); - /// Builder for [`CoseKey`] objects. #[derive(Default)] pub struct CoseKeyBuilder(CoseKey); @@ -216,11 +213,20 @@ impl CoseKeyBuilder { pub fn new_ec2_pub_key(curve: iana::EllipticCurve, x: Vec, y: Vec) -> Self { Self(CoseKey { kty: KeyType::Assigned(iana::KeyType::EC2), - params: btreemap! { - Label::Int(iana::Ec2KeyParameter::Crv as i128) => cbor::Value::Integer(curve as i128), - Label::Int(iana::Ec2KeyParameter::X as i128) => cbor::Value::Bytes(x), - Label::Int(iana::Ec2KeyParameter::Y as i128) => cbor::Value::Bytes(y), - }, + params: vec![ + ( + Label::Int(iana::Ec2KeyParameter::Crv as i128), + Value::Unsigned(curve as u64), + ), + ( + Label::Int(iana::Ec2KeyParameter::X as i128), + Value::ByteString(x), + ), + ( + Label::Int(iana::Ec2KeyParameter::Y as i128), + Value::ByteString(y), + ), + ], ..Default::default() }) } @@ -230,11 +236,23 @@ impl CoseKeyBuilder { pub fn new_ec2_pub_key_y_sign(curve: iana::EllipticCurve, x: Vec, y_sign: bool) -> Self { Self(CoseKey { kty: KeyType::Assigned(iana::KeyType::EC2), - params: btreemap! { - Label::Int(iana::Ec2KeyParameter::Crv as i128) => cbor::Value::Integer(curve as i128), - Label::Int(iana::Ec2KeyParameter::X as i128) => cbor::Value::Bytes(x), - Label::Int(iana::Ec2KeyParameter::Y as i128) => cbor::Value::Bool(y_sign), - }, + params: vec![ + ( + Label::Int(iana::Ec2KeyParameter::Crv as i128), + Value::Unsigned(curve as u64), + ), + ( + Label::Int(iana::Ec2KeyParameter::X as i128), + Value::ByteString(x), + ), + ( + Label::Int(iana::Ec2KeyParameter::Y as i128), + match y_sign { + true => Value::Simple(SimpleValue::TrueValue), + false => Value::Simple(SimpleValue::FalseValue), + }, + ), + ], ..Default::default() }) } @@ -248,10 +266,10 @@ impl CoseKeyBuilder { d: Vec, ) -> Self { let mut builder = Self::new_ec2_pub_key(curve, x, y); - builder.0.params.insert( + builder.0.params.push(( Label::Int(iana::Ec2KeyParameter::D as i128), - cbor::Value::Bytes(d), - ); + Value::ByteString(d), + )); builder } @@ -259,9 +277,10 @@ impl CoseKeyBuilder { pub fn new_symmetric_key(k: Vec) -> Self { Self(CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => cbor::Value::Bytes(k), - }, + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128), + Value::ByteString(k), + )], ..Default::default() }) } @@ -284,11 +303,11 @@ impl CoseKeyBuilder { /// /// This function will panic if it used to set a parameter label from the [`iana::KeyParameter`] /// range. - pub fn param(mut self, label: i128, value: cbor::Value) -> Self { + pub fn param(mut self, label: i128, value: Value) -> Self { if iana::KeyParameter::from_i128(label).is_some() { panic!("param() method used to set KeyParameter"); // safe: invalid input } - self.0.params.insert(Label::Int(label), value); + self.0.params.push((Label::Int(label), value)); self } } diff --git a/src/key/tests.rs b/src/key/tests.rs index f1f541f..d835a28 100644 --- a/src/key/tests.rs +++ b/src/key/tests.rs @@ -15,42 +15,8 @@ //////////////////////////////////////////////////////////////////////////////// use super::*; -use crate::{iana, util::expect_err, CborSerializable}; -use maplit::{btreemap, btreeset}; -use serde_cbor as cbor; - -#[test] -fn test_cbor_sort() { - // Pairs of objects with the "smaller" first. - // Comparing `cbor::Value`s should give the same answer as comparing - // the encoded versions according to RFC 8949 section 4.2.1. - // This is *different* than the canonical ordering defined in - // RFC 7049 section 3.9, where the primary sorting criterion - // is the length of the encoded form. - let pairs = vec![ - ( - cbor::Value::Integer(0x1234), - cbor::Value::Text("a".to_owned()), - ), - ( - cbor::Value::Integer(0x1234), - cbor::Value::Text("ab".to_owned()), - ), - (cbor::Value::Integer(10), cbor::Value::Integer(-1)), - (cbor::Value::Integer(0x12), cbor::Value::Integer(0x1234)), - (cbor::Value::Integer(0x99), cbor::Value::Integer(0x1234)), - (cbor::Value::Integer(0x1234), cbor::Value::Integer(0x1235)), - ]; - for (left, right) in pairs.into_iter() { - let value_cmp = left.cmp(&right); - let left_data = cbor::to_vec(&left).unwrap(); - let right_data = cbor::to_vec(&right).unwrap(); - let data_cmp = left_data.cmp(&right_data); - - assert_eq!(value_cmp, std::cmp::Ordering::Less); - assert_eq!(data_cmp, std::cmp::Ordering::Less); - } -} +use crate::{cbor::Value, iana, util::expect_err, CborSerializable}; +use alloc::{borrow::ToOwned, vec}; #[test] fn test_cose_key_encode() { @@ -139,11 +105,13 @@ fn test_cose_key_encode() { CoseKey { kty: KeyType::Assigned(iana::KeyType::OKP), key_id: vec![1, 2, 3], - key_ops: btreeset! { + key_ops: vec![ KeyOperation::Assigned(iana::KeyOperation::Encrypt), KeyOperation::Assigned(iana::KeyOperation::Decrypt), KeyOperation::Text("abc".to_owned()), - }, + ] + .into_iter() + .collect(), ..Default::default() }, concat!( @@ -156,10 +124,10 @@ fn test_cose_key_encode() { ( CoseKey { kty: KeyType::Assigned(iana::KeyType::OKP), - params: btreemap! { - Label::Int(0x46) => cbor::Value::Integer(0x47), - Label::Int(0x66) => cbor::Value::Integer(0x67), - }, + params: vec![ + (Label::Int(0x46), Value::Unsigned(0x47)), + (Label::Int(0x66), Value::Unsigned(0x67)), + ], ..Default::default() }, concat!( @@ -172,10 +140,10 @@ fn test_cose_key_encode() { ( CoseKey { kty: KeyType::Assigned(iana::KeyType::OKP), - params: btreemap! { - Label::Text("a".to_owned()) => cbor::Value::Integer(0x67), - Label::Int(0x1234) => cbor::Value::Integer(0x47), - }, + params: vec![ + (Label::Int(0x1234), Value::Unsigned(0x47)), + (Label::Text("a".to_owned()), Value::Unsigned(0x67)), + ], ..Default::default() }, concat!( @@ -189,10 +157,10 @@ fn test_cose_key_encode() { ( CoseKey { kty: KeyType::Assigned(iana::KeyType::OKP), - params: btreemap! { - Label::Int(0x66) => cbor::Value::Integer(0x67), - Label::Text("a".to_owned()) => cbor::Value::Integer(0x47), - }, + params: vec![ + (Label::Int(0x66), Value::Unsigned(0x67)), + (Label::Text("a".to_owned()), Value::Unsigned(0x47)), + ], ..Default::default() }, concat!( @@ -211,7 +179,7 @@ fn test_cose_key_encode() { .unwrap(), ) .algorithm(iana::Algorithm::ES256) - .param(-70000, cbor::Value::Null) + .param(-70000, Value::Simple(SimpleValue::NullValue)) .build(), concat!( "a60102032620012158206b4ad240073b", @@ -224,19 +192,20 @@ fn test_cose_key_encode() { ), ]; for (i, (key, key_data)) in tests.iter().enumerate() { - let got = cbor::ser::to_vec(&key).unwrap(); + let got = key.clone().to_vec().unwrap(); assert_eq!(*key_data, hex::encode(&got), "case {}", i); let got = CoseKey::from_slice(&got).unwrap(); assert_eq!(*key, got); } + // Now combine all of the keys into a `CoseKeySet` - let keyset: CoseKeySet = tests.iter().map(|(l, _v)| l.clone()).collect(); + let keyset = CoseKeySet(tests.iter().map(|(l, _v)| l.clone()).collect()); let mut keyset_data: Vec = vec![0x80u8 + (tests.len() as u8)]; // assumes fewer than 24 keys for (_, key_data) in tests.iter() { keyset_data.extend_from_slice(&hex::decode(key_data).unwrap()); } - let got = cbor::ser::to_vec(&keyset).unwrap(); + let got = keyset.to_vec().unwrap(); assert_eq!(hex::encode(keyset_data), hex::encode(got)); } @@ -305,7 +274,7 @@ fn test_rfc8152_public_cose_key_decode() { ), ]; for (i, (key, key_data)) in tests.iter().enumerate() { - let got = cbor::ser::to_vec(&key).unwrap(); + let got = key.clone().to_vec().unwrap(); assert_eq!(*key_data, hex::encode(&got), "case {}", i); let got = CoseKey::from_slice(&got).unwrap(); @@ -313,12 +282,12 @@ fn test_rfc8152_public_cose_key_decode() { } // Now combine all of the keys into a `CoseKeySet` - let keyset: CoseKeySet = tests.iter().map(|(l, _v)| l.clone()).collect(); + let keyset = CoseKeySet(tests.iter().map(|(l, _v)| l.clone()).collect()); let mut keyset_data: Vec = vec![0x80u8 + (tests.len() as u8)]; // assumes fewer than 24 keys for (_, key_data) in tests.iter() { keyset_data.extend_from_slice(&hex::decode(key_data).unwrap()); } - let got = cbor::ser::to_vec(&keyset).unwrap(); + let got = keyset.to_vec().unwrap(); assert_eq!(hex::encode(keyset_data), hex::encode(got)); } @@ -380,10 +349,10 @@ fn test_rfc8152_private_cose_key_decode() { CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), key_id: b"our-secret".to_vec(), - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(hex::decode("849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188").unwrap()), - }, + params: vec![ + (Label::Int(iana::SymmetricKeyParameter::K as i128) , + Value::ByteString(hex::decode("849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188").unwrap())), + ], ..Default::default() }, concat!("a3", @@ -412,10 +381,10 @@ fn test_rfc8152_private_cose_key_decode() { CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), key_id: b"our-secret2".to_vec(), - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(hex::decode("849b5786457c1491be3a76dcea6c4271").unwrap()), - }, + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128) , + Value::ByteString(hex::decode("849b5786457c1491be3a76dcea6c4271").unwrap()), + )], ..Default::default() }, concat!("a3", @@ -428,10 +397,10 @@ fn test_rfc8152_private_cose_key_decode() { CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), key_id: b"018c0ae5-4d9b-471b-bfd6-eef314bc7037".to_vec(), - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(hex::decode("849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188").unwrap()), - }, + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128) , + Value::ByteString(hex::decode("849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188").unwrap()), + )], ..Default::default() }, concat!("a3", @@ -442,7 +411,7 @@ fn test_rfc8152_private_cose_key_decode() { ), ]; for (i, (key, key_data)) in tests.iter().enumerate() { - let got = cbor::ser::to_vec(&key).unwrap(); + let got = key.clone().to_vec().unwrap(); assert_eq!(*key_data, hex::encode(&got), "case {}", i); let got = CoseKey::from_slice(&got).unwrap(); @@ -450,12 +419,12 @@ fn test_rfc8152_private_cose_key_decode() { } // Now combine all of the keys into a `CoseKeySet` - let keyset: CoseKeySet = tests.iter().map(|(l, _v)| l.clone()).collect(); + let keyset = CoseKeySet(tests.iter().map(|(l, _v)| l.clone()).collect()); let mut keyset_data: Vec = vec![0x80u8 + (tests.len() as u8)]; // assumes fewer than 24 keys for (_, key_data) in tests.iter() { keyset_data.extend_from_slice(&hex::decode(key_data).unwrap()); } - let got = cbor::ser::to_vec(&keyset).unwrap(); + let got = keyset.to_vec().unwrap(); assert_eq!(hex::encode(keyset_data), hex::encode(got)); } @@ -466,7 +435,6 @@ fn test_cose_key_decode_fail() { concat!( "82", // 2-tuple (invalid) "01", "01", // 1 (kty) => OKP - "02", "43", "010203" // 2 (kid) => 3-bstr ), "expected map", ), @@ -581,9 +549,7 @@ fn test_cose_key_decode_fail() { } } -// TODO(#1): get serde_cbor to generate an error on duplicate keys in map #[test] -#[ignore] fn test_cose_key_decode_dup_fail() { let tests = vec![ ( @@ -593,7 +559,7 @@ fn test_cose_key_decode_dup_fail() { "1866", "1867", // 66 => 67 "1866", "1847", // 66 => 47 ), - "expected unique map label", + "OutOfOrderKey", ), ( concat!( @@ -602,7 +568,7 @@ fn test_cose_key_decode_dup_fail() { "02", "41", "01", // 2 (kid) => 1-bstr "01", "01", // 1 (kty) => OKP (duplicate label) ), - "expected unique map label", + "OutOfOrderKey", ), ]; for (key_data, err_msg) in tests.iter() { @@ -612,6 +578,18 @@ fn test_cose_key_decode_dup_fail() { } } +#[test] +fn test_cose_key_encode_dup_fail() { + let tests = vec![CoseKeyBuilder::new() + .param(10, Value::Unsigned(0)) + .param(10, Value::Unsigned(0)) + .build()]; + for key in tests { + let result = key.clone().to_vec(); + expect_err(result, "encode CBOR failure"); + } +} + #[test] fn test_key_builder() { let tests = vec![ @@ -619,10 +597,10 @@ fn test_key_builder() { CoseKeyBuilder::new_symmetric_key(vec![1, 2, 3]).build(), CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(vec![1,2,3]), - }, + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128), + Value::ByteString(vec![1, 2, 3]), + )], ..Default::default() }, ), @@ -633,10 +611,10 @@ fn test_key_builder() { CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), alg: Some(Algorithm::Assigned(iana::Algorithm::A128GCM)), - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(vec![1,2,3]), - }, + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128), + Value::ByteString(vec![1, 2, 3]), + )], ..Default::default() }, ), @@ -647,10 +625,10 @@ fn test_key_builder() { CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), key_id: vec![4, 5], - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(vec![1,2,3]), - }, + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128), + Value::ByteString(vec![1, 2, 3]), + )], ..Default::default() }, ), @@ -661,14 +639,16 @@ fn test_key_builder() { .build(), CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), - key_ops: btreeset! { + key_ops: vec![ KeyOperation::Assigned(iana::KeyOperation::Encrypt), KeyOperation::Assigned(iana::KeyOperation::Decrypt), - }, - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(vec![1,2,3]), - }, + ] + .into_iter() + .collect(), + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128), + Value::ByteString(vec![1, 2, 3]), + )], ..Default::default() }, ), @@ -679,10 +659,10 @@ fn test_key_builder() { CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), base_iv: vec![4, 5], - params: btreemap! { - Label::Int(iana::SymmetricKeyParameter::K as i128) => - cbor::Value::Bytes(vec![1,2,3]), - }, + params: vec![( + Label::Int(iana::SymmetricKeyParameter::K as i128), + Value::ByteString(vec![1, 2, 3]), + )], ..Default::default() }, ), @@ -698,6 +678,6 @@ fn test_key_builder_core_param_panic() { // Attempting to set a core `KeyParameter` (in range [1,5]) via `.param()` panics. let _key = CoseKeyBuilder::new_ec2_pub_key(iana::EllipticCurve::P_256, vec![1, 2, 3], vec![2, 3, 4]) - .param(1, cbor::Value::Null) + .param(1, Value::Simple(SimpleValue::NullValue)) .build(); } diff --git a/src/lib.rs b/src/lib.rs index fcfff54..a0a558c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ //! Set of types for supporting [CBOR Object Signing and Encryption (COSE)][COSE]. //! -//! Builds on the [`serde_cbor`](https://docs.rs/serde-cbor) crate for underlying [CBOR][CBOR] support. +//! Builds on the [`sk-cbor`](https://docs.rs/sk-cbor) crate for underlying [CBOR][CBOR] support. //! //! ## Usage //! @@ -60,7 +60,7 @@ //! "'{}' + '{}' => {}", //! String::from_utf8_lossy(pt), //! String::from_utf8_lossy(aad), -//! hex::encode(sign1.to_vec().unwrap()) +//! hex::encode(&sign1_data) //! ); //! //! // At the receiving end, deserialize the bytes back to a `CoseSign1` object. @@ -93,7 +93,12 @@ //! [COSE]: https://tools.ietf.org/html/rfc8152 //! [CBOR]: https://tools.ietf.org/html/rfc7049 +#![no_std] #![deny(broken_intra_doc_links)] +extern crate alloc; + +/// Re-export of the `sk-cbor` crate used for underlying CBOR encoding. +pub use sk_cbor as cbor; #[macro_use] pub(crate) mod util; diff --git a/src/mac/mod.rs b/src/mac/mod.rs index c2ffccf..328715b 100644 --- a/src/mac/mod.rs +++ b/src/mac/mod.rs @@ -17,12 +17,13 @@ //! COSE_Mac functionality. use crate::{ + cbor, + cbor::values::{SimpleValue, Value}, iana, util::{cbor_type_error, AsCborValue}, - CborSerializable, CoseRecipient, Header, + CborSerializable, CoseError, CoseRecipient, Header, }; -use serde::de::Unexpected; -use serde_cbor as cbor; +use alloc::{borrow::ToOwned, vec, vec::Vec}; #[cfg(test)] mod tests; @@ -53,60 +54,60 @@ impl crate::TaggedCborSerializable for CoseMac { } impl AsCborValue for CoseMac { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 5 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 5 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 5 items")); } // Remove array elements in reverse order to avoid shifts. let mut recipients = Vec::new(); match a.remove(4) { - cbor::Value::Array(a) => { + Value::Array(a) => { for val in a { recipients.push(CoseRecipient::from_cbor_value(val)?); } } - v => return cbor_type_error(&v, &"array"), + v => return cbor_type_error(&v, "array"), } Ok(Self { recipients, tag: match a.remove(3) { - cbor::Value::Bytes(b) => b, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => b, + v => return cbor_type_error(&v, "bstr"), }, payload: match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - cbor::Value::Null => None, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => Some(b), + Value::Simple(SimpleValue::NullValue) => None, + v => return cbor_type_error(&v, "bstr"), }, unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - match &self.payload { - None => cbor::Value::Null, - Some(b) => cbor::Value::Bytes(b.clone()), + fn to_cbor_value(self) -> Result { + let mut v = vec![ + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + match self.payload { + None => Value::Simple(SimpleValue::NullValue), + Some(b) => Value::ByteString(b), }, - cbor::Value::Bytes(self.tag.clone()), - cbor::Value::Array(self.recipients.iter().map(|r| r.to_cbor_value()).collect()), - ]) + Value::ByteString(self.tag), + ]; + let mut arr = Vec::new(); + for r in self.recipients { + arr.push(r.to_cbor_value()?); + } + v.push(Value::Array(arr)); + Ok(Value::Array(v)) } } -cbor_serialize!(CoseMac); - impl CoseMac { /// Verify the `tag` value using the provided `mac` function, feeding it /// the `tag` value and the combined to-be-MACed data (in that order). @@ -131,7 +132,7 @@ impl CoseMac { fn tbm(&self, external_aad: &[u8]) -> Vec { mac_structure_data( MacContext::CoseMac, - &self.protected, + self.protected.clone(), external_aad, self.payload.as_ref().expect("payload missing"), // safe: documented ) @@ -209,49 +210,44 @@ impl crate::TaggedCborSerializable for CoseMac0 { } impl AsCborValue for CoseMac0 { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 4 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 4 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 4 items")); } // Remove array elements in reverse order to avoid shifts. Ok(Self { tag: match a.remove(3) { - cbor::Value::Bytes(b) => b, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => b, + v => return cbor_type_error(&v, "bstr"), }, payload: match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - cbor::Value::Null => None, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => Some(b), + Value::Simple(SimpleValue::NullValue) => None, + v => return cbor_type_error(&v, "bstr"), }, unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - match &self.payload { - None => cbor::Value::Null, - Some(b) => cbor::Value::Bytes(b.clone()), + fn to_cbor_value(self) -> Result { + Ok(Value::Array(vec![ + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + match self.payload { + None => Value::Simple(SimpleValue::NullValue), + Some(b) => Value::ByteString(b), }, - cbor::Value::Bytes(self.tag.clone()), - ]) + Value::ByteString(self.tag), + ])) } } -cbor_serialize!(CoseMac0); - impl CoseMac0 { /// Verify the `tag` value using the provided `mac` function, feeding it /// the `tag` value and the combined to-be-MACed data (in that order). @@ -276,7 +272,7 @@ impl CoseMac0 { fn tbm(&self, external_aad: &[u8]) -> Vec { mac_structure_data( MacContext::CoseMac0, - &self.protected, + self.protected.clone(), external_aad, self.payload.as_ref().expect("payload missing"), // safe: documented ) @@ -352,22 +348,25 @@ impl MacContext { /// ``` pub fn mac_structure_data( context: MacContext, - protected: &Header, + protected: Header, external_aad: &[u8], payload: &[u8], ) -> Vec { let arr = vec![ - cbor::Value::Text(context.text().to_owned()), + Value::TextString(context.text().to_owned()), if protected.is_empty() { - cbor::Value::Bytes(vec![]) + Value::ByteString(vec![]) } else { - cbor::Value::Bytes( + Value::ByteString( protected.to_vec().expect("failed to serialize header"), /* safe: always * serializable */ ) }, - cbor::Value::Bytes(external_aad.to_vec()), - cbor::Value::Bytes(payload.to_vec()), + Value::ByteString(external_aad.to_vec()), + Value::ByteString(payload.to_vec()), ]; - cbor::to_vec(&cbor::Value::Array(arr)).expect("failed to serialize Enc_structure") // safe: always serializable + + let mut data = Vec::new(); + cbor::writer::write(Value::Array(arr), &mut data).unwrap(); // safe: always serializable + data } diff --git a/src/mac/tests.rs b/src/mac/tests.rs index c2433d6..c9fbc15 100644 --- a/src/mac/tests.rs +++ b/src/mac/tests.rs @@ -16,8 +16,13 @@ use super::*; use crate::{ - util::expect_err, CborSerializable, ContentType, CoseKeyBuilder, CoseRecipientBuilder, - HeaderBuilder, TaggedCborSerializable, + cbor::values::Value, util::expect_err, CborSerializable, ContentType, CoseKeyBuilder, + CoseRecipientBuilder, HeaderBuilder, TaggedCborSerializable, +}; +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, }; #[test] @@ -47,7 +52,7 @@ fn test_cose_mac_decode() { ), ]; for (i, (mac, mac_data)) in tests.iter().enumerate() { - let got = mac.to_vec().unwrap(); + let got = mac.clone().to_vec().unwrap(); assert_eq!(*mac_data, hex::encode(&got), "case {}", i); let got = CoseMac::from_slice(&got).unwrap(); @@ -60,11 +65,11 @@ fn test_cose_mac_decode_fail() { let tests = vec![ ( concat!( - "a2", // 2-map (should be tuple) - "40", // 0-bstr (special case for empty protected headers, rather than 41a0) - "a0", // 0-map - "40", // 0-bstr - "40", // 0-bstr + "a2", // 2-map (should be tuple) + "40", // 0-bstr (special case for empty protected headers, rather than 41a0) + "a0", // 0-map + "4100", // 1-bstr + "40", // 0-bstr ), "expected array", ), @@ -75,7 +80,6 @@ fn test_cose_mac_decode_fail() { "a0", // 0-map "40", // 0-bstr "40", // 0-bstr - "80", // 0-arr ), "expected array with 5 items", ), @@ -202,11 +206,11 @@ fn test_rfc8152_cose_mac_decode() { .key_id(b"meriadoc.brandybuck@buckland.example".to_vec()) .value( iana::HeaderAlgorithmParameter::StaticKeyId as i128, - cbor::Value::Bytes(b"peregrin.took@tuckborough.example".to_vec()) + Value::ByteString(b"peregrin.took@tuckborough.example".to_vec()) ) .value( iana::HeaderAlgorithmParameter::PartyUNonce as i128, - cbor::Value::Bytes(hex::decode("4d8553e7e74f3c6a3a9dd3ef286a8195cbf8a23d19558ccfec7d34b824f42d92bd06bd2c7f0271f0214e141fb779ae2856abf585a58368b017e7f2a9e5ce4db5").unwrap()) + Value::ByteString(hex::decode("4d8553e7e74f3c6a3a9dd3ef286a8195cbf8a23d19558ccfec7d34b824f42d92bd06bd2c7f0271f0214e141fb779ae2856abf585a58368b017e7f2a9e5ce4db5").unwrap()) ) .build(), ) @@ -284,7 +288,7 @@ fn test_rfc8152_cose_mac_decode() { CoseKeyBuilder::new_ec2_pub_key_y_sign(iana::EllipticCurve::P_521, hex::decode("0043b12669acac3fd27898ffba0bcd2e6c366d53bc4db71f909a759304acfb5e18cdc7ba0b13ff8c7636271a6924b1ac63c02688075b55ef2d613574e7dc242f79c3").unwrap(), true) - .build().to_cbor_value()) + .build().to_cbor_value().unwrap()) .key_id(b"bilbo.baggins@hobbiton.example".to_vec()) .build(), ) @@ -341,7 +345,7 @@ fn test_rfc8152_cose_mac_decode() { ]; for (i, (mac, mac_data)) in tests.iter().enumerate() { - let got = mac.to_tagged_vec().unwrap(); + let got = mac.clone().to_tagged_vec().unwrap(); assert_eq!(*mac_data, hex::encode(&got), "case {}", i); let got = CoseMac::from_tagged_slice(&got).unwrap(); @@ -374,7 +378,7 @@ fn test_cose_mac0_decode() { ), ]; for (i, (mac, mac_data)) in tests.iter().enumerate() { - let got = mac.to_vec().unwrap(); + let got = mac.clone().to_vec().unwrap(); assert_eq!(*mac_data, hex::encode(&got), "case {}", i); let got = CoseMac0::from_slice(&got).unwrap(); @@ -386,11 +390,11 @@ fn test_cose_mac0_decode_fail() { let tests = vec![ ( concat!( - "a2", // 2-map (should be tuple) - "40", // 0-bstr (special case for empty protected headers, rather than 41a0) - "a0", // 0-map - "40", // 0-bstr - "40", // 0-bstr + "a2", // 2-map (should be tuple) + "40", // 0-bstr (special case for empty protected headers, rather than 41a0) + "a0", // 0-map + "4100", // 0-bstr + "40", // 0-bstr ), "expected array", ), @@ -478,7 +482,7 @@ fn test_rfc8152_cose_mac0_decode() { )]; for (i, (mac, mac_data)) in tests.iter().enumerate() { - let got = mac.to_tagged_vec().unwrap(); + let got = mac.clone().to_tagged_vec().unwrap(); assert_eq!(*mac_data, hex::encode(&got), "case {}", i); let got = CoseMac0::from_tagged_slice(&got).unwrap(); diff --git a/src/sign/mod.rs b/src/sign/mod.rs index aab3732..380ec09 100644 --- a/src/sign/mod.rs +++ b/src/sign/mod.rs @@ -17,13 +17,13 @@ //! COSE_Sign* functionality. use crate::{ - common::CborSerializable, + cbor, + cbor::{SimpleValue, Value}, iana, util::{cbor_type_error, AsCborValue}, - Header, + CborSerializable, CoseError, Header, }; -use serde::de::Unexpected; -use serde_cbor as cbor; +use alloc::{borrow::ToOwned, vec, vec::Vec}; #[cfg(test)] mod tests; @@ -46,40 +46,35 @@ pub struct CoseSignature { impl crate::CborSerializable for CoseSignature {} impl AsCborValue for CoseSignature { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 3 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 3 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 3 items")); } // Remove array elements in reverse order to avoid shifts. Ok(Self { signature: match a.remove(2) { - cbor::Value::Bytes(b) => b, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => b, + v => return cbor_type_error(&v, "bstr"), }, unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - cbor::Value::Bytes(self.signature.clone()), - ]) + fn to_cbor_value(self) -> Result { + Ok(Value::Array(vec![ + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + Value::ByteString(self.signature), + ])) } } -cbor_serialize!(CoseSignature); - /// Builder for [`CoseSignature`] objects. #[derive(Default)] pub struct CoseSignatureBuilder(CoseSignature); @@ -114,71 +109,66 @@ impl crate::TaggedCborSerializable for CoseSign { } impl AsCborValue for CoseSign { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 4 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 4 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 4 items")); } // Remove array elements in reverse order to avoid shifts. let mut signatures = vec![]; match a.remove(3) { - cbor::Value::Array(sigs) => { + Value::Array(sigs) => { for sig in sigs.into_iter() { - match CoseSignature::from_cbor_value::(sig) { + match CoseSignature::from_cbor_value(sig) { Ok(s) => signatures.push(s), Err(_e) => { - return Err(serde::de::Error::invalid_value( - Unexpected::StructVariant, - &"map for COSE_Signature", + return Err(CoseError::UnexpectedType( + "non-signature", + "map for COSE_Signature", )); } } } } v => { - return cbor_type_error(&v, &"array of COSE_Signature"); + return cbor_type_error(&v, "array of COSE_Signature"); } }; Ok(Self { signatures, payload: match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - cbor::Value::Null => None, - v => return cbor_type_error(&v, &"bstr or nil"), + Value::ByteString(b) => Some(b), + Value::Simple(SimpleValue::NullValue) => None, + v => return cbor_type_error(&v, "bstr or nil"), }, unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - match &self.payload { - Some(b) => cbor::Value::Bytes(b.clone()), - None => cbor::Value::Null, + fn to_cbor_value(self) -> Result { + let mut v = vec![ + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + match self.payload { + Some(b) => Value::ByteString(b), + None => Value::Simple(SimpleValue::NullValue), }, - cbor::Value::Array( - self.signatures - .iter() - .map(|sig| sig.to_cbor_value()) - .collect(), - ), - ]) + ]; + let mut arr = Vec::new(); + for sig in self.signatures { + arr.push(sig.to_cbor_value()?); + } + v.push(Value::Array(arr)); + Ok(Value::Array(v)) } } -cbor_serialize!(CoseSign); - impl CoseSign { /// Verify the indidated signature value, using `verifier` on the signature value and serialized /// data (in that order). @@ -199,8 +189,8 @@ impl CoseSign { fn tbs_data(&self, aad: &[u8], sig: &CoseSignature) -> Vec { sig_structure_data( SignatureContext::CoseSignature, - &self.protected, - Some(&sig.protected), + self.protected.clone(), + Some(sig.protected.clone()), aad, self.payload.as_ref().unwrap_or(&vec![]), ) @@ -276,49 +266,44 @@ impl crate::TaggedCborSerializable for CoseSign1 { } impl AsCborValue for CoseSign1 { - fn from_cbor_value(value: cbor::Value) -> Result { + fn from_cbor_value(value: Value) -> Result { let mut a = match value { - cbor::Value::Array(a) => a, - v => return cbor_type_error(&v, &"array"), + Value::Array(a) => a, + v => return cbor_type_error(&v, "array"), }; if a.len() != 4 { - return Err(serde::de::Error::invalid_value( - Unexpected::TupleVariant, - &"array with 4 items", - )); + return Err(CoseError::UnexpectedType("array", "array with 4 items")); } // Remove array elements in reverse order to avoid shifts. Ok(Self { signature: match a.remove(3) { - cbor::Value::Bytes(b) => b, - v => return cbor_type_error(&v, &"bstr"), + Value::ByteString(b) => b, + v => return cbor_type_error(&v, "bstr"), }, payload: match a.remove(2) { - cbor::Value::Bytes(b) => Some(b), - cbor::Value::Null => None, - v => return cbor_type_error(&v, &"bstr or nil"), + Value::ByteString(b) => Some(b), + Value::Simple(SimpleValue::NullValue) => None, + v => return cbor_type_error(&v, "bstr or nil"), }, unprotected: Header::from_cbor_value(a.remove(1))?, protected: Header::from_cbor_bstr(a.remove(0))?, }) } - fn to_cbor_value(&self) -> cbor::Value { - cbor::Value::Array(vec![ - self.protected.to_cbor_bstr(), - self.unprotected.to_cbor_value(), - match &self.payload { - Some(b) => cbor::Value::Bytes(b.clone()), - None => cbor::Value::Null, + fn to_cbor_value(self) -> Result { + Ok(Value::Array(vec![ + self.protected.cbor_bstr()?, + self.unprotected.to_cbor_value()?, + match self.payload { + Some(b) => Value::ByteString(b), + None => Value::Simple(SimpleValue::NullValue), }, - cbor::Value::Bytes(self.signature.clone()), - ]) + Value::ByteString(self.signature), + ])) } } -cbor_serialize!(CoseSign1); - impl CoseSign1 { /// Verify the signature value, using `verifier` on the signature value and serialized data (in /// that order). @@ -334,7 +319,7 @@ impl CoseSign1 { fn tbs_data(&self, aad: &[u8]) -> Vec { sig_structure_data( SignatureContext::CoseSign1, - &self.protected, + self.protected.clone(), None, aad, self.payload.as_ref().unwrap_or(&vec![]), @@ -406,31 +391,33 @@ impl SignatureContext { /// ``` pub fn sig_structure_data( context: SignatureContext, - body: &Header, - sign: Option<&Header>, + body: Header, + sign: Option
, aad: &[u8], payload: &[u8], ) -> Vec { let mut arr = vec![ - cbor::Value::Text(context.text().to_owned()), + Value::TextString(context.text().to_owned()), if body.is_empty() { - cbor::Value::Bytes(vec![]) + Value::ByteString(vec![]) } else { - cbor::Value::Bytes( + Value::ByteString( body.to_vec().expect("failed to serialize header"), // safe: always serializable ) }, ]; if let Some(sign) = sign { if sign.is_empty() { - arr.push(cbor::Value::Bytes(vec![])); + arr.push(Value::ByteString(vec![])); } else { - arr.push(cbor::Value::Bytes( + arr.push(Value::ByteString( sign.to_vec().expect("failed to serialize header"), // safe: always serializable )); } } - arr.push(cbor::Value::Bytes(aad.to_vec())); - arr.push(cbor::Value::Bytes(payload.to_vec())); - cbor::to_vec(&cbor::Value::Array(arr)).expect("failed to serialize Sig_structure") // safe: always serializable + arr.push(Value::ByteString(aad.to_vec())); + arr.push(Value::ByteString(payload.to_vec())); + let mut data = Vec::new(); + cbor::writer::write(Value::Array(arr), &mut data).unwrap(); // safe: always serializable + data } diff --git a/src/sign/tests.rs b/src/sign/tests.rs index dffc931..e919d6c 100644 --- a/src/sign/tests.rs +++ b/src/sign/tests.rs @@ -16,10 +16,14 @@ use super::*; use crate::{ - iana, util::expect_err, Algorithm, CborSerializable, ContentType, HeaderBuilder, - RegisteredLabel, TaggedCborSerializable, + cbor::values::Value, iana, util::expect_err, Algorithm, CborSerializable, ContentType, + HeaderBuilder, RegisteredLabel, TaggedCborSerializable, +}; +use alloc::{ + format, + string::{String, ToString}, + vec, }; -use serde_cbor as cbor; #[test] fn test_cose_signature_encode() { @@ -90,7 +94,7 @@ fn test_cose_signature_encode() { ), ]; for (i, (sig, sig_data)) in tests.iter().enumerate() { - let got = cbor::ser::to_vec(&sig).unwrap(); + let got = sig.clone().to_vec().unwrap(); assert_eq!(*sig_data, hex::encode(&got), "case {}", i); let got = CoseSignature::from_slice(&got).unwrap(); @@ -165,7 +169,7 @@ fn test_cose_signature_decode_fail() { "a0", // 0-map "43616263", // 0-bstr ), - "expected header struct", + "expected int/tstr", ), ]; for (sig_data, err_msg) in tests.iter() { @@ -348,34 +352,19 @@ fn test_cose_sign_encode() { ), ]; for (i, (sign, sign_data)) in tests.iter().enumerate() { - let got = cbor::ser::to_vec(&sign).unwrap(); + let got = sign.clone().to_vec().unwrap(); assert_eq!(*sign_data, hex::encode(&got), "case {}", i); let got = CoseSign::from_slice(&got).unwrap(); assert_eq!(*sign, got); // Repeat with tagged variant. - let got = sign.to_tagged_vec().unwrap(); + let got = sign.clone().to_tagged_vec().unwrap(); let tagged_sign_data = format!("d862{}", sign_data); assert_eq!(tagged_sign_data, hex::encode(&got), "tagged case {}", i); let got = CoseSign::from_tagged_slice(&got).unwrap(); assert_eq!(*sign, got); - - // Also exercise the `Read` / `Write` versions. - let mut got = vec![]; - sign.to_writer(&mut got).unwrap(); - assert_eq!(*sign_data, hex::encode(&got), "case {}", i); - - let got = CoseSign::from_reader(std::io::Cursor::new(&got)).unwrap(); - assert_eq!(*sign, got); - - let mut got = vec![]; - sign.to_tagged_writer(&mut got).unwrap(); - assert_eq!(tagged_sign_data, hex::encode(&got), "case {}", i); - - let got = CoseSign::from_tagged_reader(std::io::Cursor::new(&got)).unwrap(); - assert_eq!(*sign, got); } } @@ -476,7 +465,7 @@ fn test_cose_sign_decode_fail() { "43616263", // 3-bstr "80", // 0-tuple ), - "expected header struct", + "expected int/tstr", ), ]; for (sign_data, err_msg) in tests.iter() { @@ -553,13 +542,13 @@ fn test_cose_sign_tagged_decode_fail() { "43010203", // 3-bstr "80", // 0-tuple ), - "TrailingData", + "ExtraneousData", ), ( concat!( "18", // incomplete int ), - "EofWhileParsingValue", + "IncompleteCborData", ), ]; for (sign_data, err_msg) in tests.iter() { @@ -680,7 +669,7 @@ fn test_rfc8152_cose_sign_decode() { ( CoseSignBuilder::new() .protected(HeaderBuilder::new() - .text_value("reserved".to_owned(), cbor::Value::Bool(false)) + .text_value("reserved".to_owned(), Value::Simple(SimpleValue::FalseValue)) .add_critical_label(RegisteredLabel::Text("reserved".to_owned())) .build()) .payload(b"This is the content.".to_vec()) @@ -713,8 +702,14 @@ fn test_rfc8152_cose_sign_decode() { ]; for (i, (sign, sign_data)) in tests.iter().enumerate() { - let got = sign.to_tagged_vec().unwrap(); - assert_eq!(*sign_data, hex::encode(&got), "case {}", i); + let got = sign.clone().to_tagged_vec().unwrap(); + assert_eq!( + *sign_data, + hex::encode(&got), + "case {}: encode {:?}", + i, + sign + ); let got = CoseSign::from_tagged_slice(&got).unwrap(); assert_eq!(*sign, got); @@ -789,14 +784,14 @@ fn test_cose_sign1_encode() { ), ]; for (i, (sign, sign_data)) in tests.iter().enumerate() { - let got = cbor::ser::to_vec(&sign).unwrap(); + let got = sign.clone().to_vec().unwrap(); assert_eq!(*sign_data, hex::encode(&got), "case {}", i); let got = CoseSign1::from_slice(&got).unwrap(); assert_eq!(*sign, got); // Repeat with tagged variant. - let got = sign.to_tagged_vec().unwrap(); + let got = sign.clone().to_tagged_vec().unwrap(); let want_hex = format!("d2{}", sign_data); assert_eq!(want_hex, hex::encode(&got), "tagged case {}", i); @@ -888,7 +883,7 @@ fn test_cose_sign1_decode_fail() { "43616263", // 3-bstr "40", // 0-bstr ), - "expected header struct", + "expected int/tstr", ), ]; for (sign_data, err_msg) in tests.iter() { @@ -965,7 +960,7 @@ fn test_cose_sign1_tagged_decode_fail() { "43010203", // 3-bstr "40", // 0-bstr ), - "TrailingData", + "ExtraneousData", ), ( concat!( @@ -1006,7 +1001,7 @@ fn test_rfc8152_cose_sign1_decode() { ]; for (i, (sign, sign_data)) in tests.iter().enumerate() { - let got = sign.to_tagged_vec().unwrap(); + let got = sign.clone().to_tagged_vec().unwrap(); assert_eq!(*sign_data, hex::encode(&got), "case {}", i); let got = CoseSign1::from_tagged_slice(&got).unwrap(); diff --git a/src/util/mod.rs b/src/util/mod.rs index 4047f50..a85a9a0 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -16,42 +16,46 @@ //! Common internal utilities. -use serde_cbor as cbor; +use crate::{ + cbor::values::{SimpleValue, Value}, + CoseError, +}; #[cfg(test)] mod tests; -/// Map a `serde_cbor::Value` into a serde type error. -pub(crate) fn cbor_type_error(v: &cbor::Value, msg: &M) -> Result -where - M: serde::de::Expected, - E: serde::de::Error, -{ - Err(serde::de::Error::invalid_type( - match v { - cbor::Value::Integer(i) => serde::de::Unexpected::Signed(*i as i64), - cbor::Value::Text(t) => serde::de::Unexpected::Str(t), - cbor::Value::Null => serde::de::Unexpected::Unit, - cbor::Value::Bool(b) => serde::de::Unexpected::Bool(*b), - cbor::Value::Float(f) => serde::de::Unexpected::Float(*f), - cbor::Value::Bytes(b) => serde::de::Unexpected::Bytes(b), - cbor::Value::Array(_) => serde::de::Unexpected::TupleVariant, - cbor::Value::Map(_) => serde::de::Unexpected::StructVariant, - _ => serde::de::Unexpected::Other("invalid type"), +/// Return an error indicating that an unexpected CBOR type was encountered. +pub(crate) fn cbor_type_error(value: &Value, want: &'static str) -> Result { + let got = match value { + Value::Unsigned(_) => "uint", + Value::Negative(_) => "nint", + Value::ByteString(_) => "bstr", + Value::TextString(_) => "tstr", + Value::Array(_) => "array", + Value::Map(_) => "map", + Value::Tag(_, _) => "tag", + Value::Simple(s) => match s { + SimpleValue::FalseValue => "false", + SimpleValue::TrueValue => "true", + SimpleValue::NullValue => "null", + SimpleValue::Undefined => "undefined", }, - msg, - )) + }; + Err(CoseError::UnexpectedType(got, want)) } -/// Trait for types that can be converted to/from a `serde_cbor::Value` +/// Trait for types that can be converted to/from a [`Value`]. pub trait AsCborValue: Sized { - fn from_cbor_value(value: cbor::Value) -> Result; - fn to_cbor_value(&self) -> cbor::Value; + /// Convert a [`Value`] into an instance of the type. + fn from_cbor_value(value: Value) -> Result; + /// Convert the object into a [`Value`], consuming it along the way. + fn to_cbor_value(self) -> Result; } /// Check for an expected error. #[cfg(test)] -pub fn expect_err(result: Result, err_msg: &str) { +pub fn expect_err(result: Result, err_msg: &str) { + use alloc::format; assert!(result.is_err(), "expected error containing '{}'", err_msg); let err = result.err(); assert!( @@ -62,24 +66,6 @@ pub fn expect_err(result: Result, err_msg: &str) { ); } -/// Macro that emits implementations of `Serialize` and `Deserialize` for -/// types that implement the [`AsCborValue`] trait. -macro_rules! cbor_serialize { - ( $otype: ty ) => { - impl ::serde::Serialize for $otype { - fn serialize(&self, serializer: S) -> Result { - self.to_cbor_value().serialize(serializer) - } - } - - impl<'de> ::serde::Deserialize<'de> for $otype { - fn deserialize>(deserializer: D) -> Result { - Self::from_cbor_value(cbor::Value::deserialize(deserializer)?) - } - } - }; -} - // Macros to reduce boilerplate when creating `CoseSomethingBuilder` structures. /// Add `new()` and `build()` methods to the builder. diff --git a/src/util/tests.rs b/src/util/tests.rs index aeefa93..9f6ffae 100644 --- a/src/util/tests.rs +++ b/src/util/tests.rs @@ -15,38 +15,28 @@ //////////////////////////////////////////////////////////////////////////////// use super::*; -use crate::util::expect_err; -use maplit::btreemap; -use serde::de::value::Error; -use serde_cbor as cbor; +use crate::{ + cbor::values::{SimpleValue, Value}, + util::expect_err, +}; +use alloc::{borrow::ToOwned, boxed::Box, vec}; #[test] fn test_cbor_type_error() { - let val = cbor::Value::Null; - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "unit value"); - let val = cbor::Value::Bool(true); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "boolean"); - let val = cbor::Value::Integer(128); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "integer"); - let val = cbor::Value::Float(64.0); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "float"); - let val = cbor::Value::Bytes(vec![1, 2]); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "byte array"); - let val = cbor::Value::Text("string".to_owned()); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "string"); - let val = cbor::Value::Array(vec![cbor::Value::Null]); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "tuple variant"); - let val = cbor::Value::Map(btreemap! {}); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "struct variant"); - let val = cbor::Value::Tag(1, Box::new(cbor::Value::Null)); - let e = cbor_type_error::<(), _, Error>(&val, &"a"); - expect_err(e, "invalid type"); + let cases = vec![ + (Value::Simple(SimpleValue::NullValue), "null"), + (Value::Simple(SimpleValue::TrueValue), "true"), + (Value::Simple(SimpleValue::FalseValue), "false"), + (Value::Simple(SimpleValue::Undefined), "undefined"), + (Value::Unsigned(128), "uint"), + (Value::ByteString(vec![1, 2]), "bstr"), + (Value::TextString("string".to_owned()), "tstr"), + (Value::Array(vec![Value::Unsigned(0)]), "array"), + (Value::Map(vec![]), "map"), + (Value::Tag(1, Box::new(Value::Unsigned(0))), "tag"), + ]; + for (val, want) in cases { + let e = cbor_type_error::<()>(&val, "a"); + expect_err(e, want); + } }