diff --git a/Cargo.lock b/Cargo.lock index 4b4a114b15..4e207b7902 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix" @@ -550,7 +550,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attestation-agent" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components.git?rev=4fbededdbf02ee6381c29b1728993d542fef730d#4fbededdbf02ee6381c29b1728993d542fef730d" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=591d0bb#591d0bb45cd7a2c66f3778428940c40f7eec3b7d" dependencies = [ "anyhow", "async-trait", @@ -560,7 +560,7 @@ dependencies = [ "const_format", "crypto", "hex", - "kbs-types 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kbs-types", "log", "serde", "serde_json", @@ -581,6 +581,7 @@ dependencies = [ "assert-json-diff", "async-trait", "base64 0.22.1", + "canon-json", "cfg-if", "clap", "ear", @@ -588,7 +589,7 @@ dependencies = [ "futures", "hex", "jsonwebtoken", - "kbs-types 0.12.0 (git+https://github.com/virtee/kbs-types?rev=868958e)", + "kbs-types", "lazy_static", "log", "openssl", @@ -610,6 +611,7 @@ dependencies = [ "thiserror 2.0.12", "time", "tokio", + "toml 0.8.23", "tonic", "tonic-build", "uuid", @@ -619,7 +621,7 @@ dependencies = [ [[package]] name = "attester" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components.git?rev=4fbededdbf02ee6381c29b1728993d542fef730d#4fbededdbf02ee6381c29b1728993d542fef730d" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=591d0bb#591d0bb45cd7a2c66f3778428940c40f7eec3b7d" dependencies = [ "anyhow", "async-trait", @@ -629,19 +631,19 @@ dependencies = [ "cfg-if", "codicon", "crypto", - "csv-rs 0.1.0 (git+https://github.com/openanolis/csv-rs?rev=3045440)", + "csv-rs 0.1.0 (git+https://github.com/panpingsheng/csv-rs.git?rev=7538198)", "hex", "hyper 0.14.32", "hyper-tls 0.5.0", "iocuddle", - "kbs-types 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kbs-types", "log", "occlum_dcap", "s390_pv", "scroll", "serde", "serde_json", - "serde_with 3.13.0", + "serde_with 3.14.0", "sev 6.2.1", "sha2", "strum", @@ -983,6 +985,18 @@ dependencies = [ "bytes", ] +[[package]] +name = "canon-json" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ae9f90437d2e2efba2a6c75b8279aa6b8f2f4017e0a4aeb64a76cd9d3a2bab" +dependencies = [ + "serde", + "serde_derive", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "cbc" version = "0.1.2" @@ -1016,7 +1030,7 @@ dependencies = [ [[package]] name = "ccatoken" version = "0.1.0" -source = "git+https://github.com/veraison/rust-ccatoken?branch=trustee-cca#5a980d7a4eb93dd72fd4ccbc68d418bea40fde88" +source = "git+https://github.com/veraison/rust-ccatoken?rev=dfe9ca2#dfe9ca2d949c28eb0944fe16769f06a96676dda1" dependencies = [ "base64 0.21.7", "bitmask", @@ -1031,7 +1045,7 @@ dependencies = [ "openssl", "serde", "serde_json", - "serde_with 3.13.0", + "serde_with 3.14.0", "thiserror 1.0.69", ] @@ -1435,7 +1449,7 @@ checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components.git?rev=4fbededdbf02ee6381c29b1728993d542fef730d#4fbededdbf02ee6381c29b1728993d542fef730d" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=591d0bb#591d0bb45cd7a2c66f3778428940c40f7eec3b7d" dependencies = [ "aes-gcm", "aes-kw", @@ -1443,7 +1457,7 @@ dependencies = [ "base64 0.22.1", "concat-kdf", "ctr", - "kbs-types 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kbs-types", "p256", "rand 0.8.5", "rand 0.9.1", @@ -1504,21 +1518,25 @@ dependencies = [ [[package]] name = "csv-rs" version = "0.1.0" -source = "git+https://github.com/openanolis/csv-rs?rev=3045440#3045440238a4487fc468a69dce07313662992a64" +source = "git+https://github.com/panpingsheng/csv-rs.git?rev=7538198#753819821493b0299ec8db622e10802eb6243c07" dependencies = [ + "bincode", "bitfield 0.13.2", "bitflags 1.3.2", "codicon", "dirs 5.0.1", + "hex", "hyper 0.14.32", "hyper-tls 0.5.0", "iocuddle", "libc", + "log", "openssl", "openssl-sys", "rand 0.8.5", "serde", "serde-big-array", + "serde_bytes", "static_assertions", "tokio", ] @@ -2917,6 +2935,17 @@ dependencies = [ "bindgen", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "iocuddle" version = "0.1.1" @@ -3121,7 +3150,7 @@ dependencies = [ "josekit", "jsonwebtoken", "jwt-simple", - "kbs-types 0.12.0 (git+https://github.com/virtee/kbs-types?rev=868958e)", + "kbs-types", "kms", "lazy_static", "log", @@ -3175,19 +3204,7 @@ dependencies = [ [[package]] name = "kbs-types" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be9fa1f64b3c8da5043cdc634007c6f7444b3ef1f3668f66b34180bdb398ec7" -dependencies = [ - "base64 0.22.1", - "serde", - "serde_json", - "thiserror 2.0.12", -] - -[[package]] -name = "kbs-types" -version = "0.12.0" -source = "git+https://github.com/virtee/kbs-types?rev=868958e#868958ee919a480cebd3cfeb62dc2ee97cbbd680" +source = "git+https://github.com/virtee/kbs-types.git?rev=f4055a1#f4055a17979c509d86924c9cb459221e2d45ff12" dependencies = [ "base64 0.22.1", "serde", @@ -3198,15 +3215,16 @@ dependencies = [ [[package]] name = "kbs_protocol" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components.git?rev=4fbededdbf02ee6381c29b1728993d542fef730d#4fbededdbf02ee6381c29b1728993d542fef730d" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=591d0bb#591d0bb45cd7a2c66f3778428940c40f7eec3b7d" dependencies = [ "anyhow", "async-trait", "attester", "base64 0.22.1", + "canon-json", "crypto", "jwt-simple", - "kbs-types 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kbs-types", "log", "reqwest 0.12.20", "resource_uri", @@ -3222,7 +3240,7 @@ dependencies = [ [[package]] name = "kms" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components.git?rev=4fbededdbf02ee6381c29b1728993d542fef730d#4fbededdbf02ee6381c29b1728993d542fef730d" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=591d0bb#591d0bb45cd7a2c66f3778428940c40f7eec3b7d" dependencies = [ "anyhow", "async-trait", @@ -4672,7 +4690,7 @@ dependencies = [ [[package]] name = "resource_uri" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components.git?rev=4fbededdbf02ee6381c29b1728993d542fef730d#4fbededdbf02ee6381c29b1728993d542fef730d" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=591d0bb#591d0bb45cd7a2c66f3778428940c40f7eec3b7d" dependencies = [ "anyhow", "serde", @@ -5027,6 +5045,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scientific" version = "0.5.3" @@ -5237,20 +5267,21 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", - "schemars", + "schemars 0.9.0", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", - "serde_with_macros 3.13.0", + "serde_with_macros 3.14.0", "time", ] @@ -5268,9 +5299,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling 0.20.11", "proc-macro2", @@ -5823,17 +5854,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot 0.12.4", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -6335,14 +6368,14 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "veraison-apiclient" version = "0.0.2" -source = "git+https://github.com/veraison/rust-apiclient?branch=trustee-cca#4f5facf82e494a5dcee12f37b157d4c0448ec73e" +source = "git+https://github.com/veraison/rust-apiclient?rev=fe149cd#fe149cdace19ee4b171b2ff38f708265daf155cc" dependencies = [ "base64 0.13.1", "chrono", "jsonwebkey", "reqwest 0.12.20", "serde", - "serde_with 3.13.0", + "serde_with 3.14.0", "thiserror 2.0.12", "tokio", "url", @@ -6372,7 +6405,7 @@ dependencies = [ "intel-tee-quote-verification-rs", "jsonwebkey", "jsonwebtoken", - "kbs-types 0.12.0 (git+https://github.com/virtee/kbs-types?rev=868958e)", + "kbs-types", "log", "openssl", "reqwest 0.12.20", @@ -6586,7 +6619,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e6467ccb65..aa71e819ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,10 +35,10 @@ hex = "0.4.3" jwt-simple = { version = "0.12", default-features = false, features = [ "pure-rust", ] } -kbs_protocol = { git = "https://github.com/confidential-containers/guest-components.git", rev = "4fbededdbf02ee6381c29b1728993d542fef730d", default-features = false } +kbs_protocol = { git = "https://github.com/confidential-containers/guest-components.git", rev = "591d0bb", default-features = false } # TODO: Change this to kbs-types release -kbs-types = { git = "https://github.com/virtee/kbs-types", rev = "868958e" } -kms = { git = "https://github.com/confidential-containers/guest-components.git", rev = "4fbededdbf02ee6381c29b1728993d542fef730d", default-features = false } +kbs-types = { "git" = "https://github.com/virtee/kbs-types.git", rev = "f4055a1" } +kms = { git = "https://github.com/confidential-containers/guest-components.git", rev = "591d0bb", default-features = false } jsonwebtoken = { version = "9", default-features = false } log = "0.4.17" openssl = "0.10.73" @@ -64,6 +64,7 @@ shadow-rs = "0.19.0" strum = { version = "0.27", features = ["derive"] } thiserror = "2.0" tokio = { version = "1", features = ["full"], default-features = false } +toml = "0.8.23" tempfile = "3.20.0" tonic = "0.12" tonic-build = "0.12" diff --git a/attestation-service/Cargo.toml b/attestation-service/Cargo.toml index c777dc22bb..844f3a147c 100644 --- a/attestation-service/Cargo.toml +++ b/attestation-service/Cargo.toml @@ -37,6 +37,7 @@ actix-web = { workspace = true, optional = true } anyhow.workspace = true async-trait.workspace = true base64.workspace = true +canon-json = "0.2.1" cfg-if.workspace = true clap = { workspace = true, optional = true } ear.workspace = true @@ -63,6 +64,7 @@ tempfile.workspace = true time = { version = "0.3.40", features = ["std"] } thiserror.workspace = true tokio.workspace = true +toml.workspace = true tonic = { workspace = true, optional = true } uuid = { version = "1.1.2", features = ["v4"] } verifier = { path = "../deps/verifier", default-features = false } diff --git a/attestation-service/docs/restful-as.md b/attestation-service/docs/restful-as.md index b8e65471d1..599a6b8d3c 100644 --- a/attestation-service/docs/restful-as.md +++ b/attestation-service/docs/restful-as.md @@ -179,21 +179,22 @@ RESTful CoCo-AS's endpoints are as following: "init_data": { // `init_data` is optional. If given, the init data binding will // be checked. // The field `raw` and `structured` are exclusive. - "raw": "YWFhCg==...", // Base64 encoded init data slice. The whole string will be base64 - // decoded. The result one will then be accumulated into a digest which - // is used as the expected init data to check against the one inside - // evidence. The hash algorithm is defined by `init_data_hash_algorithm`. - // - // The alphabet is URL_SAFE_NO_PAD. - // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5 + "init_data_digest": "YWFhCg==...", // Base64 encoded init data digest slice. The whole string will be base64 + // decoded. The result one will then be accumulated into a digest which + // is used as the expected init data to check against the one inside + // evidence. + // + // The alphabet is URL_SAFE_NO_PAD. + // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5 - "structured": {} // Init data in a JSON map. CoCoAS will rearrange each layer of the - // data JSON object in dictionary order by key, then serialize and output - // it into a compact string, and perform hash calculation on the whole - // to check against the one inside evidence. - // - // After the verification, the structured init data field will be included - // inside the token claims. + "init_data_toml": "algorithm = \"sha384\"..." // Init data TOML. CoCoAS will perform hash calculation on the whole + // to check against the one inside evidence. The hash algorithm to use + // is inside the Initdata Toml's metadata. + // + // See https://github.com/confidential-containers/trustee/blob/main/kbs/docs/initdata.md#toml-version + // + // After the verification, the `.data` field of init data field will + // be included inside the token claims. }, "runtime_data_hash_algorithm": "sha384",// Hash algorithm used to calculate runtime data. Currently can be // "sha256", "sha384" or "sha512". If not specified, "sha384" will be selected. diff --git a/attestation-service/src/bin/grpc/mod.rs b/attestation-service/src/bin/grpc/mod.rs index e4c12a5f55..6dcc1da18b 100644 --- a/attestation-service/src/bin/grpc/mod.rs +++ b/attestation-service/src/bin/grpc/mod.rs @@ -124,7 +124,7 @@ impl AttestationService for Arc> { let raw_runtime = URL_SAFE_NO_PAD.decode(raw).map_err(|e| { Status::aborted(format!("base64 decode runtime data: {e}")) })?; - Some(attestation_service::Data::Raw(raw_runtime)) + Some(attestation_service::RuntimeData::Raw(raw_runtime)) } crate::as_api::individual_attestation_request::RuntimeData::StructuredRuntimeData( structured, @@ -132,7 +132,7 @@ impl AttestationService for Arc> { let structured = serde_json::from_str(&structured).map_err(|e| { Status::aborted(format!("parse structured runtime data: {e}")) })?; - Some(attestation_service::Data::Structured(structured)) + Some(attestation_service::RuntimeData::Structured(structured)) } }, None => None, @@ -140,20 +140,17 @@ impl AttestationService for Arc> { let init_data = match verification_request.init_data { Some(init_data) => match init_data { - crate::as_api::individual_attestation_request::InitData::RawInitData(raw) => { + crate::as_api::individual_attestation_request::InitData::InitDataDigest( + raw, + ) => { let raw_init = URL_SAFE_NO_PAD.decode(raw).map_err(|e| { Status::aborted(format!("base64 decode init data: {e}")) })?; - Some(attestation_service::Data::Raw(raw_init)) + Some(attestation_service::InitDataInput::Digest(raw_init)) } - crate::as_api::individual_attestation_request::InitData::StructuredInitData( + crate::as_api::individual_attestation_request::InitData::InitDataToml( structured, - ) => { - let structured = serde_json::from_str(&structured).map_err(|e| { - Status::aborted(format!("parse structured init data: {e}")) - })?; - Some(attestation_service::Data::Structured(structured)) - } + ) => Some(attestation_service::InitDataInput::Toml(structured)), }, None => None, }; @@ -172,29 +169,12 @@ impl AttestationService for Arc> { } }; - let init_data_hash_algorithm = match verification_request - .init_data_hash_algorithm - .is_empty() - { - false => { - HashAlgorithm::try_from(&verification_request.init_data_hash_algorithm[..]) - .map_err(|e| { - Status::aborted(format!("parse init data HashAlgorithm failed: {e}")) - })? - } - true => { - info!("No Init Data Hash Algorithm provided, use `sha384` by default."); - HashAlgorithm::Sha384 - } - }; - verification_requests.push(VerificationRequest { evidence, tee, runtime_data, runtime_data_hash_algorithm, init_data, - init_data_hash_algorithm, }); } let policy_ids = match request.policy_ids.is_empty() { diff --git a/attestation-service/src/bin/restful/mod.rs b/attestation-service/src/bin/restful/mod.rs index 7f73d9318a..48ff3828a3 100644 --- a/attestation-service/src/bin/restful/mod.rs +++ b/attestation-service/src/bin/restful/mod.rs @@ -2,7 +2,10 @@ use std::{collections::HashMap, sync::Arc}; use actix_web::{body::BoxBody, web, HttpRequest, HttpResponse, ResponseError}; use anyhow::{anyhow, bail, Context}; -use attestation_service::{AttestationService, HashAlgorithm, VerificationRequest}; +use attestation_service::{ + AttestationService, HashAlgorithm, InitDataInput as InnerInitDataInput, + RuntimeData as InnerRuntimeData, VerificationRequest, +}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use kbs_types::Tee; use log::{debug, error, info}; @@ -34,20 +37,19 @@ impl ResponseError for Error { type Result = std::result::Result; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] pub struct AttestationRequest { verification_requests: Vec, policy_ids: Vec, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] pub struct IndividualAttestationRequest { tee: String, evidence: String, - runtime_data: Option, - init_data: Option, + runtime_data: Option, + init_data: Option, runtime_data_hash_algorithm: Option, - init_data_hash_algorithm: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -60,11 +62,18 @@ pub struct ChallengeRequest { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -enum Data { +enum RuntimeData { Raw(String), Structured(Value), } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +enum InitDataInput { + InitDataDigest(String), + InitDataToml(String), +} + fn to_tee(tee: &str) -> anyhow::Result { let res = match tee { "azsnpvtpm" => Tee::AzSnpVtpm, @@ -85,15 +94,29 @@ fn to_tee(tee: &str) -> anyhow::Result { Ok(res) } -fn parse_data(data: Data) -> Result { +fn parse_runtime_data(data: RuntimeData) -> Result { let res = match data { - Data::Raw(raw) => { + RuntimeData::Raw(raw) => { let data = URL_SAFE_NO_PAD .decode(raw) - .context("base64 decode raw data")?; - attestation_service::Data::Raw(data) + .context("base64 decode raw runtime data")?; + InnerRuntimeData::Raw(data) } - Data::Structured(structured) => attestation_service::Data::Structured(structured), + RuntimeData::Structured(structured) => InnerRuntimeData::Structured(structured), + }; + + Ok(res) +} + +fn parse_init_data(data: InitDataInput) -> Result { + let res = match data { + InitDataInput::InitDataDigest(raw) => { + let data = URL_SAFE_NO_PAD + .decode(raw) + .context("base64 decode raw init data")?; + InnerInitDataInput::Digest(data) + } + InitDataInput::InitDataToml(structured) => InnerInitDataInput::Toml(structured), }; Ok(res) @@ -122,13 +145,13 @@ pub async fn attestation( let runtime_data = attestation_request .runtime_data - .map(parse_data) + .map(parse_runtime_data) .transpose() .context("decode given Runtime Data")?; let init_data = attestation_request .init_data - .map(parse_data) + .map(parse_init_data) .transpose() .context("decode given Init Data")?; @@ -141,23 +164,12 @@ pub async fn attestation( } }; - let init_data_hash_algorithm = match attestation_request.init_data_hash_algorithm { - Some(alg) => { - HashAlgorithm::try_from(&alg[..]).context("parse init data HashAlgorithm failed")? - } - None => { - info!("No Init Data Hash Algorithm provided, use `sha384` by default."); - HashAlgorithm::Sha384 - } - }; - verification_requests.push(VerificationRequest { evidence, tee, runtime_data, runtime_data_hash_algorithm, init_data, - init_data_hash_algorithm, }); } diff --git a/attestation-service/src/lib.rs b/attestation-service/src/lib.rs index 9b7169f01b..d6c18da638 100644 --- a/attestation-service/src/lib.rs +++ b/attestation-service/src/lib.rs @@ -10,6 +10,7 @@ pub mod token; use crate::token::AttestationTokenBroker; +use canon_json::CanonicalFormatter; pub use kbs_types::{Attestation, Tee}; pub use serde_json::Value; @@ -17,6 +18,7 @@ use anyhow::{anyhow, bail, Context, Result}; use config::Config; use log::{debug, info}; use rvps::{RvpsApi, RvpsError}; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256, Sha384, Sha512}; use std::collections::HashMap; use strum::{AsRefStr, Display, EnumString}; @@ -25,15 +27,18 @@ use tokio::fs; use verifier::{InitDataHash, ReportData, TeeEvidenceParsedClaim}; /// Hash algorithms used to calculate runtime/init data binding -#[derive(Debug, Display, EnumString, AsRefStr)] +#[derive(Debug, Display, EnumString, AsRefStr, Serialize, Deserialize)] pub enum HashAlgorithm { #[strum(ascii_case_insensitive)] + #[serde(rename = "sha256")] Sha256, #[strum(ascii_case_insensitive)] + #[serde(rename = "sha384")] Sha384, #[strum(ascii_case_insensitive)] + #[serde(rename = "sha512")] Sha512, } @@ -59,6 +64,13 @@ impl HashAlgorithm { } } +fn serialize_canon_json(value: T) -> Result> { + let mut buf = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, CanonicalFormatter::new()); + value.serialize(&mut ser)?; + Ok(buf) +} + pub type TeeEvidence = serde_json::Value; pub type TeeClass = String; @@ -73,15 +85,15 @@ pub struct TeeClaims { runtime_data_claims: serde_json::Value, } -/// Runtime/Init Data used to check the binding relationship with report data +/// Runtime Data used to check the binding relationship with report data /// in Evidence #[derive(Debug)] -pub enum Data { - /// This will be used as the expected runtime/init data to check against +pub enum RuntimeData { + /// This will be used as the expected runtime data to check against /// the one inside evidence. Raw(Vec), - /// Runtime/Init data in a JSON map. CoCoAS will rearrange each layer of the + /// Runtime data in a JSON map. CoCoAS will rearrange each layer of the /// data JSON object in dictionary order by key, then serialize and output /// it into a compact string, and perform hash calculation on the whole /// to check against the one inside evidence. @@ -102,6 +114,31 @@ pub enum ServiceError { Anyhow(#[from] anyhow::Error), } +/// Initdata defined in +/// +#[derive(Debug, Deserialize, Serialize)] +pub struct Initdata { + pub version: String, + pub algorithm: HashAlgorithm, + pub data: HashMap, +} + +/// Init Data used to check the binding relationship with report data +/// in Evidence +#[derive(Debug)] +pub enum InitDataInput { + /// This will be used as the expected init data to check against + /// the one inside evidence. + Digest(Vec), + + /// Init data TOML. CoCoAS will perform hash calculation on the whole + /// to check against the one inside evidence. + /// + /// After the verification, the `.data` field of init data field will + /// be included inside the token claims. + Toml(String), +} + /// A VerificationRequest contains hw evidence that the AS will verify along with some /// metadata required for verification. /// @@ -114,15 +151,13 @@ pub struct VerificationRequest { /// These data field will be used to check against the counterpart inside the evidence. /// The concrete way of checking is decide by the enum type. If this parameter is set `None`, the comparation /// will not be performed. - pub runtime_data: Option, + pub runtime_data: Option, /// The hash algorithm that is used to calculate the digest of `runtime_data`. pub runtime_data_hash_algorithm: HashAlgorithm, /// These data field will be used to check against the counterpart inside the evidence. /// The concrete way of checking is decide by the enum type. If this parameter is set `None`, the comparation /// will not be performed. - pub init_data: Option, - /// The hash algorithm that is used to calculate the digest of `init_data`. - pub init_data_hash_algorithm: HashAlgorithm, + pub init_data: Option, } pub struct AttestationService { @@ -195,7 +230,7 @@ impl AttestationService { for verification_request in verification_requests { let verifier = verifier::to_verifier(&verification_request.tee)?; - let (report_data, runtime_data_claims) = parse_data( + let (report_data, runtime_data_claims) = parse_runtime_data( verification_request.runtime_data, &verification_request.runtime_data_hash_algorithm, ) @@ -206,11 +241,8 @@ impl AttestationService { None => ReportData::NotProvided, }; - let (init_data, init_data_claims) = parse_data( - verification_request.init_data, - &verification_request.init_data_hash_algorithm, - ) - .context("parse init data")?; + let (init_data, init_data_claims) = + parse_init_data(verification_request.init_data).context("parse init data")?; let init_data_hash = match &init_data { Some(data) => InitDataHash::Value(data), @@ -277,19 +309,19 @@ impl AttestationService { } } -/// Get the expected init/runtime data and potential claims due to the given input +/// Get the expected runtime data and potential claims due to the given input /// and the hash algorithm -fn parse_data( - data: Option, +fn parse_runtime_data( + data: Option, hash_algorithm: &HashAlgorithm, ) -> Result<(Option>, Value)> { match data { Some(value) => match value { - Data::Raw(raw) => Ok((Some(raw), Value::Null)), - Data::Structured(structured) => { + RuntimeData::Raw(raw) => Ok((Some(raw), Value::Null)), + RuntimeData::Structured(structured) => { // by default serde_json will enforence the alphabet order for keys let hash_materials = - serde_json::to_vec(&structured).context("parse JSON structured data")?; + serialize_canon_json(&structured).context("parse JSON structured data")?; let digest = hash_algorithm.accumulate_hash(hash_materials); Ok((Some(digest), structured)) } @@ -298,25 +330,44 @@ fn parse_data( } } +/// Get the expected init data and potential claims due to the given input +/// and the hash algorithm +fn parse_init_data(data: Option) -> Result<(Option>, Value)> { + match data { + Some(value) => match value { + InitDataInput::Digest(raw) => Ok((Some(raw), Value::Null)), + InitDataInput::Toml(structured) => { + let initdata = toml::from_str::(&structured) + .context("parse TOML structured data")?; + let digest = initdata.algorithm.accumulate_hash(structured.into_bytes()); + let claims = serde_json::to_value(initdata.data)?; + Ok((Some(digest), claims)) + } + }, + None => Ok((None, Value::Null)), + } +} + #[cfg(test)] mod tests { use assert_json_diff::assert_json_eq; use rstest::rstest; use serde_json::{json, Value}; - use crate::{Data, HashAlgorithm}; + use crate::{HashAlgorithm, RuntimeData}; #[rstest] - #[case(Some(Data::Raw(b"aaaaa".to_vec())), Some(b"aaaaa".to_vec()), HashAlgorithm::Sha384, Value::Null)] + #[case(Some(RuntimeData::Raw(b"aaaaa".to_vec())), Some(b"aaaaa".to_vec()), HashAlgorithm::Sha384, Value::Null)] #[case(None, None, HashAlgorithm::Sha384, Value::Null)] - #[case(Some(Data::Structured(json!({"b": 1, "a": "test", "c": {"d": "e"}}))), Some(hex::decode(b"e71ce8e70d814ba6639c3612ebee0ff1f76f650f8dbb5e47157e0f3f525cd22c4597480a186427c813ca941da78870c3").unwrap()), HashAlgorithm::Sha384, json!({"b": 1, "a": "test", "c": {"d": "e"}}))] - fn parse_data_json_binding( - #[case] input: Option, + #[case(Some(RuntimeData::Structured(json!({"b": 1, "a": "test", "c": {"d": "e"}}))), Some(hex::decode(b"e71ce8e70d814ba6639c3612ebee0ff1f76f650f8dbb5e47157e0f3f525cd22c4597480a186427c813ca941da78870c3").unwrap()), HashAlgorithm::Sha384, json!({"b": 1, "a": "test", "c": {"d": "e"}}))] + fn parse_runtimedata_json_binding( + #[case] input: Option, #[case] expected_data: Option>, #[case] hash_algorithm: HashAlgorithm, #[case] expected_claims: Value, ) { - let (data, data_claims) = crate::parse_data(input, &hash_algorithm).expect("parse failed"); + let (data, data_claims) = + crate::parse_runtime_data(input, &hash_algorithm).expect("parse failed"); assert_eq!(data, expected_data); assert_json_eq!(data_claims, expected_claims); } diff --git a/deps/verifier/Cargo.toml b/deps/verifier/Cargo.toml index e4ad9083cc..49ac5958ac 100644 --- a/deps/verifier/Cargo.toml +++ b/deps/verifier/Cargo.toml @@ -67,8 +67,8 @@ sha2.workspace = true tokio = { workspace = true, optional = true } intel-tee-quote-verification-rs = { git = "https://github.com/intel/SGXDataCenterAttestationPrimitives", tag = "DCAP_1.22", optional = true } strum.workspace = true -veraison-apiclient = { git = "https://github.com/veraison/rust-apiclient", branch = "trustee-cca", optional = true } -ccatoken = { git = "https://github.com/veraison/rust-ccatoken", branch = "trustee-cca", optional = true } +veraison-apiclient = { git = "https://github.com/veraison/rust-apiclient", rev = "fe149cd", optional = true } +ccatoken = { git = "https://github.com/veraison/rust-ccatoken", rev = "dfe9ca2", optional = true } ear = { version = "0.3.0", optional = true } x509-parser = { version = "0.17.0", optional = true } reqwest.workspace = true diff --git a/deps/verifier/src/cca/local.rs b/deps/verifier/src/cca/local.rs index 0401d6dd33..572e4d0feb 100644 --- a/deps/verifier/src/cca/local.rs +++ b/deps/verifier/src/cca/local.rs @@ -16,7 +16,7 @@ use ear::{Extensions, TrustTier}; use log::debug; use serde_json::json; use std::collections::BTreeMap; -use std::fs; +use std::{fs, io::Cursor}; pub fn verify(config: Config, token: &Vec, expected_report_data: &Vec) -> Result { debug!("using config: {:?}", config); @@ -40,7 +40,8 @@ pub fn verify(config: Config, token: &Vec, expected_report_data: &Vec) - rv_store.display() ))?; - let mut e: Evidence = Evidence::decode(token).context("decoding CCA evidence")?; + let cursor = Cursor::new(token); + let mut e: Evidence = Evidence::decode(cursor).context("decoding CCA evidence")?; e.verify(&tas).context("verifying CCA evidence")?; e.appraise(&rvs).context("appraising CCA evidence")?; diff --git a/deps/verifier/src/lib.rs b/deps/verifier/src/lib.rs index 17bf2d4f89..f6f0fab959 100644 --- a/deps/verifier/src/lib.rs +++ b/deps/verifier/src/lib.rs @@ -138,6 +138,8 @@ pub fn to_verifier(tee: &Tee) -> Result> { } } } + + Tee::Tpm => todo!(), } } diff --git a/deps/verifier/src/sample/mod.rs b/deps/verifier/src/sample/mod.rs index 50e701ee3f..5df38f112e 100644 --- a/deps/verifier/src/sample/mod.rs +++ b/deps/verifier/src/sample/mod.rs @@ -1,4 +1,4 @@ -use log::debug; +use log::{debug, warn}; extern crate serde; use self::serde::{Deserialize, Serialize}; use super::*; @@ -12,9 +12,6 @@ struct SampleTeeEvidence { #[serde(default = "String::default")] report_data: String, - - #[serde(default = "String::default")] - init_data: String, } #[derive(Debug, Default)] @@ -60,15 +57,8 @@ async fn verify_tee_evidence( } } - // Emulate the init data hash. - if let InitDataHash::Value(expected_init_data_hash) = expected_init_data_hash { - debug!("Check the binding of init_data_digest."); - let ev_init_data_hash = base64::engine::general_purpose::STANDARD - .decode(&evidence.init_data) - .context("base64 decode init data hash for sample evidence")?; - if *expected_init_data_hash != ev_init_data_hash { - bail!("INIT DATA HASH is different from that in Sample Quote"); - } + if let InitDataHash::Value(_) = expected_init_data_hash { + warn!("Sample does not support init data hash mechanism. skip."); } Ok(()) @@ -80,7 +70,6 @@ fn parse_tee_evidence(quote: &SampleTeeEvidence) -> Result, } /// Number of bytes in a nonce. @@ -298,28 +314,53 @@ impl AttestationService { // deserialize evidence let composite_evidence: CompositeEvidence = serde_json::from_value(attestation.tee_evidence) - .context("Failed to deserialize composite evidence.")?; + .context("failed to parse composite evidence")?; let mut evidence_to_verify: Vec = vec![]; - let additional_runtime_data = json!({"tee-pubkey": attestation.tee_pubkey, "nonce": nonce}); - let primary_runtime_data = json!({"tee-pubkey": attestation.tee_pubkey, "nonce": nonce, "additional-evidence": composite_evidence.additional_evidence}); + let runtime_data: RuntimeData = serde_json::from_value(attestation.runtime_data) + .context("parse kbs protocol runtime data")?; - // primary evidence - evidence_to_verify.push(IndependentEvidence { + if nonce != runtime_data.nonce { + bail!("the nonce in the handshake session is different from the client side in KBS protocol's Attestation message"); + } + + let kbs_evidence_runtime_data = json!({ + "tee-pubkey": runtime_data.tee_pubkey, + "nonce": nonce, + }); + + let primary_runtime_data = json!({ + "tee-pubkey": runtime_data.tee_pubkey, + "nonce": nonce, + "additional-evidence": composite_evidence.additional_evidence, + }); + + let mut primary_evidence = IndependentEvidence { tee, tee_evidence: composite_evidence.primary_evidence, runtime_data: primary_runtime_data, - }); + init_data: None, + }; + + if let Some(init_data) = attestation.init_data { + let initdata: InitData = + serde_json::from_value(init_data).context("parse initdata failed")?; + primary_evidence.init_data = Some(initdata); + } + + // primary evidence + evidence_to_verify.push(primary_evidence); // additional evidence if !composite_evidence.additional_evidence.is_empty() { let additional_evidence: HashMap = serde_json::from_str(&composite_evidence.additional_evidence)?; - for (tee, evidence) in additional_evidence.iter() { + for (tee, tee_evidence) in additional_evidence { evidence_to_verify.push(IndependentEvidence { - tee: *tee, - tee_evidence: evidence.clone(), - runtime_data: additional_runtime_data.clone(), + tee, + tee_evidence, + runtime_data: kbs_evidence_runtime_data.clone(), + init_data: None, }); } } diff --git a/kbs/src/attestation/coco/builtin.rs b/kbs/src/attestation/coco/builtin.rs index 0878163400..532b423d0a 100644 --- a/kbs/src/attestation/coco/builtin.rs +++ b/kbs/src/attestation/coco/builtin.rs @@ -5,7 +5,8 @@ use anyhow::*; use async_trait::async_trait; use attestation_service::{ - config::Config as AsConfig, AttestationService, Data, HashAlgorithm, VerificationRequest, + config::Config as AsConfig, AttestationService, HashAlgorithm, InitDataInput, RuntimeData, + VerificationRequest, }; use kbs_types::{Challenge, Tee}; use std::collections::HashMap; @@ -31,14 +32,21 @@ impl Attest for BuiltInCoCoAs { let mut verification_requests = vec![]; for evidence in evidence_to_verify { - verification_requests.push(VerificationRequest { + let mut request = VerificationRequest { evidence: evidence.tee_evidence, tee: evidence.tee, - runtime_data: Some(Data::Structured(evidence.runtime_data)), + runtime_data: Some(RuntimeData::Structured(evidence.runtime_data)), runtime_data_hash_algorithm: HashAlgorithm::Sha384, init_data: None, - init_data_hash_algorithm: HashAlgorithm::Sha384, - }); + }; + if let Some(init_data) = evidence.init_data { + if init_data.format != "toml" { + bail!("Unsupported initdata format: {}", init_data.format); + } + request.init_data = Some(InitDataInput::Toml(init_data.body)); + } + + verification_requests.push(request); } let policy_ids = vec!["default".to_string()]; diff --git a/kbs/src/attestation/coco/grpc.rs b/kbs/src/attestation/coco/grpc.rs index 02e2f6ce34..d24e90420b 100644 --- a/kbs/src/attestation/coco/grpc.rs +++ b/kbs/src/attestation/coco/grpc.rs @@ -20,8 +20,8 @@ use crate::attestation::backend::{make_nonce, Attest, IndependentEvidence}; use self::attestation::{ attestation_service_client::AttestationServiceClient, - individual_attestation_request::RuntimeData, AttestationRequest, ChallengeRequest, - IndividualAttestationRequest, SetPolicyRequest, + individual_attestation_request::{InitData, RuntimeData}, + AttestationRequest, ChallengeRequest, IndividualAttestationRequest, SetPolicyRequest, }; mod attestation { @@ -106,16 +106,23 @@ impl Attest for GrpcClientPool { .trim_start_matches('"') .to_string(); - verification_requests.push(IndividualAttestationRequest { + let mut request = IndividualAttestationRequest { tee, evidence: URL_SAFE_NO_PAD.encode(evidence.tee_evidence.to_string()), runtime_data_hash_algorithm: COCO_AS_HASH_ALGORITHM.into(), - init_data_hash_algorithm: COCO_AS_HASH_ALGORITHM.into(), runtime_data: Some(RuntimeData::StructuredRuntimeData( evidence.runtime_data.to_string(), )), init_data: None, - }); + }; + + if let Some(init_data) = evidence.init_data { + if init_data.format != "toml" { + bail!("Unsupported initdata format: {}", init_data.format); + } + request.init_data = Some(InitData::InitDataToml(init_data.body)); + } + verification_requests.push(request); } let attestation_request = tonic::Request::new(AttestationRequest { diff --git a/protos/attestation.proto b/protos/attestation.proto index 1f2bbfa987..28a15b2f99 100644 --- a/protos/attestation.proto +++ b/protos/attestation.proto @@ -47,32 +47,29 @@ message IndividualAttestationRequest { // Init Data used to check the binding relationship with init data in // Evidence oneof init_data { - // Base64 encoded init data slice. The whole string will be base64 + // Base64 encoded init data digest slice. The whole string will be base64 // decoded. The result one will then be accumulated into a digest which // is used as the expected init data to check against the one inside // evidence. // // The alphabet is URL_SAFE_NO_PAD. // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5 - string raw_init_data = 6; + string init_data_digest = 6; - // Init data in a JSON map. CoCoAS will rearrange each layer of the - // data JSON object in dictionary order by key, then serialize and output - // it into a compact string, and perform hash calculation on the whole - // to check against the one inside evidence. + // Init data TOML. CoCoAS will perform hash calculation on the whole + // to check against the one inside evidence. The hash algorithm to use + // is inside the Initdata Toml's metadata. // - // After the verification, the structured init data field will be included - // inside the token claims. - string structured_init_data = 7; + // See https://github.com/confidential-containers/trustee/blob/main/kbs/docs/initdata.md#toml-version + // + // After the verification, the `.data` field of init data field will + // be included inside the token claims. + string init_data_toml = 7; } // Hash algorithm used to calculate runtime data. Currently can be "sha256", // "sha384" or "sha512". If not specified, "sha384" will be selected. string runtime_data_hash_algorithm = 8; - - // Hash algorithm used to calculate init data. Currently can be "sha256", - // "sha384" or "sha512". If not specified, "sha384" will be selected. - string init_data_hash_algorithm = 9; } message AttestationResponse {