diff --git a/Cargo.lock b/Cargo.lock index ce84e44e5..f7e1901e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,9 +605,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -1138,9 +1138,9 @@ checksum = "12170080f3533d6f09a19f81596f836854d0fa4867dc32c8172b8474b4e9de61" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "config" @@ -2677,7 +2677,7 @@ dependencies = [ "prost 0.12.6", "rand", "regorus", - "reqwest 0.12.8", + "reqwest 0.12.9", "rsa 0.9.6", "rstest", "scc", @@ -2705,7 +2705,7 @@ dependencies = [ "jwt-simple 0.11.9", "kbs_protocol", "log", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde_json", "tokio", @@ -2734,7 +2734,7 @@ dependencies = [ "jwt-simple 0.12.9", "kbs-types", "log", - "reqwest 0.12.8", + "reqwest 0.12.9", "resource_uri", "serde", "serde_json", @@ -2762,7 +2762,7 @@ dependencies = [ "p12", "prost 0.11.9", "rand", - "reqwest 0.12.8", + "reqwest 0.12.9", "resource_uri", "ring", "serde", @@ -2863,9 +2863,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "local-channel" @@ -3071,9 +3071,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" dependencies = [ "num-bigint", "num-complex", @@ -3085,10 +3085,11 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ + "autocfg", "num-integer", "num-traits", ] @@ -3112,9 +3113,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" dependencies = [ "num-traits", ] @@ -3147,9 +3148,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -3158,10 +3159,11 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ + "autocfg", "num-bigint", "num-integer", "num-traits", @@ -3169,9 +3171,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.19" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -3278,9 +3280,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -4172,9 +4174,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -5879,6 +5881,7 @@ dependencies = [ "kbs-types", "log", "openssl", + "reqwest 0.12.9", "rstest", "s390_pv", "scroll 0.11.0", diff --git a/deps/verifier/Cargo.toml b/deps/verifier/Cargo.toml index e65188a51..bbb236b6a 100644 --- a/deps/verifier/Cargo.toml +++ b/deps/verifier/Cargo.toml @@ -49,6 +49,7 @@ strum.workspace = true veraison-apiclient = { git = "https://github.com/chendave/rust-apiclient", branch = "token", optional = true } ear = { git = "https://github.com/veraison/rust-ear", rev = "43f7f480d09ea2ebc03137af8fbcd70fe3df3468", optional = true } x509-parser = { version = "0.14.0", optional = true } +reqwest.workspace = true [build-dependencies] shadow-rs.workspace = true diff --git a/deps/verifier/src/snp/mod.rs b/deps/verifier/src/snp/mod.rs index b5e4276b7..7f20c5be2 100644 --- a/deps/verifier/src/snp/mod.rs +++ b/deps/verifier/src/snp/mod.rs @@ -14,6 +14,7 @@ use openssl::{ sha::sha384, x509::{self, X509}, }; +use reqwest::{get, Response as ReqwestResponse, StatusCode}; use serde_json::json; use sev::firmware::guest::AttestationReport; use sev::firmware::host::{CertTableEntry, CertType}; @@ -32,11 +33,18 @@ const SNP_SPL_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .3); const TEE_SPL_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .2); const LOADER_SPL_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .1); +// KDS URL parameters +const KDS_CERT_SITE: &str = "https://kdsintf.amd.com"; +const KDS_VCEK: &str = "/vcek/v1"; + #[derive(Debug)] pub struct Snp { vendor_certs: VendorCertificates, } +/// Loads the Milan certificate chain and returns a static reference to it. +/// The chain is loaded lazily using `OnceLock` to ensure it's only initialized once. +/// Certificates are loaded from a PEM file and must contain exactly three certificates (ASK, ARK, ASVK). pub(crate) fn load_milan_cert_chain() -> &'static Result { static MILAN_CERT_CHAIN: OnceLock> = OnceLock::new(); MILAN_CERT_CHAIN.get_or_init(|| { @@ -55,6 +63,8 @@ pub(crate) fn load_milan_cert_chain() -> &'static Result { } impl Snp { + /// Creates a new `Snp` instance by loading the Milan certificate chain. + /// Returns an error if the certificate chain can not be loaded. pub fn new() -> Result { let Result::Ok(vendor_certs) = load_milan_cert_chain() else { bail!("Failed to load Milan cert chain"); @@ -73,6 +83,9 @@ pub(crate) struct VendorCertificates { #[async_trait] impl Verifier for Snp { + /// Evaluates the provided evidence against the expected report data and initialize data hash. + /// Validates the report signature, version, VMPL, and other fields. + /// Returns parsed claims if the verification is successful. async fn evaluate( &self, evidence: &[u8], @@ -84,8 +97,9 @@ impl Verifier for Snp { cert_chain, } = serde_json::from_slice(evidence).context("Deserialize Quote failed.")?; - let Some(cert_chain) = cert_chain else { - bail!("Cert chain is unset"); + let cert_chain = match cert_chain { + Some(chain) if !chain.is_empty() => chain, + _ => fetch_vcek_from_kds(report).await?, }; verify_report_signature(&report, &cert_chain, &self.vendor_certs)?; @@ -128,6 +142,8 @@ impl Verifier for Snp { } } +/// Retrieves the octet string value for a given OID from a certificate's extensions. +/// Supports both raw and DER-encoded formats. fn get_oid_octets( vcek: &x509_parser::certificate::TbsCertificate, oid: Oid, @@ -152,6 +168,7 @@ fn get_oid_octets( .context("Unexpected data size") } +/// Retrieves an integer value for a given OID from a certificate's extensions. fn get_oid_int(cert: &x509_parser::certificate::TbsCertificate, oid: Oid) -> Result { let val = cert .get_extension_unique(&oid)? @@ -162,6 +179,7 @@ fn get_oid_int(cert: &x509_parser::certificate::TbsCertificate, oid: Oid) -> Res val_int.as_u8().context("Unexpected data size") } +/// Verifies the signature of the attestation report using the provided certificate chain and vendor certificates. pub(crate) fn verify_report_signature( report: &AttestationReport, cert_chain: &[CertTableEntry], @@ -223,12 +241,15 @@ pub(crate) fn verify_report_signature( Ok(()) } +/// Verifies the signature of a certificate against its issuer's public key. fn verify_signature(cert: &X509, issuer: &X509, name: &str) -> Result<()> { cert.verify(&(issuer.public_key()? as PKey))? .then_some(()) .ok_or_else(|| anyhow!("Invalid {name} signature")) } +/// Verifies the certificate chain based on the provided VCEK or VLEK. +/// Ensures the chain is valid by verifying signatures and relationships between certificates. fn verify_cert_chain( cert_chain: &[CertTableEntry], ask: &X509, @@ -267,6 +288,8 @@ fn verify_cert_chain( Ok(decoded_key) } +/// Parses the attestation report and extracts the TEE evidence claims. +/// Returns a JSON-formatted map of parsed claims. pub(crate) fn parse_tee_evidence(report: &AttestationReport) -> TeeEvidenceParsedClaim { let claims_map = json!({ // policy fields @@ -294,6 +317,7 @@ pub(crate) fn parse_tee_evidence(report: &AttestationReport) -> TeeEvidenceParse claims_map as TeeEvidenceParsedClaim } +/// Extracts the common name (CN) from the subject name of a certificate. fn get_common_name(cert: &x509::X509) -> Result { let mut entries = cert.subject_name().entries_by_nid(Nid::COMMONNAME); let Some(e) = entries.next() else { @@ -307,6 +331,42 @@ fn get_common_name(cert: &x509::X509) -> Result { Ok(e.data().as_utf8()?.to_string()) } +/// Asynchronously fetches the VCEK from the Key Distribution Service (KDS) using the provided attestation report. +/// Returns the VCEK in DER format as part of a certificate table entry. +async fn fetch_vcek_from_kds(att_report: AttestationReport) -> Result> { + // Use attestation report to get data for URL + let hw_id: String = hex::encode(att_report.chip_id); + + let vcek_url: String = format!( + "{KDS_CERT_SITE}{KDS_VCEK}/Milan/\ + {hw_id}?blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}", + att_report.reported_tcb.bootloader, + att_report.reported_tcb.tee, + att_report.reported_tcb.snp, + att_report.reported_tcb.microcode + ); + // VCEK in DER format + let vcek_rsp: ReqwestResponse = get(vcek_url) + .await + .context("Unable to send request for VCEK")?; + + match vcek_rsp.status() { + StatusCode::OK => { + let vcek_rsp_bytes: Vec = vcek_rsp + .bytes() + .await + .context("Unable to parse VCEK")? + .to_vec(); + let key = CertTableEntry { + cert_type: CertType::VCEK, + data: vcek_rsp_bytes, + }; + Ok(vec![key]) + } + status => Err(anyhow!("Unable to fetch VCEK from URL: {status:?}")), + } +} + #[cfg(test)] mod tests { use super::*;