From 3bae6ca265fc7f5d832af98a1be58c8845926c88 Mon Sep 17 00:00:00 2001 From: Richard Zak Date: Tue, 8 Nov 2022 09:58:04 -0500 Subject: [PATCH] feat: configuration for attestation Signed-off-by: Richard Zak --- Cargo.lock | 25 +++++++++ crates/sgx_validation/Cargo.toml | 3 ++ crates/sgx_validation/src/config.rs | 74 +++++++++++++++++++++++++ crates/sgx_validation/src/lib.rs | 5 +- crates/sgx_validation/src/main.rs | 46 ++++++++++++++++ crates/snp_validation/Cargo.toml | 2 + crates/snp_validation/src/config.rs | 84 +++++++++++++++++++++++++++++ crates/snp_validation/src/lib.rs | 11 ++-- crates/snp_validation/src/main.rs | 35 ++++++++++++ 9 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 crates/sgx_validation/src/config.rs create mode 100644 crates/sgx_validation/src/main.rs create mode 100644 crates/snp_validation/src/config.rs create mode 100644 crates/snp_validation/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index b6f9604c..95835215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,6 +447,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -986,6 +992,20 @@ name = "serde" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" @@ -1016,8 +1036,11 @@ dependencies = [ "anyhow", "cryptography", "der", + "hex", + "serde", "sgx", "testaso", + "toml", ] [[package]] @@ -1075,7 +1098,9 @@ dependencies = [ "cryptography", "der", "flagset", + "serde", "testaso", + "toml", ] [[package]] diff --git a/crates/sgx_validation/Cargo.toml b/crates/sgx_validation/Cargo.toml index 9291343d..41bf0e00 100644 --- a/crates/sgx_validation/Cargo.toml +++ b/crates/sgx_validation/Cargo.toml @@ -9,7 +9,10 @@ description = "Intel SGX Attestation validation library for Steward" cryptography = { path = "../cryptography" } anyhow = { version = "^1.0.55", default-features = false } der = { version = "0.6", features = ["std"], default-features = false } +hex = "0.4" +serde = { version = "1.0", features = ["derive", "std"] } sgx = { version = "0.5.0", default-features = false } [dev-dependencies] testaso = { version = "0.1", default-features = false } +toml = "0.5" diff --git a/crates/sgx_validation/src/config.rs b/crates/sgx_validation/src/config.rs new file mode 100644 index 00000000..bdbee900 --- /dev/null +++ b/crates/sgx_validation/src/config.rs @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2022 Profian Inc. +// SPDX-License-Identifier: AGPL-3.0-only + +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Clone, Deserialize, Debug, Default, Serialize)] +struct Config { + /// Values for `mrsigner` in the report body. + /// This is the list of public keys which have signed the `enarx` binary. + #[serde(default)] + #[serde(deserialize_with = "from_hex")] + enarx_signer: Option>>, + + /// Values allowed for `cpusvn`. + cpu_svn: Option>, + + /// Value for `isv_svn`, do not allow versions below this. + enclave_security_version: Option, +} + +fn from_hex<'de, D>(deserializer: D) -> Result>>, D::Error> +where + D: Deserializer<'de>, +{ + let s: Vec<&str> = match Deserialize::deserialize(deserializer) { + Ok(x) => x, + Err(_) => return Ok(None), + }; + + let mut outer_vec = Vec::new(); + for hash_string in s { + outer_vec.push(hex::decode(hash_string).map_err(|_| Error::custom("invalid hex"))?); + } + + Ok(Some(outer_vec)) +} + +#[cfg(test)] +mod tests { + use crate::config::Config; + + #[test] + fn test_empty_config() { + let config_raw = r#" + something_else = 1 + "#; + + let config_obj: Config = toml::from_str(config_raw).expect("Couldn't deserialize"); + assert!(config_obj.enarx_signer.is_none()); + assert!(config_obj.enclave_security_version.is_none()); + assert!(config_obj.cpu_svn.is_none()); + } + + #[test] + fn test_list_of_hashes() { + let config_raw = r#" + enarx_signer = ["1234567890", "00112233445566778899"] + "#; + + let config_obj: Config = toml::from_str(config_raw).expect("Couldn't deserialize"); + assert!(config_obj.enarx_signer.is_some()); + assert_eq!(config_obj.enarx_signer.clone().unwrap().len(), 2); + assert_eq!( + config_obj.enarx_signer.clone().unwrap().first().unwrap(), + &hex::decode("1234567890").unwrap() + ); + assert_eq!( + config_obj.enarx_signer.unwrap().get(1).unwrap(), + &hex::decode("00112233445566778899").unwrap() + ); + assert!(config_obj.cpu_svn.is_none()); + } +} diff --git a/crates/sgx_validation/src/lib.rs b/crates/sgx_validation/src/lib.rs index 20e5952f..2f8c7c79 100644 --- a/crates/sgx_validation/src/lib.rs +++ b/crates/sgx_validation/src/lib.rs @@ -1,7 +1,8 @@ // SPDX-FileCopyrightText: 2022 Profian Inc. // SPDX-License-Identifier: AGPL-3.0-only -mod quote; +pub mod config; +pub mod quote; use cryptography::ext::*; use quote::traits::ParseBytes; @@ -29,7 +30,7 @@ impl Sgx { pub const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.6.1.4.1.58270.1.2"); pub const ATT: bool = true; - fn trusted<'c>(&'c self, chain: &'c [Certificate<'c>]) -> Result<&'c TbsCertificate<'c>> { + pub fn trusted<'c>(&'c self, chain: &'c [Certificate<'c>]) -> Result<&'c TbsCertificate<'c>> { let mut signer = &self.0[0].tbs_certificate; for cert in self.0.iter().chain(chain.iter()) { signer = signer.verify_crt(cert)?; diff --git a/crates/sgx_validation/src/main.rs b/crates/sgx_validation/src/main.rs new file mode 100644 index 00000000..2be6f83c --- /dev/null +++ b/crates/sgx_validation/src/main.rs @@ -0,0 +1,46 @@ +use cryptography; +use cryptography::x509::attr::Attribute; +use cryptography::x509::request::{CertReq, ExtensionReq}; +use cryptography::x509::Certificate; +use der::Decode; +use sgx_validation::quote::traits::ParseBytes; +use sgx_validation::quote::Quote; +use sgx_validation::Sgx; +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::Path; + +fn main() { + let args: Vec = env::args().collect(); + let fname = Path::new(args.get(1).expect("CSR file not specified")); + let mut file = File::open(fname).expect("no such file"); + let mut contents = Vec::new(); + file.read_to_end(&mut contents) + .expect("failed to read file"); + + let cr = CertReq::from_der(&contents).expect("failed to decode DER"); + let cri = cr.info; + #[allow(unused_variables)] + for Attribute { oid, values } in cri.attributes.iter() { + for any in values.iter() { + let ereq: ExtensionReq<'_> = any.decode_into().unwrap(); + for ext in Vec::from(ereq) { + let (quote, bytes): (Quote<'_>, _) = + ext.extn_value.parse().expect("failed to parse"); + let chain = quote.chain().unwrap(); + let chain = chain + .iter() + .map(|c| Certificate::from_der(c)) + .collect::, _>>() + .unwrap(); + + // Validate the report. + let sgx = Sgx::default(); + let pck = sgx.trusted(&chain).unwrap(); + let report = quote.verify(pck).unwrap(); + println!("{:?}", report); + } + } + } +} diff --git a/crates/snp_validation/Cargo.toml b/crates/snp_validation/Cargo.toml index a33087b4..ddcce37a 100644 --- a/crates/snp_validation/Cargo.toml +++ b/crates/snp_validation/Cargo.toml @@ -10,6 +10,8 @@ cryptography = { path = "../cryptography" } anyhow = { version = "^1.0.55", default-features = false } der = { version = "0.6", features = ["std"], default-features = false } flagset = { version = "0.4.3", default-features = false} +serde = { version = "1.0", features = ["derive"] } [dev-dependencies] testaso = { version = "0.1", default-features = false } +toml = "0.5" diff --git a/crates/snp_validation/src/config.rs b/crates/snp_validation/src/config.rs new file mode 100644 index 00000000..7d72945e --- /dev/null +++ b/crates/snp_validation/src/config.rs @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2022 Profian Inc. +// SPDX-License-Identifier: AGPL-3.0-only + +use crate::{PlatformInfoFlags, PolicyFlags}; +use flagset::FlagSet; +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Clone, Deserialize, Debug, Default, Serialize)] +struct Config { + #[serde(default)] + #[serde(deserialize_with = "from_policy_string")] + policy_flags: Option, + platform_info_flags: Option>, +} + +fn from_policy_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: &str = match Deserialize::deserialize(deserializer) { + Ok(x) => x, + Err(_) => return Ok(None), + }; + + let mut flags = FlagSet::::from(PolicyFlags::Reserved); + + eprintln!("String: {}", s); + + for flag in s.to_string().split("|") { + match flag.trim() { + "Debug" => { + flags = flags | PolicyFlags::Debug; + } + "MigrateMA" => { + flags = flags | PolicyFlags::MigrateMA; + } + "SingleSocket" => { + flags = flags | PolicyFlags::SingleSocket; + } + "SMT" => { + flags = flags | PolicyFlags::SMT; + } + _ => return Err(D::Error::custom(format!("unknown flag '{}'", flag))), + } + } + + Ok(Some(flags.bits())) +} + +#[cfg(test)] +mod tests { + use crate::config::Config; + use crate::PolicyFlags; + use flagset::FlagSet; + + #[test] + fn test_empty_config() { + let config_raw = r#" + something_else = 1 + "#; + + let config_obj: Config = toml::from_str(config_raw).expect("Couldn't deserialize"); + assert!(config_obj.policy_flags.is_none()); + assert!(config_obj.platform_info_flags.is_none()); + } + + #[test] + fn test_flags() { + let config_raw = r#" + policy_flags = "SingleSocket | Debug" + "#; + + let config_obj: Config = toml::from_str(config_raw).expect("Couldn't deserialize"); + assert!(config_obj.policy_flags.is_some()); + assert!(config_obj.platform_info_flags.is_none()); + + let flags = FlagSet::::new(config_obj.policy_flags.unwrap()).unwrap(); + assert_eq!( + flags, + PolicyFlags::SingleSocket | PolicyFlags::Debug | PolicyFlags::Reserved + ); + } +} diff --git a/crates/snp_validation/src/lib.rs b/crates/snp_validation/src/lib.rs index e7c046bb..649ae243 100644 --- a/crates/snp_validation/src/lib.rs +++ b/crates/snp_validation/src/lib.rs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Profian Inc. // SPDX-License-Identifier: AGPL-3.0-only +pub mod config; + use cryptography::ext::*; use std::{fmt::Debug, mem::size_of}; @@ -16,6 +18,7 @@ use cryptography::x509::{PkiPath, TbsCertificate}; use der::asn1::UIntRef; use der::{Decode, Encode, Sequence}; use flagset::{flags, FlagSet}; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Sequence)] pub struct Evidence<'a> { @@ -26,6 +29,7 @@ pub struct Evidence<'a> { } flags! { + #[derive(Deserialize, Serialize)] pub enum PolicyFlags: u8 { /// Indicates if only one socket is permitted SingleSocket = 1 << 4, @@ -39,6 +43,7 @@ flags! { SMT = 1 << 0, } + #[derive(Deserialize, Serialize)] pub enum PlatformInfoFlags: u8 { TSME = 1 << 1, SMT = 1 << 0, @@ -69,7 +74,7 @@ pub struct PlatformInfo { #[repr(C, packed)] #[derive(Copy, Clone, Debug)] -struct Body { +pub struct Body { /// The version of the attestation report, currently 2 pub version: u32, /// Guest Security Version Number (SVN) @@ -178,7 +183,7 @@ impl Es384 { #[repr(C, packed)] #[derive(Copy, Clone)] -union Signature { +pub union Signature { bytes: [u8; 512], es384: Es384, } @@ -186,7 +191,7 @@ union Signature { /// The attestation report from the trusted environment on an AMD system #[repr(C, packed)] #[derive(Copy, Clone)] -struct Report { +pub struct Report { pub body: Body, pub signature: Signature, } diff --git a/crates/snp_validation/src/main.rs b/crates/snp_validation/src/main.rs new file mode 100644 index 00000000..ebc2c520 --- /dev/null +++ b/crates/snp_validation/src/main.rs @@ -0,0 +1,35 @@ +use cryptography; +use cryptography::x509::attr::Attribute; +use cryptography::x509::request::{CertReq, ExtensionReq}; +use der::Decode; +use snp_validation; +use snp_validation::Evidence; +use snp_validation::Report; +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::Path; + +fn main() { + let args: Vec = env::args().collect(); + let fname = Path::new(args.get(1).expect("CSR file not specified")); + let mut file = File::open(fname).expect("no such file"); + let mut contents = Vec::new(); + file.read_to_end(&mut contents) + .expect("failed to read file"); + + let cr = CertReq::from_der(&contents).expect("failed to decode DER"); + let cri = cr.info; + #[allow(unused_variables)] + for Attribute { oid, values } in cri.attributes.iter() { + for any in values.iter() { + let ereq: ExtensionReq<'_> = any.decode_into().unwrap(); + for ext in Vec::from(ereq) { + let evidence = Evidence::from_der(ext.extn_value).unwrap(); + let array = evidence.report.try_into().unwrap(); + let report = Report::cast(array); + println!("{:?}", report); + } + } + } +}