Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of MCD capability descriptor and SA-Attestation Object (SAAO) as per ISO/IEC JTC 1/SC 17/WG 4 N 4566 #89

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
498ebc2
Integrate coset crate.
radumarias Jul 22, 2024
82ff8fd
Integrate coset crate.
radumarias Jul 22, 2024
9a6bf23
Integrate coset crate.
radumarias Jul 23, 2024
87d75c2
Add CoseSign1 and CoseMac0 and migrate parts of the serilization to c…
radumarias Aug 4, 2024
abaf78a
Change serialized value for tests of CoseMac0
radumarias Aug 4, 2024
0943283
Add `DeviceAuthType` to specify what kind of signature to use for dev…
radumarias Aug 4, 2024
10b96da
Document `sign1` and `mac0` modules.
radumarias Aug 4, 2024
f3981ae
Use CoseKey from coset
radumarias Aug 5, 2024
a73e3e4
Move to `key` module.
radumarias Aug 5, 2024
a9fdf0f
Add serialize and convert for CoseKey
radumarias Aug 5, 2024
5cffb26
Extract some duplicated code.
radumarias Aug 5, 2024
83280a7
Extract some duplicated code.
radumarias Aug 5, 2024
bcde4d9
Update src/cose/sign1.rs
radumarias Aug 5, 2024
6c8aba9
Update src/cose/mac0.rs
radumarias Aug 5, 2024
797e8a7
Revert "Extract some duplicated code."
radumarias Aug 5, 2024
40bde51
Revert "Extract some duplicated code."
radumarias Aug 5, 2024
84acfb6
Revert "Add serialize and convert for CoseKey"
radumarias Aug 5, 2024
33333b6
Revert "Move to `key` module."
radumarias Aug 5, 2024
ffe7d22
Revert "Use CoseKey from coset"
radumarias Aug 5, 2024
63af7aa
Revert migration to `coset::CoseKey`. Small refactoring.
radumarias Aug 5, 2024
34bd8d6
Revert migration to `coset::CoseKey`. Small refactoring.
radumarias Aug 5, 2024
837cce5
Fix missing import in docs examples.
radumarias Aug 5, 2024
c2323de
Implementation of MCD capability descriptor and SA-Attestation Object…
radumarias Aug 5, 2024
6a0cf0a
Merge branch 'main' into skit-448-investigate-coset
radumarias Aug 6, 2024
a22e550
Rename the `CoseMac0` key variables. Removed check if the signature i…
radumarias Aug 6, 2024
efb814f
Merge branch 'main' into cred-587-implement-support-to-read-the-mcd-a…
radumarias Aug 6, 2024
690b8dc
Merge branch 'skit-448-investigate-coset' into cred-587-implement-sup…
radumarias Aug 6, 2024
d63bc86
merge from main
radumarias Aug 29, 2024
9f947d6
Merge branch 'main' into cred-587-implement-support-to-read-the-mcd-a…
radumarias Sep 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,21 @@ async-signature = "0.3.0"
base64 = "0.13"
pem-rfc7468 = "0.7.0"
x509-cert = { version = "0.1.1", features = ["pem"] }
coset = { version = "0.3.7", features = ["std"] }
ciborium = "0.2.2"

ssi-jwk = "0.2.1"
isomdl-macros = { version = "0.1.0", path = "macros" }
clap = { version = "4", features = ["derive"] }
clap-stdin = "0.2.1"

strum = "0.24"
strum_macros = "0.24"

[dependencies.cose-rs]
git = "https://github.com/spruceid/cose-rs"
rev = "4104505"
clap-stdin = "0.5.1"
digest = "0.10.7"
serde_bytes = "0.11.15"
enum_stringify = "0.5.0"
strum = "0.26.3"
strum_macros = "0.26.3"

[dev-dependencies]
hex = "0.4.3"
p256 = "0.13.0"
serde_json = "*"

297 changes: 297 additions & 0 deletions macros/src/cbor_serializable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, Attribute, Data, DeriveInput, Fields, Lit, Meta, MetaList, NestedMeta, Type,
};

pub(crate) fn cbor_serializable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;

let methods: Vec<proc_macro2::TokenStream> = Vec::new();

let mut struct_rename_all_strategy: Option<String> = None;

// Check for serde(rename_all = "...") at the struct level
for attr in &input.attrs {
if attr.path.is_ident("isomdl") {
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
for meta in nested {
if let NestedMeta::Meta(Meta::NameValue(meta_name_value)) = meta {
if meta_name_value.path.is_ident("rename_field_all") {
if let Lit::Str(lit_str) = meta_name_value.lit {
struct_rename_all_strategy = Some(lit_str.value());
}
}
}
}
}
}
}

// enum FieldHandling {
// Named,
// Unnamed,
// }
// let mut struct_type = FieldHandling::Named;
let field_handling = match &input.data {
Data::Struct(data_struct) => match &data_struct.fields {
Fields::Named(fields_named) => {
let field_deserialization = fields_named.named.iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_name_str = field_name.to_string();
// let key = format!("{}::{}()", format_ident!("{struct_name}"), field_name_str);
let key = get_field_name(
field_name,
&f.attrs,
&struct_rename_all_strategy,
);
quote! {
#field_name: {
let value = fields.remove(&#key.to_string())
.ok_or_else(|| coset::CoseError::DecodeFailed(
ciborium::de::Error::Semantic(None, format!("Missing field: {}", #field_name_str))
))?;
<_ as coset::AsCborValue>::from_cbor_value(value)?
}
}
});

let field_serialization = fields_named.named.iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_type = &f.ty;
let field_name_ts = quote!(self.#field_name);
let field_value = generate_field_serialization(field_type, field_name_ts);
// let field_name_str = field_name.to_string();
// let key = format!("{}::{}()", format_ident!("{struct_name}"), field_name_str);
let key = get_field_name(field_name, &f.attrs, &struct_rename_all_strategy);
quote! {
map.push((ciborium::Value::Text(#key.to_string()), #field_value));
}
});

(
quote! { #(#field_deserialization),* },
quote! { #(#field_serialization)* },
)
}
Fields::Unnamed(fields_unnamed) => {
// struct_type = FieldHandling::Unnamed;
let field_deserialization = fields_unnamed.unnamed.iter().enumerate().map(|(i, _f)| {
let index = syn::Index::from(i);
quote! {
{
let value = fields.remove(&#index.to_string())
.ok_or_else(|| coset::CoseError::DecodeFailed(
ciborium::de::Error::Semantic(None, format!("Missing field at index: {}", #index))
))?;
<_ as coset::AsCborValue>::from_cbor_value(value)?
}
}
});

let field_serialization =
fields_unnamed.unnamed.iter().enumerate().map(|(i, f)| {
let index = syn::Index::from(i);
let field_type = &f.ty;
let field_value =
generate_field_serialization(field_type, quote!(self.#index));
quote! {
array.push(#field_value);
}
});

(
quote! { #(#field_deserialization),* },
quote! { #(#field_serialization)* },
)
}
_ => panic!("CborSerializable doesn't work for unit structs"),
},
_ => panic!("CborSerializable can only be derived for structs"),
};

let (field_deserialization, field_serialization) = field_handling;

let expanded = quote! {
#(#methods)*
impl coset::CborSerializable for #struct_name {}
impl coset::AsCborValue for #struct_name {
fn from_cbor_value(value: ciborium::Value) -> coset::Result<Self> {
let mut fields = value.into_map().map_err(|_| {
coset::CoseError::DecodeFailed(
ciborium::de::Error::Semantic(None, format!("{} is not a map", stringify!(#struct_name)))
)
})?.into_iter().flat_map(|f| match f.0 {
ciborium::Value::Text(s) => Ok((s, f.1)),
_ => Err(coset::CoseError::UnexpectedItem(
"key",
"text for field",
)),
}).collect::<std::collections::HashMap<String, ciborium::Value>>();
Ok(Self {
#field_deserialization
})
}
fn to_cbor_value(self) -> coset::Result<ciborium::Value> {
let mut map = Vec::new();
#field_serialization
Ok(ciborium::Value::Map(map))
}
}
};

TokenStream::from(expanded)
}

pub(crate) fn generate_field_serialization(
field_type: &Type,
field_access: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
match field_type {
// Handling ciborium::Value directly
Type::Path(type_path) if type_path.path.is_ident("ciborium::Value") => {
quote! { #field_access }
}

// Handling integer types
Type::Path(type_path)
if type_path.path.is_ident("u8")
|| type_path.path.is_ident("u16")
|| type_path.path.is_ident("u32")
|| type_path.path.is_ident("u64")
|| type_path.path.is_ident("i8")
|| type_path.path.is_ident("i16")
|| type_path.path.is_ident("i32")
|| type_path.path.is_ident("i64") =>
{
quote! { ciborium::Value::Integer(#field_access as i128) }
}

// Handling Vec<u8>
Type::Path(type_path) if type_path.path.is_ident("Vec<u8>") => {
quote! { ciborium::Value::Bytes(#field_access) }
}

// Handling f64 (floating point numbers)
Type::Path(type_path) if type_path.path.is_ident("f64") => {
quote! { ciborium::Value::Float(#field_access) }
}

// Handling String
Type::Path(type_path) if type_path.path.is_ident("String") => {
quote! { ciborium::Value::Text(#field_access) }
}

// Handling boolean types
Type::Path(type_path) if type_path.path.is_ident("bool") => {
quote! { ciborium::Value::Bool(#field_access) }
}

// Handling Option (Null)
Type::Path(type_path) if type_path.path.is_ident("Option") => {
quote! { #field_access.map_or(ciborium::Value::Null, |v| v.to_cbor_value().unwrap()) }
}

// Handling Array of Values
Type::Path(type_path) if type_path.path.is_ident("Vec") => {
quote! { ciborium::Value::Array(#field_access.into_iter().map(|v| v.to_cbor_value().unwrap()).collect()) }
}

// Handling Maps of Values
Type::Path(type_path) if type_path.path.is_ident("std::collections::HashMap") => {
quote! { ciborium::Value::Map(#field_access.into_iter().map(|(k, v)| (k.to_cbor_value().unwrap(), v.to_cbor_value().unwrap())).collect()) }
}

// Default case to handle custom types
_ => quote! { #field_access.to_cbor_value().unwrap() },
}
}

pub(crate) fn get_field_name(
field_name: &syn::Ident,
attrs: &[Attribute],
rename_all_strategy: &Option<String>,
) -> String {
let field_name_str = field_name.to_string();
let mut rename_value = field_name_str.clone();
let mut field_rename_all_strategy = rename_all_strategy.clone();

// Check for serde(rename = "...") and serde(rename_all = "...") at the field level
for attr in attrs {
if attr.path.is_ident("isomdl") {
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
for meta in nested {
if let NestedMeta::Meta(Meta::NameValue(meta_name_value)) = meta {
if meta_name_value.path.is_ident("rename") {
if let Lit::Str(lit_str) = meta_name_value.lit {
rename_value = lit_str.value();
}
} else if meta_name_value.path.is_ident("rename_field_all") {
if let Lit::Str(lit_str) = meta_name_value.lit {
field_rename_all_strategy = Some(lit_str.value());
}
}
}
}
}
}
}

// Apply the rename_all strategy if no specific rename is found
if rename_value == field_name_str {
if let Some(strategy) = &field_rename_all_strategy {
rename_value = apply_rename_all_strategy(&field_name_str, strategy);
}
}
rename_value
}

// Helper function to apply rename_all strategies
pub(crate) fn apply_rename_all_strategy(field_name: &str, strategy: &str) -> String {
match strategy {
"camelCase" => to_camel_case(field_name),
"snake_case" => to_snake_case(field_name),
"PascalCase" => to_pascal_case(field_name),
_ => field_name.to_string(),
}
}

// Convert to camelCase
pub(crate) fn to_camel_case(field_name: &str) -> String {
let mut s = String::new();
let mut capitalize = false;
for c in field_name.chars() {
if c == '_' {
capitalize = true;
} else if capitalize {
s.push(c.to_ascii_uppercase());
capitalize = false;
} else {
s.push(c);
}
}
s
}

// Convert to snake_case (field names are typically already in snake_case)
pub(crate) fn to_snake_case(field_name: &str) -> String {
field_name.to_string()
}

// Convert to PascalCase
pub(crate) fn to_pascal_case(field_name: &str) -> String {
let mut s = String::new();
let mut capitalize = true;
for c in field_name.chars() {
if c == '_' {
capitalize = true;
} else if capitalize {
s.push(c.to_ascii_uppercase());
capitalize = false;
} else {
s.push(c);
}
}
s
}
Loading