diff --git a/README.md b/README.md index 2d1e01b..b0721c3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,9 @@ Add the following dependency to your `Cargo.toml` file to use this crate: dcap-qvl = "0.1.0" ``` -# Example: Get Collateral from PCCS_URL and Verify Quote +# Examples + +## Get Collateral from PCCS_URL and Verify Quote To get collateral from a PCCS_URL and verify a quote, you can use the following example code: ```rust @@ -36,6 +38,22 @@ async fn main() { } ``` +## Get Collateral from Intel PCS and Verify Quote + +```rust +use dcap_qvl::collateral::get_collateral_from_pcs; +use dcap_qvl::verify::verify; + +#[tokio::main] +async fn main() { + let quote = std::fs::read("tdx_quote").expect("tdx_quote is not found"); + let collateral = get_collateral_from_pcs("e, std::time::Duration::from_secs(10)).await.expect("failed to get collateral"); + let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); + let tcb = verify("e, &collateral, now).expect("failed to verify quote"); + println!("{:?}", tcb); +} +``` + # License diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b8d3667..b0b99a3 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -13,3 +13,4 @@ clap = { version = "4.5.20", features = ["derive"] } dcap-qvl = { path = "../", features = ["hex-bytes"] } hex = "0.4.3" serde_json = "1.0.132" +tokio = { version = "1.41.0", features = ["full"] } diff --git a/cli/src/main.rs b/cli/src/main.rs index 3b35aaa..a798759 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -6,7 +6,9 @@ use std::path::PathBuf; use anyhow::{Context as _, Result}; use clap::{Args, Parser, Subcommand}; +use dcap_qvl::collateral::get_collateral_from_pcs; use dcap_qvl::quote::Quote; +use dcap_qvl::verify::verify; #[derive(Parser)] struct Cli { @@ -17,7 +19,9 @@ struct Cli { #[derive(Subcommand)] enum Commands { /// Decode a quote file - DecodeQuote(DecodeQuoteArgs), + Decode(DecodeQuoteArgs), + /// Verify a quote file + Verify(VerifyQuoteArgs), } #[derive(Args)] @@ -29,28 +33,55 @@ struct DecodeQuoteArgs { quote_file: PathBuf, } -fn decode_quote(args: DecodeQuoteArgs) -> Result { - let quote = std::fs::read(args.quote_file).context("Failed to read quote file")?; - let quote = if args.hex { - let quote = quote.strip_prefix(b"0x").unwrap_or("e); - hex::decode(quote).context("Failed to decode quote file")? +#[derive(Args)] +struct VerifyQuoteArgs { + /// Indicate the quote file is in hex format + #[arg(long)] + hex: bool, + /// The quote file + quote_file: PathBuf, +} + +fn hex_decode(input: &[u8], is_hex: bool) -> Result> { + if is_hex { + let input = input.strip_prefix(b"0x").unwrap_or(input); + hex::decode(input).context("Failed to decode quote file") } else { - quote - }; - let quote = Quote::parse("e).context("Failed to parse quote")?; - Ok(quote) + Ok(input.to_vec()) + } } fn command_decode_quote(args: DecodeQuoteArgs) -> Result<()> { - let quote = decode_quote(args).context("Failed to decode quote")?; - let json = serde_json::to_string("e).context("Failed to serialize quote")?; + let quote = std::fs::read(args.quote_file).context("Failed to read quote file")?; + let quote = hex_decode("e, args.hex)?; + let decoded_quote = Quote::parse("e).context("Failed to parse quote")?; + let json = serde_json::to_string(&decoded_quote).context("Failed to serialize quote")?; println!("{}", json); Ok(()) } -fn main() -> Result<()> { +async fn command_verify_quote(args: VerifyQuoteArgs) -> Result<()> { + let quote = std::fs::read(args.quote_file).context("Failed to read quote file")?; + let quote = hex_decode("e, args.hex)?; + println!("Getting collateral..."); + let collateral = get_collateral_from_pcs("e, std::time::Duration::from_secs(60)).await?; + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH)? + .as_secs(); + verify("e, &collateral, now) + .ok() + .context("Failed to verify quote")?; + eprintln!("Quote verified"); + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { let cli = Cli::parse(); match cli.command { - Commands::DecodeQuote(args) => command_decode_quote(args).context("Failed to decode quote"), + Commands::Decode(args) => command_decode_quote(args).context("Failed to decode quote"), + Commands::Verify(args) => command_verify_quote(args) + .await + .context("Failed to verify quote"), } } diff --git a/src/collateral.rs b/src/collateral.rs index 5e78d99..b5d4b4f 100644 --- a/src/collateral.rs +++ b/src/collateral.rs @@ -50,7 +50,6 @@ pub async fn get_collateral( .get(format!("{base_url}/pckcrl?ca=processor")) .send() .await?; - println!("{:#?}", response); pck_crl_issuer_chain = get_header(&response, "SGX-PCK-CRL-Issuer-Chain")?; pck_crl = response.text().await?; }; @@ -116,3 +115,19 @@ pub async fn get_collateral( qe_identity_signature, }) } + +/// Get collateral given DCAP quote from Intel PCS. +/// +/// # Arguments +/// +/// * `quote` - The raw quote to verify. Supported SGX and TDX quotes. +/// * `timeout` - The timeout for the request. (e.g. `Duration::from_secs(10)`) +/// +/// # Returns +/// +/// * `Ok(QuoteCollateralV3)` - The quote collateral +/// * `Err(Error)` - The error +pub async fn get_collateral_from_pcs(quote: &[u8], timeout: Duration) -> Result { + const PCS_URL: &str = "https://api.trustedservices.intel.com/sgx/certification/v4"; + get_collateral(PCS_URL, quote, timeout).await +} diff --git a/src/quote.rs b/src/quote.rs index d3b3c54..e161a6c 100644 --- a/src/quote.rs +++ b/src/quote.rs @@ -280,6 +280,12 @@ pub enum Report { TD15(TDReport15), } +impl Report { + pub fn is_sgx(&self) -> bool { + matches!(self, Report::SgxEnclave(_)) + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct Quote { pub header: Header,