diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab4adcd..f97bc28 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,6 +18,13 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: setup Rust + uses: dtolnay/rust-toolchain@stable + if: ${{ github.event.act }} + with: + toolchain: stable + components: rustfmt, clippy + - name: Build run: cargo build --all-targets diff --git a/.github/workflows/event.json b/.github/workflows/event.json index e69de29..176cfa8 100644 --- a/.github/workflows/event.json +++ b/.github/workflows/event.json @@ -0,0 +1,3 @@ +{ + "act": true +} diff --git a/macros/src/to_cbor.rs b/macros/src/to_cbor.rs index db2c8d7..f970acc 100644 --- a/macros/src/to_cbor.rs +++ b/macros/src/to_cbor.rs @@ -102,7 +102,7 @@ fn named_fields(isomdl_path: Ident, ident: Ident, input: FieldsNamed) -> TokenSt fn to_cbor(self) -> Value { let map = self.to_ns_map() .into_iter() - .map(|(k, v)| (ciborium::Value::Text(k), v.try_into().unwrap())) + .map(|(k, v)| (Value::Text(k), v.try_into().unwrap())) .collect(); Value::Map(map) } diff --git a/src/cbor.rs b/src/cbor.rs index b4358bb..2e00336 100644 --- a/src/cbor.rs +++ b/src/cbor.rs @@ -1,106 +1,17 @@ use std::io::Cursor; use ciborium::Value; -use coset::{cbor, CoseError, EndOfFile}; +use coset::{cbor, CoseError}; use serde::{de, Serialize}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum CborError { - /// CBOR decoding failure. - #[error("CBOR decoding failure: {0}")] - DecodeFailed(cbor::de::Error), - /// Duplicate map key detected. - #[error("duplicate map key")] - DuplicateMapKey, - /// CBOR encoding failure. - #[error("CBOR encoding failure")] - EncodeFailed, - /// CBOR input had extra data. - #[error("extraneous data")] - ExtraneousData, - /// Integer value on the wire is outside the range of integers representable in this crate. - /// See . - #[error("integer value out of range")] - OutOfRangeIntegerValue, - /// Unexpected CBOR item encountered (got, want). - #[error("unexpected item: {0}, want {1}")] - UnexpectedItem(&'static str, &'static str), - /// Unrecognized value in IANA-controlled range (with no private range). - #[error("unregistered IANA value")] - UnregisteredIanaValue, - /// Unrecognized value in neither IANA-controlled range nor private range. - #[error("unregistered non-private IANA value")] - UnregisteredIanaNonPrivateValue, - /// Value contains non-finite float (NaN or Infinity). - #[error("non finite floats")] - NonFiniteFloats, -} - -impl PartialEq for CborError { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::DecodeFailed(_), Self::DecodeFailed(_)) => true, - (Self::DuplicateMapKey, Self::DuplicateMapKey) => true, - (Self::EncodeFailed, Self::EncodeFailed) => true, - (Self::ExtraneousData, Self::ExtraneousData) => true, - (Self::OutOfRangeIntegerValue, Self::OutOfRangeIntegerValue) => true, - (Self::UnexpectedItem(l_msg, l_want), Self::UnexpectedItem(r_msg, r_want)) => { - l_msg == r_msg && l_want == r_want - } - (Self::UnregisteredIanaValue, Self::UnregisteredIanaValue) => true, - (Self::UnregisteredIanaNonPrivateValue, Self::UnregisteredIanaNonPrivateValue) => true, - (Self::NonFiniteFloats, Self::NonFiniteFloats) => true, - _ => false, - } - } -} - -impl Eq for CborError {} - -impl Clone for CborError { - fn clone(&self) -> Self { - match self { - CborError::DecodeFailed(_) => panic!("cannot clone"), - CborError::DuplicateMapKey => CborError::DuplicateMapKey, - CborError::EncodeFailed => CborError::EncodeFailed, - CborError::ExtraneousData => CborError::ExtraneousData, - CborError::OutOfRangeIntegerValue => CborError::OutOfRangeIntegerValue, - CborError::UnexpectedItem(msg, want) => CborError::UnexpectedItem(msg, want), - CborError::UnregisteredIanaValue => CborError::UnregisteredIanaValue, - CborError::UnregisteredIanaNonPrivateValue => { - CborError::UnregisteredIanaNonPrivateValue - } - CborError::NonFiniteFloats => CborError::NonFiniteFloats, - } - } -} - -impl From for CborError { - fn from(e: CoseError) -> Self { - match e { - CoseError::DecodeFailed(e) => CborError::DecodeFailed(e), - CoseError::DuplicateMapKey => CborError::DuplicateMapKey, - CoseError::EncodeFailed => CborError::EncodeFailed, - CoseError::ExtraneousData => CborError::ExtraneousData, - CoseError::OutOfRangeIntegerValue => CborError::OutOfRangeIntegerValue, - CoseError::UnexpectedItem(s, s2) => CborError::UnexpectedItem(s, s2), - CoseError::UnregisteredIanaValue => CborError::UnregisteredIanaValue, - CoseError::UnregisteredIanaNonPrivateValue => { - CborError::UnregisteredIanaNonPrivateValue - } - } - } -} +use std::error::Error; +use std::fmt; pub fn to_vec(value: &T) -> Result, CborError> where T: serde::Serialize, { let mut buf = Vec::new(); - ciborium::into_writer(value, &mut buf) - .map_err(coset::CoseError::from) - .map_err(CborError::from)?; + ciborium::into_writer(value, &mut buf).map_err(|_| CborError(CoseError::EncodeFailed))?; Ok(buf) } @@ -108,29 +19,49 @@ pub fn from_slice(slice: &[u8]) -> Result where T: de::DeserializeOwned, { - ciborium::from_reader(Cursor::new(&slice)) - .map_err(|e| CoseError::DecodeFailed(ciborium::de::Error::Semantic(None, e.to_string()))) - .map_err(CborError::from) + ciborium::from_reader(Cursor::new(&slice)).map_err(|e| { + CborError(CoseError::DecodeFailed(ciborium::de::Error::Semantic( + None, + e.to_string(), + ))) + }) } /// Convert a `ciborium::Value` into a type `T` #[allow(clippy::needless_pass_by_value)] -pub fn from_value(value: ciborium::Value) -> Result +pub fn from_value(value: Value) -> Result where T: de::DeserializeOwned, { - // TODO implement in a way that doesn't require - // roundtrip through buffer (i.e. by implementing - // `serde::de::Deserializer` for `Value` and then doing - // `T::deserialize(value)`). - let buf = to_vec(&value)?; - from_slice(buf.as_slice()) + Value::deserialized(&value).map_err(|_| { + CoseError::DecodeFailed(cbor::de::Error::Semantic( + None, + "cannot deserialize".to_string(), + )) + }) } -pub fn into_value(v: S) -> Result +pub fn into_value(v: S) -> Result where S: Serialize, { - let bytes = to_vec(&v)?; - from_slice(&bytes) + Value::serialized(&v).map_err(|_| CoseError::EncodeFailed) } + +// Wrapper struct to implement Error for CoseError +#[derive(Debug)] +pub struct CborError(pub CoseError); + +impl From for CborError { + fn from(err: CoseError) -> CborError { + CborError(err) + } +} + +impl fmt::Display for CborError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl Error for CborError {} diff --git a/src/cose.rs b/src/cose.rs index 609aa60..157e0bd 100644 --- a/src/cose.rs +++ b/src/cose.rs @@ -30,6 +30,16 @@ where pub fn new(tagged: bool, inner: T) -> Self { Self { tagged, inner } } + + /// If we are serialized as tagged. + pub fn is_tagged(&self) -> bool { + self.tagged + } + + /// Set serialization to tagged. + pub fn set_tagged(&mut self) { + self.tagged = true; + } } impl Deref for MaybeTagged diff --git a/src/cose/mac0.rs b/src/cose/mac0.rs index 02528fc..0d99eed 100644 --- a/src/cose/mac0.rs +++ b/src/cose/mac0.rs @@ -209,16 +209,6 @@ impl MaybeTagged { ), } } - - /// If we are serialized as tagged. - pub fn is_tagged(&self) -> bool { - self.tagged - } - - /// Set serialization to tagged. - pub fn set_tagged(&mut self) { - self.tagged = true; - } } mod hmac { diff --git a/src/cose/sign1.rs b/src/cose/sign1.rs index 4d1dfa6..e3e8e51 100644 --- a/src/cose/sign1.rs +++ b/src/cose/sign1.rs @@ -223,16 +223,6 @@ impl MaybeTagged { ), } } - - /// If we are serialized as tagged. - pub fn is_tagged(&self) -> bool { - self.tagged - } - - /// Set serialization to tagged. - pub fn set_tagged(&mut self) { - self.tagged = true; - } } mod p256 { diff --git a/src/definitions/device_engagement.rs b/src/definitions/device_engagement.rs index fcb4a06..9edf7e0 100644 --- a/src/definitions/device_engagement.rs +++ b/src/definitions/device_engagement.rs @@ -168,19 +168,15 @@ impl From for ciborium::Value { if let Some(methods) = device_engagement.device_retrieval_methods { let methods = Vec::from(methods) .into_iter() - .map(cbor::into_value) - .collect::, CborError>>() - .unwrap(); + .map(ciborium::Value::from) + .collect(); map.push(( ciborium::Value::Integer(2.into()), ciborium::Value::Array(methods), )); } if let Some(methods) = device_engagement.server_retrieval_methods { - map.push(( - ciborium::Value::Integer(3.into()), - cbor::into_value(methods).unwrap(), - )); + map.push((ciborium::Value::Integer(3.into()), methods.into())); } if let Some(_info) = device_engagement.protocol_info { // Usage of protocolinfo is RFU and should for now be none @@ -286,8 +282,7 @@ impl TryFrom for DeviceRetrievalMethod { if >::into(*i1) == 1 && >::into(*i11) == 1 => { - let nfc_options = - NfcOptions::try_from(methods.clone()).map_err(|_| Error::Malformed)?; + let nfc_options = NfcOptions::try_from(methods.clone())?; Ok(DeviceRetrievalMethod::NFC(nfc_options)) } [ciborium::Value::Integer(i2), ciborium::Value::Integer(i1), methods] @@ -320,9 +315,9 @@ impl From for ciborium::Value { let transport_type = drm.transport_type().into(); let version = drm.version().into(); let retrieval_method = match drm { - DeviceRetrievalMethod::NFC(opts) => cbor::into_value(opts).unwrap(), - DeviceRetrievalMethod::BLE(opts) => cbor::into_value(opts).unwrap(), - DeviceRetrievalMethod::WIFI(opts) => cbor::into_value(opts).unwrap(), + DeviceRetrievalMethod::NFC(opts) => opts.into(), + DeviceRetrievalMethod::BLE(opts) => opts.into(), + DeviceRetrievalMethod::WIFI(opts) => opts.into(), }; ciborium::Value::Array(vec![transport_type, version, retrieval_method]) } @@ -340,8 +335,8 @@ impl TryFrom for BleOptions { Ok((k, v)) }) .collect::, Error>>()?; - let v1: Option = map.remove(&1).map(ciborium::Value::into); - let v2: Option = map.remove(&11).map(ciborium::Value::into); + let v1: Option = map.remove(&1); + let v2: Option = map.remove(&11); let central_client_mode = match (v1, v2) { (Some(ciborium::Value::Bool(true)), Some(ciborium::Value::Bytes(uuid))) => { let uuid_bytes: [u8; 16] = uuid.try_into().map_err(|_| Error::Malformed)?; @@ -353,10 +348,7 @@ impl TryFrom for BleOptions { _ => return Err(Error::Malformed), }; - let peripheral_server_mode = match ( - map.remove(&0).map(ciborium::Value::into), - map.remove(&10).map(ciborium::Value::into), - ) { + let peripheral_server_mode = match (map.remove(&0), map.remove(&10)) { (Some(ciborium::Value::Bool(true)), Some(ciborium::Value::Bytes(uuid))) => { let uuid_bytes: [u8; 16] = uuid.try_into().map_err(|_| Error::Malformed)?; let ble_device_address = match map.remove(&20) { @@ -419,10 +411,7 @@ impl From for ciborium::Value { ciborium::Value::Bytes(uuid.as_bytes().to_vec()), )); if let Some(address) = ble_device_address { - map.push(( - ciborium::Value::Integer(20.into()), - cbor::into_value(address).unwrap(), - )); + map.push((ciborium::Value::Integer(20.into()), address.into())); } } None => { @@ -445,7 +434,7 @@ impl TryFrom for WifiOptions { map: &BTreeMap, idx: i128, ) -> Result, Error> { - match map.get(&idx).cloned().map(ciborium::Value::into) { + match map.get(&idx) { None => Ok(None), Some(ciborium::Value::Text(text)) => Ok(Some(text.to_string())), _ => Err(Error::InvalidWifiOptions), @@ -456,10 +445,11 @@ impl TryFrom for WifiOptions { map: &BTreeMap, idx: i128, ) -> Result, Error> { - match map.get(&idx).cloned().map(ciborium::Value::into) { + match map.get(&idx) { None => Ok(None), Some(ciborium::Value::Integer(int_val)) => { - let uint_val = u64::try_from(int_val).map_err(|_| Error::InvalidWifiOptions)?; + let uint_val = + u64::try_from(*int_val).map_err(|_| Error::InvalidWifiOptions)?; Ok(Some(uint_val)) } _ => Err(Error::InvalidWifiOptions), @@ -530,28 +520,16 @@ impl From for ciborium::Value { fn from(o: WifiOptions) -> ciborium::Value { let mut map = vec![]; if let Some(v) = o.pass_phrase { - map.push(( - ciborium::Value::Integer(0.into()), - cbor::into_value(v).unwrap(), - )); + map.push((ciborium::Value::Integer(0.into()), v.into())); } if let Some(v) = o.channel_info_operating_class { - map.push(( - ciborium::Value::Integer(1.into()), - cbor::into_value(v).unwrap(), - )); + map.push((ciborium::Value::Integer(1.into()), v.into())); } if let Some(v) = o.channel_info_channel_number { - map.push(( - ciborium::Value::Integer(2.into()), - cbor::into_value(v).unwrap(), - )); + map.push((ciborium::Value::Integer(2.into()), v.into())); } if let Some(v) = o.band_info { - map.push(( - ciborium::Value::Integer(3.into()), - cbor::into_value(v).unwrap(), - )); + map.push((ciborium::Value::Integer(3.into()), v.into())); } ciborium::Value::Map(map) @@ -565,22 +543,14 @@ impl From for ciborium::Value { if let Some((x, y, z)) = m.web_api { map.push(( "webApi".to_string().into(), - ciborium::Value::Array(vec![ - cbor::into_value(x).unwrap(), - cbor::into_value(y).unwrap(), - cbor::into_value(z).unwrap(), - ]), + ciborium::Value::Array(vec![x.into(), y.into(), z.into()]), )); } if let Some((x, y, z)) = m.oidc { map.push(( "oidc".to_string().into(), - ciborium::Value::Array(vec![ - cbor::into_value(x).unwrap(), - cbor::into_value(y).unwrap(), - cbor::into_value(z).unwrap(), - ]), + ciborium::Value::Array(vec![x.into(), y.into(), z.into()]), )); } diff --git a/src/definitions/device_engagement/error.rs b/src/definitions/device_engagement/error.rs index d433659..365cc73 100644 --- a/src/definitions/device_engagement/error.rs +++ b/src/definitions/device_engagement/error.rs @@ -25,8 +25,6 @@ pub enum Error { Tag24Error, #[error("Could not deserialize from cbor")] CborError, - #[error("Could not deserialize from cbor")] - CborErrorWithSource(CborError), #[error("NFC Command Data Length must be between 255 and 65535")] InvalidNfcCommandDataLengthError, #[error("NFC Response Data Length must be between 256 and 65536")] @@ -45,12 +43,6 @@ impl From for Error { } } -impl From for Error { - fn from(_: coset::CoseError) -> Self { - Error::CborError - } -} - impl From for Error { fn from(_: CborError) -> Self { Error::CborError diff --git a/src/definitions/device_key/cose_key.rs b/src/definitions/device_key/cose_key.rs index b532739..ab40966 100644 --- a/src/definitions/device_key/cose_key.rs +++ b/src/definitions/device_key/cose_key.rs @@ -30,8 +30,6 @@ use p256::EncodedPoint; use serde::{Deserialize, Serialize}; use ssi_jwk::JWK; -use crate::cbor::CborError; - /// An implementation of RFC-8152 [COSE_Key](https://datatracker.ietf.org/doc/html/rfc8152#section-13) /// restricted to the requirements of ISO/IEC 18013-5:2021. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -88,8 +86,6 @@ pub enum Error { InvalidCoseKey, #[error("Constructing a JWK from CoseKey with point-compression is not supported.")] UnsupportedFormat, - #[error("could not serialize from to cbor: {0}")] - CborErrorWithSource(CborError), #[error("could not serialize from to cbor")] CborError, } @@ -270,7 +266,7 @@ impl TryFrom for EC2Y { type Error = Error; fn try_from(v: ciborium::Value) -> Result { - match v.clone() { + match v { ciborium::Value::Bytes(s) => Ok(EC2Y::Value(s)), ciborium::Value::Bool(b) => Ok(EC2Y::SignBit(b)), _ => Err(Error::InvalidTypeY(v)), diff --git a/src/definitions/mod.rs b/src/definitions/mod.rs index 387ee32..cf8954c 100644 --- a/src/definitions/mod.rs +++ b/src/definitions/mod.rs @@ -1,6 +1,4 @@ -//! This module contains the definitions for various components related to device engagement, -//! device keys, device requests, device responses, device signing, helpers, issuer signing, -//! MSO (Mobile Security Object), namespaces, session management, traits, and validity information. +//! This module contains the definitions for all components involved in the lib. pub mod device_engagement; pub mod device_key; pub mod device_request; diff --git a/src/definitions/traits/to_cbor.rs b/src/definitions/traits/to_cbor.rs index 0db35bc..8098b06 100644 --- a/src/definitions/traits/to_cbor.rs +++ b/src/definitions/traits/to_cbor.rs @@ -4,6 +4,8 @@ use crate::cbor; use crate::cbor::CborError; use std::collections::BTreeMap; +use std::error::Error; +use std::fmt; pub type Bytes = Vec; @@ -22,10 +24,35 @@ pub trait ToNamespaceMap { fn to_ns_map(self) -> BTreeMap; } -#[derive(Debug, thiserror::Error)] +// Define your error enum with just the CoseError variant +#[derive(Debug)] pub enum ToCborError { - #[error("cbor error: {0}")] - CoseError(#[from] CborError), + CborError(CborError), +} + +// Implement Display for ToCborError +impl fmt::Display for ToCborError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ToCborError::CborError(err) => write!(f, "COSE error: {}", err), + } + } +} + +// Implement Error for ToCborError +impl Error for ToCborError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + ToCborError::CborError(err) => Some(err), + } + } +} + +// Implement From to easily convert CoseError into ToCborError +impl From for ToCborError { + fn from(err: CborError) -> ToCborError { + ToCborError::CborError(err) + } } impl ToCbor for T diff --git a/src/definitions/validity_info.rs b/src/definitions/validity_info.rs index ade7c9c..da977af 100644 --- a/src/definitions/validity_info.rs +++ b/src/definitions/validity_info.rs @@ -28,7 +28,6 @@ //! - [std::collections::BTreeMap]: Provides the [BTreeMap] type for storing key-value pairs in a sorted order. //! - [time]: Provides date and time manipulation functionality. //! - [thiserror]: Provides the [thiserror::Error] trait for defining custom error types. -use crate::cbor::CborError; use serde::{ ser::{Error as SerError, Serializer}, Deserialize, Serialize, @@ -67,7 +66,7 @@ pub enum Error { #[error("Failed to parse date string as rfc3339 date: {0}")] UnableToParseDate(#[from] ParseError), #[error("Could not serialize to cbor: {0}")] - CborErrorWithSource(CborError), + CborErrorWithSource(coset::CoseError), #[error("Could not serialize to cbor")] CborError, } diff --git a/src/presentation/device.rs b/src/presentation/device.rs index 97e78df..4c5964c 100644 --- a/src/presentation/device.rs +++ b/src/presentation/device.rs @@ -132,7 +132,7 @@ pub enum Error { SharedSecretGeneration(anyhow::Error), /// Error encoding value to CBOR. #[error("error encoding value to CBOR: {0}")] - CborEncoding(CborError), + CborEncoding(coset::CoseError), /// Session manager was used incorrectly. #[error("session manager was used incorrectly")] ApiMisuse, @@ -143,7 +143,7 @@ pub enum Error { #[error("age_over element identifier is malformed")] PrefixError, #[error("Could not serialize to cbor: {0}")] - CborError(CborError), + CborError(coset::CoseError), } /// The documents the device owns. diff --git a/src/presentation/reader.rs b/src/presentation/reader.rs index 11e8f6b..b1c95cc 100644 --- a/src/presentation/reader.rs +++ b/src/presentation/reader.rs @@ -87,12 +87,6 @@ pub enum Error { CborError(CborError), } -impl From for Error { - fn from(_: coset::CoseError) -> Self { - Error::CborDecodingError - } -} - impl From for Error { fn from(_: CborError) -> Self { Error::CborDecodingError @@ -234,7 +228,7 @@ impl SessionManager { &mut self, response: &[u8], ) -> Result>, Error> { - let session_data: SessionData = crate::cbor::from_slice(response)?; + let session_data: SessionData = cbor::from_slice(response)?; let encrypted_response = match session_data.data { None => return Err(Error::HolderError), Some(r) => r,