diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab4adcd3..f97bc288 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 new file mode 100644 index 00000000..176cfa80 --- /dev/null +++ b/.github/workflows/event.json @@ -0,0 +1,3 @@ +{ + "act": true +} diff --git a/Cargo.toml b/Cargo.toml index b86b22df..0ffaa348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ p256 = { version = "0.13.0", features = ["serde", "ecdh"] } p384 = { version = "0.13.0", features = ["serde", "ecdh"] } rand = { version = "0.8.5", features = ["getrandom"] } serde = { version = "1.0", features = ["derive"] } -serde_cbor = { version = "0.11.2", features = ["tags"] } serde_json = "1.0" +serde_bytes = "0.11.0" sha2 = "0.10.6" thiserror = "1.0" elliptic-curve = "0.13.1" @@ -46,9 +46,9 @@ clap-stdin = "0.2.1" strum = "0.24" strum_macros = "0.24" -[dependencies.cose-rs] -git = "https://github.com/spruceid/cose-rs" -rev = "4104505" +coset = "0.3.8" +ciborium = "0.2.2" +digest = "0.10.7" [dev-dependencies] hex = "0.4.3" diff --git a/README.md b/README.md index 69602685..1d6d4da4 100644 --- a/README.md +++ b/README.md @@ -131,3 +131,4 @@ stateDiagram You can see the full example in [simulated_device_and_reader](tests/simulated_device_and_reader.rs) and a version that uses `State` pattern, `Arc` and `Mutex` [simulated_device_and_reader](tests/simulated_device_and_reader_state.rs). + diff --git a/macros/src/to_cbor.rs b/macros/src/to_cbor.rs index c2ec7d84..6832d592 100644 --- a/macros/src/to_cbor.rs +++ b/macros/src/to_cbor.rs @@ -86,7 +86,7 @@ fn named_fields(isomdl_path: Ident, ident: Ident, input: FieldsNamed) -> TokenSt let output = quote! { mod #mod_name { - use serde_cbor::Value; + use ciborium::Value; use super::*; use #isomdl_path::definitions::traits::{ToCbor, ToNamespaceMap}; impl ToNamespaceMap for #ident { @@ -140,9 +140,8 @@ fn unnamed_fields(isomdl_path: Ident, ident: Ident, mut input: FieldsUnnamed) -> mod #mod_name { use super::*; use #isomdl_path::definitions::traits::{ToCbor, ToCborError}; - use serde_cbor::Value; impl ToCbor for #ident { - fn to_cbor(self) -> Value { + fn to_cbor(self) -> ciborium::Value { <#field_type as ToCbor>::to_cbor(self) } } diff --git a/src/cbor.rs b/src/cbor.rs new file mode 100644 index 00000000..2e003368 --- /dev/null +++ b/src/cbor.rs @@ -0,0 +1,67 @@ +use std::io::Cursor; + +use ciborium::Value; +use coset::{cbor, CoseError}; +use serde::{de, Serialize}; +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(|_| CborError(CoseError::EncodeFailed))?; + Ok(buf) +} + +pub fn from_slice(slice: &[u8]) -> Result +where + T: de::DeserializeOwned, +{ + 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: Value) -> Result +where + T: de::DeserializeOwned, +{ + Value::deserialized(&value).map_err(|_| { + CoseError::DecodeFailed(cbor::de::Error::Semantic( + None, + "cannot deserialize".to_string(), + )) + }) +} + +pub fn into_value(v: S) -> Result +where + S: Serialize, +{ + 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 new file mode 100644 index 00000000..157e0bd5 --- /dev/null +++ b/src/cose.rs @@ -0,0 +1,127 @@ +use std::borrow::{Borrow, BorrowMut}; +use std::ops::{Deref, DerefMut}; + +use coset::{iana, AsCborValue, TaggedCborSerializable}; + +use crate::cose::serialized_as_cbor_value::SerializedAsCborValue; + +pub mod mac0; +mod serialized_as_cbor_value; +pub mod sign1; + +/// Trait to represent the signature algorithm of a signer or verifier. +pub trait SignatureAlgorithm { + fn algorithm(&self) -> iana::Algorithm; +} + +#[derive(Debug, Clone)] +pub struct MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + pub tagged: bool, + pub inner: T, +} + +impl MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + 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 +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Borrow for MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + fn borrow(&self) -> &T { + &self.inner + } +} + +impl BorrowMut for MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + fn borrow_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl AsRef for MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + fn as_ref(&self) -> &T { + &self.inner + } +} + +/// Serialize manually using `ciborium::tag::Captured`, putting the tag if +/// necessary. +impl serde::Serialize for MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let tag = if self.tagged { Some(T::TAG) } else { None }; + + ciborium::tag::Captured(tag, SerializedAsCborValue(&self.inner)).serialize(serializer) + } +} + +/// Deserialize manually using `ciborium::tag::Captured`, checking the tag. +impl<'de, T> serde::Deserialize<'de> for MaybeTagged +where + T: AsCborValue + TaggedCborSerializable + Clone, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let ciborium::tag::Captured(tag, SerializedAsCborValue(inner)) = + ciborium::tag::Captured::deserialize(deserializer)?; + let tagged = match tag { + Some(tag) if tag == T::TAG => true, + Some(_) => return Err(serde::de::Error::custom("unexpected tag")), + None => false, + }; + + Ok(Self { tagged, inner }) + } +} diff --git a/src/cose/mac0.rs b/src/cose/mac0.rs new file mode 100644 index 00000000..0d99eed6 --- /dev/null +++ b/src/cose/mac0.rs @@ -0,0 +1,400 @@ +use ::hmac::Hmac; +use coset::cwt::ClaimsSet; +use coset::{ + mac_structure_data, CborSerializable, CoseError, CoseMac0, MacContext, + RegisteredLabelWithPrivate, +}; +use digest::{Mac, MacError}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +use crate::cose::{MaybeTagged, SignatureAlgorithm}; + +/// Prepared `COSE_Mac0` for remote signing. +/// +/// To produce a `COSE_Mac0` do the following: +/// +/// 1. Set the signature algorithm with [coset::HeaderBuilder::algorithm]. +/// 2. Produce a signature remotely, according to the chosen signature algorithm, +/// using the [PreparedCoseMac0::signature_payload] as the payload. +/// 3. Generate the `COSE_Mac0` by passing the produced signature into +/// [PreparedCoseMac0::finalize]. +/// +/// Example: +/// ``` +/// use coset::iana; +/// use digest::Mac; +/// use hex::FromHex;use hmac::Hmac; +/// use sha2::Sha256; +/// use isomdl::cose::mac0::PreparedCoseMac0; +/// +/// let key = Vec::::from_hex("a361316953796d6d6574726963613305622d318f187418681869187318201869187318201874186818651820186b18651879").unwrap(); +/// let signer = Hmac::::new_from_slice(&key).expect("failed to create HMAC signer"); +/// let protected = coset::HeaderBuilder::new() +/// .algorithm(iana::Algorithm::HMAC_256_256) +/// .build(); +/// let unprotected = coset::HeaderBuilder::new().key_id(b"11".to_vec()).build(); +/// let builder = coset::CoseMac0Builder::new() +/// .protected(protected) +/// .unprotected(unprotected) +/// .payload(b"This is the content.".to_vec()); +/// let prepared = PreparedCoseMac0::new(builder, None, None, true).unwrap(); +/// let signature_payload = prepared.signature_payload(); +/// let signature = tag(signature_payload, &signer).unwrap(); +/// let cose_mac0 = prepared.finalize(signature); +/// fn tag(signature_payload: &[u8], s: &Hmac) -> anyhow::Result> { +/// let mut mac = s.clone(); +/// mac.reset(); +/// mac.update(signature_payload); +/// Ok(mac.finalize().into_bytes().to_vec()) +/// } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PreparedCoseMac0 { + cose_mac0: MaybeTagged, + tag_payload: Vec, +} + +/// Errors that can occur when building, signing or verifying a COSE_Mac0. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("the COSE_Mac0 has an attached payload but an detached payload was provided")] + DoublePayload, + #[error("the COSE_Mac0 has a detached payload which was not provided")] + NoPayload, + #[error("tag did not match the structure expected by the verifier: {0}")] + MalformedTag(MacError), + #[error("tag is already present")] + AlreadyTagged, + #[error("error occurred when tagging COSE_Mac0: {0}")] + Tagging(MacError), + #[error("unable to set ClaimsSet: {0}")] + UnableToDeserializeIntoClaimsSet(CoseError), +} + +/// Result with error type: [`Error`]. +pub type Result = std::result::Result; + +/// Result for verification of a COSE_Mac0. +#[derive(Debug)] +pub enum VerificationResult { + Success, + Failure(String), + Error(Error), +} + +impl VerificationResult { + /// Result of verification. + /// + /// `false` implies the signature is inauthentic or the verification algorithm encountered an + /// error. + pub fn is_success(&self) -> bool { + matches!(self, VerificationResult::Success) + } + + /// Translate to a std::result::Result. + /// + /// Converts failure reasons and errors into a String. + pub fn into_result(self) -> Result<(), String> { + match self { + VerificationResult::Success => Ok(()), + VerificationResult::Failure(reason) => Err(reason), + VerificationResult::Error(e) => Err(format!("{}", e)), + } + } + + /// Retrieve the error if the verification algorithm encountered an error. + pub fn into_error(self) -> Option { + match self { + VerificationResult::Error(e) => Some(e), + _ => None, + } + } +} + +impl PreparedCoseMac0 { + pub fn new( + builder: coset::CoseMac0Builder, + detached_payload: Option<&[u8]>, + aad: Option<&[u8]>, + tagged: bool, + ) -> Result { + let cose_mac0 = builder.build(); + + // Check if the payload is present and if it is attached or detached. + // Needs to be exclusively attached or detached. + let payload = match (cose_mac0.payload.as_ref(), detached_payload) { + (Some(_), Some(_)) => return Err(Error::DoublePayload), + (None, None) => return Err(Error::NoPayload), + (Some(payload), None) => payload.clone(), + (None, Some(payload)) => payload.to_vec(), + }; + // Create the signature payload ot be used later on signing. + let tag_payload = mac_structure_data( + MacContext::CoseMac0, + cose_mac0.protected.clone(), + aad.unwrap_or_default(), + &payload, + ); + + Ok(Self { + cose_mac0: MaybeTagged::new(tagged, cose_mac0), + tag_payload, + }) + } + + /// Returns the signature payload that needs to be used to tag it. + pub fn signature_payload(&self) -> &[u8] { + &self.tag_payload + } + + /// Finalize the COSE_Mac0 by adding the tag. + pub fn finalize(self, tag: Vec) -> MaybeTagged { + let mut cose_mac0 = self.cose_mac0; + cose_mac0.inner.tag = tag; + cose_mac0 + } +} + +impl MaybeTagged { + /// Verify that the tag of a `COSE_Mac0` is authentic. + pub fn verify( + &self, + verifier: &Hmac, + detached_payload: Option<&[u8]>, + external_aad: Option<&[u8]>, + ) -> VerificationResult { + if let Some(RegisteredLabelWithPrivate::Assigned(alg)) = + self.inner.protected.header.alg.as_ref() + { + if verifier.algorithm() != *alg { + return VerificationResult::Failure( + "algorithm in protected headers did not match verifier's algorithm".into(), + ); + } + } + + let payload = match (self.inner.payload.as_ref(), detached_payload) { + (None, None) => return VerificationResult::Error(Error::NoPayload), + (Some(attached), None) => attached, + (None, Some(detached)) => detached, + _ => return VerificationResult::Error(Error::DoublePayload), + }; + + let tag = &self.inner.tag; + + // Create the signature payload ot be used later on signing. + let tag_payload = mac_structure_data( + MacContext::CoseMac0, + self.inner.protected.clone(), + external_aad.unwrap_or_default(), + payload, + ); + + let mut mac = verifier.clone(); + mac.reset(); + mac.update(&tag_payload); + match mac.verify_slice(tag) { + Ok(()) => VerificationResult::Success, + Err(e) => VerificationResult::Failure(format!("tag is not authentic: {}", e)), + } + } + + /// Retrieve the CWT claims set. + pub fn claims_set(&self) -> Result> { + match self.inner.payload.as_ref() { + None => Ok(None), + Some(payload) => ClaimsSet::from_slice(payload).map_or_else( + |e| Err(Error::UnableToDeserializeIntoClaimsSet(e)), + |c| Ok(Some(c)), + ), + } + } +} + +mod hmac { + use coset::iana; + use hmac::Hmac; + use sha2::Sha256; + + use super::super::SignatureAlgorithm; + + /// Implement [`SignatureAlgorithm`]. + + impl SignatureAlgorithm for Hmac { + fn algorithm(&self) -> iana::Algorithm { + iana::Algorithm::HMAC_256_256 + } + } +} + +#[cfg(test)] +mod tests { + use crate::cbor; + use crate::cose::mac0::{CoseMac0, PreparedCoseMac0}; + use crate::cose::MaybeTagged; + use coset::cwt::{ClaimsSet, Timestamp}; + use coset::{iana, CborSerializable, Header}; + use digest::Mac; + use hex::FromHex; + use hmac::Hmac; + use sha2::Sha256; + + static COSE_MAC0: &str = include_str!("../../test/definitions/cose/mac0/serialized.cbor"); + static KEY: &str = include_str!("../../test/definitions/cose/mac0/secret_key"); + + const RFC8392_KEY: &str = "6c1382765aec5358f117733d281c1c7bdc39884d04a45a1e6c67c858bc206c19"; + const RFC8392_MAC0: &str = "d18443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b715820a377dfe17a3c3c3bdb363c426f85d3c1a1f11007765965017602f207700071b0"; + + #[test] + fn roundtrip() { + let bytes = Vec::::from_hex(COSE_MAC0).unwrap(); + let parsed: MaybeTagged = + cbor::from_slice(&bytes).expect("failed to parse COSE_MAC0 from bytes"); + let roundtripped = cbor::to_vec(&parsed).expect("failed to serialize COSE_MAC0"); + assert_eq!( + bytes, roundtripped, + "original bytes and roundtripped bytes do not match" + ); + } + + #[test] + fn tagging() { + let key = Vec::::from_hex(KEY).unwrap(); + let signer = Hmac::::new_from_slice(&key).expect("failed to create HMAC signer"); + let protected = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::HMAC_256_256) + .build(); + let unprotected = coset::HeaderBuilder::new().key_id(b"11".to_vec()).build(); + let builder = coset::CoseMac0Builder::new() + .protected(protected) + .unprotected(unprotected) + .payload(b"This is the content.".to_vec()); + let prepared = PreparedCoseMac0::new(builder, None, None, true).unwrap(); + let signature_payload = prepared.signature_payload(); + let signature = tag(signature_payload, &signer).unwrap(); + let cose_mac0 = prepared.finalize(signature); + let serialized = cbor::to_vec(&cose_mac0).expect("failed to serialize COSE_MAC0"); + + let expected = Vec::::from_hex(COSE_MAC0).unwrap(); + assert_eq!( + expected, serialized, + "expected COSE_MAC0 and signed data do not match" + ); + } + + fn tag(signature_payload: &[u8], s: &Hmac) -> anyhow::Result> { + let mut mac = s.clone(); + mac.reset(); + mac.update(signature_payload); + Ok(mac.finalize().into_bytes().to_vec()) + } + + #[test] + fn verifying() { + let key = Vec::::from_hex(KEY).unwrap(); + let verifier = + Hmac::::new_from_slice(&key).expect("failed to create HMAC verifier"); + + let cose_mac0_bytes = Vec::::from_hex(COSE_MAC0).unwrap(); + let cose_mac0: MaybeTagged = + cbor::from_slice(&cose_mac0_bytes).expect("failed to parse COSE_MAC0 from bytes"); + + cose_mac0 + .verify(&verifier, None, None) + .into_result() + .expect("COSE_MAC0 could not be verified") + } + + #[test] + fn remote_tagging() { + let key = Vec::::from_hex(KEY).unwrap(); + let signer = Hmac::::new_from_slice(&key).expect("failed to create HMAC signer"); + let protected = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::HMAC_256_256) + .build(); + let unprotected = coset::HeaderBuilder::new().key_id(b"11".to_vec()).build(); + let builder = coset::CoseMac0Builder::new() + .protected(protected) + .unprotected(unprotected) + .payload(b"This is the content.".to_vec()); + let prepared = PreparedCoseMac0::new(builder, None, None, true).unwrap(); + let signature_payload = prepared.signature_payload(); + let signature = tag(signature_payload, &signer).unwrap(); + let cose_mac0 = prepared.finalize(signature); + + let serialized = cbor::to_vec(&cose_mac0).expect("failed to serialize COSE_MAC0"); + let expected = Vec::::from_hex(COSE_MAC0).unwrap(); + assert_eq!( + expected, serialized, + "expected COSE_MAC0 and signed data do not match" + ); + + let verifier = + Hmac::::new_from_slice(&key).expect("failed to create HMAC verifier"); + cose_mac0 + .verify(&verifier, None, None) + .into_result() + .expect("COSE_MAC0 could not be verified") + } + + fn rfc8392_example_inputs() -> (Header, Header, ClaimsSet) { + let protected = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(); + + let unprotected = coset::HeaderBuilder::new() + .key_id( + hex::decode("4173796d6d65747269634543445341323536").expect("error decoding key id"), + ) + .build(); + + let claims_set = ClaimsSet { + issuer: Some("coap://as.example.com".to_string()), + subject: Some("erikw".to_string()), + audience: Some("coap://light.example.com".to_string()), + expiration_time: Some(Timestamp::WholeSeconds(1444064944)), + not_before: Some(Timestamp::WholeSeconds(1443944944)), + issued_at: Some(Timestamp::WholeSeconds(1443944944)), + cwt_id: Some(hex::decode("0b71").unwrap()), + rest: vec![], + }; + + (protected, unprotected, claims_set) + } + + #[test] + fn tagging_cwt() { + // Using key from RFC8392 example + let key = hex::decode(RFC8392_KEY).unwrap(); + let signer = Hmac::::new_from_slice(&key).expect("failed to create HMAC signer"); + let (protected, unprotected, claims_set) = rfc8392_example_inputs(); + let builder = coset::CoseMac0Builder::new() + .protected(protected) + .unprotected(unprotected) + .payload(claims_set.to_vec().expect("failed to set claims set")); + let prepared = PreparedCoseMac0::new(builder, None, None, true).unwrap(); + let signature_payload = prepared.signature_payload(); + let signature = tag(signature_payload, &signer).expect("failed to sign CWT"); + let cose_mac0 = prepared.finalize(signature); + let serialized = cbor::to_vec(&cose_mac0).expect("failed to serialize COSE_MAC0"); + let expected = hex::decode(RFC8392_MAC0).unwrap(); + + assert_eq!( + expected, serialized, + "expected COSE_MAC0 and signed CWT do not match" + ); + } + + #[test] + fn deserializing_tdeserializing_signed_cwtagged_cwt() { + let cose_mac0_bytes = hex::decode(RFC8392_MAC0).unwrap(); + let cose_mac0: MaybeTagged = + cbor::from_slice(&cose_mac0_bytes).expect("failed to parse COSE_MAC0 from bytes"); + let parsed_claims_set = cose_mac0 + .claims_set() + .expect("failed to parse claims set from payload") + .expect("retrieved empty claims set"); + let (_, _, expected_claims_set) = rfc8392_example_inputs(); + assert_eq!(parsed_claims_set, expected_claims_set); + } +} diff --git a/src/cose/serialized_as_cbor_value.rs b/src/cose/serialized_as_cbor_value.rs new file mode 100644 index 00000000..04dc815b --- /dev/null +++ b/src/cose/serialized_as_cbor_value.rs @@ -0,0 +1,30 @@ +use coset::AsCborValue; +use serde::{Deserialize, Serialize}; + +/// This is a small helper wrapper to deal with `coset` types that don't +/// implement `Serialize`/`Deserialize` but only `AsCborValue`. +pub struct SerializedAsCborValue(pub T); + +impl<'a, T: Clone + AsCborValue> Serialize for SerializedAsCborValue<&'a T> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0 + .clone() + .to_cbor_value() + .map_err(serde::ser::Error::custom)? + .serialize(serializer) + } +} + +impl<'de, T: AsCborValue> Deserialize<'de> for SerializedAsCborValue { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + T::from_cbor_value(ciborium::Value::deserialize(deserializer)?) + .map_err(serde::de::Error::custom) + .map(Self) + } +} diff --git a/src/cose/sign1.rs b/src/cose/sign1.rs new file mode 100644 index 00000000..e3e8e51d --- /dev/null +++ b/src/cose/sign1.rs @@ -0,0 +1,474 @@ +use coset::cwt::ClaimsSet; +use coset::{ + sig_structure_data, CborSerializable, CoseError, CoseSign1, RegisteredLabelWithPrivate, + SignatureContext, +}; +use serde::{Deserialize, Serialize}; +use signature::Verifier; + +use crate::cose::{MaybeTagged, SignatureAlgorithm}; + +/// Prepared `COSE_Sign1` for remote signing. +/// +/// To produce a `COSE_Sign1,` do the following: +/// +/// 1. Set the signature algorithm with [coset::HeaderBuilder::algorithm]. +/// 2. Produce a signature remotely, according to the chosen signature algorithm, +/// using the [PreparedCoseSign1::signature_payload] as the payload. +/// 3. Generate the `COSE_Sign1` by passing the produced signature into +/// [PreparedCoseSign1::finalize]. +/// +/// Example: +/// ``` +/// use coset::iana; +/// use hex::FromHex; +/// use p256::ecdsa::{Signature, SigningKey}; +/// use p256::SecretKey; +/// use signature::{SignatureEncoding, Signer, SignerMut}; +/// use isomdl::cose::sign1::{Error, PreparedCoseSign1}; +/// use isomdl::cose::SignatureAlgorithm; +/// +/// let key = Vec::::from_hex("57c92077664146e876760c9520d054aa93c3afb04e306705db6090308507b4d3").unwrap(); +/// let signer: SigningKey = SecretKey::from_slice(&key).unwrap().into(); +/// let protected = coset::HeaderBuilder::new() +/// .algorithm(iana::Algorithm::ES256) +/// .build(); +/// let unprotected = coset::HeaderBuilder::new().key_id(b"11".to_vec()).build(); +/// let builder = coset::CoseSign1Builder::new() +/// .protected(protected) +/// .unprotected(unprotected) +/// .payload(b"This is the content.".to_vec()); +/// let prepared = PreparedCoseSign1::new(builder, None, None, true).unwrap(); +/// let signature_payload = prepared.signature_payload(); +/// let signature = sign::(signature_payload, &signer).unwrap(); +/// let cose_sign1 = prepared.finalize(signature); +/// +/// fn sign(signature_payload: &[u8], s: &S) -> anyhow::Result> +/// where +/// S: Signer + SignatureAlgorithm, +/// Sig: SignatureEncoding, +/// { +/// Ok(s.try_sign(signature_payload) +/// .map_err(Error::Signing)? +/// .to_vec()) +/// } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PreparedCoseSign1 { + cose_sign1: MaybeTagged, + #[serde(with = "serde_bytes")] // This optimizes (de)serialization of byte vectors + signature_payload: Vec, +} + +/// Errors that can occur when building, signing or verifying a COSE_Sign1. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("the COSE_Sign1 has an attached payload but an detached payload was provided")] + DoublePayload, + #[error("the COSE_Sign1 has a detached payload which was not provided")] + NoPayload, + #[error("signature did not match the structure expected by the verifier: {0}")] + MalformedSignature(signature::Error), + #[error("signature is already present")] + AlreadySigned, + #[error("error occurred when signing COSE_Sign1: {0}")] + Signing(signature::Error), + #[error("unable to set ClaimsSet: {0}")] + UnableToDeserializeIntoClaimsSet(CoseError), +} + +/// Result with error type: [`Error`]. +pub type Result = std::result::Result; + +/// Result for verification of a COSE_Sign1. +#[derive(Debug)] +pub enum VerificationResult { + Success, + Failure(String), + Error(Error), +} + +impl VerificationResult { + /// Result of verification. + /// + /// `false` implies the signature is inauthentic or the verification algorithm encountered an + /// error. + pub fn is_success(&self) -> bool { + matches!(self, VerificationResult::Success) + } + + /// Translate to a std::result::Result. + /// + /// Converts failure reasons and errors into a String. + pub fn into_result(self) -> Result<(), String> { + match self { + VerificationResult::Success => Ok(()), + VerificationResult::Failure(reason) => Err(reason), + VerificationResult::Error(e) => Err(format!("{}", e)), + } + } + + /// Retrieve the error if the verification algorithm encountered an error. + pub fn into_error(self) -> Option { + match self { + VerificationResult::Error(e) => Some(e), + _ => None, + } + } +} + +impl PreparedCoseSign1 { + pub fn new( + builder: coset::CoseSign1Builder, + detached_payload: Option<&[u8]>, + aad: Option<&[u8]>, + tagged: bool, + ) -> Result { + let cose_sign1 = builder.build(); + + // Check if the payload is present and if it is attached or detached. + // Needs to be exclusively attached or detached. + let payload = match (cose_sign1.payload.as_ref(), detached_payload) { + (Some(_), Some(_)) => return Err(Error::DoublePayload), + (None, None) => return Err(Error::NoPayload), + (Some(payload), None) => payload.clone(), + (None, Some(payload)) => payload.to_vec(), + }; + // Create the signature payload ot be used later on signing. + let signature_payload = sig_structure_data( + SignatureContext::CoseSign1, + cose_sign1.protected.clone(), + None, + aad.unwrap_or_default(), + &payload, + ); + + Ok(Self { + cose_sign1: MaybeTagged::new(tagged, cose_sign1), + signature_payload, + }) + } + + /// Returns the signature payload that needs to be used to sign. + pub fn signature_payload(&self) -> &[u8] { + &self.signature_payload + } + + /// Finalize the COSE_Sign1 by adding the signature. + pub fn finalize(self, signature: Vec) -> MaybeTagged { + let mut cose_sign1 = self.cose_sign1; + cose_sign1.inner.signature = signature; + cose_sign1 + } +} + +impl MaybeTagged { + /// Verify that the signature of a COSE_Sign1 is authentic. + pub fn verify<'a, V, S>( + &'a self, + verifier: &V, + detached_payload: Option<&[u8]>, + external_aad: Option<&[u8]>, + ) -> VerificationResult + where + V: Verifier + SignatureAlgorithm, + S: TryFrom<&'a [u8]>, + S::Error: Into, + { + if let Some(RegisteredLabelWithPrivate::Assigned(alg)) = + self.inner.protected.header.alg.as_ref() + { + if verifier.algorithm() != *alg { + return VerificationResult::Failure( + "algorithm in protected headers did not match verifier's algorithm".into(), + ); + } + } + + let payload = match (self.inner.payload.as_ref(), detached_payload) { + (None, None) => return VerificationResult::Error(Error::NoPayload), + (Some(attached), None) => attached, + (None, Some(detached)) => detached, + _ => return VerificationResult::Error(Error::DoublePayload), + }; + + let signature = match S::try_from(self.inner.signature.as_ref()) + .map_err(Into::into) + .map_err(Error::MalformedSignature) + { + Ok(sig) => sig, + Err(e) => return VerificationResult::Error(e), + }; + + let signature_payload = sig_structure_data( + SignatureContext::CoseSign1, + self.inner.protected.clone(), + None, + external_aad.unwrap_or_default(), + payload, + ); + + match verifier.verify(&signature_payload, &signature) { + Ok(()) => VerificationResult::Success, + Err(e) => VerificationResult::Failure(format!("signature is not authentic: {}", e)), + } + } + + /// Retrieve the CWT claims set. + pub fn claims_set(&self) -> Result> { + match self.inner.payload.as_ref() { + None => Ok(None), + Some(payload) => ClaimsSet::from_slice(payload).map_or_else( + |e| Err(Error::UnableToDeserializeIntoClaimsSet(e)), + |c| Ok(Some(c)), + ), + } + } +} + +mod p256 { + use coset::iana; + use p256::ecdsa::{SigningKey, VerifyingKey}; + + use crate::cose::SignatureAlgorithm; + + /// Implement [`SignatureAlgorithm`]. + + impl SignatureAlgorithm for SigningKey { + fn algorithm(&self) -> iana::Algorithm { + iana::Algorithm::ES256 + } + } + + impl SignatureAlgorithm for VerifyingKey { + fn algorithm(&self) -> iana::Algorithm { + iana::Algorithm::ES256 + } + } +} + +mod p384 { + use coset::iana; + use p384::ecdsa::{SigningKey, VerifyingKey}; + + use crate::cose::SignatureAlgorithm; + + /// Implement [`SignatureAlgorithm`]. + + impl SignatureAlgorithm for SigningKey { + fn algorithm(&self) -> iana::Algorithm { + iana::Algorithm::ES384 + } + } + + impl SignatureAlgorithm for VerifyingKey { + fn algorithm(&self) -> iana::Algorithm { + iana::Algorithm::ES384 + } + } +} + +#[cfg(test)] +mod tests { + use crate::cbor; + use crate::cose::sign1::{CoseSign1, Error, PreparedCoseSign1}; + use crate::cose::{MaybeTagged, SignatureAlgorithm}; + use coset::cwt::{ClaimsSet, Timestamp}; + use coset::{iana, CborSerializable, Header}; + use hex::FromHex; + use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; + use p256::SecretKey; + use signature::{SignatureEncoding, Signer}; + + static COSE_SIGN1: &str = include_str!("../../test/definitions/cose/sign1/serialized.cbor"); + static COSE_KEY: &str = include_str!("../../test/definitions/cose/sign1/secret_key"); + + const RFC8392_KEY: &str = "6c1382765aec5358f117733d281c1c7bdc39884d04a45a1e6c67c858bc206c19"; + const RFC8392_COSE_SIGN1: &str = "d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30"; + + #[test] + fn roundtrip() { + let bytes = Vec::::from_hex(COSE_SIGN1).unwrap(); + let parsed: MaybeTagged = + cbor::from_slice(&bytes).expect("failed to parse COSE_Sign1 from bytes"); + let roundtripped = cbor::to_vec(&parsed).expect("failed to serialize COSE_Sign1 to bytes"); + assert_eq!( + bytes, roundtripped, + "original bytes and roundtripped bytes do not match" + ); + } + + #[test] + fn roundtrip_ciborium() { + let bytes = Vec::::from_hex(COSE_SIGN1).unwrap(); + let parsed: MaybeTagged = + cbor::from_slice(&bytes).expect("failed to parse COSE_MAC0 from bytes"); + let roundtripped = cbor::to_vec(&parsed).expect("failed to serialize COSE_Sign1 to bytes"); + println!("bytes: {:?}", hex::encode(&bytes)); + println!("roundtripped: {:?}", hex::encode(&roundtripped)); + assert_eq!( + bytes, roundtripped, + "original bytes and roundtripped bytes do not match" + ); + } + + #[test] + fn signing() { + let key = Vec::::from_hex(COSE_KEY).unwrap(); + let signer: SigningKey = SecretKey::from_slice(&key).unwrap().into(); + let protected = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(); + let unprotected = coset::HeaderBuilder::new().key_id(b"11".to_vec()).build(); + let builder = coset::CoseSign1Builder::new() + .protected(protected) + .unprotected(unprotected) + .payload(b"This is the content.".to_vec()); + let prepared = PreparedCoseSign1::new(builder, None, None, true).unwrap(); + let signature_payload = prepared.signature_payload(); + let signature = sign::(signature_payload, &signer).unwrap(); + let cose_sign1 = prepared.finalize(signature); + let serialized = + cbor::to_vec(&cose_sign1).expect("failed to serialize COSE_Sign1 to bytes"); + + let expected = Vec::::from_hex(COSE_SIGN1).unwrap(); + assert_eq!( + expected, serialized, + "expected COSE_Sign1 and signed data do not match" + ); + } + + fn sign(signature_payload: &[u8], s: &S) -> anyhow::Result> + where + S: Signer + SignatureAlgorithm, + Sig: SignatureEncoding, + { + Ok(s.try_sign(signature_payload) + .map_err(Error::Signing)? + .to_vec()) + } + + #[test] + fn verifying() { + let key = Vec::::from_hex(COSE_KEY).unwrap(); + let signer: SigningKey = SecretKey::from_slice(&key).unwrap().into(); + let verifier: VerifyingKey = (&signer).into(); + + let cose_sign1_bytes = Vec::::from_hex(COSE_SIGN1).unwrap(); + let cose_sign1: MaybeTagged = + cbor::from_slice(&cose_sign1_bytes).expect("failed to parse COSE_Sign1 from bytes"); + + cose_sign1 + .verify::(&verifier, None, None) + .into_result() + .expect("COSE_Sign1 could not be verified") + } + + #[test] + fn remote_signed() { + let key = Vec::::from_hex(COSE_KEY).unwrap(); + let signer: SigningKey = SecretKey::from_slice(&key).unwrap().into(); + let protected = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(); + let unprotected = coset::HeaderBuilder::new().key_id(b"11".to_vec()).build(); + let builder = coset::CoseSign1Builder::new() + .protected(protected) + .unprotected(unprotected) + .payload(b"This is the content.".to_vec()); + let prepared = PreparedCoseSign1::new(builder, None, None, true).unwrap(); + let signature_payload = prepared.signature_payload(); + let signature = sign::(signature_payload, &signer).unwrap(); + let cose_sign1 = prepared.finalize(signature); + + let serialized = + cbor::to_vec(&cose_sign1.clone()).expect("failed to serialize COSE_Sign1 to bytes"); + + let expected = Vec::::from_hex(COSE_SIGN1).unwrap(); + assert_eq!( + expected, serialized, + "expected COSE_Sign1 and signed data do not match" + ); + + let verifier: VerifyingKey = (&signer).into(); + cose_sign1 + .verify::(&verifier, None, None) + .into_result() + .expect("COSE_Sign1 could not be verified") + } + + fn rfc8392_example_inputs() -> (Header, Header, ClaimsSet) { + let protected = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(); + + let unprotected = coset::HeaderBuilder::new() + .key_id( + hex::decode("4173796d6d65747269634543445341323536").expect("error decoding key id"), + ) + .build(); + + let claims_set = ClaimsSet { + issuer: Some("coap://as.example.com".to_string()), + subject: Some("erikw".to_string()), + audience: Some("coap://light.example.com".to_string()), + expiration_time: Some(Timestamp::WholeSeconds(1444064944)), + not_before: Some(Timestamp::WholeSeconds(1443944944)), + issued_at: Some(Timestamp::WholeSeconds(1443944944)), + cwt_id: Some(hex::decode("0b71").unwrap()), + rest: vec![], + }; + + (protected, unprotected, claims_set) + } + + #[test] + fn signing_cwt() { + // Using key from RFC8392 example + let key = hex::decode(RFC8392_KEY).unwrap(); + let signer: SigningKey = SecretKey::from_slice(&key).unwrap().into(); + let (protected, unprotected, claims_set) = rfc8392_example_inputs(); + let builder = coset::CoseSign1Builder::new() + .protected(protected) + .unprotected(unprotected) + .payload(claims_set.to_vec().expect("failed to set claims set")); + let prepared = PreparedCoseSign1::new(builder, None, None, true).unwrap(); + let signature_payload = prepared.signature_payload(); + let signature = + sign::(signature_payload, &signer).expect("failed to sign CWT"); + let cose_sign1 = prepared.finalize(signature); + let serialized = + cbor::to_vec(&cose_sign1).expect("failed to serialize COSE_Sign1 to bytes"); + let expected = hex::decode(RFC8392_COSE_SIGN1).unwrap(); + assert_eq!( + expected, serialized, + "expected COSE_Sign1 and signed CWT do not match" + ); + } + + #[test] + fn deserializing_signed_cwt() { + let cose_sign1_bytes = hex::decode(RFC8392_COSE_SIGN1).unwrap(); + let cose_sign1: MaybeTagged = + cbor::from_slice(&cose_sign1_bytes).expect("failed to parse COSE_Sign1 from bytes"); + let parsed_claims_set = cose_sign1 + .claims_set() + .expect("failed to parse claims set from payload") + .expect("retrieved empty claims set"); + let (_, _, expected_claims_set) = rfc8392_example_inputs(); + assert_eq!(parsed_claims_set, expected_claims_set); + } + + #[test] + fn tag_coset_tagged_roundtrip() { + // this is tagged + let bytes = hex::decode(COSE_SIGN1).unwrap(); + + // can parse tagged value + let parsed: MaybeTagged = cbor::from_slice(&bytes).unwrap(); + assert!(parsed.is_tagged()); + println!("successfully deserialized Value from tagged bytes"); + + let roundtrip = cbor::to_vec(&parsed).unwrap(); + assert_eq!(bytes, roundtrip); + } +} diff --git a/src/definitions/device_engagement.rs b/src/definitions/device_engagement.rs index c8a713d1..9edf7e0c 100644 --- a/src/definitions/device_engagement.rs +++ b/src/definitions/device_engagement.rs @@ -3,33 +3,39 @@ //! The [DeviceEngagement] struct represents a device engagement object, which contains information about a device's engagement with a server. //! It includes fields such as the `version`, `security details, `device retrieval methods, `server retrieval methods, and `protocol information. //! -//! The module also provides implementations for conversions between [DeviceEngagement] and [CborValue], as well as other utility functions. -use crate::definitions::helpers::Tag24; -use crate::definitions::helpers::{ByteStr, NonEmptyVec}; -use crate::definitions::CoseKey; +//! The module also provides implementations for conversions between [DeviceEngagement] and [ciborium::Value], as well as other utility functions. +use std::{collections::BTreeMap, vec}; + use anyhow::Result; use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; -use std::{collections::BTreeMap, vec}; use uuid::Uuid; -pub mod error; pub use error::Error; - -pub mod nfc_options; pub use nfc_options::NfcOptions; +use crate::cbor; +use crate::cbor::CborError; +use crate::definitions::helpers::Tag24; +use crate::definitions::helpers::{ByteStr, NonEmptyVec}; +use crate::definitions::CoseKey; + +pub mod error; +pub mod nfc_options; pub type EDeviceKeyBytes = Tag24; pub type EReaderKeyBytes = Tag24; pub type DeviceRetrievalMethods = NonEmptyVec; -pub type ProtocolInfo = CborValue; +pub type ProtocolInfo = ciborium::Value; pub type Oidc = (u64, String, String); pub type WebApi = (u64, String, String); /// Represents a device engagement. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(try_from = "CborValue", into = "CborValue", rename_all = "camelCase")] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde( + try_from = "ciborium::Value", + into = "ciborium::Value", + rename_all = "camelCase" +)] pub struct DeviceEngagement { /// The version of the device engagement. pub version: String, @@ -50,10 +56,22 @@ pub struct DeviceEngagement { pub protocol_info: Option, } +impl PartialEq for DeviceEngagement { + fn eq(&self, other: &Self) -> bool { + self.version == other.version + && self.security == other.security + && self.device_retrieval_methods == other.device_retrieval_methods + && self.server_retrieval_methods == other.server_retrieval_methods + && self.protocol_info == other.protocol_info + } +} + +impl Eq for DeviceEngagement {} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(try_from = "CborValue", into = "CborValue")] +#[serde(try_from = "ciborium::Value", into = "ciborium::Value")] pub enum DeviceRetrievalMethod { - /// Represents the options for a WiFi connection. + /// Represents the options for a Wi-Fi connection. WIFI(WifiOptions), /// Represents the BLE options for device engagement. @@ -85,7 +103,7 @@ pub struct ServerRetrievalMethods { /// Represents the options for `Bluetooth Low Energy` (BLE) device engagement. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(try_from = "CborValue", into = "CborValue")] +#[serde(try_from = "ciborium::Value", into = "ciborium::Value")] pub struct BleOptions { /// The peripheral server mode for `BLE` device engagement. #[serde(skip_serializing_if = "Option::is_none")] @@ -112,89 +130,98 @@ pub struct CentralClientMode { pub uuid: Uuid, } -/// Represents the options for a `WiFi` device engagement. +/// Represents the options for a `Wi-Fi` device engagement. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)] -#[serde(try_from = "CborValue", into = "CborValue")] +#[serde(try_from = "ciborium::Value", into = "ciborium::Value")] pub struct WifiOptions { - /// The passphrase for the `WiFi connection. If [None], no passphrase is required. + /// The passphrase for the `Wi-Fi connection. If [None], no passphrase is required. #[serde(skip_serializing_if = "Option::is_none")] pass_phrase: Option, - /// The operating class of the `WiFi` channel. If [None], the operating class is not specified. + /// The operating class of the `Wi-Fi` channel. If [None], the operating class is not specified. #[serde(skip_serializing_if = "Option::is_none")] channel_info_operating_class: Option, - /// The channel number of the `WiFi` channel. If [None], the channel number is not specified. + /// The channel number of the `Wi-Fi` channel. If [None], the channel number is not specified. #[serde(skip_serializing_if = "Option::is_none")] channel_info_channel_number: Option, - /// The band information of the `WiFi channel. If [None], the band information is not specified. + /// The band information of the `Wi-Fi channel. If [None], the band information is not specified. #[serde(skip_serializing_if = "Option::is_none")] band_info: Option, } -impl From for CborValue { - fn from(device_engagement: DeviceEngagement) -> CborValue { - let mut map = BTreeMap::new(); - map.insert( - CborValue::Integer(0), - CborValue::Text(device_engagement.version), - ); - map.insert( - CborValue::Integer(1), - CborValue::Array(vec![ - device_engagement.security.0.into(), - device_engagement.security.1.into(), +impl From for ciborium::Value { + fn from(device_engagement: DeviceEngagement) -> ciborium::Value { + let mut map = vec![]; + map.push(( + ciborium::Value::Integer(0.into()), + ciborium::Value::Text(device_engagement.version), + )); + map.push(( + ciborium::Value::Integer(1.into()), + ciborium::Value::Array(vec![ + cbor::into_value(device_engagement.security.0).unwrap(), + cbor::into_value(device_engagement.security.1).unwrap(), ]), - ); + )); if let Some(methods) = device_engagement.device_retrieval_methods { - let methods = Vec::from(methods).into_iter().map(Into::into).collect(); - map.insert(CborValue::Integer(2), CborValue::Array(methods)); + let methods = Vec::from(methods) + .into_iter() + .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.insert(CborValue::Integer(3), methods.into()); + 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 } - CborValue::Map(map) + ciborium::Value::Map(map) } } -impl TryFrom for DeviceEngagement { +impl TryFrom for DeviceEngagement { type Error = Error; - fn try_from(v: CborValue) -> Result { - if let CborValue::Map(mut map) = v { - let device_engagement_version = map.remove(&CborValue::Integer(0)); - if let Some(CborValue::Text(v)) = device_engagement_version { + fn try_from(v: ciborium::Value) -> Result { + if let ciborium::Value::Map(map) = v { + let mut map: BTreeMap = map + .into_iter() + .map(|(k, v)| Ok((k.into_integer().map_err(|_| Error::CborError)?.into(), v))) + .collect::, Error>>()?; + let device_engagement_version = map.remove(&0); + if let Some(ciborium::Value::Text(v)) = device_engagement_version { if v != "1.0" { return Err(Error::UnsupportedVersion); } } else { return Err(Error::Malformed); } - let device_engagement_security = - map.remove(&CborValue::Integer(1)).ok_or(Error::Malformed)?; + let device_engagement_security = map.remove(&1).ok_or(Error::Malformed)?; - let security: Security = serde_cbor::value::from_value(device_engagement_security) - .map_err(|_| Error::Malformed)?; + let security: Security = + cbor::from_value(device_engagement_security).map_err(|_| Error::Malformed)?; let device_retrieval_methods = map - .remove(&CborValue::Integer(2)) - .map(serde_cbor::value::from_value) + .remove(&2) + .map(cbor::from_value) .transpose() .map_err(|_| Error::Malformed)?; let server_retrieval_methods = map - .remove(&CborValue::Integer(3)) - .map(serde_cbor::value::from_value) + .remove(&3) + .map(cbor::from_value) .transpose() .map_err(|_| Error::Malformed)?; if server_retrieval_methods.is_some() { //tracing::warn!("server_retrieval is unimplemented.") } - let protocol_info = map.remove(&CborValue::Integer(4)); + let protocol_info = map.remove(&4); if protocol_info.is_some() { //tracing::warn!("protocol_info is RFU and has been ignored in deserialization.") } @@ -217,7 +244,7 @@ impl TryFrom for DeviceEngagement { impl Tag24 { const BASE64_CONFIG: base64::Config = base64::Config::new(base64::CharacterSet::UrlSafe, false); - pub fn to_qr_code_uri(&self) -> Result { + pub fn to_qr_code_uri(&self) -> Result { let mut qr_code_uri = String::from("mdoc:"); base64::encode_config_buf(&self.inner_bytes, Self::BASE64_CONFIG, &mut qr_code_uri); Ok(qr_code_uri) @@ -246,25 +273,35 @@ impl DeviceRetrievalMethod { } } -impl TryFrom for DeviceRetrievalMethod { +impl TryFrom for DeviceRetrievalMethod { type Error = Error; - fn try_from(value: CborValue) -> Result { - if let CborValue::Array(list) = value { - let method: [CborValue; 3] = list.try_into().map_err(|_| Error::Malformed)?; - match method { - [CborValue::Integer(1), CborValue::Integer(1), methods] => { - let nfc_options = NfcOptions::try_from(methods)?; + fn try_from(value: ciborium::Value) -> Result { + if let ciborium::Value::Array(list) = value { + match list.as_slice() { + [ciborium::Value::Integer(i1), ciborium::Value::Integer(i11), methods] + if >::into(*i1) == 1 + && >::into(*i11) == 1 => + { + let nfc_options = NfcOptions::try_from(methods.clone())?; Ok(DeviceRetrievalMethod::NFC(nfc_options)) } - [CborValue::Integer(2), CborValue::Integer(1), methods] => { - let ble_options = BleOptions::try_from(methods)?; + [ciborium::Value::Integer(i2), ciborium::Value::Integer(i1), methods] + if >::into(*i1) == 1 + && >::into(*i2) == 2 => + { + let ble_options = + BleOptions::try_from(methods.clone()).map_err(|_| Error::Malformed)?; Ok(DeviceRetrievalMethod::BLE(ble_options)) } - [CborValue::Integer(3), CborValue::Integer(1), methods] => { - let wifi_options = WifiOptions::try_from(methods)?; + [ciborium::Value::Integer(i3), ciborium::Value::Integer(i1), methods] + if >::into(*i1) == 1 + && >::into(*i3) == 3 => + { + let wifi_options = + WifiOptions::try_from(methods.clone()).map_err(|_| Error::Malformed)?; Ok(DeviceRetrievalMethod::WIFI(wifi_options)) } - [CborValue::Integer(_), _, _] => Err(Error::UnsupportedDRM), + [ciborium::Value::Integer(_), _, _] => Err(Error::UnsupportedDRM), _ => Err(Error::Malformed), } } else { @@ -273,7 +310,7 @@ impl TryFrom for DeviceRetrievalMethod { } } -impl From for CborValue { +impl From for ciborium::Value { fn from(drm: DeviceRetrievalMethod) -> Self { let transport_type = drm.transport_type().into(); let version = drm.version().into(); @@ -282,36 +319,39 @@ impl From for CborValue { DeviceRetrievalMethod::BLE(opts) => opts.into(), DeviceRetrievalMethod::WIFI(opts) => opts.into(), }; - CborValue::Array(vec![transport_type, version, retrieval_method]) + ciborium::Value::Array(vec![transport_type, version, retrieval_method]) } } -impl TryFrom for BleOptions { +impl TryFrom for BleOptions { type Error = Error; - fn try_from(v: CborValue) -> Result { - if let CborValue::Map(mut map) = v { - let central_client_mode = match ( - map.remove(&CborValue::Integer(1)), - map.remove(&CborValue::Integer(11)), - ) { - (Some(CborValue::Bool(true)), Some(CborValue::Bytes(uuid))) => { + fn try_from(v: ciborium::Value) -> Result { + if let ciborium::Value::Map(map) = v { + let mut map: BTreeMap = map + .into_iter() + .map(|(k, v)| { + let k = k.into_integer().map_err(|_| Error::CborError)?.into(); + Ok((k, v)) + }) + .collect::, Error>>()?; + 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)?; Some(CentralClientMode { uuid: Uuid::from_bytes(uuid_bytes), }) } - (Some(CborValue::Bool(false)), _) => None, + (Some(ciborium::Value::Bool(false)), _) => None, _ => return Err(Error::Malformed), }; - let peripheral_server_mode = match ( - map.remove(&CborValue::Integer(0)), - map.remove(&CborValue::Integer(10)), - ) { - (Some(CborValue::Bool(true)), Some(CborValue::Bytes(uuid))) => { + 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(&CborValue::Integer(20)) { + let ble_device_address = match map.remove(&20) { Some(value) => Some(value.try_into().map_err(|_| Error::Malformed)?), None => None, }; @@ -320,7 +360,7 @@ impl TryFrom for BleOptions { ble_device_address, }) } - (Some(CborValue::Bool(false)), _) => None, + (Some(ciborium::Value::Bool(false)), _) => None, _ => return Err(Error::Malformed), }; @@ -334,20 +374,26 @@ impl TryFrom for BleOptions { } } -impl From for CborValue { - fn from(o: BleOptions) -> CborValue { - let mut map = BTreeMap::new(); +impl From for ciborium::Value { + fn from(o: BleOptions) -> ciborium::Value { + let mut map = vec![]; match o.central_client_mode { Some(CentralClientMode { uuid }) => { - map.insert(CborValue::Integer(1), CborValue::Bool(true)); - map.insert( - CborValue::Integer(11), - CborValue::Bytes(uuid.as_bytes().to_vec()), - ); + map.push(( + ciborium::Value::Integer(1.into()), + ciborium::Value::Bool(true), + )); + map.push(( + ciborium::Value::Integer(11.into()), + ciborium::Value::Bytes(uuid.as_bytes().to_vec()), + )); } None => { - map.insert(CborValue::Integer(1), CborValue::Bool(false)); + map.push(( + ciborium::Value::Integer(1.into()), + ciborium::Value::Bool(false), + )); } } @@ -356,46 +402,52 @@ impl From for CborValue { uuid, ble_device_address, }) => { - map.insert(CborValue::Integer(0), CborValue::Bool(true)); - map.insert( - CborValue::Integer(10), - CborValue::Bytes(uuid.as_bytes().to_vec()), - ); + map.push(( + ciborium::Value::Integer(0.into()), + ciborium::Value::Bool(true), + )); + map.push(( + ciborium::Value::Integer(10.into()), + ciborium::Value::Bytes(uuid.as_bytes().to_vec()), + )); if let Some(address) = ble_device_address { - map.insert(CborValue::Integer(20), address.into()); + map.push((ciborium::Value::Integer(20.into()), address.into())); } } None => { - map.insert(CborValue::Integer(0), CborValue::Bool(false)); + map.push(( + ciborium::Value::Integer(0.into()), + ciborium::Value::Bool(false), + )); } } - CborValue::Map(map) + ciborium::Value::Map(map) } } -impl TryFrom for WifiOptions { +impl TryFrom for WifiOptions { type Error = Error; - fn try_from(v: CborValue) -> Result { + fn try_from(v: ciborium::Value) -> Result { fn lookup_opt_string( - map: &BTreeMap, + map: &BTreeMap, idx: i128, ) -> Result, Error> { - match map.get(&CborValue::Integer(idx)) { + match map.get(&idx) { None => Ok(None), - Some(CborValue::Text(text)) => Ok(Some(text.to_string())), + Some(ciborium::Value::Text(text)) => Ok(Some(text.to_string())), _ => Err(Error::InvalidWifiOptions), } } fn lookup_opt_u64( - map: &BTreeMap, + map: &BTreeMap, idx: i128, ) -> Result, Error> { - match map.get(&CborValue::Integer(idx)) { + match map.get(&idx) { None => Ok(None), - Some(CborValue::Integer(int_val)) => { + Some(ciborium::Value::Integer(int_val)) => { let uint_val = u64::try_from(*int_val).map_err(|_| Error::InvalidWifiOptions)?; Ok(Some(uint_val)) @@ -405,10 +457,10 @@ impl TryFrom for WifiOptions { } fn lookup_opt_bytestr( - map: &BTreeMap, + map: &BTreeMap, idx: i128, ) -> Result, Error> { - match map.get(&CborValue::Integer(idx)) { + match map.get(&idx) { None => Ok(None), Some(cbor_val) => { let byte_str = ByteStr::try_from(cbor_val.clone()) @@ -418,8 +470,17 @@ impl TryFrom for WifiOptions { } } - let map: BTreeMap = match v { - CborValue::Map(map) => Ok(map), + let map: BTreeMap = match v { + ciborium::Value::Map(map) => { + let map = map + .into_iter() + .map(|(k, v)| { + let k = k.into_integer().map_err(|_| Error::CborError)?.into(); + Ok((k, v)) + }) + .collect::, Error>>()?; + Ok(map) + } _ => Err(Error::InvalidWifiOptions), }?; @@ -455,54 +516,56 @@ impl TryFrom for WifiOptions { } } -impl From for CborValue { - fn from(o: WifiOptions) -> CborValue { - let mut map = BTreeMap::::new(); +impl From for ciborium::Value { + fn from(o: WifiOptions) -> ciborium::Value { + let mut map = vec![]; if let Some(v) = o.pass_phrase { - map.insert(CborValue::Integer(0), v.into()); + map.push((ciborium::Value::Integer(0.into()), v.into())); } if let Some(v) = o.channel_info_operating_class { - map.insert(CborValue::Integer(1), v.into()); + map.push((ciborium::Value::Integer(1.into()), v.into())); } if let Some(v) = o.channel_info_channel_number { - map.insert(CborValue::Integer(2), v.into()); + map.push((ciborium::Value::Integer(2.into()), v.into())); } if let Some(v) = o.band_info { - map.insert(CborValue::Integer(3), v.into()); + map.push((ciborium::Value::Integer(3.into()), v.into())); } - CborValue::Map(map) + ciborium::Value::Map(map) } } -impl From for CborValue { - fn from(m: ServerRetrievalMethods) -> CborValue { - let mut map = BTreeMap::::new(); +impl From for ciborium::Value { + fn from(m: ServerRetrievalMethods) -> ciborium::Value { + let mut map = vec![]; if let Some((x, y, z)) = m.web_api { - map.insert( + map.push(( "webApi".to_string().into(), - CborValue::Array(vec![x.into(), y.into(), z.into()]), - ); + ciborium::Value::Array(vec![x.into(), y.into(), z.into()]), + )); } if let Some((x, y, z)) = m.oidc { - map.insert( + map.push(( "oidc".to_string().into(), - CborValue::Array(vec![x.into(), y.into(), z.into()]), - ); + ciborium::Value::Array(vec![x.into(), y.into(), z.into()]), + )); } - CborValue::Map(map) + ciborium::Value::Map(map) } } #[cfg(test)] mod test { - use super::*; - use crate::definitions::session::create_p256_ephemeral_keys; use uuid::Uuid; + use crate::definitions::session::create_p256_ephemeral_keys; + + use super::*; + #[test] fn device_engagement_cbor_roundtrip() { let key_pair = create_p256_ephemeral_keys().unwrap(); @@ -526,8 +589,8 @@ mod test { protocol_info: None, }; - let bytes = serde_cbor::to_vec(&device_engagement).unwrap(); - let roundtripped = serde_cbor::from_slice(&bytes).unwrap(); + let bytes = crate::cbor::to_vec(&device_engagement).unwrap(); + let roundtripped = crate::cbor::from_slice(&bytes).unwrap(); assert_eq!(device_engagement, roundtripped) } @@ -541,8 +604,8 @@ mod test { } fn wifi_options_cbor_roundtrip_test(wifi_options: WifiOptions) { - let bytes: Vec = serde_cbor::to_vec(&wifi_options).unwrap(); - let deserialized: WifiOptions = serde_cbor::from_slice(&bytes).unwrap(); + let bytes: Vec = crate::cbor::to_vec(&wifi_options).unwrap(); + let deserialized: WifiOptions = crate::cbor::from_slice(&bytes).unwrap(); assert_eq!(wifi_options, deserialized); } diff --git a/src/definitions/device_engagement/error.rs b/src/definitions/device_engagement/error.rs index 593cb6dc..365cc731 100644 --- a/src/definitions/device_engagement/error.rs +++ b/src/definitions/device_engagement/error.rs @@ -1,6 +1,6 @@ +use crate::cbor::CborError; use crate::definitions::device_key::cose_key::Error as CoseKeyError; use crate::definitions::helpers::tag24::Error as Tag24Error; -use serde_cbor::Error as SerdeCborError; /// Errors that can occur when deserialising a DeviceEngagement. #[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)] @@ -24,7 +24,7 @@ pub enum Error { #[error("Something went wrong parsing a tag24")] Tag24Error, #[error("Could not deserialize from cbor")] - SerdeCborError, + CborError, #[error("NFC Command Data Length must be between 255 and 65535")] InvalidNfcCommandDataLengthError, #[error("NFC Response Data Length must be between 256 and 65536")] @@ -43,8 +43,8 @@ impl From for Error { } } -impl From for Error { - fn from(_: SerdeCborError) -> Self { - Error::SerdeCborError +impl From for Error { + fn from(_: CborError) -> Self { + Error::CborError } } diff --git a/src/definitions/device_engagement/nfc_options.rs b/src/definitions/device_engagement/nfc_options.rs index cbf8fe89..df6bdf74 100644 --- a/src/definitions/device_engagement/nfc_options.rs +++ b/src/definitions/device_engagement/nfc_options.rs @@ -1,50 +1,117 @@ +use crate::definitions::device_engagement::error::Error; use anyhow::Result; -use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer, Serialize}; use std::collections::BTreeMap; -use crate::definitions::device_engagement::error::Error; - /// The maximum length of the NFC command, as specified in ISO_18013-5 2021 Section 8.3.3.1.2 /// Values of this type must lie between 255 and 65,535 inclusive, as specified in Note 2. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, PartialEq, Eq)] pub struct CommandDataLength(u16); +// Implement Deserialize manually to add the validation check +impl<'de> Deserialize<'de> for CommandDataLength { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Deserialize as an u16 first + let value = u16::deserialize(deserializer)?; + + // Check the value + if value >= CommandDataLength::MIN.get() { + Ok(CommandDataLength(value)) + } else { + Err(D::Error::custom(format!( + "CommandDataLength must be greater than {}", + CommandDataLength::MIN.get() + ))) + } + } +} + /// The maximum length of the NFC response data, as specified in ISO_18013-5 2021 Section 8.3.3.1.2 /// Values of this type must lie between 256 and 65,536 inclusive, as specified in Note 2. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, PartialEq, Eq)] pub struct ResponseDataLength(u32); +// Implement Deserialize manually to add the validation check +impl<'de> Deserialize<'de> for ResponseDataLength { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Deserialize as an u32 first + let value = u32::deserialize(deserializer)?; + + // Check the value + if value >= ResponseDataLength::MIN.get() { + Ok(ResponseDataLength(value)) + } else { + Err(D::Error::custom(format!( + "ResponseDataLength must be greater than {}", + ResponseDataLength::MIN.get() + ))) + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)] -#[serde(try_from = "CborValue", into = "CborValue")] +#[serde(try_from = "ciborium::Value", into = "ciborium::Value")] pub struct NfcOptions { max_len_command_data_field: CommandDataLength, max_len_response_data_field: ResponseDataLength, } -impl TryFrom for NfcOptions { +impl TryFrom for NfcOptions { type Error = Error; - fn try_from(v: CborValue) -> Result { - let map: BTreeMap = match v { - CborValue::Map(map) => Ok(map), - _ => Err(Error::InvalidNfcOptions), - }?; + fn try_from(v: ciborium::Value) -> Result { + let mut map: BTreeMap = match v { + ciborium::Value::Map(map) => map + .into_iter() + .map(|(k, v)| { + let k = k.into_integer().map_err(|_| Error::CborError)?.into(); + Ok((k, v)) + }) + .collect::, Error>>()?, + _ => return Err(Error::InvalidNfcOptions), + }; Ok(NfcOptions::default()) .and_then(|nfc_opts| { - map.get(&CborValue::Integer(0)) + map.remove(&0) .ok_or(Error::InvalidNfcOptions) - .and_then(CommandDataLength::try_from) + .and_then(|v| { + let v: u16 = v + .into_integer() + .map_err(|_| Error::CborError)? + .try_into() + .map_err(|_| Error::CborError)?; + if v < CommandDataLength::MIN.get() { + return Err(Error::InvalidNfcCommandDataLengthError); + } + Ok(CommandDataLength(v)) + }) .map(|max_len_command_data_field| NfcOptions { max_len_command_data_field, ..nfc_opts }) }) .and_then(|nfc_opts| { - map.get(&CborValue::Integer(1)) + map.remove(&1) .ok_or(Error::InvalidNfcOptions) - .and_then(ResponseDataLength::try_from) + .and_then(|v| { + let v: u32 = v + .into_integer() + .map_err(|_| Error::CborError)? + .try_into() + .map_err(|_| Error::CborError)?; + if v < ResponseDataLength::MIN.get() { + return Err(Error::InvalidNfcResponseDataLengthError); + } + Ok(ResponseDataLength(v)) + }) .map(|max_len_response_data_field| NfcOptions { max_len_response_data_field, ..nfc_opts @@ -53,19 +120,19 @@ impl TryFrom for NfcOptions { } } -impl From for CborValue { - fn from(o: NfcOptions) -> CborValue { - let mut map = BTreeMap::::new(); - map.insert( - CborValue::Integer(0), - CborValue::from(o.max_len_command_data_field), - ); - map.insert( - CborValue::Integer(1), - CborValue::from(o.max_len_response_data_field), - ); - - CborValue::Map(map) +impl From for ciborium::Value { + fn from(o: NfcOptions) -> ciborium::Value { + let map = vec![ + ( + ciborium::Value::Integer(0.into()), + ciborium::Value::Integer(o.max_len_command_data_field.get().into()), + ), + ( + ciborium::Value::Integer(1.into()), + ciborium::Value::Integer(o.max_len_response_data_field.get().into()), + ), + ]; + ciborium::Value::Map(map) } } @@ -122,20 +189,9 @@ command_data_length_try_from! { CommandDataLength(usize); } -impl TryFrom<&CborValue> for CommandDataLength { - type Error = Error; - - fn try_from(v: &CborValue) -> Result { - match v { - CborValue::Integer(int_val) => Self::try_from(*int_val), - _ => Err(Error::InvalidNfcOptions), - } - } -} - -impl From for CborValue { - fn from(cdl: CommandDataLength) -> CborValue { - CborValue::Integer(cdl.get().into()) +impl From for ciborium::Value { + fn from(cdl: CommandDataLength) -> ciborium::Value { + ciborium::Value::Integer(cdl.get().into()) } } @@ -190,26 +246,16 @@ response_data_length_try_from! { ResponseDataLength(usize); } -impl TryFrom<&CborValue> for ResponseDataLength { - type Error = Error; - - fn try_from(v: &CborValue) -> Result { - match v { - CborValue::Integer(int_val) => Self::try_from(*int_val), - _ => Err(Error::InvalidNfcOptions), - } - } -} - -impl From for CborValue { - fn from(rdl: ResponseDataLength) -> CborValue { - CborValue::Integer(rdl.get().into()) +impl From for ciborium::Value { + fn from(rdl: ResponseDataLength) -> ciborium::Value { + ciborium::Value::Integer(rdl.get().into()) } } #[cfg(test)] mod test { use super::*; + use crate::cbor; #[test] fn command_data_length_valid_data_test() { @@ -271,8 +317,8 @@ mod test { #[test] fn command_data_length_cbor_roundtrip_test() { let cdl: CommandDataLength = CommandDataLength::new(512).unwrap(); - let bytes: Vec = serde_cbor::to_vec(&cdl).unwrap(); - let deserialized: CommandDataLength = serde_cbor::from_slice(&bytes).unwrap(); + let bytes: Vec = crate::cbor::to_vec(&cdl).unwrap(); + let deserialized: CommandDataLength = crate::cbor::from_slice(&bytes).unwrap(); assert_eq!(cdl, deserialized); } @@ -339,14 +385,14 @@ mod test { #[test] fn response_data_length_cbor_roundtrip_test() { let rdl: ResponseDataLength = ResponseDataLength::new(512).unwrap(); - let bytes: Vec = serde_cbor::to_vec(&rdl).unwrap(); - let deserialized: ResponseDataLength = serde_cbor::from_slice(&bytes).unwrap(); + let bytes: Vec = cbor::to_vec(&rdl).unwrap(); + let deserialized: ResponseDataLength = cbor::from_slice(&bytes).unwrap(); assert_eq!(rdl, deserialized); } fn nfc_options_cbor_roundtrip_test(nfc_options: NfcOptions) { - let bytes: Vec = serde_cbor::to_vec(&nfc_options).unwrap(); - let deserialized: NfcOptions = serde_cbor::from_slice(&bytes).unwrap(); + let bytes: Vec = cbor::to_vec(&nfc_options).unwrap(); + let deserialized: NfcOptions = cbor::from_slice(&bytes).unwrap(); assert_eq!(nfc_options, deserialized); } @@ -377,10 +423,10 @@ mod test { max_len_response_data_field: ResponseDataLength::MIN, }; - let bytes: Vec = serde_cbor::to_vec(&nfc_options).unwrap(); + let bytes: Vec = cbor::to_vec(&nfc_options).unwrap(); let deserialized_result: Result = - serde_cbor::from_slice(&bytes).map_err(Error::from); - assert_eq!(Err(Error::SerdeCborError), deserialized_result); + cbor::from_slice(&bytes).map_err(Error::from); + assert_eq!(Err(Error::CborError), deserialized_result); } #[test] @@ -390,9 +436,9 @@ mod test { max_len_response_data_field: ResponseDataLength::MIN, }; - let bytes: Vec = serde_cbor::to_vec(&nfc_options).unwrap(); + let bytes: Vec = cbor::to_vec(&nfc_options).unwrap(); let deserialized_result: Result = - serde_cbor::from_slice(&bytes).map_err(Error::from); - assert_eq!(Err(Error::SerdeCborError), deserialized_result); + cbor::from_slice(&bytes).map_err(Error::from); + assert_eq!(Err(Error::CborError), deserialized_result); } } diff --git a/src/definitions/device_key/cose_key.rs b/src/definitions/device_key/cose_key.rs index 1e796ace..ab409666 100644 --- a/src/definitions/device_key/cose_key.rs +++ b/src/definitions/device_key/cose_key.rs @@ -22,18 +22,18 @@ //! } //! } //! ``` +use std::collections::BTreeMap; + use aes::cipher::generic_array::{typenum::U8, GenericArray}; -use cose_rs::algorithm::Algorithm; +use coset::iana::Algorithm; use p256::EncodedPoint; use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; use ssi_jwk::JWK; -use std::collections::BTreeMap; /// 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)] -#[serde(try_from = "CborValue", into = "CborValue")] +#[serde(try_from = "ciborium::Value", into = "ciborium::Value")] pub enum CoseKey { EC2 { crv: EC2Curve, x: Vec, y: EC2Y }, OKP { crv: OKPCurve, x: Vec }, @@ -64,7 +64,7 @@ pub enum OKPCurve { Ed448, } -/// Errors that can occur when deserialising a COSE_Key. +/// Errors that can occur when deserializing a COSE_Key. #[derive(Debug, Clone, thiserror::Error)] pub enum Error { #[error("COSE_Key of kty 'EC2' missing x coordinate")] @@ -72,12 +72,13 @@ pub enum Error { #[error("COSE_Key of kty 'EC2' missing y coordinate")] EC2MissingY, #[error("Expected to parse a CBOR bool or bstr for y-coordinate, received: '{0:?}'")] - InvalidTypeY(CborValue), + InvalidTypeY(ciborium::Value), #[error("Expected to parse a CBOR map, received: '{0:?}'")] - NotAMap(CborValue), + NotAMap(ciborium::Value), #[error("Unable to discern the elliptic curve")] UnknownCurve, - #[error("This implementation of COSE_Key only supports P-256, P-384, P-521, Ed25519 and Ed448 elliptic curves")] + #[error("This implementation of COSE_Key only supports P-256, P-384, P-521, Ed25519 and Ed448 elliptic curves" + )] UnsupportedCurve, #[error("This implementation of COSE_Key only supports EC2 and OKP keys")] UnsupportedKeyType, @@ -85,6 +86,8 @@ pub enum Error { InvalidCoseKey, #[error("Constructing a JWK from CoseKey with point-compression is not supported.")] UnsupportedFormat, + #[error("could not serialize from to cbor")] + CborError, } impl CoseKey { @@ -116,60 +119,83 @@ impl CoseKey { } } -impl From for CborValue { - fn from(key: CoseKey) -> CborValue { - let mut map = BTreeMap::new(); +impl From for ciborium::Value { + fn from(key: CoseKey) -> ciborium::Value { + let mut map = vec![]; match key { CoseKey::EC2 { crv, x, y } => { // kty: 1, EC2: 2 - map.insert(CborValue::Integer(1), CborValue::Integer(2)); + map.push(( + ciborium::Value::Integer(1.into()), + ciborium::Value::Integer(2.into()), + )); // crv: -1 - map.insert(CborValue::Integer(-1), crv.into()); + map.push((ciborium::Value::Integer((-1).into()), { + let cbor: ciborium::Value = crv.into(); + cbor + })); // x: -2 - map.insert(CborValue::Integer(-2), CborValue::Bytes(x)); + map.push(( + ciborium::Value::Integer((-2).into()), + ciborium::Value::Bytes(x), + )); // y: -3 - map.insert(CborValue::Integer(-3), y.into()); + map.push((ciborium::Value::Integer((-3).into()), { + let cbor: ciborium::Value = y.into(); + cbor + })); } CoseKey::OKP { crv, x } => { // kty: 1, OKP: 1 - map.insert(CborValue::Integer(1), CborValue::Integer(1)); + map.push(( + ciborium::Value::Integer(1.into()), + ciborium::Value::Integer(1.into()), + )); // crv: -1 - map.insert(CborValue::Integer(-1), crv.into()); + map.push((ciborium::Value::Integer((-1).into()), { + let cbor: ciborium::Value = crv.into(); + cbor + })); // x: -2 - map.insert(CborValue::Integer(-2), CborValue::Bytes(x)); + map.push(( + ciborium::Value::Integer((-2).into()), + ciborium::Value::Bytes(x), + )); } } - CborValue::Map(map) + ciborium::Value::Map(map) } } -impl TryFrom for CoseKey { +impl TryFrom for CoseKey { type Error = Error; - fn try_from(v: CborValue) -> Result { - if let CborValue::Map(mut map) = v { - match ( - map.remove(&CborValue::Integer(1)), - map.remove(&CborValue::Integer(-1)), - map.remove(&CborValue::Integer(-2)), - ) { + fn try_from(v: ciborium::Value) -> Result { + if let ciborium::Value::Map(map) = v.clone() { + let mut map: BTreeMap = map + .into_iter() + .map(|(k, v)| { + let k = k.into_integer().map_err(|_| Error::CborError)?.into(); + Ok((k, v)) + }) + .collect::, Error>>()?; + match (map.remove(&1), map.remove(&-1), map.remove(&-2)) { ( - Some(CborValue::Integer(2)), - Some(CborValue::Integer(crv_id)), - Some(CborValue::Bytes(x)), - ) => { + Some(ciborium::Value::Integer(i2)), + Some(ciborium::Value::Integer(crv_id)), + Some(ciborium::Value::Bytes(x)), + ) if >::into(i2) == 2 => { + let crv_id: i128 = crv_id.into(); let crv = crv_id.try_into()?; - let y = map - .remove(&CborValue::Integer(-3)) - .ok_or(Error::EC2MissingY)? - .try_into()?; + let y = map.remove(&-3).ok_or(Error::EC2MissingY)?.try_into()?; Ok(Self::EC2 { crv, x, y }) } ( - Some(CborValue::Integer(1)), - Some(CborValue::Integer(crv_id)), - Some(CborValue::Bytes(x)), - ) => { + Some(ciborium::Value::Integer(i1)), + Some(ciborium::Value::Integer(crv_id)), + Some(ciborium::Value::Bytes(x)), + ) if >::into(i1) == 1 => { + let crv_id: i128 = crv_id.into(); let crv = crv_id.try_into()?; Ok(Self::OKP { crv, x }) } @@ -227,34 +253,34 @@ impl TryFrom for EncodedPoint { } } -impl From for CborValue { - fn from(y: EC2Y) -> CborValue { +impl From for ciborium::Value { + fn from(y: EC2Y) -> ciborium::Value { match y { - EC2Y::Value(s) => CborValue::Bytes(s), - EC2Y::SignBit(b) => CborValue::Bool(b), + EC2Y::Value(s) => ciborium::Value::Bytes(s), + EC2Y::SignBit(b) => ciborium::Value::Bool(b), } } } -impl TryFrom for EC2Y { +impl TryFrom for EC2Y { type Error = Error; - fn try_from(v: CborValue) -> Result { + fn try_from(v: ciborium::Value) -> Result { match v { - CborValue::Bytes(s) => Ok(EC2Y::Value(s)), - CborValue::Bool(b) => Ok(EC2Y::SignBit(b)), + ciborium::Value::Bytes(s) => Ok(EC2Y::Value(s)), + ciborium::Value::Bool(b) => Ok(EC2Y::SignBit(b)), _ => Err(Error::InvalidTypeY(v)), } } } -impl From for CborValue { - fn from(crv: EC2Curve) -> CborValue { +impl From for ciborium::Value { + fn from(crv: EC2Curve) -> ciborium::Value { match crv { - EC2Curve::P256 => CborValue::Integer(1), - EC2Curve::P384 => CborValue::Integer(2), - EC2Curve::P521 => CborValue::Integer(3), - EC2Curve::P256K => CborValue::Integer(8), + EC2Curve::P256 => ciborium::Value::Integer(1.into()), + EC2Curve::P384 => ciborium::Value::Integer(2.into()), + EC2Curve::P521 => ciborium::Value::Integer(3.into()), + EC2Curve::P256K => ciborium::Value::Integer(8.into()), } } } @@ -273,13 +299,13 @@ impl TryFrom for EC2Curve { } } -impl From for CborValue { - fn from(crv: OKPCurve) -> CborValue { +impl From for ciborium::Value { + fn from(crv: OKPCurve) -> ciborium::Value { match crv { - OKPCurve::X25519 => CborValue::Integer(4), - OKPCurve::X448 => CborValue::Integer(5), - OKPCurve::Ed25519 => CborValue::Integer(6), - OKPCurve::Ed448 => CborValue::Integer(7), + OKPCurve::X25519 => ciborium::Value::Integer(4.into()), + OKPCurve::X448 => ciborium::Value::Integer(5.into()), + OKPCurve::Ed25519 => ciborium::Value::Integer(6.into()), + OKPCurve::Ed448 => ciborium::Value::Integer(7.into()), } } } @@ -420,21 +446,24 @@ impl TryFrom<&ssi_jwk::OctetParams> for OKPCurve { #[cfg(test)] mod test { - use super::*; use hex::FromHex; + use crate::cbor; + + use super::*; + static EC_P256: &str = include_str!("../../../test/definitions/cose_key/ec_p256.cbor"); #[test] fn ec_p256() { let key_bytes = >::from_hex(EC_P256).expect("unable to convert cbor hex to bytes"); - let key = serde_cbor::from_slice(&key_bytes).unwrap(); + let key = crate::cbor::from_slice(&key_bytes).unwrap(); match &key { CoseKey::EC2 { crv, .. } => assert_eq!(crv, &EC2Curve::P256), _ => panic!("expected an EC2 cose key"), }; assert_eq!( - serde_cbor::to_vec(&key).unwrap(), + cbor::to_vec(&key).unwrap(), key_bytes, "cbor encoding roundtrip failed" ); diff --git a/src/definitions/device_key/mod.rs b/src/definitions/device_key/mod.rs index af08bca0..7b045d58 100644 --- a/src/definitions/device_key/mod.rs +++ b/src/definitions/device_key/mod.rs @@ -43,7 +43,6 @@ //! ``` use crate::definitions::helpers::{NonEmptyMap, NonEmptyVec}; use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; use std::collections::BTreeMap; pub mod cose_key; @@ -63,7 +62,7 @@ pub struct DeviceKeyInfo { /// Optional key information. #[serde(skip_serializing_if = "Option::is_none")] - pub key_info: Option>, + pub key_info: Option>, } #[derive(Clone, Serialize, Deserialize, Debug, Default)] diff --git a/src/definitions/device_request.rs b/src/definitions/device_request.rs index c3e4a97f..03470aaf 100644 --- a/src/definitions/device_request.rs +++ b/src/definitions/device_request.rs @@ -1,6 +1,7 @@ //! This module contains the definitions for the device request functionality. +use crate::cose::MaybeTagged; use crate::definitions::helpers::{NonEmptyMap, NonEmptyVec, Tag24}; -use cose_rs::CoseSign1; +use coset::CoseSign1; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -11,7 +12,7 @@ pub type IntentToRetain = bool; pub type DataElementIdentifier = String; pub type DataElements = NonEmptyMap; pub type Namespaces = NonEmptyMap; -pub type ReaderAuth = CoseSign1; +pub type ReaderAuth = MaybeTagged; /// Represents a device request. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -50,7 +51,7 @@ pub struct ItemsRequest { /// Additional information for the request. #[serde(skip_serializing_if = "Option::is_none")] - pub request_info: Option>, + pub request_info: Option>, } impl DeviceRequest { @@ -65,8 +66,8 @@ mod test { fn items_request() { const HEX: &str = "D8185868A267646F6354797065756F72672E69736F2E31383031332E352E312E6D444C6A6E616D65537061636573A1716F72672E69736F2E31383031332E352E31A36B66616D696C795F6E616D65F46A676976656E5F6E616D65F46F646F63756D656E745F6E756D626572F4"; let bytes: Vec = hex::decode(HEX).unwrap(); - let req: Tag24 = serde_cbor::from_slice(&bytes).unwrap(); - let roundtripped = serde_cbor::to_vec(&req).unwrap(); + let req: Tag24 = crate::cbor::from_slice(&bytes).unwrap(); + let roundtripped = crate::cbor::to_vec(&req).unwrap(); assert_eq!(bytes, roundtripped); } @@ -74,8 +75,8 @@ mod test { fn doc_request() { const HEX: &str = "A16C6974656D7352657175657374D8185868A267646F6354797065756F72672E69736F2E31383031332E352E312E6D444C6A6E616D65537061636573A1716F72672E69736F2E31383031332E352E31A36B66616D696C795F6E616D65F46A676976656E5F6E616D65F46F646F63756D656E745F6E756D626572F4"; let bytes: Vec = hex::decode(HEX).unwrap(); - let req: DocRequest = serde_cbor::from_slice(&bytes).unwrap(); - let roundtripped = serde_cbor::to_vec(&req).unwrap(); + let req: DocRequest = crate::cbor::from_slice(&bytes).unwrap(); + let roundtripped = crate::cbor::to_vec(&req).unwrap(); assert_eq!(bytes, roundtripped); } @@ -83,8 +84,8 @@ mod test { fn device_request() { const HEX: &str = "A26776657273696F6E63312E306B646F63526571756573747381A16C6974656D7352657175657374D8185868A267646F6354797065756F72672E69736F2E31383031332E352E312E6D444C6A6E616D65537061636573A1716F72672E69736F2E31383031332E352E31A36B66616D696C795F6E616D65F46A676976656E5F6E616D65F46F646F63756D656E745F6E756D626572F4"; let bytes: Vec = hex::decode(HEX).unwrap(); - let req: DeviceRequest = serde_cbor::from_slice(&bytes).unwrap(); - let roundtripped = serde_cbor::to_vec(&req).unwrap(); + let req: DeviceRequest = crate::cbor::from_slice(&bytes).unwrap(); + let roundtripped = crate::cbor::to_vec(&req).unwrap(); assert_eq!(bytes, roundtripped); } } diff --git a/src/definitions/device_response.rs b/src/definitions/device_response.rs index 9c1091d2..1d8bea82 100644 --- a/src/definitions/device_response.rs +++ b/src/definitions/device_response.rs @@ -1,10 +1,12 @@ //! This module contains the definition of the `DeviceResponse` struct and related types. +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + use crate::definitions::{ helpers::{NonEmptyMap, NonEmptyVec}, DeviceSigned, IssuerSigned, }; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; /// Represents a device response. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -123,22 +125,95 @@ impl TryFrom for Status { #[cfg(test)] mod test { - use super::DeviceResponse; + use crate::cbor; + use crate::cose::MaybeTagged; + use crate::definitions::device_signed::{ + DeviceNamespaces, DeviceNamespacesBytes, DeviceSignedItems, + }; + use crate::definitions::helpers::NonEmptyVec; + use crate::definitions::issuer_signed::{IssuerNamespaces, IssuerSignedItemBytes}; + use crate::definitions::{ + DeviceAuth, DeviceSigned, DigestId, Document, IssuerSigned, IssuerSignedItem, + }; + use coset::{CoseMac0, CoseSign1}; use hex::FromHex; + use super::{ + DeviceResponse, DocumentError, DocumentErrorCode, DocumentErrors, Documents, Status, + }; + static DEVICE_RESPONSE_CBOR: &str = include_str!("../../test/definitions/device_response.cbor"); #[test] - fn serde_device_response() { + fn device_response() { let cbor_bytes = >::from_hex(DEVICE_RESPONSE_CBOR).expect("unable to convert cbor hex to bytes"); let response: DeviceResponse = - serde_cbor::from_slice(&cbor_bytes).expect("unable to decode cbor as a DeviceResponse"); + cbor::from_slice(&cbor_bytes).expect("unable to decode cbor as a DeviceResponse"); let roundtripped_bytes = - serde_cbor::to_vec(&response).expect("unable to encode DeviceResponse as cbor bytes"); + cbor::to_vec(&response).expect("unable to encode DeviceResponse as cbor bytes"); assert_eq!( cbor_bytes, roundtripped_bytes, "original cbor and re-serialized DeviceResponse do not match" ); } + + #[test] + fn device_response_roundtrip() { + static COSE_SIGN1: &str = include_str!("../../test/definitions/cose/sign1/serialized.cbor"); + static COSE_MAC0: &str = include_str!("../../test/definitions/cose/mac0/serialized.cbor"); + + let bytes = Vec::::from_hex(COSE_SIGN1).unwrap(); + let cose_sign1: MaybeTagged = + cbor::from_slice(&bytes).expect("failed to parse COSE_Sign1 from bytes"); + let bytes = Vec::::from_hex(COSE_MAC0).unwrap(); + let cose_mac0: MaybeTagged = + cbor::from_slice(&bytes).expect("failed to parse COSE_MAC0 from bytes"); + + let issuer_signed_item = IssuerSignedItem { + digest_id: DigestId::new(42), + random: vec![42_u8].into(), + element_identifier: "42".to_string(), + element_value: ciborium::Value::Null, + }; + let issuer_signed_item_bytes = IssuerSignedItemBytes::new(issuer_signed_item).unwrap(); + let vec = NonEmptyVec::new(issuer_signed_item_bytes); + let issuer_namespaces = IssuerNamespaces::new("a".to_string(), vec); + let device_signed_items = DeviceSignedItems::new("a".to_string(), ciborium::Value::Null); + let mut device_namespaces = DeviceNamespaces::new(); + device_namespaces.insert("a".to_string(), device_signed_items); + let device_namespaces_bytes = DeviceNamespacesBytes::new(device_namespaces).unwrap(); + let doc = Document { + doc_type: "aaa".to_string(), + issuer_signed: IssuerSigned { + namespaces: Some(issuer_namespaces), + issuer_auth: cose_sign1.clone(), + }, + device_signed: DeviceSigned { + namespaces: device_namespaces_bytes, + device_auth: DeviceAuth::Mac { + device_mac: cose_mac0, + }, + }, + errors: None, + }; + let docs = Documents::new(doc); + let document_error_code = DocumentErrorCode::DataNotReturned; + let mut error = DocumentError::new(); + error.insert("a".to_string(), document_error_code); + let errors = DocumentErrors::new(error); + let res = DeviceResponse { + version: "1.0".to_string(), + documents: Some(docs), + document_errors: Some(errors), + status: Status::OK, + }; + let bytes = cbor::to_vec(&res).unwrap(); + let res: DeviceResponse = cbor::from_slice(&bytes).unwrap(); + let roundtripped_bytes = cbor::to_vec(&res).unwrap(); + assert_eq!( + bytes, roundtripped_bytes, + "original cbor and re-serialized DeviceResponse do not match" + ); + } } diff --git a/src/definitions/device_signed.rs b/src/definitions/device_signed.rs index 438ae6c1..0b231e1f 100644 --- a/src/definitions/device_signed.rs +++ b/src/definitions/device_signed.rs @@ -4,13 +4,13 @@ //! //! The [Error] enum represents the possible errors that can occur in this module. //! - [Error::UnableToEncode]: Indicates an error when encoding a value as CBOR. +use crate::cose::MaybeTagged; use crate::definitions::{ helpers::{NonEmptyMap, Tag24}, session::SessionTranscript, }; -use cose_rs::sign1::CoseSign1; +use coset::{CoseMac0, CoseSign1}; use serde::{Deserialize, Serialize}; -use serde_cbor::{Error as CborError, Value as CborValue}; use std::collections::BTreeMap; /// Represents a device-signed structure. @@ -27,19 +27,27 @@ pub struct DeviceSigned { pub type DeviceNamespacesBytes = Tag24; pub type DeviceNamespaces = BTreeMap; -pub type DeviceSignedItems = NonEmptyMap; +pub type DeviceSignedItems = NonEmptyMap; /// Represents a device signature. /// /// This struct contains the device signature in the form of a [CoseSign1] object. /// The [CoseSign1] object represents a `COSE (CBOR Object Signing and Encryption) signature. #[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(untagged)] +// #[serde(untagged)] pub enum DeviceAuth { #[serde(rename_all = "camelCase")] - Signature { device_signature: CoseSign1 }, + Signature { + device_signature: MaybeTagged, + }, #[serde(rename_all = "camelCase")] - Mac { device_mac: CborValue }, + Mac { device_mac: MaybeTagged }, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum DeviceAuthType { + Sign1, + Mac0, } pub type DeviceAuthenticationBytes = Tag24>; @@ -67,5 +75,31 @@ impl DeviceAuthentication { #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Unable to encode value as CBOR: {0}")] - UnableToEncode(CborError), + UnableToEncode(coset::CoseError), +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cbor; + use hex::FromHex; + + static COSE_SIGN1: &str = include_str!("../../test/definitions/cose/sign1/serialized.cbor"); + + #[test] + fn device_auth() { + let bytes = Vec::::from_hex(COSE_SIGN1).unwrap(); + let cose_sign1: MaybeTagged = + cbor::from_slice(&bytes).expect("failed to parse COSE_Sign1 from bytes"); + let bytes2 = cbor::to_vec(&cose_sign1).unwrap(); + assert_eq!(bytes, bytes2); + let device_auth = DeviceAuth::Signature { + device_signature: cose_sign1, + }; + let bytes = cbor::to_vec(&device_auth).unwrap(); + println!("bytes {}", hex::encode(&bytes)); + let roundtripped: DeviceAuth = cbor::from_slice(&bytes).unwrap(); + let roundtripped_bytes = cbor::to_vec(&roundtripped).unwrap(); + assert_eq!(bytes, roundtripped_bytes); + } } diff --git a/src/definitions/helpers/bytestr.rs b/src/definitions/helpers/bytestr.rs index 4308b965..81f765be 100644 --- a/src/definitions/helpers/bytestr.rs +++ b/src/definitions/helpers/bytestr.rs @@ -1,8 +1,7 @@ use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(try_from = "CborValue", into = "CborValue")] +#[serde(try_from = "ciborium::Value", into = "ciborium::Value")] pub struct ByteStr(Vec); type Result = std::result::Result; @@ -10,7 +9,7 @@ type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Expected to parse a CBOR byte string, received: '{0:?}'")] - NotAByteString(CborValue), + NotAByteString(ciborium::Value), } impl From> for ByteStr { @@ -31,17 +30,17 @@ impl AsRef<[u8]> for ByteStr { } } -impl From for CborValue { - fn from(ByteStr(bytes): ByteStr) -> CborValue { - CborValue::Bytes(bytes) +impl From for ciborium::Value { + fn from(ByteStr(bytes): ByteStr) -> ciborium::Value { + ciborium::Value::Bytes(bytes) } } -impl TryFrom for ByteStr { +impl TryFrom for ByteStr { type Error = Error; - fn try_from(v: CborValue) -> Result { - if let CborValue::Bytes(bytes) = v { + fn try_from(v: ciborium::Value) -> Result { + if let ciborium::Value::Bytes(bytes) = v.clone() { Ok(ByteStr(bytes)) } else { Err(Error::NotAByteString(v)) diff --git a/src/definitions/helpers/tag24.rs b/src/definitions/helpers/tag24.rs index 25962ba4..24680218 100644 --- a/src/definitions/helpers/tag24.rs +++ b/src/definitions/helpers/tag24.rs @@ -1,11 +1,12 @@ //! Support for embedded //! [CBOR Data Items](https://www.ietf.org/rfc/rfc8949.html#name-encoded-cbor-data-item), //! also known as a tagged data item with tag number 24. +use crate::cbor; +use crate::cbor::CborError; use serde::{ de::{self, Error as DeError}, ser, Deserialize, Serialize, }; -use serde_cbor::{from_slice, to_vec, Error as CborError, Value as CborValue}; /// A wrapper for a struct that is to be encoded as a CBOR tagged item, with tag number 24. /// @@ -22,9 +23,9 @@ type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Expected a CBOR byte string, received: '{0:?}'")] - InvalidTag24(Box), + InvalidTag24(Box), #[error("Expected a CBOR tagged data item with tag number 24, received: '{0:?}'")] - NotATag24(CborValue), + NotATag24(ciborium::Value), #[error("Unable to encode value as CBOR: {0}")] UnableToEncode(CborError), #[error("Unable to decode bytes to inner type: {0}")] @@ -39,26 +40,26 @@ impl Tag24 { impl Tag24 { pub fn new(inner: T) -> Result> { - let inner_bytes = to_vec(&inner).map_err(Error::UnableToEncode)?; + let inner_bytes = cbor::to_vec(&inner).map_err(Error::UnableToEncode)?; Ok(Self { inner, inner_bytes }) } } impl Tag24 { pub fn from_bytes(inner_bytes: Vec) -> Result> { - let inner = from_slice(&inner_bytes).map_err(Error::UnableToDecode)?; + let inner = cbor::from_slice(&inner_bytes).map_err(Error::UnableToDecode)?; Ok(Self { inner, inner_bytes }) } } -impl TryFrom for Tag24 { +impl TryFrom for Tag24 { type Error = Error; - fn try_from(v: CborValue) -> Result> { - match v { - CborValue::Tag(24, inner_value) => match inner_value.as_ref() { - CborValue::Bytes(inner_bytes) => { - let inner: T = from_slice(inner_bytes).map_err(Error::UnableToDecode)?; + fn try_from(v: ciborium::Value) -> Result> { + match v.clone() { + ciborium::Value::Tag(24, inner_value) => match inner_value.as_ref() { + ciborium::Value::Bytes(inner_bytes) => { + let inner: T = cbor::from_slice(inner_bytes).map_err(Error::UnableToDecode)?; Ok(Tag24 { inner, inner_bytes: inner_bytes.to_vec(), @@ -71,9 +72,9 @@ impl TryFrom for Tag24 { } } -impl From> for CborValue { - fn from(Tag24 { inner_bytes, .. }: Tag24) -> CborValue { - CborValue::Tag(24, Box::new(CborValue::Bytes(inner_bytes))) +impl From> for ciborium::Value { + fn from(Tag24 { inner_bytes, .. }: Tag24) -> ciborium::Value { + ciborium::Value::Tag(24, Box::new(ciborium::Value::Bytes(inner_bytes))) } } @@ -85,7 +86,11 @@ impl AsRef for Tag24 { impl Serialize for Tag24 { fn serialize(&self, s: S) -> Result { - CborValue::Tag(24, Box::new(CborValue::Bytes(self.inner_bytes.clone()))).serialize(s) + ciborium::Value::Tag( + 24, + Box::new(ciborium::Value::Bytes(self.inner_bytes.clone())), + ) + .serialize(s) } } @@ -94,9 +99,8 @@ impl<'de, T: de::DeserializeOwned> Deserialize<'de> for Tag24 { where D: de::Deserializer<'de>, { - CborValue::deserialize(d)? - .try_into() - .map_err(D::Error::custom) + let cbor: ciborium::Value = ciborium::Value::deserialize(d)?; + cbor.try_into().map_err(D::Error::custom) } } @@ -106,7 +110,7 @@ mod test { #[test] #[should_panic] - // A Tag24 cannot be serialised directly into a non-cbor format as it will lose the tag. + // A Tag24 cannot be serialized directly into a non-cbor format as it will lose the tag. fn non_cbor_roundtrip() { let original = Tag24::new(String::from("some data")).unwrap(); let json = serde_json::to_vec(&original).unwrap(); diff --git a/src/definitions/issuer_signed.rs b/src/definitions/issuer_signed.rs index eae4a8f9..736353e4 100644 --- a/src/definitions/issuer_signed.rs +++ b/src/definitions/issuer_signed.rs @@ -9,13 +9,14 @@ //! - [IssuerSignedItemBytes] type is an alias for [`Tag24`]. //! - [IssuerSignedItem] struct represents a signed item within the [IssuerSigned] object, including information such as digest ID, random bytes, element identifier, and element value. //! - [IssuerSigned] struct also includes a test module with a unit test for serialization and deserialization. + +use crate::cose::MaybeTagged; use crate::definitions::{ helpers::{ByteStr, NonEmptyMap, NonEmptyVec, Tag24}, DigestId, }; -use cose_rs::sign1::CoseSign1; +use coset::CoseSign1; use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; /// Represents an issuer-signed object. /// @@ -27,7 +28,7 @@ use serde_cbor::Value as CborValue; pub struct IssuerSigned { #[serde(skip_serializing_if = "Option::is_none", rename = "nameSpaces")] pub namespaces: Option, - pub issuer_auth: CoseSign1, + pub issuer_auth: MaybeTagged, } pub type IssuerNamespaces = NonEmptyMap>; @@ -48,12 +49,13 @@ pub struct IssuerSignedItem { pub element_identifier: String, /// The value of the element. - pub element_value: CborValue, + pub element_value: ciborium::Value, } #[cfg(test)] mod test { use super::IssuerSigned; + use crate::cbor; use hex::FromHex; static ISSUER_SIGNED_CBOR: &str = include_str!("../../test/definitions/issuer_signed.cbor"); @@ -63,9 +65,9 @@ mod test { let cbor_bytes = >::from_hex(ISSUER_SIGNED_CBOR).expect("unable to convert cbor hex to bytes"); let signed: IssuerSigned = - serde_cbor::from_slice(&cbor_bytes).expect("unable to decode cbor as an IssuerSigned"); + cbor::from_slice(&cbor_bytes).expect("unable to decode cbor as an IssuerSigned"); let roundtripped_bytes = - serde_cbor::to_vec(&signed).expect("unable to encode IssuerSigned as cbor bytes"); + cbor::to_vec(&signed).expect("unable to encode IssuerSigned as cbor bytes"); assert_eq!( cbor_bytes, roundtripped_bytes, "original cbor and re-serialized IssuerSigned do not match" diff --git a/src/definitions/mod.rs b/src/definitions/mod.rs index 387ee320..cf8954c2 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/mso.rs b/src/definitions/mso.rs index 01d0b765..0564fa6a 100644 --- a/src/definitions/mso.rs +++ b/src/definitions/mso.rs @@ -83,6 +83,7 @@ impl DigestId { #[cfg(test)] mod test { + use crate::cbor; use crate::definitions::{helpers::Tag24, IssuerSigned, Mso}; use hex::FromHex; @@ -93,15 +94,14 @@ mod test { let cbor_bytes = >::from_hex(ISSUER_SIGNED_CBOR).expect("unable to convert cbor hex to bytes"); let signed: IssuerSigned = - serde_cbor::from_slice(&cbor_bytes).expect("unable to decode cbor as an IssuerSigned"); - let mso_bytes = signed + cbor::from_slice(&cbor_bytes).expect("unable to decode cbor as an IssuerSigned"); + let mso_bytes = &signed .issuer_auth - .payload() + .inner + .payload .expect("expected a COSE_Sign1 with attached payload, found detached payload"); - let mso: Tag24 = - serde_cbor::from_slice(mso_bytes).expect("unable to parse payload as Mso"); - let roundtripped_bytes = - serde_cbor::to_vec(&mso).expect("unable to encode Mso as cbor bytes"); + let mso: Tag24 = cbor::from_slice(mso_bytes).expect("unable to parse payload as Mso"); + let roundtripped_bytes = cbor::to_vec(&mso).expect("unable to encode Mso as cbor bytes"); assert_eq!( mso_bytes, &roundtripped_bytes, "original cbor and re-serialized Mso do not match" diff --git a/src/definitions/namespaces/fulldate.rs b/src/definitions/namespaces/fulldate.rs index 49483fad..b4c9083c 100644 --- a/src/definitions/namespaces/fulldate.rs +++ b/src/definitions/namespaces/fulldate.rs @@ -1,5 +1,4 @@ use anyhow::Error; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::{fmt, str::FromStr}; use time::{format_description::FormatItem, macros::format_description, Date}; @@ -12,9 +11,9 @@ const FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] #[derive(Clone, Debug)] pub struct FullDate(Date); -impl From for Cbor { - fn from(d: FullDate) -> Cbor { - Cbor::Tag(1004, Box::new(Cbor::Text(d.to_string()))) +impl From for ciborium::Value { + fn from(d: FullDate) -> ciborium::Value { + ciborium::Value::Tag(1004, Box::new(ciborium::Value::Text(d.to_string()))) } } diff --git a/src/definitions/namespaces/latin1.rs b/src/definitions/namespaces/latin1.rs index a4b40400..d7fb62fa 100644 --- a/src/definitions/namespaces/latin1.rs +++ b/src/definitions/namespaces/latin1.rs @@ -1,4 +1,3 @@ -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::{ops::Deref, str::FromStr}; @@ -27,8 +26,8 @@ impl Deref for Latin1 { } } -impl From for Cbor { - fn from(l: Latin1) -> Cbor { +impl From for ciborium::Value { + fn from(l: Latin1) -> ciborium::Value { l.0.into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1/age_over.rs b/src/definitions/namespaces/org_iso_18013_5_1/age_over.rs index 3bcaac27..f3600554 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/age_over.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/age_over.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, FromJsonMap, ToNamespaceMap}; -use serde_cbor::Value as Cbor; use serde_json::{Map, Value as Json}; use std::{collections::BTreeMap, ops::Deref}; @@ -47,10 +46,10 @@ impl FromJsonMap for AgeOver { } impl ToNamespaceMap for AgeOver { - fn to_ns_map(self) -> BTreeMap { + fn to_ns_map(self) -> BTreeMap { self.0 .into_iter() - .map(|(Age(x, y), v)| (format!("age_over_{x}{y}"), v.into())) + .map(|(Age(x, y), v)| (format!("age_over_{x}{y}"), ciborium::Value::Bool(v))) .collect() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1/alpha2.rs b/src/definitions/namespaces/org_iso_18013_5_1/alpha2.rs index fbee70ab..23ff243f 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/alpha2.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/alpha2.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::str::FromStr; @@ -263,9 +262,10 @@ pub enum Error { Unrecognized(String), } -impl From for Cbor { - fn from(a: Alpha2) -> Cbor { - a.as_str().to_string().into() +impl From for ciborium::Value { + fn from(a: Alpha2) -> ciborium::Value { + let cbor: ciborium::Value = a.as_str().to_string().into(); + cbor } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1/biometric_template.rs b/src/definitions/namespaces/org_iso_18013_5_1/biometric_template.rs index 8b7fe1e7..57c62df2 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/biometric_template.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/biometric_template.rs @@ -2,7 +2,6 @@ use crate::definitions::{ helpers::ByteStr, traits::{FromJson, FromJsonError, FromJsonMap, ToNamespaceMap}, }; -use serde_cbor::Value as Cbor; use serde_json::{Map, Value as Json}; use std::collections::BTreeMap; @@ -23,7 +22,7 @@ impl FromJsonMap for BiometricTemplate { } impl ToNamespaceMap for BiometricTemplate { - fn to_ns_map(self) -> BTreeMap { + fn to_ns_map(self) -> BTreeMap { self.0 .into_iter() .map(|(k, v)| (format!("biometric_template_{k}"), v.into())) diff --git a/src/definitions/namespaces/org_iso_18013_5_1/driving_privileges.rs b/src/definitions/namespaces/org_iso_18013_5_1/driving_privileges.rs index 9b1beb58..fcfc78ec 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/driving_privileges.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/driving_privileges.rs @@ -1,4 +1,5 @@ use super::FullDate; + use crate::{ definitions::{ helpers::NonEmptyVec, @@ -6,7 +7,6 @@ use crate::{ }, macros::{FromJson, ToCbor}, }; -use serde_cbor::Value as Cbor; use strum_macros::{AsRefStr, EnumString, EnumVariantNames}; /// `driving_privileges` in the org.iso.18013.5.1 namespace. @@ -14,9 +14,9 @@ use strum_macros::{AsRefStr, EnumString, EnumVariantNames}; #[isomdl(crate = "crate")] pub struct DrivingPrivileges(Vec); -impl From for Cbor { - fn from(d: DrivingPrivileges) -> Cbor { - Cbor::Array(d.0.into_iter().map(ToCbor::to_cbor).collect()) +impl From for ciborium::Value { + fn from(d: DrivingPrivileges) -> ciborium::Value { + ciborium::Value::Array(d.0.into_iter().map(|v| v.to_cbor()).collect()) } } @@ -45,19 +45,19 @@ pub enum VehicleCategoryCode { A2, /// Light vehicles B1, - /// Medium sized goods vehicles + /// Medium-sized goods vehicles C1, - /// Medium sized passenger vehicles (e.g.minibuses) + /// Medium-sized passenger vehicles (e.g.minibuses) D1, - /// Medium sized goods vehicles with trailers + /// Medium-sized goods vehicles with trailers C1E, - /// Medium sized passenger vehicles (e.g. minibuses) with trailers + /// Medium-sized passenger vehicles (e.g., minibuses) with trailers D1E, } -impl From for Cbor { - fn from(c: VehicleCategoryCode) -> Cbor { - Cbor::Text(c.as_ref().to_string()) +impl From for ciborium::Value { + fn from(c: VehicleCategoryCode) -> ciborium::Value { + ciborium::Value::Text(c.as_ref().to_string()) } } @@ -83,9 +83,9 @@ pub struct DrivingPrivilege { #[isomdl(crate = "crate")] pub struct Codes(NonEmptyVec); -impl From for Cbor { - fn from(c: Codes) -> Cbor { - Cbor::Array(c.0.into_inner().into_iter().map(ToCbor::to_cbor).collect()) +impl From for ciborium::Value { + fn from(c: Codes) -> ciborium::Value { + ciborium::Value::Array(c.0.into_inner().into_iter().map(|v| v.to_cbor()).collect()) } } @@ -99,9 +99,8 @@ pub struct Code { #[cfg(test)] mod tests { - use crate::definitions::traits::{FromJson, ToCbor}; - use super::VehicleCategoryCode; + use crate::definitions::traits::{FromJson, ToCbor}; #[test] fn vehicle_category_code() { @@ -110,7 +109,10 @@ mod tests { assert_eq!(c, VehicleCategoryCode::A); let v = c.to_cbor(); - assert_eq!(v, serde_cbor::Value::Text("A".to_string())); + assert_eq!( + >::into(v), + ciborium::Value::Text("A".to_string()) + ); let j = serde_json::json!("A"); let c = VehicleCategoryCode::from_json(&j).unwrap(); diff --git a/src/definitions/namespaces/org_iso_18013_5_1/eye_colour.rs b/src/definitions/namespaces/org_iso_18013_5_1/eye_colour.rs index d55a45c1..8927ad92 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/eye_colour.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/eye_colour.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::str::FromStr; @@ -41,9 +40,9 @@ impl EyeColour { } } -impl From for Cbor { - fn from(h: EyeColour) -> Cbor { - Cbor::Text(h.to_str().to_string()) +impl From for ciborium::Value { + fn from(h: EyeColour) -> ciborium::Value { + ciborium::Value::Text(h.to_str().to_string()) } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1/hair_colour.rs b/src/definitions/namespaces/org_iso_18013_5_1/hair_colour.rs index 3aa12c0d..bbf04cc4 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/hair_colour.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/hair_colour.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::str::FromStr; @@ -41,9 +40,9 @@ impl HairColour { } } -impl From for Cbor { - fn from(h: HairColour) -> Cbor { - Cbor::Text(h.to_str().to_string()) +impl From for ciborium::Value { + fn from(h: HairColour) -> ciborium::Value { + ciborium::Value::Text(h.to_str().to_string()) } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1/issuing_jurisdiction.rs b/src/definitions/namespaces/org_iso_18013_5_1/issuing_jurisdiction.rs index 4e5875cd..036eb666 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/issuing_jurisdiction.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/issuing_jurisdiction.rs @@ -2,7 +2,6 @@ use crate::definitions::{ namespaces::org_iso_18013_5_1::Alpha2, traits::{FromJson, FromJsonError, FromJsonMap}, }; -use serde_cbor::Value as Cbor; use serde_json::{Map, Value as Json}; /// `issuing_jurisdiction` in the org.iso.18013.5.1 namespace. @@ -23,8 +22,8 @@ impl Error { } } -impl From for Cbor { - fn from(i: IssuingJurisdiction) -> Cbor { +impl From for ciborium::Value { + fn from(i: IssuingJurisdiction) -> ciborium::Value { i.0.into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1/sex.rs b/src/definitions/namespaces/org_iso_18013_5_1/sex.rs index 29b8faf5..9c95df3f 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/sex.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/sex.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; /// `sex` in the org.iso.18013.5.1 namespace. @@ -17,8 +16,8 @@ pub enum Error { Unrecognized(u32), } -impl From for Cbor { - fn from(s: Sex) -> Cbor { +impl From for ciborium::Value { + fn from(s: Sex) -> ciborium::Value { u8::from(s).into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1/tdate.rs b/src/definitions/namespaces/org_iso_18013_5_1/tdate.rs index 0f666a0d..3a071bf1 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/tdate.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/tdate.rs @@ -2,7 +2,6 @@ pub use super::FullDate; use crate::definitions::traits::{FromJson, FromJsonError}; use anyhow::anyhow; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset}; @@ -52,14 +51,14 @@ impl FromJson for TDateOrFullDate { } } -impl From for Cbor { - fn from(t: TDate) -> Cbor { - Cbor::Tag(0, Box::new(t.0.into())) +impl From for ciborium::Value { + fn from(t: TDate) -> ciborium::Value { + ciborium::Value::Tag(0, Box::new(t.0.into())) } } -impl From for Cbor { - fn from(t: TDateOrFullDate) -> Cbor { +impl From for ciborium::Value { + fn from(t: TDateOrFullDate) -> ciborium::Value { match t { TDateOrFullDate::TDate(t) => t.into(), TDateOrFullDate::FullDate(f) => f.into(), diff --git a/src/definitions/namespaces/org_iso_18013_5_1/un_distinguishing_sign.rs b/src/definitions/namespaces/org_iso_18013_5_1/un_distinguishing_sign.rs index 214124b1..5c8887bc 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1/un_distinguishing_sign.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1/un_distinguishing_sign.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; /// United Nations Distinguishing Sign, as per ISO/IEC 18013-1:2018 Annex F. @@ -189,8 +188,8 @@ pub enum UNDistinguishingSign { NoneApplicable(String), } -impl From for Cbor { - fn from(s: UNDistinguishingSign) -> Cbor { +impl From for ciborium::Value { + fn from(s: UNDistinguishingSign) -> ciborium::Value { String::from(s).into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/county_code.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/county_code.rs index de582864..1b9d1b3e 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/county_code.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/county_code.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; /// `county_code` in the org.iso.18013.5.1.aamva namespace, as per the AAMVA mDL Implementation @@ -18,7 +17,7 @@ pub enum Error { } impl ToCbor for CountyCode { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { let CountyCode((a, b, c)) = self; format!("{a}{b}{c}").into() } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/dhs_compliance.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/dhs_compliance.rs index ff6fa7a1..518442f3 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/dhs_compliance.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/dhs_compliance.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::str::FromStr; @@ -27,7 +26,7 @@ impl DHSCompliance { } impl ToCbor for DHSCompliance { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { self.to_str().to_string().into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/domestic_driving_privileges.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/domestic_driving_privileges.rs index 0ea100fd..f146bd56 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/domestic_driving_privileges.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/domestic_driving_privileges.rs @@ -1,9 +1,9 @@ use super::FullDate; + use crate::{ definitions::{helpers::NonEmptyVec, traits::ToCbor}, macros::{FromJson, ToCbor}, }; -use serde_cbor::Value as Cbor; /// `domestic_driving_privileges` in the org.iso.18013.5.1.aamva namespace, as per the AAMVA mDL Implementation /// Guidelines (Version 1.0). @@ -12,8 +12,8 @@ use serde_cbor::Value as Cbor; pub struct DomesticDrivingPrivileges(Vec); impl ToCbor for DomesticDrivingPrivileges { - fn to_cbor(self) -> Cbor { - Cbor::Array(self.0.into_iter().map(ToCbor::to_cbor).collect()) + fn to_cbor(self) -> ciborium::Value { + ciborium::Value::Array(self.0.into_iter().map(|v| v.to_cbor()).collect()) } } @@ -39,12 +39,12 @@ pub struct DomesticVehicleClass { pub struct DomesticVehicleRestrictions(NonEmptyVec); impl ToCbor for DomesticVehicleRestrictions { - fn to_cbor(self) -> Cbor { - Cbor::Array( + fn to_cbor(self) -> ciborium::Value { + ciborium::Value::Array( self.0 .into_inner() .into_iter() - .map(ToCbor::to_cbor) + .map(|v| v.to_cbor()) .collect(), ) } @@ -62,12 +62,12 @@ pub struct DomesticVehicleRestriction { pub struct DomesticVehicleEndorsements(NonEmptyVec); impl ToCbor for DomesticVehicleEndorsements { - fn to_cbor(self) -> Cbor { - Cbor::Array( + fn to_cbor(self) -> ciborium::Value { + ciborium::Value::Array( self.0 .into_inner() .into_iter() - .map(ToCbor::to_cbor) + .map(|v| v.to_cbor()) .collect(), ) } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/edl_indicator.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/edl_indicator.rs index d1db25be..9fb4cd2d 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/edl_indicator.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/edl_indicator.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; /// `EDL_indicator` in the org.iso.18013.5.1.aamva namespace, as per the AAMVA mDL Implementation @@ -26,7 +25,7 @@ impl From for u8 { } impl ToCbor for EDLIndicator { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { u8::from(self).into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_suffix.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_suffix.rs index 9073e753..98524374 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_suffix.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_suffix.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::str::FromStr; @@ -63,7 +62,7 @@ impl NameSuffix { } impl ToCbor for NameSuffix { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { self.to_str().to_string().into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_truncation.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_truncation.rs index 51cbdb9f..6c8b59cb 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_truncation.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/name_truncation.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::str::FromStr; @@ -29,7 +28,7 @@ impl NameTruncation { } impl ToCbor for NameTruncation { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { self.to_str().to_string().into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/present.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/present.rs index 25cd210b..671b89d1 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/present.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/present.rs @@ -1,6 +1,5 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; use anyhow::anyhow; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; /// Indicator of presence for elements in the org.iso.18013.5.1.aamva namespace, as per the AAMVA mDL Implementation @@ -18,7 +17,7 @@ impl FromJson for Present { } impl ToCbor for Present { - fn to_cbor(self) -> Cbor { - Cbor::Integer(1) + fn to_cbor(self) -> ciborium::Value { + ciborium::Value::Integer(1.into()) } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/race_and_ethnicity.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/race_and_ethnicity.rs index 9ab63b8f..69a25a4e 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/race_and_ethnicity.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/race_and_ethnicity.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; use std::str::FromStr; @@ -37,7 +36,7 @@ impl RaceAndEthnicity { } impl ToCbor for RaceAndEthnicity { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { self.to_str().to_string().into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/sex.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/sex.rs index 5bf37ed4..9c7ab87a 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/sex.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/sex.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; /// `sex` in the org.iso.18013.5.1.aamva namespace, as per the AAMVA mDL Implementation @@ -28,7 +27,7 @@ impl From for u8 { } impl ToCbor for Sex { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { u8::from(self).into() } } diff --git a/src/definitions/namespaces/org_iso_18013_5_1_aamva/weight_range.rs b/src/definitions/namespaces/org_iso_18013_5_1_aamva/weight_range.rs index bf1e9242..81ae8b92 100644 --- a/src/definitions/namespaces/org_iso_18013_5_1_aamva/weight_range.rs +++ b/src/definitions/namespaces/org_iso_18013_5_1_aamva/weight_range.rs @@ -1,5 +1,4 @@ use crate::definitions::traits::{FromJson, FromJsonError, ToCbor}; -use serde_cbor::Value as Cbor; use serde_json::Value as Json; /// `weight_range` in the org.iso.18013.5.1.aamva namespace, as per the AAMVA mDL Implementation @@ -42,7 +41,7 @@ impl From for u8 { } impl ToCbor for WeightRange { - fn to_cbor(self) -> Cbor { + fn to_cbor(self) -> ciborium::Value { u8::from(self).into() } } diff --git a/src/definitions/session.rs b/src/definitions/session.rs index a8b51711..22260d52 100644 --- a/src/definitions/session.rs +++ b/src/definitions/session.rs @@ -199,7 +199,10 @@ pub fn derive_session_key( session_transcript: &SessionTranscriptBytes, reader: bool, ) -> Result> { - let salt = Sha256::digest(serde_cbor::to_vec(session_transcript)?); + let salt = Sha256::digest( + crate::cbor::to_vec(session_transcript) + .map_err(|e| anyhow::anyhow!("failed to serialize session transcript: {e}"))?, + ); let hkdf = shared_secret.extract::(Some(salt.as_ref())); let mut okm = [0u8; 32]; let sk_device = "SKDevice".as_bytes(); @@ -284,6 +287,7 @@ pub fn get_initialization_vector(message_count: &mut u32, reader: bool) -> [u8; #[cfg(test)] mod test { use super::*; + use crate::cbor; use crate::definitions::device_engagement::Security; use crate::definitions::device_request::DeviceRequest; @@ -292,12 +296,12 @@ mod test { // null let cbor = hex::decode("F6").expect("failed to decode hex"); let handover: Handover = - serde_cbor::from_slice(&cbor).expect("failed to deserialize as handover"); + cbor::from_slice(&cbor).expect("failed to deserialize as handover"); if !matches!(handover, Handover::QR) { panic!("expected 'Handover::QR', received {handover:?}") } else { let roundtripped = - serde_cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); + cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); assert_eq!( cbor, roundtripped, "re-serialized handover did not match initial bytes" @@ -311,12 +315,12 @@ mod test { // [] let cbor = hex::decode("80").expect("failed to decode hex"); let handover: Handover = - serde_cbor::from_slice(&cbor).expect("failed to deserialize as handover"); + cbor::from_slice(&cbor).expect("failed to deserialize as handover"); if !matches!(handover, Handover::QR) { panic!("expected 'Handover::QR', received {handover:?}") } else { let roundtripped = - serde_cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); + cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); assert_eq!( cbor, roundtripped, "re-serialized handover did not match initial bytes" @@ -330,12 +334,12 @@ mod test { // {} let cbor = hex::decode("A0").expect("failed to decode hex"); let handover: Handover = - serde_cbor::from_slice(&cbor).expect("failed to deserialize as handover"); + cbor::from_slice(&cbor).expect("failed to deserialize as handover"); if !matches!(handover, Handover::QR) { panic!("expected 'Handover::QR', received {handover:?}") } else { let roundtripped = - serde_cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); + cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); assert_eq!( cbor, roundtripped, "re-serialized handover did not match initial bytes" @@ -348,12 +352,12 @@ mod test { // ['hello', null] let cbor = hex::decode("824568656C6C6FF6").expect("failed to decode hex"); let handover: Handover = - serde_cbor::from_slice(&cbor).expect("failed to deserialize as handover"); + cbor::from_slice(&cbor).expect("failed to deserialize as handover"); if !matches!(handover, Handover::NFC(..)) { panic!("expected 'Handover::NFC(..)', received {handover:?}") } else { let roundtripped = - serde_cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); + cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); assert_eq!( cbor, roundtripped, "re-serialized handover did not match initial bytes" @@ -366,12 +370,12 @@ mod test { // ['hello', 'world'] let cbor = hex::decode("824568656C6C6F45776F726C64").expect("failed to decode hex"); let handover: Handover = - serde_cbor::from_slice(&cbor).expect("failed to deserialize as handover"); + cbor::from_slice(&cbor).expect("failed to deserialize as handover"); if !matches!(handover, Handover::NFC(..)) { panic!("expected 'Handover::NFC(..)', received {handover:?}") } else { let roundtripped = - serde_cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); + cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); assert_eq!( cbor, roundtripped, "re-serialized handover did not match initial bytes" @@ -384,7 +388,7 @@ mod test { // ["aud", "nonce"] let cbor = hex::decode("8263617564656E6F6E6365").expect("failed to decode hex"); let handover: Handover = - serde_cbor::from_slice(&cbor).expect("failed to deserialize as handover"); + cbor::from_slice(&cbor).expect("failed to deserialize as handover"); if !matches!(handover, Handover::OID4VP(..)) { panic!( "expected '{}', received {:?}", @@ -392,7 +396,7 @@ mod test { ) } else { let roundtripped = - serde_cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); + cbor::to_vec(&handover).expect("failed to serialize handover as cbor"); assert_eq!( cbor, roundtripped, "re-serialized handover did not match initial bytes" @@ -477,7 +481,7 @@ mod test { let session_establishment_bytes = hex::decode(SESSION_ESTABLISHMENT).unwrap(); let session_establishment: SessionEstablishment = - serde_cbor::from_slice(&session_establishment_bytes).unwrap(); + cbor::from_slice(&session_establishment_bytes).unwrap(); let e_reader_key = session_establishment.e_reader_key; let encrypted_request = session_establishment.data; @@ -489,7 +493,7 @@ mod test { let session_transcript_bytes = hex::decode(SESSION_TRANSCRIPT).unwrap(); let session_transcript: SessionTranscriptBytes = - serde_cbor::from_slice(&session_transcript_bytes).unwrap(); + cbor::from_slice(&session_transcript_bytes).unwrap(); let session_key = derive_session_key(&shared_secret, &session_transcript, true).unwrap(); let session_key_hex = hex::encode(session_key); @@ -497,6 +501,6 @@ mod test { let plaintext = decrypt_reader_data(&session_key, encrypted_request.as_ref(), &mut 0).unwrap(); - let _device_request: DeviceRequest = serde_cbor::from_slice(&plaintext).unwrap(); + let _device_request: DeviceRequest = crate::cbor::from_slice(&plaintext).unwrap(); } } diff --git a/src/definitions/traits/to_cbor.rs b/src/definitions/traits/to_cbor.rs index 33399f74..8098b066 100644 --- a/src/definitions/traits/to_cbor.rs +++ b/src/definitions/traits/to_cbor.rs @@ -1,37 +1,65 @@ //! ToCbor is specifically NOT implemented for `Vec` where `T: ToCbor`, as `Vec` likely should be //! represented as a `bytestr` instead of an `array` in `cbor`. -use serde_cbor::Value; +use crate::cbor; +use crate::cbor::CborError; use std::collections::BTreeMap; +use std::error::Error; +use std::fmt; pub type Bytes = Vec; pub trait ToCbor: Sized { - fn to_cbor(self) -> Value; + fn to_cbor(self) -> ciborium::Value; fn to_cbor_bytes(self) -> Result { - serde_cbor::to_vec(&self.to_cbor()).map_err(Into::into) + cbor::to_vec(&self.to_cbor()).map_err(Into::into) } } pub trait ToCborMap { - fn to_cbor_map(self) -> BTreeMap; + fn to_cbor_map(self) -> BTreeMap; } pub trait ToNamespaceMap { - fn to_ns_map(self) -> BTreeMap; + 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 serialization: {0}")] - Serde(#[from] serde_cbor::Error), + 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 where - T: Into, + T: Into, { - fn to_cbor(self) -> Value { + fn to_cbor(self) -> ciborium::Value { self.into() } } diff --git a/src/definitions/validity_info.rs b/src/definitions/validity_info.rs index e13f8c09..da977afd 100644 --- a/src/definitions/validity_info.rs +++ b/src/definitions/validity_info.rs @@ -15,7 +15,8 @@ //! # Conversion to and from CBOR //! //! The [ValidityInfo] struct also provides implementations of the [TryFrom] trait for converting -//! to and from [CborValue], which is a type provided by the [serde_cbor] crate for representing CBOR values. +//! to and from [`ciborium::Value`], +//! which is a type provided by the [`ciborium`] crate for representing CBOR values. //! These implementations allow you to convert [ValidityInfo] objects to `CBOR` format and vice versa. //! //! # Dependencies @@ -23,7 +24,7 @@ //! This module depends on the following external crates: //! //! - [serde]: Provides the serialization and deserialization traits and macros. -//! - [serde_cbor]: Provides the `CBOR` serialization and deserialization functionality. +//! - [ciborium] and [coset]: Provides the `CBOR` serialization and deserialization functionality. //! - [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. @@ -31,7 +32,6 @@ use serde::{ ser::{Error as SerError, Serializer}, Deserialize, Serialize, }; -use serde_cbor::Value as CborValue; use std::collections::BTreeMap; use time::{ error::Format as FormatError, error::Parse as ParseError, @@ -39,7 +39,7 @@ use time::{ }; #[derive(Clone, Debug, Deserialize)] -#[serde(try_from = "CborValue")] +#[serde(try_from = "ciborium::Value")] pub struct ValidityInfo { pub signed: OffsetDateTime, pub valid_from: OffsetDateTime, @@ -52,38 +52,42 @@ type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("When parsing a CBOR map, could not find required field: '{0:?}'")] - MissingField(CborValue), + MissingField(ciborium::Value), #[error("Expected to parse a CBOR map, received: '{0:?}'")] - NotAMap(CborValue), + NotAMap(ciborium::Value), #[error("Expected to parse a CBOR text string, received: '{0:?}'")] - NotATextString(Box), + NotATextString(Box), #[error("Expected to parse a CBOR tag (number {0}), received: '{1:?}'")] - NotATag(u64, CborValue), + NotATag(u64, ciborium::Value), #[error(transparent)] OutOfRange(#[from] time::error::ComponentRange), #[error("Failed to format date string as rfc3339 date: {0}")] UnableToFormatDate(#[from] FormatError), #[error("Failed to parse date string as rfc3339 date: {0}")] UnableToParseDate(#[from] ParseError), + #[error("Could not serialize to cbor: {0}")] + CborErrorWithSource(coset::CoseError), + #[error("Could not serialize to cbor")] + CborError, } -impl TryFrom for CborValue { +impl TryFrom for ciborium::Value { type Error = Error; - fn try_from(v: ValidityInfo) -> Result { + fn try_from(v: ValidityInfo) -> Result { macro_rules! insert_date { ($map:ident, $date:ident, $name:literal) => { - let key = CborValue::Text(String::from($name)); - let value = CborValue::Tag( + let key = ciborium::Value::Text(String::from($name)); + let value = ciborium::Value::Tag( 0, - Box::new(CborValue::Text( + Box::new(ciborium::Value::Text( $date .replace_millisecond(0)? .to_offset(UtcOffset::UTC) .format(&Rfc3339)?, )), ); - $map.insert(key, value); + $map.push((key, value)); }; ($map:ident, $struct: ident, $field:ident, $name:literal) => { let date = $struct.$field; @@ -91,7 +95,7 @@ impl TryFrom for CborValue { }; } - let mut map = BTreeMap::new(); + let mut map = vec![]; insert_date!(map, v, signed, "signed"); insert_date!(map, v, valid_from, "validFrom"); @@ -101,20 +105,27 @@ impl TryFrom for CborValue { insert_date!(map, expected_update, "expectedUpdate"); } - Ok(CborValue::Map(map)) + Ok(ciborium::Value::Map(map)) } } -impl TryFrom for ValidityInfo { +impl TryFrom for ValidityInfo { type Error = Error; - fn try_from(v: CborValue) -> Result { - if let CborValue::Map(mut map) = v { + fn try_from(v: ciborium::Value) -> Result { + if let ciborium::Value::Map(map) = v.clone() { + let mut map: BTreeMap = map + .into_iter() + .map(|(k, v)| { + let k = k.into_text().map_err(|_| Error::CborError)?; + Ok((k, v)) + }) + .collect::>>()?; macro_rules! extract_date { ($map:ident, $name:literal) => {{ - let key = CborValue::Text(String::from($name)); + let key = String::from($name); $map.remove(&key) - .ok_or(Error::MissingField(key)) + .ok_or(Error::MissingField(key.into())) .and_then(cbor_to_datetime)? }}; } @@ -123,7 +134,7 @@ impl TryFrom for ValidityInfo { let valid_from = extract_date!(map, "validFrom"); let valid_until = extract_date!(map, "validUntil"); - let expected_update_key = CborValue::Text(String::from("expectedUpdate")); + let expected_update_key = String::from("expectedUpdate"); let expected_update = map .remove(&expected_update_key) .map(cbor_to_datetime) @@ -146,15 +157,15 @@ impl Serialize for ValidityInfo { where S: Serializer, { - CborValue::try_from(self.clone()) + ciborium::Value::try_from(self.clone()) .map_err(S::Error::custom)? .serialize(s) } } -fn cbor_to_datetime(v: CborValue) -> Result { - if let CborValue::Tag(0, inner) = v { - if let CborValue::Text(date_str) = inner.as_ref() { +fn cbor_to_datetime(v: ciborium::Value) -> Result { + if let ciborium::Value::Tag(0, inner) = v.clone() { + if let ciborium::Value::Text(date_str) = inner.as_ref() { Ok(OffsetDateTime::parse(date_str, &Rfc3339)?) } else { Err(Error::NotATextString(inner)) @@ -171,8 +182,8 @@ mod test { #[test] fn roundtrip() { let cbor = hex::decode("A3667369676E6564C074323032302D30312D30315430303A30303A30305A6976616C696446726F6DC074323032302D30312D30315430303A30303A30305A6A76616C6964556E74696CC074323032302D30312D30315430303A30303A30305A").unwrap(); - let validity_info: ValidityInfo = serde_cbor::from_slice(&cbor).unwrap(); - let roundtripped = serde_cbor::to_vec(&validity_info).unwrap(); + let validity_info: ValidityInfo = crate::cbor::from_slice(&cbor).unwrap(); + let roundtripped = crate::cbor::to_vec(&validity_info).unwrap(); assert_eq!(cbor, roundtripped); } @@ -180,8 +191,8 @@ mod test { #[test] fn trim() { let cbor = hex::decode("A3667369676E6564C07818323032302D30312D30315430303A30303A30302E3130315A6976616C696446726F6DC0781B323032302D30312D30315430303A30303A30302E3131323231395A6A76616C6964556E74696CC0781E323032302D30312D30315430303A30303A30302E3939393939393939395A").unwrap(); - let validity_info: ValidityInfo = serde_cbor::from_slice(&cbor).unwrap(); - let roundtripped = serde_cbor::to_vec(&validity_info).unwrap(); + let validity_info: ValidityInfo = crate::cbor::from_slice(&cbor).unwrap(); + let roundtripped = crate::cbor::to_vec(&validity_info).unwrap(); let trimmed = hex::decode("A3667369676E6564C074323032302D30312D30315430303A30303A30305A6976616C696446726F6DC074323032302D30312D30315430303A30303A30305A6A76616C6964556E74696CC074323032302D30312D30315430303A30303A30305A").unwrap(); assert_eq!(trimmed, roundtripped); } diff --git a/src/issuance/mdoc.rs b/src/issuance/mdoc.rs index dceecc1b..737191af 100644 --- a/src/issuance/mdoc.rs +++ b/src/issuance/mdoc.rs @@ -1,3 +1,16 @@ +use std::collections::{BTreeMap, HashSet}; + +use anyhow::{anyhow, Result}; +use async_signature::AsyncSigner; +use coset::iana::Algorithm; +use coset::{CoseSign1, Label}; +use rand::Rng; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256, Sha384, Sha512}; +use signature::{SignatureEncoding, Signer}; + +use crate::cose::sign1::PreparedCoseSign1; +use crate::cose::{MaybeTagged, SignatureAlgorithm}; use crate::{ definitions::{ helpers::{NonEmptyMap, NonEmptyVec, Tag24}, @@ -6,33 +19,21 @@ use crate::{ }, issuance::x5chain::{X5Chain, X5CHAIN_HEADER_LABEL}, }; -use anyhow::{anyhow, Result}; -use async_signature::AsyncSigner; -use cose_rs::{ - algorithm::{Algorithm, SignatureAlgorithm}, - sign1::{CoseSign1, PreparedCoseSign1}, -}; -use rand::Rng; -use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; -use sha2::{Digest, Sha256, Sha384, Sha512}; -use signature::{SignatureEncoding, Signer}; -use std::collections::{BTreeMap, HashSet}; -pub type Namespaces = BTreeMap>; +pub type Namespaces = BTreeMap>; +/// A signed mdoc. #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] -/// A signed mdoc. pub struct Mdoc { pub doc_type: String, pub mso: Mso, pub namespaces: IssuerNamespaces, - pub issuer_auth: CoseSign1, + pub issuer_auth: MaybeTagged, } -#[derive(Debug, Clone, Serialize, Deserialize)] /// An incomplete mdoc, requiring a remotely signed signature to be completed. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PreparedMdoc { doc_type: String, mso: Mso, @@ -82,13 +83,15 @@ impl Mdoc { validity_info, }; - let mso_bytes = serde_cbor::to_vec(&Tag24::new(&mso)?)?; + let mso_bytes = crate::cbor::to_vec(&Tag24::new(&mso)?)?; - let prepared_sig = CoseSign1::builder() - .payload(mso_bytes) - .signature_algorithm(signature_algorithm) - .prepare() - .map_err(|e| anyhow!("error preparing cosesign1: {}", e))?; + let protected = coset::HeaderBuilder::new() + .algorithm(signature_algorithm) + .build(); + let builder = coset::CoseSign1Builder::new() + .protected(protected) + .payload(mso_bytes); + let prepared_sig = PreparedCoseSign1::new(builder, None, None, true)?; let preparation_mdoc = PreparedMdoc { doc_type, @@ -190,9 +193,10 @@ impl PreparedMdoc { let mut issuer_auth = prepared_sig.finalize(signature); issuer_auth - .unprotected_mut() - .insert_i(X5CHAIN_HEADER_LABEL, x5chain.into_cbor()); - + .inner + .unprotected + .rest + .push((Label::Int(X5CHAIN_HEADER_LABEL as i64), x5chain.into_cbor())); Mdoc { doc_type, mso, @@ -366,7 +370,7 @@ fn to_issuer_namespaces(namespaces: Namespaces) -> Result { } fn to_issuer_signed_items( - elements: BTreeMap, + elements: BTreeMap, ) -> impl Iterator { let mut used_ids = HashSet::new(); elements.into_iter().map(move |(key, value)| { @@ -425,7 +429,7 @@ fn digest_namespace( elements .iter() - .map(|item| Ok((item.as_ref().digest_id, serde_cbor::to_vec(item)?))) + .map(|item| Ok((item.as_ref().digest_id, crate::cbor::to_vec(item)?))) .chain(random_digests) .map(|result| { let (digest_id, bytes) = result?; @@ -452,19 +456,20 @@ fn generate_digest_id(used_ids: &mut HashSet) -> DigestId { #[cfg(test)] pub mod test { - use super::*; - use crate::definitions::device_key::cose_key::{CoseKey, EC2Curve, EC2Y}; - use crate::definitions::namespaces::{ - org_iso_18013_5_1::OrgIso1801351, org_iso_18013_5_1_aamva::OrgIso1801351Aamva, - }; - - use crate::definitions::traits::{FromJson, ToNamespaceMap}; use elliptic_curve::sec1::ToEncodedPoint; use p256::ecdsa::{Signature, SigningKey}; use p256::pkcs8::DecodePrivateKey; use p256::SecretKey; use time::OffsetDateTime; + use crate::definitions::device_key::cose_key::{CoseKey, EC2Curve, EC2Y}; + use crate::definitions::namespaces::{ + org_iso_18013_5_1::OrgIso1801351, org_iso_18013_5_1_aamva::OrgIso1801351Aamva, + }; + use crate::definitions::traits::{FromJson, ToNamespaceMap}; + + use super::*; + static ISSUER_CERT: &[u8] = include_bytes!("../../test/issuance/issuer-cert.pem"); static ISSUER_KEY: &str = include_str!("../../test/issuance/issuer-key.pem"); diff --git a/src/issuance/x5chain.rs b/src/issuance/x5chain.rs index 655aeb73..e68e55d2 100644 --- a/src/issuance/x5chain.rs +++ b/src/issuance/x5chain.rs @@ -59,7 +59,6 @@ //! The [X5Chain] struct also provides a [X5Chain::builder] method for creating a new [Builder] instance. use crate::definitions::helpers::NonEmptyVec; use anyhow::{anyhow, Result}; -use serde_cbor::Value as CborValue; use std::{fs::File, io::Read}; use x509_cert::{ certificate::Certificate, @@ -93,17 +92,17 @@ impl X5Chain { Builder::default() } - /// Converts the [X5Chain] object into a [CborValue]. - pub fn into_cbor(&self) -> CborValue { + /// Converts the [X5Chain] object into a [`ciborium::Value`]. + pub fn into_cbor(self) -> ciborium::Value { match &self.0.as_ref() { - &[cert] => CborValue::Bytes(cert.bytes.clone()), - certs => CborValue::Array( + &[cert] => ciborium::Value::Bytes(cert.bytes.clone()), + certs => ciborium::Value::Array( certs .iter() .cloned() .map(|x509| x509.bytes) - .map(CborValue::Bytes) - .collect::>(), + .map(ciborium::Value::Bytes) + .collect::>(), ), } } diff --git a/src/lib.rs b/src/lib.rs index 86a92b94..bea09387 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,8 +231,8 @@ //! ```ignore #![doc = include_str!("../tests/simulated_device_and_reader_state.rs")] //! ``` -pub use cose_rs; - +pub mod cbor; +pub mod cose; pub mod definitions; pub mod issuance; pub mod presentation; diff --git a/src/presentation/device.rs b/src/presentation/device.rs index 1dce6063..4c5964cc 100644 --- a/src/presentation/device.rs +++ b/src/presentation/device.rs @@ -16,8 +16,14 @@ //! //! You can view examples in `tests` directory in `simulated_device_and_reader.rs`, for a basic example and //! `simulated_device_and_reader_state.rs` which uses `State` pattern, `Arc` and `Mutex`. +use crate::cbor::CborError; +use crate::cose::mac0::PreparedCoseMac0; +use crate::cose::sign1::PreparedCoseSign1; +use crate::cose::MaybeTagged; +use crate::definitions::device_signed::DeviceAuthType; use crate::definitions::IssuerSignedItem; use crate::{ + cbor, definitions::{ device_engagement::{DeviceRetrievalMethod, Security, ServerRetrievalMethods}, device_request::{DeviceRequest, DocRequest, ItemsRequest}, @@ -35,10 +41,9 @@ use crate::{ }, issuance::Mdoc, }; -use cose_rs::sign1::{CoseSign1, PreparedCoseSign1}; +use coset::{CoseMac0Builder, CoseSign1, CoseSign1Builder}; use p256::FieldBytes; use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; use session::SessionTranscript180135; use std::collections::BTreeMap; use std::num::ParseIntError; @@ -98,6 +103,7 @@ pub struct SessionManager { sk_reader: [u8; 32], reader_message_counter: u32, state: State, + device_auth_type: DeviceAuthType, } /// The internal states of the [SessionManager]. @@ -126,7 +132,7 @@ pub enum Error { SharedSecretGeneration(anyhow::Error), /// Error encoding value to CBOR. #[error("error encoding value to CBOR: {0}")] - CborEncoding(serde_cbor::Error), + CborEncoding(coset::CoseError), /// Session manager was used incorrectly. #[error("session manager was used incorrectly")] ApiMisuse, @@ -136,6 +142,8 @@ pub enum Error { /// `age_over` element identifier is malformed. #[error("age_over element identifier is malformed")] PrefixError, + #[error("Could not serialize to cbor: {0}")] + CborError(coset::CoseError), } /// The documents the device owns. @@ -146,7 +154,7 @@ type DocType = String; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Document { pub id: Uuid, - pub issuer_auth: CoseSign1, + pub issuer_auth: MaybeTagged, pub mso: Mso, pub namespaces: Namespaces, } @@ -174,10 +182,25 @@ struct PreparedDocument { doc_type: String, issuer_signed: IssuerSigned, device_namespaces: DeviceNamespacesBytes, - prepared_cose_sign1: PreparedCoseSign1, + prepared_cose: PreparedCose, errors: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +enum PreparedCose { + Sign1(PreparedCoseSign1), + Mac0(PreparedCoseMac0), +} + +impl PreparedCose { + fn signature_payload(&self) -> &[u8] { + match self { + PreparedCose::Sign1(inner) => inner.signature_payload(), + PreparedCose::Mac0(inner) => inner.signature_payload(), + } + } +} + /// Elements in a namespace. type Namespaces = NonEmptyMap>; type Namespace = String; @@ -279,6 +302,7 @@ impl SessionManagerEngaged { sk_reader, reader_message_counter: 0, state: State::AwaitingRequest, + device_auth_type: DeviceAuthType::Sign1, }; let requested_data = sm.handle_decoded_request(SessionData { @@ -292,12 +316,12 @@ impl SessionManagerEngaged { impl SessionManager { fn parse_request(&self, request: &[u8]) -> Result { - let request: CborValue = serde_cbor::from_slice(request).map_err(|_| { + let request: ciborium::Value = cbor::from_slice(request).map_err(|_| { // tracing::error!("unable to decode DeviceRequest bytes as cbor: {}", error); PreparedDeviceResponse::empty(Status::CborDecodingError) })?; - serde_cbor::value::from_value(request).map_err(|_| { + cbor::from_value(request).map_err(|_| { // tracing::error!("unable to validate DeviceRequest cbor: {}", error); PreparedDeviceResponse::empty(Status::CborValidationError) }) @@ -378,7 +402,7 @@ impl SessionManager { /// /// It returns the requested items by the reader. pub fn handle_request(&mut self, request: &[u8]) -> anyhow::Result { - let session_data: SessionData = serde_cbor::from_slice(request)?; + let session_data: SessionData = cbor::from_slice(request).map_err(CborError::from)?; self.handle_decoded_request(session_data) } @@ -422,8 +446,12 @@ impl SessionManager { p.submit_next_signature(signature); if p.is_complete() { let response = p.finalize_response(); + let bytes = cbor::to_vec(&response)?; + let response2: DeviceResponse = cbor::from_slice(&bytes).unwrap(); + let bytes2 = cbor::to_vec(&response2)?; + assert_eq!(bytes, bytes2); let mut status: Option = None; - let response_bytes = serde_cbor::to_vec(&response)?; + let response_bytes = cbor::to_vec(&response)?; let encrypted_response = session::encrypt_device_data( &self.sk_device.into(), &response_bytes, @@ -440,7 +468,7 @@ impl SessionManager { Some(encrypted_response.into()) }; let session_data = SessionData { status, data }; - let encoded_response = serde_cbor::to_vec(&session_data)?; + let encoded_response = crate::cbor::to_vec(&session_data)?; self.state = State::ReadyToRespond(encoded_response); } else { self.state = State::Signing(p) @@ -501,7 +529,7 @@ impl PreparedDeviceResponse { pub fn get_next_signature_payload(&self) -> Option<(Uuid, &[u8])> { self.prepared_documents .last() - .map(|doc| (doc.id, doc.prepared_cose_sign1.signature_payload())) + .map(|doc| (doc.id, doc.prepared_cose.signature_payload())) } /// Submit the externally signed signature for object @@ -540,17 +568,22 @@ impl PreparedDocument { let Self { issuer_signed, device_namespaces, - prepared_cose_sign1, + prepared_cose, errors, doc_type, .. } = self; - let cose_sign1 = prepared_cose_sign1.finalize(signature); + let device_auth = match prepared_cose { + PreparedCose::Sign1(inner) => DeviceAuth::Signature { + device_signature: inner.finalize(signature), + }, + PreparedCose::Mac0(inner) => DeviceAuth::Mac { + device_mac: inner.finalize(signature), + }, + }; let device_signed = DeviceSigned { namespaces: device_namespaces, - device_auth: DeviceAuth::Signature { - device_signature: cose_sign1, - }, + device_auth, }; DeviceResponseDoc { doc_type, @@ -570,6 +603,7 @@ pub trait DeviceSession { /// Get the device documents. fn documents(&self) -> &Documents; fn session_transcript(&self) -> Self::ST; + fn device_auth_type(&self) -> DeviceAuthType; /// Prepare the response based on the requested items and permitted ones. fn prepare_response( @@ -682,7 +716,7 @@ pub trait DeviceSession { continue; } }; - let device_auth_bytes = match serde_cbor::to_vec(&device_auth) { + let device_auth_bytes = match cbor::to_vec(&device_auth) { Ok(dab) => dab, Err(_e) => { let error: DocumentError = [(doc_type, DocumentErrorCode::DataNotReturned)] @@ -692,19 +726,50 @@ pub trait DeviceSession { continue; } }; - let prepared_cose_sign1 = match CoseSign1::builder() - .detached() - .payload(device_auth_bytes) - .signature_algorithm(signature_algorithm) - .prepare() - { - Ok(prepared) => prepared, - Err(_e) => { - let error: DocumentError = [(doc_type, DocumentErrorCode::DataNotReturned)] - .into_iter() - .collect(); - document_errors.push(error); - continue; + let header = coset::HeaderBuilder::new() + .algorithm(signature_algorithm) + .build(); + + let prepared_cose = match self.device_auth_type() { + DeviceAuthType::Sign1 => { + let cose_sign1_builder = CoseSign1Builder::new().protected(header); + let prepared_cose_sign1 = match PreparedCoseSign1::new( + cose_sign1_builder, + Some(&device_auth_bytes), + None, + true, + ) { + Ok(prepared) => prepared, + Err(_e) => { + let error: DocumentError = + [(doc_type, DocumentErrorCode::DataNotReturned)] + .into_iter() + .collect(); + document_errors.push(error); + continue; + } + }; + PreparedCose::Sign1(prepared_cose_sign1) + } + DeviceAuthType::Mac0 => { + let cose_mac0_builder = CoseMac0Builder::new().protected(header); + let prepared_cose_mac0 = match PreparedCoseMac0::new( + cose_mac0_builder, + Some(&device_auth_bytes), + None, + true, + ) { + Ok(prepared) => prepared, + Err(_e) => { + let error: DocumentError = + [(doc_type, DocumentErrorCode::DataNotReturned)] + .into_iter() + .collect(); + document_errors.push(error); + continue; + } + }; + PreparedCose::Mac0(prepared_cose_mac0) } }; @@ -716,7 +781,7 @@ pub trait DeviceSession { issuer_auth: document.issuer_auth.clone(), }, device_namespaces, - prepared_cose_sign1, + prepared_cose, errors: errors.try_into().ok(), }; prepared_documents.push(prepared_document); @@ -740,6 +805,10 @@ impl DeviceSession for SessionManager { fn session_transcript(&self) -> SessionTranscript180135 { self.session_transcript.clone() } + + fn device_auth_type(&self) -> DeviceAuthType { + self.device_auth_type + } } impl From for Document { @@ -834,9 +903,9 @@ pub fn nearest_age_attestation( .collect(); let (true_age_over_claims, false_age_over_claims): (Vec<_>, Vec<_>) = - age_over_claims_numerical? - .into_iter() - .partition(|x| x.1.to_owned().into_inner().element_value == CborValue::Bool(true)); + age_over_claims_numerical?.into_iter().partition(|x| { + x.1.to_owned().into_inner().element_value == ciborium::Value::Bool(true) + }); let nearest_age_over = true_age_over_claims .iter() @@ -974,21 +1043,21 @@ mod test { digest_id: DigestId::new(1), random: ByteStr::from(random.clone()), element_identifier: element_identifier1.clone(), - element_value: CborValue::Bool(true), + element_value: ciborium::Value::Bool(true), }; let issuer_signed_item2 = IssuerSignedItem { digest_id: DigestId::new(2), random: ByteStr::from(random.clone()), element_identifier: element_identifier2.clone(), - element_value: CborValue::Bool(false), + element_value: ciborium::Value::Bool(false), }; let issuer_signed_item3 = IssuerSignedItem { digest_id: DigestId::new(3), random: ByteStr::from(random), element_identifier: element_identifier3.clone(), - element_value: CborValue::Bool(false), + element_value: ciborium::Value::Bool(false), }; let issuer_item1 = Tag24::new(issuer_signed_item1).unwrap(); diff --git a/src/presentation/mod.rs b/src/presentation/mod.rs index 9d65b430..1ae372e1 100644 --- a/src/presentation/mod.rs +++ b/src/presentation/mod.rs @@ -58,16 +58,17 @@ pub trait Stringify: Serialize + for<'a> Deserialize<'a> { /// ``` /// use base64::decode; /// use serde::Serialize; + /// use isomdl::cbor::from_slice; /// use isomdl::presentation::{device, Stringify}; /// use isomdl::presentation::device::Document; /// /// let doc_str = include_str!("../../test/stringified-mdl.txt").to_string(); - /// let doc : Document = serde_cbor::from_slice(&decode(doc_str).unwrap()).unwrap(); + /// let doc : Document = from_slice(&decode(doc_str).unwrap()).unwrap(); /// let serialized = doc.stringify().unwrap(); /// assert_eq!(serialized, Document::parse(serialized.clone()).unwrap().stringify().unwrap()); /// ``` fn stringify(&self) -> Result { - let data = serde_cbor::to_vec(self)?; + let data = crate::cbor::to_vec(self)?; let encoded = encode(data); Ok(encoded) } @@ -82,17 +83,18 @@ pub trait Stringify: Serialize + for<'a> Deserialize<'a> { /// ``` /// use base64::decode; /// use serde::Serialize; + /// use isomdl::cbor::from_slice; /// use isomdl::presentation::{device, Stringify}; /// use isomdl::presentation::device::Document; /// /// let doc_str = include_str!("../../test/stringified-mdl.txt").to_string(); - /// let doc : Document = serde_cbor::from_slice(&decode(doc_str).unwrap()).unwrap(); + /// let doc : Document = from_slice(&decode(doc_str).unwrap()).unwrap(); /// let serialized = doc.stringify().unwrap(); /// assert_eq!(serialized, Document::parse(serialized.clone()).unwrap().stringify().unwrap()); /// ``` fn parse(encoded: String) -> Result { let data = decode(encoded)?; - let this = serde_cbor::from_slice(&data)?; + let this = crate::cbor::from_slice(&data)?; Ok(this) } } @@ -108,7 +110,7 @@ use hkdf::Hkdf; use sha2::Sha256; fn calculate_ble_ident(e_device_key: &Tag24) -> Result<[u8; 16]> { - let e_device_key_bytes = serde_cbor::to_vec(e_device_key)?; + let e_device_key_bytes = crate::cbor::to_vec(e_device_key)?; let mut ble_ident = [0u8; 16]; Hkdf::::new(None, &e_device_key_bytes) diff --git a/src/presentation/reader.rs b/src/presentation/reader.rs index c38738f0..b1c95cc2 100644 --- a/src/presentation/reader.rs +++ b/src/presentation/reader.rs @@ -13,6 +13,8 @@ //! //! You can view examples in `tests` directory in `simulated_device_and_reader.rs`, for a basic example and //! `simulated_device_and_reader_state.rs` which uses `State` pattern, `Arc` and `Mutex`. +use crate::cbor; +use crate::cbor::CborError; use crate::definitions::{ device_engagement::DeviceRetrievalMethod, device_request::{self, DeviceRequest, DocRequest, ItemsRequest}, @@ -25,7 +27,6 @@ use crate::definitions::{ }; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; -use serde_cbor::Value as CborValue; use serde_json::json; use serde_json::Value; use std::collections::BTreeMap; @@ -82,10 +83,12 @@ pub enum Error { /// Request for data is invalid. #[error("Request for data is invalid.")] InvalidRequest, + #[error("Could not serialize to cbor: {0}")] + CborError(CborError), } -impl From for Error { - fn from(_: serde_cbor::Error) -> Self { +impl From for Error { + fn from(_: CborError) -> Self { Error::CborDecodingError } } @@ -153,7 +156,7 @@ impl SessionManager { data: request.into(), e_reader_key: e_reader_key_public, }; - let session_request = serde_cbor::to_vec(&session)?; + let session_request = cbor::to_vec(&session)?; Ok((session_manager, session_request, ble_ident)) } @@ -184,7 +187,7 @@ impl SessionManager { data: Some(request.into()), status: None, }; - serde_cbor::to_vec(&session).map_err(Into::into) + cbor::to_vec(&session).map_err(Into::into) } fn build_request(&mut self, namespaces: device_request::Namespaces) -> Result> { @@ -206,7 +209,7 @@ impl SessionManager { version: DeviceRequest::VERSION.to_string(), doc_requests: NonEmptyVec::new(doc_request), }; - let device_request_bytes = serde_cbor::to_vec(&device_request)?; + let device_request_bytes = cbor::to_vec(&device_request)?; session::encrypt_reader_data( &self.sk_reader.into(), &device_request_bytes, @@ -225,7 +228,7 @@ impl SessionManager { &mut self, response: &[u8], ) -> Result>, Error> { - let session_data: SessionData = serde_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, @@ -236,7 +239,7 @@ impl SessionManager { &mut self.device_message_counter, ) .map_err(|_e| Error::DecryptionError)?; - let response: DeviceResponse = serde_cbor::from_slice(&decrypted_response)?; + let response: DeviceResponse = cbor::from_slice(&decrypted_response)?; let mut core_namespace = BTreeMap::::new(); let mut aamva_namespace = BTreeMap::::new(); let mut parsed_response = BTreeMap::>::new(); @@ -294,17 +297,17 @@ impl SessionManager { } } -fn parse_response(value: CborValue) -> Result { +fn parse_response(value: ciborium::Value) -> Result { match value { - CborValue::Text(s) => Ok(Value::String(s)), - CborValue::Tag(_t, v) => { - if let CborValue::Text(d) = *v { + ciborium::Value::Text(s) => Ok(Value::String(s)), + ciborium::Value::Tag(_t, v) => { + if let ciborium::Value::Text(d) = *v { Ok(Value::String(d)) } else { Err(Error::ParsingError) } } - CborValue::Array(v) => { + ciborium::Value::Array(v) => { let mut array_response = Vec::::new(); for a in v { let r = parse_response(a)?; @@ -312,10 +315,10 @@ fn parse_response(value: CborValue) -> Result { } Ok(json!(array_response)) } - CborValue::Map(m) => { + ciborium::Value::Map(m) => { let mut map_response = serde_json::Map::::new(); for (key, value) in m { - if let CborValue::Text(k) = key { + if let ciborium::Value::Text(k) = key { let parsed = parse_response(value)?; map_response.insert(k, parsed); } @@ -323,9 +326,9 @@ fn parse_response(value: CborValue) -> Result { let json = json!(map_response); Ok(json) } - CborValue::Bytes(b) => Ok(json!(b)), - CborValue::Bool(b) => Ok(json!(b)), - CborValue::Integer(i) => Ok(json!(i)), + ciborium::Value::Bytes(b) => Ok(json!(b)), + ciborium::Value::Bool(b) => Ok(json!(b)), + ciborium::Value::Integer(i) => Ok(json!(>::into(i))), _ => Err(Error::ParsingError), } } @@ -356,7 +359,7 @@ mod test { #[test] fn nested_response_values() { - let domestic_driving_privileges = serde_cbor::from_slice(&hex::decode("81A276646F6D65737469635F76656869636C655F636C617373A46A69737375655F64617465D903EC6A323032342D30322D31346B6578706972795F64617465D903EC6A323032382D30332D3131781B646F6D65737469635F76656869636C655F636C6173735F636F64656243207822646F6D65737469635F76656869636C655F636C6173735F6465736372697074696F6E76436C6173732043204E4F4E2D434F4D4D45524349414C781D646F6D65737469635F76656869636C655F7265737472696374696F6E7381A27821646F6D65737469635F76656869636C655F7265737472696374696F6E5F636F64656230317828646F6D65737469635F76656869636C655F7265737472696374696F6E5F6465736372697074696F6E78284D555354205745415220434F5252454354495645204C454E534553205748454E2044524956494E47").unwrap()).unwrap(); + let domestic_driving_privileges = crate::cbor::from_slice(&hex::decode("81A276646F6D65737469635F76656869636C655F636C617373A46A69737375655F64617465D903EC6A323032342D30322D31346B6578706972795F64617465D903EC6A323032382D30332D3131781B646F6D65737469635F76656869636C655F636C6173735F636F64656243207822646F6D65737469635F76656869636C655F636C6173735F6465736372697074696F6E76436C6173732043204E4F4E2D434F4D4D45524349414C781D646F6D65737469635F76656869636C655F7265737472696374696F6E7381A27821646F6D65737469635F76656869636C655F7265737472696374696F6E5F636F64656230317828646F6D65737469635F76656869636C655F7265737472696374696F6E5F6465736372697074696F6E78284D555354205745415220434F5252454354495645204C454E534553205748454E2044524956494E47").unwrap()).unwrap(); let json = parse_response(domestic_driving_privileges).unwrap(); let expected = serde_json::json!( [ diff --git a/test/definitions/cose/mac0/secret_key b/test/definitions/cose/mac0/secret_key new file mode 100644 index 00000000..9fb6a674 --- /dev/null +++ b/test/definitions/cose/mac0/secret_key @@ -0,0 +1 @@ +a361316953796d6d6574726963613305622d318f187418681869187318201869187318201874186818651820186b18651879 \ No newline at end of file diff --git a/test/definitions/cose/mac0/serialized.cbor b/test/definitions/cose/mac0/serialized.cbor new file mode 100644 index 00000000..0efde9a1 --- /dev/null +++ b/test/definitions/cose/mac0/serialized.cbor @@ -0,0 +1 @@ +d18443a10105a10442313154546869732069732074686520636f6e74656e742e58203f30c4a2c740c3a0d90310b48cd282bcdb29ab8073a32e287fa07e188d317e8a \ No newline at end of file diff --git a/test/definitions/cose/sign1/secret_key b/test/definitions/cose/sign1/secret_key new file mode 100644 index 00000000..408d0692 --- /dev/null +++ b/test/definitions/cose/sign1/secret_key @@ -0,0 +1 @@ +57c92077664146e876760c9520d054aa93c3afb04e306705db6090308507b4d3 \ No newline at end of file diff --git a/test/definitions/cose/sign1/serialized.cbor b/test/definitions/cose/sign1/serialized.cbor new file mode 100644 index 00000000..3e3da0da --- /dev/null +++ b/test/definitions/cose/sign1/serialized.cbor @@ -0,0 +1 @@ +D28443A10126A10442313154546869732069732074686520636F6E74656E742E58408EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36 \ No newline at end of file diff --git a/test/definitions/device_response.cbor b/test/definitions/device_response.cbor index 12b5f08b..bda9f89b 100644 --- a/test/definitions/device_response.cbor +++ b/test/definitions/device_response.cbor @@ -1 +1 @@ -a36776657273696f6e63312e3069646f63756d656e747381a367646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c6973737565725369676e6564a26a6e616d65537061636573a1716f72672e69736f2e31383031332e352e3186d8185863a4686469676573744944006672616e646f6d58208798645b20ea200e19ffabac92624bee6aec63aceedecfb1b80077d22bfc20e971656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f65d818586ca4686469676573744944036672616e646f6d5820b23f627e8999c706df0c0a4ed98ad74af988af619b4bb078b89058553f44615d71656c656d656e744964656e7469666965726a69737375655f646174656c656c656d656e7456616c7565d903ec6a323031392d31302d3230d818586da4686469676573744944046672616e646f6d5820c7ffa307e5de921e67ba5878094787e8807ac8e7b5b3932d2ce80f00f3e9abaf71656c656d656e744964656e7469666965726b6578706972795f646174656c656c656d656e7456616c7565d903ec6a323032342d31302d3230d818586da4686469676573744944076672616e646f6d582026052a42e5880557a806c1459af3fb7eb505d3781566329d0b604b845b5f9e6871656c656d656e744964656e7469666965726f646f63756d656e745f6e756d6265726c656c656d656e7456616c756569313233343536373839d818590471a4686469676573744944086672616e646f6d5820d094dad764a2eb9deb5210e9d899643efbd1d069cc311d3295516ca0b024412d71656c656d656e744964656e74696669657268706f7274726169746c656c656d656e7456616c7565590412ffd8ffe000104a46494600010101009000900000ffdb004300130d0e110e0c13110f11151413171d301f1d1a1a1d3a2a2c2330453d4947443d43414c566d5d4c51685241435f82606871757b7c7b4a5c869085778f6d787b76ffdb0043011415151d191d381f1f38764f434f7676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676ffc00011080018006403012200021101031101ffc4001b00000301000301000000000000000000000005060401020307ffc400321000010303030205020309000000000000010203040005110612211331141551617122410781a1163542527391b2c1f1ffc4001501010100000000000000000000000000000001ffc4001a110101010003010000000000000000000000014111213161ffda000c03010002110311003f00a5bbde22da2329c7d692bc7d0d03f52cfb0ff75e7a7ef3e7709723a1d0dae146ddfbb3c039ce07ad2bd47a7e32dbb8dd1d52d6ef4b284f64a480067dfb51f87ffb95ff00eb9ff14d215de66af089ce44b7dbde9cb6890a2838eddf18078f7add62d411ef4db9b10a65d6b95a147381ea0d495b933275fe6bba75c114104a8ba410413e983dff004f5af5d34b4b4cde632d0bf1fd1592bdd91c6411f3934c2fa6af6b54975d106dcf4a65ae56e856001ebc03c7ce29dd9eef1ef10fc447dc9da76ad2aee93537a1ba7e4f70dd8eff0057c6dffb5e1a19854a83758e54528750946ec6704850cd037bceb08b6d7d2cc76d3317fc7b5cc04fb6707269c5c6e0c5b60ae549242123b0e493f602a075559e359970d98db89525456b51c951c8afa13ea8e98e3c596836783d5c63f5a61a99fdb7290875db4be88ab384bbbbbfc7183fdeaa633e8951db7da396dc48524fb1a8bd611a5aa2a2432f30ab420a7a6d3240c718cf031fa9ef4c9ad550205aa02951df4a1d6c8421b015b769db8c9229837ea2be8b1b0d39d0eba9c51484efdb8c0efd8d258daf3c449699f2edbd4584e7af9c64e3f96b9beb28d4ac40931e6478c8e76a24a825449501d867d2b1dcdebae99b9c752ae4ecd6dde4a179c1c1e460938f9149ef655e515c03919a289cb3dca278fb7bf177f4faa829dd8ce3f2ac9a7ecde490971fafd7dce15eed9b71c018c64fa514514b24e8e4f8c5c9b75c1e82579dc1233dfec08238f6add62d391acc1c5256a79e706d52d431c7a0145140b9fd149eb3a60dc5e88cbbc2da092411e9dc71f39a7766b447b344e847dcac9dcb5abba8d145061d43a6fcf1e65cf15d0e90231d3dd9cfe62995c6dcc5ca12a2c904a15f71dd27d451453e09d1a21450961cbb3ea8a956433b781f1ce33dfed54f0e2b50a2b71d84ed6db18028a28175f74fc6bda105c529a791c25c4f3c7a11f71586268f4a66b726e33de9ea6f1b52b181c760724e47b514520a5a28a283ffd9d81858ffa4686469676573744944096672616e646f6d58204599f81beaa2b20bd0ffcc9aa03a6f985befab3f6beaffa41e6354cdb2ab2ce471656c656d656e744964656e7469666965727264726976696e675f70726976696c656765736c656c656d656e7456616c756582a37576656869636c655f63617465676f72795f636f646561416a69737375655f64617465d903ec6a323031382d30382d30396b6578706972795f64617465d903ec6a323032342d31302d3230a37576656869636c655f63617465676f72795f636f646561426a69737375655f64617465d903ec6a323031372d30322d32336b6578706972795f64617465d903ec6a323032342d31302d32306a697373756572417574688443a10126a118215901f3308201ef30820195a00302010202143c4416eed784f3b413e48f56f075abfa6d87eb84300a06082a8648ce3d04030230233114301206035504030c0b75746f7069612069616361310b3009060355040613025553301e170d3230313030313030303030305a170d3231313030313030303030305a30213112301006035504030c0975746f706961206473310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d03010703420004ace7ab7340e5d9648c5a72a9a6f56745c7aad436a03a43efea77b5fa7b88f0197d57d8983e1b37d3a539f4d588365e38cbbf5b94d68c547b5bc8731dcd2f146ba381a83081a5301e0603551d120417301581136578616d706c65406578616d706c652e636f6d301c0603551d1f041530133011a00fa00d820b6578616d706c652e636f6d301d0603551d0e0416041414e29017a6c35621ffc7a686b7b72db06cd12351301f0603551d2304183016801454fa2383a04c28e0d930792261c80c4881d2c00b300e0603551d0f0101ff04040302078030150603551d250101ff040b3009060728818c5d050102300a06082a8648ce3d040302034800304502210097717ab9016740c8d7bcdaa494a62c053bbdecce1383c1aca72ad08dbc04cbb202203bad859c13a63c6d1ad67d814d43e2425caf90d422422c04a8ee0304c0d3a68d5903a2d81859039da66776657273696f6e63312e306f646967657374416c676f726974686d675348412d3235366c76616c756544696765737473a2716f72672e69736f2e31383031332e352e31ad00582075167333b47b6c2bfb86eccc1f438cf57af055371ac55e1e359e20f254adcebf01582067e539d6139ebd131aef441b445645dd831b2b375b390ca5ef6279b205ed45710258203394372ddb78053f36d5d869780e61eda313d44a392092ad8e0527a2fbfe55ae0358202e35ad3c4e514bb67b1a9db51ce74e4cb9b7146e41ac52dac9ce86b8613db555045820ea5c3304bb7c4a8dcb51c4c13b65264f845541341342093cca786e058fac2d59055820fae487f68b7a0e87a749774e56e9e1dc3a8ec7b77e490d21f0e1d3475661aa1d0658207d83e507ae77db815de4d803b88555d0511d894c897439f5774056416a1c7533075820f0549a145f1cf75cbeeffa881d4857dd438d627cf32174b1731c4c38e12ca936085820b68c8afcb2aaf7c581411d2877def155be2eb121a42bc9ba5b7312377e068f660958200b3587d1dd0c2a07a35bfb120d99a0abfb5df56865bb7fa15cc8b56a66df6e0c0a5820c98a170cf36e11abb724e98a75a5343dfa2b6ed3df2ecfbb8ef2ee55dd41c8810b5820b57dd036782f7b14c6a30faaaae6ccd5054ce88bdfa51a016ba75eda1edea9480c5820651f8736b18480fe252a03224ea087b5d10ca5485146c67c74ac4ec3112d4c3a746f72672e69736f2e31383031332e352e312e5553a4005820d80b83d25173c484c5640610ff1a31c949c1d934bf4cf7f18d5223b15dd4f21c0158204d80e1e2e4fb246d97895427ce7000bb59bb24c8cd003ecf94bf35bbd2917e340258208b331f3b685bca372e85351a25c9484ab7afcdf0d2233105511f778d98c2f544035820c343af1bd1690715439161aba73702c474abf992b20c9fb55c36a336ebe01a876d6465766963654b6579496e666fa1696465766963654b6579a40102200121582096313d6c63e24e3372742bfdb1a33ba2c897dcd68ab8c753e4fbd48dca6b7f9a2258201fb3269edd418857de1b39a4e4a44b92fa484caa722c228288f01d0c03a2c3d667646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c76616c6964697479496e666fa3667369676e6564c074323032302d31302d30315431333a33303a30325a6976616c696446726f6dc074323032302d31302d30315431333a33303a30325a6a76616c6964556e74696cc074323032312d31302d30315431333a33303a30325a584059e64205df1e2f708dd6db0847aed79fc7c0201d80fa55badcaf2e1bcf5902e1e5a62e4832044b890ad85aa53f129134775d733754d7cb7a413766aeff13cb2e6c6465766963655369676e6564a26a6e616d65537061636573d81841a06a64657669636541757468a1696465766963654d61638443a10105a0f65820e99521a85ad7891b806a07f8b5388a332d92c189a7bf293ee1f543405ae6824d6673746174757300 \ No newline at end of file +a46776657273696f6e63312e3069646f63756d656e747381a367646f6354797065636161616c6973737565725369676e6564a26a6e616d65537061636573a1616181d8185838a4686469676573744944182a6672616e646f6d412a71656c656d656e744964656e7469666965726234326c656c656d656e7456616c7565f66a69737375657241757468d28443a10126a10442313154546869732069732074686520636f6e74656e742e58408eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4d25a91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b0916e5a4c345cacb366c6465766963655369676e6564a26a6e616d65537061636573d81847a16161a16161f66a64657669636541757468a1634d6163a1696465766963654d6163d18443a10105a10442313154546869732069732074686520636f6e74656e742e58203f30c4a2c740c3a0d90310b48cd282bcdb29ab8073a32e287fa07e188d317e8a6e646f63756d656e744572726f727381a16161006673746174757300 \ No newline at end of file diff --git a/tests/common.rs b/tests/common.rs index c47b25c3..6cd060af 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,14 +1,14 @@ #![allow(dead_code)] use anyhow::{anyhow, Context, Result}; -use signature::Signer; -use uuid::Uuid; - +use isomdl::cbor; use isomdl::definitions::device_engagement::{CentralClientMode, DeviceRetrievalMethods}; use isomdl::definitions::device_request::{DataElements, DocType, Namespaces}; use isomdl::definitions::helpers::NonEmptyMap; use isomdl::definitions::{self, BleOptions, DeviceRetrievalMethod}; use isomdl::presentation::device::{Document, Documents, RequestedItems, SessionManagerEngaged}; use isomdl::presentation::{device, reader, Stringify}; +use signature::Signer; +use uuid::Uuid; pub const DOC_TYPE: &str = "org.iso.18013.5.1.mDL"; pub const NAMESPACE: &str = "org.iso.18013.5.1"; @@ -64,7 +64,7 @@ impl Device { ) -> Result<(device::SessionManager, RequestedItems)> { let (session_manager, items_requests) = { let session_establishment: definitions::SessionEstablishment = - serde_cbor::from_slice(&request).context("could not deserialize request")?; + cbor::from_slice(&request).context("could not deserialize request")?; state .process_session_establishment(session_establishment) .context("could not process process session establishment")? diff --git a/tests/simulated_device_and_reader_state.rs b/tests/simulated_device_and_reader_state.rs index 93025e94..7b663460 100644 --- a/tests/simulated_device_and_reader_state.rs +++ b/tests/simulated_device_and_reader_state.rs @@ -1,14 +1,14 @@ use std::sync::{Arc, Mutex}; use anyhow::{Context, Result}; -use signature::Signer; -use uuid::Uuid; - +use isomdl::cbor; use isomdl::definitions::device_engagement::{CentralClientMode, DeviceRetrievalMethods}; use isomdl::definitions::device_request::{DataElements, Namespaces}; use isomdl::definitions::{self, BleOptions, DeviceRetrievalMethod}; use isomdl::presentation::device::{Documents, RequestedItems}; use isomdl::presentation::{device, reader}; +use signature::Signer; +use uuid::Uuid; use crate::common::{Device, AGE_OVER_21_ELEMENT, DOC_TYPE, NAMESPACE}; @@ -111,7 +111,7 @@ fn handle_request( ) -> Result> { let (session_manager, items_requests) = { let session_establishment: definitions::SessionEstablishment = - serde_cbor::from_slice(&request).context("could not deserialize request")?; + cbor::from_slice(&request).context("could not deserialize request")?; state .0 .clone()