diff --git a/.gitignore b/.gitignore index 06aba01..1e7caa9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ Cargo.lock -/target +target/ diff --git a/Cargo.toml b/Cargo.toml index afdd551..a81871d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,3 +82,4 @@ std = [ ] report = ["std", "tracing", "futures"] js = ["ring/wasm32_unknown_unknown_js", "getrandom", "serde-wasm-bindgen", "wasm-bindgen"] +hex-bytes = [] diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..b8d3667 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "dcap-qvl-cli" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "dcap-qvl" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.91" +clap = { version = "4.5.20", features = ["derive"] } +dcap-qvl = { path = "../", features = ["hex-bytes"] } +hex = "0.4.3" +serde_json = "1.0.132" diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..8b9e197 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,11 @@ +# dcap-qvl-cli + +A CLI tool to decode TDX/SGX quote files. + +## Usage + +``` +git clone https://github.com/Phala-Network/dcap-qvl.git +cd dcap-qvl/cli +cargo run -- decode-quote --hex ../sample/tdx-quote.hex | jq . +``` diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..842e510 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,55 @@ +//! CLI for dcap-qvl +//! Usage: +//! dcap-qvl decode-quote [--hex] +//! dcap-qvl verify-quote + +use std::path::PathBuf; + +use anyhow::{Context as _, Result}; +use clap::{Args, Parser, Subcommand}; +use dcap_qvl::quote::Quote; + +#[derive(Parser)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Decode a quote file + DecodeQuote(DecodeQuoteArgs), +} + +#[derive(Args)] +struct DecodeQuoteArgs { + #[arg(long)] + hex: bool, + 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")? + } else { + quote + }; + let quote = Quote::parse("e).context("Failed to parse quote")?; + Ok(quote) +} + +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")?; + println!("{}", json); + Ok(()) +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + match cli.command { + Commands::DecodeQuote(args) => command_decode_quote(args).context("Failed to decode quote"), + } +} diff --git a/sample/tdx-quote.hex b/sample/tdx-quote.hex new file mode 100644 index 0000000..bed1435 --- /dev/null +++ b/sample/tdx-quote.hex @@ -0,0 +1 @@ +0x040002008100000000000000939a7233f79c4ca9940a0db3957f060783fbfe61525f55581315cd9dc950f44700000000050102000000000000000000000000001cc6a17ab799e9a693fac7536be61c12ee1e0fabada82d0c999e08ccee2aa86de77b0870f558c570e7ffe55d6d47fa0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000e7020600000000007ba9e262ce6979087e34632603f354dd8f8a870f5947d116af8114db6c9d0d74c48bec4280e5b4f4a37025a10905bb290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004574c098915caf3e82057817dbd135c1ed0ee1b39ac300c921479e2f5ebf5726a13ee0c8745ac891b6aee7c4f9664610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000547fcba4630bfb981169a8a1903b79c244933413409dd0387acbd8e3b985bcc9164cf52735cd31f60bf2c5d1220c113f7148f47ef58b475fce69b386e2d6b4c964a9533cc328ea8e544db66612a5174698d006951cefa8fd4450e884300638e567e22f9a012ef5754aa6a9d9564fcd8acc100000cdef13e9c54d734e810d8fc23df4a68ae65bf687754b021710cb25b7354f4a3ee34aa7e5b2aab30d0e85193c8c68b71118c0ac5e14e1d0368d41046609f38c024996a9e56e40ac6c0b019709537f16d751c03e8c0d905d79f224ff06ddc4102860a8770107748c011cdbfcccc857e418735b699ac89dc2ed4da11d5125cb925e0600461000000202191b03ff0006000000000000000000000000000000000000000000000000000000000000000000000000000000001500000000000000e700000000000000e5a3a7b5d830c2953b98534c6c59a3a34fdc34e933f7f5898f0a85cf08846bca0000000000000000000000000000000000000000000000000000000000000000dc9e2a7c6f948f17474e34a7fc43ed030f7c1563f1babddf6340c82e0e54a8c500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000503bbfe5befa55a13e21747c3859f0b618a050312a0340e980187eea232356d60000000000000000000000000000000000000000000000000000000000000000791fc77d0260080a376494a35aa3a3dc64ff532d7642ec07e3bfd2da320f9180f2d4b27ecf6c2057f25326d7c0cf43d73504a3f9dbc7de8855bffb65ec52bf742000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f05005e0e00002d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d49494538444343424a65674177494241674956414c5235544954392b396e73423142545a3173725851346c627752424d416f4743437147534d343942414d430a4d484178496a416742674e5642414d4d47556c756447567349464e4857434251513073675547786864475a76636d306751304578476a415942674e5642416f4d0a45556c756447567349454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155450a4341774351304578437a414a42674e5642415954416c56544d423458445449304d4467774d6a45784d54557a4e316f5844544d784d4467774d6a45784d54557a0a4e316f77634445694d434147413155454177775a535735305a5777675530645949464244537942445a584a3061575a70593246305a5445614d426747413155450a43677752535735305a577767513239796347397959585270623234784644415342674e564241634d43314e68626e526849454e7359584a684d517377435159440a5651514944414a445154454c4d416b474131554542684d4356564d775754415442676371686b6a4f5051494242676771686b6a4f50514d4242774e43414154590a77777155344778504a6a596f6a4d4752686136327970346a425164355744764b776d54366c6c314147786a59363870694a50676950686462387a544766374b620a314f79643153464f4d5a70594c795054427a59646f3449444444434341776777487759445652306a42426777466f41556c5739647a62306234656c4153636e550a3944504f4156634c336c5177617759445652306642475177596a42676f46366758495a616148523063484d364c79396863476b7564484a316333526c5a484e6c0a636e5a705932567a4c6d6c75644756734c6d4e766253397a5a3367765932567964476c6d61574e6864476c76626939324e4339775932746a636d772f593245390a6347786864475a76636d306d5a57356a62325270626d63395a4756794d423047413155644467515742425146303476507654474b7762416c356f54765664664d0a2b356a6e7554414f42674e56485138424166384542414d434273417744415944565230544151482f4241497741444343416a6b4743537147534962345451454e0a4151534341696f776767496d4d42344743697147534962345451454e41514545454e3564416f7135634b356e383277396f793165346e34776767466a42676f710a686b69472b453042445145434d494942557a415142677371686b69472b4530424451454341514942416a415142677371686b69472b45304244514543416749420a416a415142677371686b69472b4530424451454341774942416a415142677371686b69472b4530424451454342414942416a415142677371686b69472b4530420a4451454342514942417a415142677371686b69472b45304244514543426749424154415142677371686b69472b453042445145434277494241444151426773710a686b69472b4530424451454343414942417a415142677371686b69472b45304244514543435149424144415142677371686b69472b45304244514543436749420a4144415142677371686b69472b45304244514543437749424144415142677371686b69472b45304244514543444149424144415142677371686b69472b4530420a44514543445149424144415142677371686b69472b45304244514543446749424144415142677371686b69472b453042445145434477494241444151426773710a686b69472b45304244514543454149424144415142677371686b69472b4530424451454345514942437a416642677371686b69472b45304244514543456751510a4167494341674d4241414d4141414141414141414144415142676f71686b69472b45304244514544424149414144415542676f71686b69472b453042445145450a4241617777473841414141774477594b4b6f5a496876684e4151304242516f424154416542676f71686b69472b453042445145474242424a316472685349736d0a682b2f46793074746a6a762f4d45514743697147534962345451454e415163774e6a415142677371686b69472b45304244514548415145422f7a4151426773710a686b69472b45304244514548416745422f7a415142677371686b69472b45304244514548417745422f7a414b42676771686b6a4f5051514441674e48414442450a41694270455738754f726b537469486b4c4b6e6a426855416f637a39545733366a4e2f303765416844503635617749674d2f31474c58745a70446436706150760a535a386d4e7472543830305635346b465944474f7a4f78504374383d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436c6a4343416a32674177494241674956414a567658633239472b487051456e4a3150517a7a674658433935554d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c756447567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a62334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a45784d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484178496a41670a42674e5642414d4d47556c756447567349464e4857434251513073675547786864475a76636d306751304578476a415942674e5642416f4d45556c75644756730a49454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b474131554543417743513045780a437a414a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741454e53422f377432316c58534f0a3243757a7078773734654a423732457944476757357258437478327456544c7136684b6b367a2b5569525a436e71523770734f766771466553786c6d546c4a6c0a65546d693257597a33714f42757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f536347724442530a42674e5648523845537a424a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e5648513445466751556c5739640a7a62306234656c4153636e553944504f4156634c336c517744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159420a4166384341514177436759494b6f5a497a6a30454177494452774177524149675873566b6930772b6936565947573355462f32327561586530594a446a3155650a6e412b546a44316169356343494359623153416d4435786b66545670766f34556f79695359787244574c6d5552344349394e4b7966504e2b0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a6737535655723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d4267474131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e760a636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a55723951477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b533943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c72486f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/src/quote.rs b/src/quote.rs index 02076f5..d3b3c54 100644 --- a/src/quote.rs +++ b/src/quote.rs @@ -7,12 +7,65 @@ use serde::{Deserialize, Serialize}; use crate::{constants::*, utils, Error}; +#[cfg(feature = "hex-bytes")] +mod serde_bytes { + use serde::Deserialize; + + pub(crate) trait FromBytes { + fn from_bytes(bytes: Vec) -> Option + where + Self: Sized; + } + impl FromBytes for Vec { + fn from_bytes(bytes: Vec) -> Option { + Some(bytes) + } + } + impl FromBytes for [u8; N] { + fn from_bytes(bytes: Vec) -> Option { + bytes.try_into().ok() + } + } + + pub(crate) fn serialize( + data: impl AsRef<[u8]>, + serializer: S, + ) -> Result { + let hex_str = hex::encode(data); + serializer.serialize_str(&hex_str) + } + + pub(crate) fn deserialize<'de, D: serde::Deserializer<'de>, T: FromBytes>( + deserializer: D, + ) -> Result { + let hex_str = String::deserialize(deserializer)?; + let bytes = hex::decode(hex_str).map_err(serde::de::Error::custom)?; + T::from_bytes(bytes).ok_or_else(|| serde::de::Error::custom("invalid bytes")) + } +} + #[derive(Debug, Clone)] pub struct Data { pub data: Vec, _marker: core::marker::PhantomData, } +impl Serialize for Data { + fn serialize(&self, serializer: S) -> Result { + serde_bytes::serialize(&self.data, serializer) + } +} + +impl<'de, T> Deserialize<'de> for Data { + fn deserialize>(deserializer: D) -> Result { + let data = serde_bytes::deserialize(deserializer)?; + Ok(Data { + data, + _marker: core::marker::PhantomData, + }) + } +} + impl> Decode for Data { fn decode(input: &mut I) -> Result { let len = T::decode(input)?; @@ -25,14 +78,16 @@ impl> Decode for Data { } } -#[derive(Decode, Debug)] +#[derive(Decode, Debug, Serialize, Deserialize)] pub struct Header { pub version: u16, pub attestation_key_type: u16, pub tee_type: u32, pub qe_svn: u16, pub pce_svn: u16, + #[serde(with = "serde_bytes")] pub qe_vendor_id: [u8; 16], + #[serde(with = "serde_bytes")] pub user_data: [u8; 20], } @@ -110,7 +165,7 @@ pub struct TDReport15 { pub mr_service_td: [u8; 48], } -#[derive(Decode)] +#[derive(Decode, Serialize, Deserialize)] pub struct CertificationData { pub cert_type: u16, pub body: Data, @@ -126,27 +181,35 @@ impl core::fmt::Debug for CertificationData { } } -#[derive(Decode, Debug)] +#[derive(Decode, Debug, Serialize, Deserialize)] pub struct QEReportCertificationData { + #[serde(with = "serde_bytes")] pub qe_report: [u8; ENCLAVE_REPORT_BYTE_LEN], + #[serde(with = "serde_bytes")] pub qe_report_signature: [u8; QE_REPORT_SIG_BYTE_LEN], pub qe_auth_data: Data, pub certification_data: CertificationData, } -#[derive(Decode, Debug)] +#[derive(Decode, Debug, Serialize, Deserialize)] pub struct AuthDataV3 { + #[serde(with = "serde_bytes")] pub ecdsa_signature: [u8; ECDSA_SIGNATURE_BYTE_LEN], + #[serde(with = "serde_bytes")] pub ecdsa_attestation_key: [u8; ECDSA_PUBKEY_BYTE_LEN], + #[serde(with = "serde_bytes")] pub qe_report: [u8; ENCLAVE_REPORT_BYTE_LEN], + #[serde(with = "serde_bytes")] pub qe_report_signature: [u8; QE_REPORT_SIG_BYTE_LEN], pub qe_auth_data: Data, pub certification_data: CertificationData, } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct AuthDataV4 { + #[serde(with = "serde_bytes")] pub ecdsa_signature: [u8; ECDSA_SIGNATURE_BYTE_LEN], + #[serde(with = "serde_bytes")] pub ecdsa_attestation_key: [u8; ECDSA_PUBKEY_BYTE_LEN], pub certification_data: CertificationData, pub qe_report_data: QEReportCertificationData, @@ -181,7 +244,7 @@ impl Decode for AuthDataV4 { } } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub enum AuthData { V3(AuthDataV3), V4(AuthDataV4), @@ -217,7 +280,7 @@ pub enum Report { TD15(TDReport15), } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct Quote { pub header: Header, pub report: Report,