diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a274d9e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: ci + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + - name: Formatting checks + run: cargo fmt --all -- --check + - name: Clippy checks + run: cargo clippy --all-targets -- -D clippy::all -D clippy::cargo -A clippy::multiple-crate-versions + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + +# Recommended pipeline if using advisories, to avoid sudden breakages +# From: https://github.com/EmbarkStudios/cargo-deny-action + cargo-deny: + runs-on: ubuntu-latest + strategy: + matrix: + checks: + - advisories + - bans licenses sources + + # Prevent sudden announcement of a new advisory from failing ci: + continue-on-error: ${{ matrix.checks == 'advisories' }} + + steps: + - uses: actions/checkout@v5 + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check ${{ matrix.checks }} diff --git a/Cargo.toml b/Cargo.toml index 83bcd9c..27109ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,25 @@ name = "cmw" version = "0.1.0" edition = "2021" +authors = ["Ionut Mihalcea "] +homepage = "https://github.com/veraison/rust-cmw" +repository = "https://github.com/veraison/rust-cmw" +description = "Rust implementation of CMW" +keywords = ["cmw", "ietf", "cbor", "json", "attestation", "rats"] +categories = [ + "os", + "os::linux-apis", + "parsing", + "network-programming", + "hardware-support", +] +exclude = [".gitignore", ".github/*"] +rust-version = "1.85.0" +license = "Apache-2.0" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_cbor = "0.11" base64 = { version = "0.22", features = ["alloc"]} base64-serde = "0.8.0" iri-string = "0.7.8" @@ -19,6 +33,7 @@ mime = "0.3.17" hex = "0.4.3" lazy_static = "1.5.0" minicbor = { version = "1.0.0", features = ["std"]} +minicbor-serde = { version = "0.6.2", features = ["alloc"] } [build-dependencies] xml-rs = "0.8" @@ -27,4 +42,4 @@ reqwest = { version = "0.12", features = ["blocking"] } [features] default = [] -rebuild-cfmap = [] \ No newline at end of file +rebuild-cfmap = [] diff --git a/LICENSE b/LICENSE index 261eeb9..92962ff 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2025 Contributors to the Veraison project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..5255019 --- /dev/null +++ b/deny.toml @@ -0,0 +1,246 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for unmaintained crates +unmaintained = "workspace" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ +# NOTE: This is a TEMPORARY recognition of the cbindgen use of clap+atty that +# requires clap to update its dependencies + #"RUSTSEC-2021-0145", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "BlueOak-1.0.0", + "BSD-3-Clause", + "ISC", + "OpenSSL", + "Unicode-3.0", + #"Apache-2.0 WITH LLVM-exception", + # Considered Copyleft, but permitted in this project + "MPL-2.0", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +# The name of the crate the clarification applies to +name = "ring" +# The optional version constraint for the crate +version = "*" +# The SPDX expression for the license requirements of the crate +expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + { path = "LICENSE", hash = 0xbd0eed23 } +] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +# NOTE: Veraison has unusually complex dependencies across platforms +# Explicitly allowing duplicates here TEMPORARILY. +multiple-versions = "allow" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overriden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overriden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#name = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +# github = [""] +# 1 or more gitlab.com organizations to allow git sources for +# gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +# bitbucket = [""] diff --git a/src/monad.rs b/src/monad.rs index da20a0b..b1e5f46 100644 --- a/src/monad.rs +++ b/src/monad.rs @@ -132,7 +132,7 @@ impl Monad { let tval = serde_json::to_value(&self.typ).map_err(Error::Json)?; arr.push(tval); // value - let vval = serde_json::to_value(&self.val).map_err(Error::Json)?; + let vval = self.val.to_json_value()?; arr.push(vval); // indicator if nonzero if let Some(ind) = self.ind { @@ -162,7 +162,7 @@ impl Monad { // type let typ: Type = serde_json::from_value(arr[0].clone()).map_err(Error::Json)?; // value - let val: Value = serde_json::from_value(arr[1].clone()).map_err(Error::Json)?; + let val: Value = Value::from_json_value(&arr[1])?; let ind = if alen == 3 { let ind_num: u8 = serde_json::from_value(arr[2].clone()).map_err(Error::Json)?; Some(Indicator::from_bits_truncate(ind_num)) diff --git a/src/value.rs b/src/value.rs index d6e3764..84ed791 100644 --- a/src/value.rs +++ b/src/value.rs @@ -3,11 +3,10 @@ //! Value wrapper around a byte vector, with base64‐URL‐safe JSON <-> byte[] mapping. -use base64::Engine; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde_cbor::{de::from_slice as cbor_from_slice, ser::to_vec as cbor_to_vec}; - use crate::cmw::Error; +use base64::Engine; +use minicbor_serde::{from_slice as cbor_from_slice, to_vec as cbor_to_vec}; +use serde_json::Value as JsonValue; #[derive(Clone, Debug, PartialEq)] pub struct Value(pub(crate) Vec); @@ -26,66 +25,57 @@ impl Value { } } -// JSON: base64 URL‐safe encoding of the raw bytes. -impl Serialize for Value { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&self.0); - serializer.serialize_str(&b64) - } -} - -impl<'de> Deserialize<'de> for Value { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD - .decode(&s) - .map_err(serde::de::Error::custom)?; - if decoded.is_empty() { - Err(serde::de::Error::custom("empty value")) - } else { - Ok(Value(decoded)) - } - } -} - impl Value { /// Helper to produce CBOR‐serialized value (raw bytes). pub fn to_cbor_bytes(&self) -> Result, Error> { - cbor_to_vec(&serde_cbor::Value::Bytes(self.0.clone())) + cbor_to_vec(self.0.clone()) .map_err(|e| Error::CborEncode(format!("Failed to encode Value to CBOR: {e}"))) } /// Helper to decode CBOR raw bytes into Value. pub fn from_cbor_bytes(b: &[u8]) -> Result { - match cbor_from_slice::(b) { - Ok(serde_cbor::Value::Bytes(v)) if !v.is_empty() => Ok(Value(v)), + match cbor_from_slice::>(b) { + Ok(v) if !v.is_empty() => Ok(Value(v)), Err(e) => Err(Error::CborDecode(format!( "Failed to decode Value from CBOR: {e}" ))), _ => Err(Error::CborDecode("got wrong CBOR type".into())), } } + + /// Helper to produce JSON‐serialized value (base64 URL‐safe string). + pub fn to_json_value(&self) -> Result { + let b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&self.0); + Ok(JsonValue::String(b64)) + } + + /// Helper to decode JSON base64 URL‐safe string into Value. + pub fn from_json_value(v: &JsonValue) -> Result { + if let JsonValue::String(s) = v { + match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(s) { + Ok(bytes) if !bytes.is_empty() => Ok(Value(bytes)), + Err(e) => Err(Error::InvalidData(format!( + "Failed to decode Value from JSON: {e}" + ))), + _ => Err(Error::InvalidData("got empty byte array".into())), + } + } else { + Err(Error::InvalidData("got wrong JSON type".into())) + } + } } #[cfg(test)] mod tests { use super::Value; - use serde_json; #[test] fn test_value_json_roundtrip() { let raw = vec![0xde, 0xad, 0xbe, 0xef]; let v = Value::new(raw.clone()).unwrap(); - let s = serde_json::to_string(&v).unwrap(); - // Base64 of deadbeef is '3q2-7w' - assert!(s.contains("3q2-7w")); - let d: Value = serde_json::from_str(&s).unwrap(); + let json_value = v.to_json_value().unwrap(); + assert_eq!(json_value, serde_json::Value::String("3q2-7w".into())); + let d = Value::from_json_value(&json_value).unwrap(); assert_eq!(d, Value(raw)); }