feat: add TDX support

Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
Harald Hoyer 2024-12-20 09:07:36 +01:00
parent f4fba51e3e
commit 4610475fae
Signed by: harald
GPG key ID: F519A1143B3FBE32
18 changed files with 2316 additions and 369 deletions

184
Cargo.lock generated
View file

@ -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-codec"
@ -311,6 +311,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anstyle"
version = "1.0.8"
@ -358,6 +367,17 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.3.0"
@ -535,6 +555,29 @@ dependencies = [
"num-traits",
]
[[package]]
name = "bindgen"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
dependencies = [
"bitflags 1.3.2",
"cexpr",
"clang-sys",
"clap 2.34.0",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bindgen"
version = "0.65.1"
@ -849,6 +892,21 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap"
version = "4.5.17"
@ -1486,6 +1544,19 @@ dependencies = [
"syn 2.0.77",
]
[[package]]
name = "env_logger"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "envy"
version = "0.4.2"
@ -1948,6 +2019,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
@ -2066,6 +2146,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.30"
@ -2794,7 +2880,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [
"hermit-abi",
"hermit-abi 0.3.9",
"libc",
"log",
"wasi",
@ -4702,6 +4788,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
@ -4827,12 +4919,28 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tdx-attest-rs"
version = "0.1.2"
source = "git+https://github.com/intel/SGXDataCenterAttestationPrimitives.git?rev=aa239d25a437a28f3f4de92c38f5b6809faac842#aa239d25a437a28f3f4de92c38f5b6809faac842"
dependencies = [
"tdx-attest-sys",
]
[[package]]
name = "tdx-attest-sys"
version = "0.1.0"
source = "git+https://github.com/intel/SGXDataCenterAttestationPrimitives.git?rev=aa239d25a437a28f3f4de92c38f5b6809faac842#aa239d25a437a28f3f4de92c38f5b6809faac842"
dependencies = [
"bindgen 0.59.2",
]
[[package]]
name = "tee-key-preexec"
version = "0.3.0"
dependencies = [
"anyhow",
"clap",
"clap 4.5.17",
"rand",
"secp256k1 0.29.1",
"teepot",
@ -4846,7 +4954,7 @@ name = "tee-ratls-preexec"
version = "0.3.0"
dependencies = [
"anyhow",
"clap",
"clap 4.5.17",
"rsa",
"teepot",
"tracing",
@ -4874,7 +4982,7 @@ version = "0.3.0"
dependencies = [
"actix-web",
"anyhow",
"clap",
"clap 4.5.17",
"serde",
"teepot",
"tracing",
@ -4890,7 +4998,7 @@ dependencies = [
"anyhow",
"awc",
"bytemuck",
"clap",
"clap 4.5.17",
"hex",
"rustls 0.22.4",
"serde_json",
@ -4909,7 +5017,7 @@ dependencies = [
"actix-web",
"anyhow",
"awc",
"clap",
"clap 4.5.17",
"rustls 0.22.4",
"serde_json",
"teepot",
@ -4929,7 +5037,7 @@ dependencies = [
"base64 0.22.1",
"bytemuck",
"bytes",
"clap",
"clap 4.5.17",
"const-oid",
"enumset",
"futures-core",
@ -4948,12 +5056,15 @@ dependencies = [
"serde_with 3.9.0",
"sha2",
"signature 2.2.0",
"tdx-attest-rs",
"teepot-tee-quote-verification-rs",
"testaso",
"thiserror",
"tokio",
"tracing",
"tracing-log 0.2.0",
"tracing-subscriber",
"tracing-test",
"webpki-roots",
"x509-cert",
"zeroize",
@ -4966,7 +5077,7 @@ dependencies = [
"actix-web",
"anyhow",
"awc",
"clap",
"clap 4.5.17",
"serde_json",
"teepot",
"tracing",
@ -4989,7 +5100,7 @@ dependencies = [
"actix-web",
"anyhow",
"awc",
"clap",
"clap 4.5.17",
"serde_json",
"teepot",
"tracing",
@ -5010,6 +5121,15 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
@ -5026,6 +5146,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b4d2149a2f578665ca39f8115084635847e9dd6921b5442dcafc7f87bb8e99"
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.63"
@ -5432,6 +5561,27 @@ dependencies = [
"tracing-serde",
]
[[package]]
name = "tracing-test"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68"
dependencies = [
"tracing-core",
"tracing-subscriber",
"tracing-test-macro",
]
[[package]]
name = "tracing-test-macro"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568"
dependencies = [
"quote",
"syn 2.0.77",
]
[[package]]
name = "try-lock"
version = "0.2.5"
@ -5583,7 +5733,7 @@ dependencies = [
"actix-web",
"anyhow",
"bytemuck",
"clap",
"clap 4.5.17",
"hex",
"pgp",
"serde_json",
@ -5600,7 +5750,7 @@ dependencies = [
"actix-web",
"anyhow",
"base64 0.22.1",
"clap",
"clap 4.5.17",
"serde_json",
"teepot",
"tracing",
@ -5614,12 +5764,18 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "verify-attestation"
version = "0.3.0"
dependencies = [
"anyhow",
"clap",
"clap 4.5.17",
"hex",
"secp256k1 0.29.1",
"teepot",
@ -5631,7 +5787,7 @@ name = "verify-era-proof-attestation"
version = "0.3.0"
dependencies = [
"anyhow",
"clap",
"clap 4.5.17",
"ctrlc",
"hex",
"jsonrpsee-types",

View file

@ -17,18 +17,15 @@ homepage = "https://github.com/matter-labs/teepot"
[workspace.dependencies]
actix-http = "3"
actix-tls = "3"
actix-web = { version = "4.5", features = ["rustls-0_22"] }
anyhow = "1.0.82"
awc = { version = "3.4", features = ["rustls-0_22-webpki-roots"] }
base64 = "0.22.0"
bitflags = "2.5"
bytemuck = { version = "1.15.0", features = ["derive", "min_const_generics", "extern_crate_std"] }
bytes = "1"
clap = { version = "4.5", features = ["std", "derive", "env", "error-context", "help", "usage", "wrap_help"], default-features = false }
const-oid = { version = "0.9", default-features = false }
ctrlc = "3.4"
der = "0.7.9"
enumset = { version = "1.1", features = ["serde"] }
futures-core = { version = "0.3.30", features = ["alloc"], default-features = false }
getrandom = "0.2.14"
@ -36,7 +33,6 @@ hex = { version = "0.4.3", features = ["std"], default-features = false }
intel-tee-quote-verification-rs = { package = "teepot-tee-quote-verification-rs", path = "crates/teepot-tee-quote-verification-rs", version = "0.3.0" }
intel-tee-quote-verification-sys = { version = "0.2.1" }
jsonrpsee-types = { version = "0.23", default-features = false }
log = "0.4"
num-integer = "0.1.46"
num-traits = "0.2.18"
p256 = "0.13.2"
@ -44,17 +40,15 @@ pgp = "0.13"
pkcs8 = { version = "0.10" }
rand = "0.8"
reqwest = { version = "0.12", features = ["json"] }
ring = { version = "0.17.8", features = ["std"], default-features = false }
rsa = { version = "0.9.6", features = ["sha2", "pem"] }
rustls = { version = "0.22" }
rustls-pemfile = "2"
sec1 = { version = "0.7.3", features = ["der"], default-features = false }
secp256k1 = { version = "0.29", features = ["rand-std", "global-context"] }
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
serde_with = { version = "3.8", features = ["base64", "hex"] }
sha2 = "0.10.8"
signature = "2.2.0"
tdx-attest-rs = { version = "0.1.2", git = "https://github.com/intel/SGXDataCenterAttestationPrimitives.git", rev = "aa239d25a437a28f3f4de92c38f5b6809faac842" }
teepot = { path = "crates/teepot" }
testaso = "0.1.0"
thiserror = "1.0.59"
@ -63,6 +57,7 @@ tracing = "0.1"
tracing-actix-web = "0.7"
tracing-log = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-test = { version = "0.2.5", features = ["no-env-filter"] }
url = "2.5.2"
webpki-roots = "0.26.1"
x509-cert = { version = "0.2", features = ["builder", "signature"] }

View file

@ -9,9 +9,7 @@
use anyhow::{Context, Result};
use clap::Parser;
use secp256k1::{rand, Keypair, PublicKey, Secp256k1, SecretKey};
use std::ffi::OsString;
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::{ffi::OsString, os::unix::process::CommandExt, process::Command};
use teepot::quote::get_quote;
use tracing::error;
use tracing_log::LogTracer;
@ -47,15 +45,15 @@ fn main_with_error() -> Result<()> {
let verifying_key = PublicKey::from_keypair(&keypair);
let verifying_key_bytes = verifying_key.serialize();
let tee_type = match get_quote(verifying_key_bytes.as_ref()) {
Ok(quote) => {
Ok((tee_type, quote)) => {
// save quote to file
std::fs::write(TEE_QUOTE_FILE, quote)?;
"sgx"
tee_type.to_string()
}
Err(e) => {
error!("Failed to get quote: {}", e);
std::fs::write(TEE_QUOTE_FILE, [])?;
"none"
"none".to_string()
}
};

View file

@ -9,7 +9,7 @@ use secp256k1::{ecdsa::Signature, Message, PublicKey};
use std::{fs, io::Read, path::PathBuf, str::FromStr, time::UNIX_EPOCH};
use teepot::{
client::TcbLevel,
sgx::{tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
quote::{error, tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
};
use zksync_basic_types::H256;
@ -84,7 +84,7 @@ fn verify_signature(
quote_verification_result: &QuoteVerificationResult,
signature_args: &SignatureArgs,
) -> Result<()> {
let reportdata = &quote_verification_result.quote.report_body.reportdata;
let reportdata = &quote_verification_result.quote.get_report_data();
let public_key = PublicKey::from_slice(reportdata)?;
println!("Public key from attestation quote: {}", public_key);
let signature_bytes = fs::read(&signature_args.signature_file)?;
@ -103,8 +103,10 @@ fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerif
"Verifying quote ({} bytes)...",
attestation_quote_bytes.len()
);
let collateral =
tee_qv_get_collateral(attestation_quote_bytes).context("Failed to get collateral")?;
let collateral = error::QuoteContext::context(
tee_qv_get_collateral(attestation_quote_bytes),
"Failed to get collateral",
)?;
let unix_time: i64 = std::time::SystemTime::now()
.duration_since(UNIX_EPOCH)?
.as_secs() as _;
@ -128,7 +130,6 @@ fn print_quote_verification_summary(quote_verification_result: &QuoteVerificatio
println!("\tInfo: Advisory ID: {advisory}");
}
println!("Quote verification result: {}", tcblevel);
println!("mrsigner: {}", hex::encode(quote.report_body.mrsigner));
println!("mrenclave: {}", hex::encode(quote.report_body.mrenclave));
println!("reportdata: {}", hex::encode(quote.report_body.reportdata));
println!("{:#}", &quote.report);
}

View file

@ -1,21 +1,22 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2024 Matter Labs
use crate::{args::AttestationPolicyArgs, client::JsonRpcClient};
use anyhow::{Context, Result};
use hex::encode;
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
use teepot::{
client::TcbLevel,
sgx::{tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
quote::{
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
QuoteVerificationResult, Report,
},
};
use tracing::{debug, info, warn};
use zksync_basic_types::{L1BatchNumber, H256};
use crate::args::AttestationPolicyArgs;
use crate::client::JsonRpcClient;
pub async fn verify_batch_proof(
quote_verification_result: &QuoteVerificationResult<'_>,
quote_verification_result: &QuoteVerificationResult,
attestation_policy: &AttestationPolicyArgs,
node_client: &impl JsonRpcClient,
signature: &[u8],
@ -28,7 +29,7 @@ pub async fn verify_batch_proof(
let batch_no = batch_number.0;
let public_key = PublicKey::from_slice(
&quote_verification_result.quote.report_body.reportdata[..PUBLIC_KEY_SIZE],
&quote_verification_result.quote.get_report_data()[..PUBLIC_KEY_SIZE],
)?;
debug!(batch_no, "public key: {}", public_key);
@ -45,8 +46,10 @@ pub async fn verify_batch_proof(
}
pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerificationResult> {
let collateral =
tee_qv_get_collateral(attestation_quote_bytes).context("Failed to get collateral!")?;
let collateral = QuoteContext::context(
tee_qv_get_collateral(attestation_quote_bytes),
"Failed to get collateral!",
)?;
let unix_time: i64 = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as _;
@ -66,17 +69,19 @@ pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificat
warn!("Freshly fetched collateral expired!");
}
let tcblevel = TcbLevel::from(*result);
info!(
"Quote verification result: {}. mrsigner: {}, mrenclave: {}, reportdata: {}. Advisory IDs: {}.",
tcblevel,
hex::encode(quote.report_body.mrsigner),
hex::encode(quote.report_body.mrenclave),
hex::encode(quote.report_body.reportdata),
if advisories.is_empty() {
let advisories = if advisories.is_empty() {
"None".to_string()
} else {
advisories.iter().map(ToString::to_string).collect::<Vec<_>>().join(", ")
}
advisories
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
};
info!(
"Quote verification result: {tcblevel}. {report}. Advisory IDs: {advisories}.",
report = &quote.report
);
}
@ -88,7 +93,7 @@ fn verify_signature(signature: &[u8], public_key: PublicKey, root_hash: H256) ->
fn is_quote_matching_policy(
attestation_policy: &AttestationPolicyArgs,
quote_verification_result: &QuoteVerificationResult<'_>,
quote_verification_result: &QuoteVerificationResult,
) -> bool {
let quote = &quote_verification_result.quote;
let tcblevel = TcbLevel::from(quote_verification_result.result);
@ -100,17 +105,21 @@ fn is_quote_matching_policy(
);
return false;
}
match &quote.report {
Report::SgxEnclave(report_body) => {
check_policy(
attestation_policy.sgx_mrsigners.as_deref(),
&quote.report_body.mrsigner,
&report_body.mr_signer,
"mrsigner",
) && check_policy(
attestation_policy.sgx_mrenclaves.as_deref(),
&quote.report_body.mrenclave,
&report_body.mr_enclave,
"mrenclave",
)
}
_ => false,
}
}
fn check_policy(policy: Option<&str>, actual_value: &[u8], field_name: &str) -> bool {
if let Some(valid_values) = policy {

View file

@ -37,6 +37,7 @@ serde_json.workspace = true
serde_with.workspace = true
sha2.workspace = true
signature.workspace = true
tdx-attest-rs.workspace = true
thiserror.workspace = true
tracing.workspace = true
tracing-log.workspace = true
@ -48,5 +49,6 @@ zeroize.workspace = true
[dev-dependencies]
anyhow.workspace = true
base64.workspace = true
hex.workspace = true
testaso.workspace = true
tokio.workspace = true
tracing-test.workspace = true

View file

@ -8,12 +8,12 @@
pub mod vault;
pub use crate::quote::verify_quote_with_collateral;
pub use crate::quote::QuoteVerificationResult;
use crate::quote::Report;
use crate::server::pki::{RaTlsCollateralExtension, RaTlsQuoteExtension};
use crate::sgx::Quote;
pub use crate::sgx::{
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
QuoteVerificationResult, TcbLevel,
};
pub use crate::sgx::{parse_tcb_levels, sgx_ql_qv_result_t, EnumSet, TcbLevel};
use actix_web::http::header;
use anyhow::Result;
use awc::{Client, Connector};
@ -195,6 +195,10 @@ impl TeeConnection {
} = verify_quote_with_collateral(quote_bytes, collateral.as_ref(), current_time)
.unwrap();
let Report::SgxEnclave(report_body) = quote.report else {
return Err(Error::General("TDX quote and not SGX quote".into()));
};
if collateral_expired || result != sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK {
if collateral_expired {
error!(
@ -230,10 +234,10 @@ impl TeeConnection {
if let Some(mrsigner) = &self.args.sgx_mrsigner {
let mrsigner_bytes = hex::decode(mrsigner)
.map_err(|e| Error::General(format!("Failed to decode mrsigner: {}", e)))?;
if quote.report_body.mrsigner[..] != mrsigner_bytes {
if report_body.mr_signer[..] != mrsigner_bytes {
return Err(Error::General(format!(
"mrsigner mismatch: got {}, expected {}",
hex::encode(quote.report_body.mrsigner),
hex::encode(report_body.mr_signer),
&mrsigner
)));
} else {
@ -245,10 +249,10 @@ impl TeeConnection {
let mrenclave_bytes = hex::decode(mrenclave).map_err(|e| {
Error::General(format!("Failed to decode mrenclave: {}", e))
})?;
if quote.report_body.mrenclave[..] != mrenclave_bytes {
if report_body.mr_enclave[..] != mrenclave_bytes {
return Err(Error::General(format!(
"mrenclave mismatch: got {}, expected {}",
hex::encode(quote.report_body.mrenclave),
hex::encode(report_body.mr_enclave),
&mrenclave
)));
} else {

View file

@ -7,26 +7,34 @@
#![deny(clippy::all)]
use super::{AttestationArgs, TeeConnection};
use crate::json::http::{AuthRequest, AuthResponse};
use crate::server::pki::make_self_signed_cert;
use crate::server::{AnyHowResponseError, HttpResponseError, Status};
pub use crate::sgx::{
parse_tcb_levels, sgx_gramine_get_quote, sgx_ql_qv_result_t, tee_qv_get_collateral,
verify_quote_with_collateral, Collateral, EnumSet, QuoteVerificationResult, TcbLevel,
use crate::{
json::http::{AuthRequest, AuthResponse},
quote::error::QuoteContext,
server::{pki::make_self_signed_cert, AnyHowResponseError, HttpResponseError, Status},
};
pub use crate::{
quote::{verify_quote_with_collateral, QuoteVerificationResult},
sgx::{
parse_tcb_levels, sgx_gramine_get_quote, sgx_ql_qv_result_t, Collateral, EnumSet, TcbLevel,
},
};
use actix_http::error::PayloadError;
use actix_web::http::header;
use actix_web::ResponseError;
use actix_web::{http::header, ResponseError};
use anyhow::{anyhow, bail, Context, Result};
use awc::error::{SendRequestError, StatusCode};
use awc::{Client, ClientResponse, Connector};
use awc::{
error::{SendRequestError, StatusCode},
Client, ClientResponse, Connector,
};
use bytes::Bytes;
use futures_core::Stream;
use intel_tee_quote_verification_rs::tee_qv_get_collateral;
use rustls::ClientConfig;
use serde_json::{json, Value};
use std::fmt::{Display, Formatter};
use std::sync::Arc;
use std::time;
use std::{
fmt::{Display, Formatter},
sync::Arc,
time,
};
use tracing::{debug, error, info, trace};
const VAULT_TOKEN_HEADER: &str = "X-Vault-Token";

View file

@ -8,8 +8,8 @@
pub mod client;
pub mod json;
pub mod server;
pub mod sgx;
pub mod log;
pub mod quote;
pub mod server;
pub mod sgx;
pub mod tdx;

View file

@ -0,0 +1,85 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024 Matter Labs
//! Quote Error type
use intel_tee_quote_verification_rs::quote3_error_t;
use std::io;
use tdx_attest_rs::tdx_attest_error_t;
use thiserror::Error;
/// Quote parsing error
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum QuoteError {
#[error("I/O Error")]
IoError { context: String, source: io::Error },
#[error("parsing bytes")]
ConvertError(#[from] bytemuck::PodCastError),
#[error("unsupported quote version")]
QuoteVersion,
#[error("invalid tee type")]
InvalidTeeType,
#[error("unsupported body type")]
UnsupportedBodyType,
#[error("quote verification error {msg}: {inner:?}")]
Quote3Error { inner: quote3_error_t, msg: String },
#[error("tdx_att_get_quote error {msg}: {inner:?}")]
TdxAttGetQuote {
inner: tdx_attest_error_t,
msg: String,
},
#[error("invalid version")]
InvalidVersion,
#[error("report data too long")]
ReportDataSize,
#[error("can't get a quote: unknown TEE")]
UnknownTee,
}
impl From<tdx_attest_error_t> for QuoteError {
fn from(code: tdx_attest_error_t) -> Self {
Self::TdxAttGetQuote {
inner: code,
msg: "code".to_string(),
}
}
}
/// Usability trait for easy QuoteError annotation
pub trait QuoteContext {
/// The Ok Type
type Ok;
/// The Context
fn context<I: Into<String>>(self, msg: I) -> Result<Self::Ok, QuoteError>;
}
impl<T> QuoteContext for Result<T, std::io::Error> {
type Ok = T;
fn context<I: Into<String>>(self, msg: I) -> Result<T, QuoteError> {
self.map_err(|e| QuoteError::IoError {
context: msg.into(),
source: e,
})
}
}
impl<T> QuoteContext for Result<T, quote3_error_t> {
type Ok = T;
fn context<I: Into<String>>(self, msg: I) -> Result<T, QuoteError> {
self.map_err(|e| QuoteError::Quote3Error {
msg: msg.into(),
inner: e,
})
}
}
impl<T> QuoteContext for Result<T, tdx_attest_error_t> {
type Ok = T;
fn context<I: Into<String>>(self, msg: I) -> Result<T, QuoteError> {
self.map_err(|e| QuoteError::TdxAttGetQuote {
msg: msg.into(),
inner: e,
})
}
}

View file

@ -1,40 +1,733 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2024 Matter Labs
// Parts of it are Copyright (c) 2024 Phala Network
// and copied from https://github.com/Phala-Network/dcap-qvl
//! Get a quote from a TEE
pub mod error;
use crate::sgx::sgx_gramine_get_quote;
use std::io;
use crate::{
quote::error::{QuoteContext as _, QuoteError},
sgx::sgx_gramine_get_quote,
tdx::tgx_get_quote,
};
use bytemuck::{cast_slice, AnyBitPattern};
use intel_tee_quote_verification_rs::{
sgx_ql_qv_result_t, sgx_ql_qv_supplemental_t, tee_get_supplemental_data_version_and_size,
tee_supp_data_descriptor_t, tee_verify_quote, Collateral,
};
use serde::{Deserialize, Serialize};
use std::{
ffi::CStr,
fmt::{Display, Formatter},
io::Read,
mem,
str::FromStr,
};
use tracing::{trace, warn};
pub use intel_tee_quote_verification_rs::tee_qv_get_collateral;
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
#[error("{msg}")]
pub struct GetQuoteError {
pub(crate) msg: Box<str>,
#[source] // optional if field name is `source`
pub(crate) source: io::Error,
pub const TEE_TYPE_SGX: u32 = 0x00000000;
#[allow(missing_docs)]
pub const TEE_TYPE_TDX: u32 = 0x00000081;
#[allow(missing_docs)]
pub const BODY_SGX_ENCLAVE_REPORT_TYPE: u16 = 1;
#[allow(missing_docs)]
pub const BODY_TD_REPORT10_TYPE: u16 = 2;
#[allow(missing_docs)]
pub const BODY_TD_REPORT15_TYPE: u16 = 3;
#[allow(missing_docs)]
pub const ENCLAVE_REPORT_BYTE_LEN: usize = 384;
#[allow(missing_docs)]
pub const ECDSA_SIGNATURE_BYTE_LEN: usize = 64;
#[allow(missing_docs)]
pub const ECDSA_PUBKEY_BYTE_LEN: usize = 64;
#[allow(missing_docs)]
pub const QE_REPORT_SIG_BYTE_LEN: usize = ECDSA_SIGNATURE_BYTE_LEN;
mod serde_bytes {
use serde::Deserialize;
pub(crate) trait FromBytes {
fn from_bytes(bytes: Vec<u8>) -> Option<Self>
where
Self: Sized;
}
impl FromBytes for Vec<u8> {
fn from_bytes(bytes: Vec<u8>) -> Option<Self> {
Some(bytes)
}
}
impl<const N: usize> FromBytes for [u8; N] {
fn from_bytes(bytes: Vec<u8>) -> Option<Self> {
bytes.try_into().ok()
}
}
pub(crate) fn serialize<S: serde::Serializer>(
data: impl AsRef<[u8]>,
serializer: S,
) -> Result<S::Ok, S::Error> {
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<T, D::Error> {
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"))
}
}
/// Trait that allows zero-copy read of value-references from slices in LE format.
pub trait Decode: Sized {
/// Attempt to deserialise the value from input.
fn decode<I: Read>(input: &mut I) -> Result<Self, error::QuoteError>;
}
impl<T: AnyBitPattern> Decode for T {
fn decode<I: Read>(input: &mut I) -> Result<Self, error::QuoteError> {
let mut bytes = vec![0u8; size_of::<T>()];
input.read(&mut bytes).context("parsing bytes")?;
bytemuck::try_pod_read_unaligned(&bytes).map_err(Into::into)
}
}
#[derive(Debug, Clone)]
#[allow(missing_docs)]
#[repr(C)]
pub struct Data<T> {
pub data: Vec<u8>,
_marker: core::marker::PhantomData<T>,
}
impl<T> Serialize for Data<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serde_bytes::serialize(&self.data, serializer)
}
}
impl<'de, T> Deserialize<'de> for Data<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let data = serde_bytes::deserialize(deserializer)?;
Ok(Data {
data,
_marker: core::marker::PhantomData,
})
}
}
impl<T: Decode + Into<u64>> Decode for Data<T> {
fn decode<I: Read>(input: &mut I) -> Result<Self, QuoteError> {
let len = T::decode(input)?;
let mut data = vec![0u8; len.into() as usize];
input.read(&mut data).context("reading bytes")?;
Ok(Data {
data,
_marker: core::marker::PhantomData,
})
}
}
#[allow(missing_docs)]
#[derive(AnyBitPattern, Debug, Serialize, Deserialize, Copy, Clone)]
#[repr(C, packed)]
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],
}
#[derive(AnyBitPattern, Debug, Copy, Clone)]
#[allow(missing_docs)]
#[repr(C, packed)]
pub struct Body {
pub body_type: u16,
pub size: u32,
}
#[derive(Serialize, Deserialize, AnyBitPattern, Debug, Clone, Copy)]
#[allow(missing_docs)]
#[repr(C, packed)]
pub struct EnclaveReport {
#[serde(with = "serde_bytes")]
pub cpu_svn: [u8; 16],
pub misc_select: u32,
#[serde(with = "serde_bytes")]
pub reserved1: [u8; 28],
#[serde(with = "serde_bytes")]
pub attributes: [u8; 16],
#[serde(with = "serde_bytes")]
pub mr_enclave: [u8; 32],
#[serde(with = "serde_bytes")]
pub reserved2: [u8; 32],
#[serde(with = "serde_bytes")]
pub mr_signer: [u8; 32],
#[serde(with = "serde_bytes")]
pub reserved3: [u8; 96],
pub isv_prod_id: u16,
pub isv_svn: u16,
#[serde(with = "serde_bytes")]
pub reserved4: [u8; 60],
#[serde(with = "serde_bytes")]
pub report_data: [u8; 64],
}
#[derive(AnyBitPattern, Debug, Copy, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C, packed)]
pub struct TDReport10 {
#[serde(with = "serde_bytes")]
pub tee_tcb_svn: [u8; 16],
#[serde(with = "serde_bytes")]
pub mr_seam: [u8; 48],
#[serde(with = "serde_bytes")]
pub mr_signer_seam: [u8; 48],
#[serde(with = "serde_bytes")]
pub seam_attributes: [u8; 8],
#[serde(with = "serde_bytes")]
pub td_attributes: [u8; 8],
#[serde(with = "serde_bytes")]
pub xfam: [u8; 8],
#[serde(with = "serde_bytes")]
pub mr_td: [u8; 48],
#[serde(with = "serde_bytes")]
pub mr_config_id: [u8; 48],
#[serde(with = "serde_bytes")]
pub mr_owner: [u8; 48],
#[serde(with = "serde_bytes")]
pub mr_owner_config: [u8; 48],
#[serde(with = "serde_bytes")]
pub rt_mr0: [u8; 48],
#[serde(with = "serde_bytes")]
pub rt_mr1: [u8; 48],
#[serde(with = "serde_bytes")]
pub rt_mr2: [u8; 48],
#[serde(with = "serde_bytes")]
pub rt_mr3: [u8; 48],
#[serde(with = "serde_bytes")]
pub report_data: [u8; 64],
}
#[derive(AnyBitPattern, Debug, Copy, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C, packed)]
pub struct TDReport15 {
pub base: TDReport10,
#[serde(with = "serde_bytes")]
pub tee_tcb_svn2: [u8; 16],
#[serde(with = "serde_bytes")]
pub mr_service_td: [u8; 48],
}
#[allow(missing_docs)]
#[derive(Serialize, Deserialize, Clone)]
#[repr(C)]
pub struct CertificationData {
pub cert_type: u16,
pub body: Data<u32>,
}
impl Decode for CertificationData {
fn decode<I: Read>(input: &mut I) -> Result<Self, QuoteError> {
Ok(Self {
cert_type: Decode::decode(input)?,
body: Decode::decode(input)?,
})
}
}
impl core::fmt::Debug for CertificationData {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let body_str = String::from_utf8_lossy(&self.body.data);
f.debug_struct("CertificationData")
.field("cert_type", &self.cert_type)
.field("body", &body_str)
.finish()
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C)]
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<u16>,
pub certification_data: CertificationData,
}
impl Decode for QEReportCertificationData {
fn decode<I: Read>(input: &mut I) -> Result<Self, QuoteError> {
Ok(Self {
qe_report: Decode::decode(input)?,
qe_report_signature: Decode::decode(input)?,
qe_auth_data: Decode::decode(input)?,
certification_data: Decode::decode(input)?,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C)]
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<u16>,
pub certification_data: CertificationData,
}
impl Decode for AuthDataV3 {
fn decode<I: Read>(input: &mut I) -> Result<Self, QuoteError> {
Ok(Self {
ecdsa_signature: Decode::decode(input)?,
ecdsa_attestation_key: Decode::decode(input)?,
qe_report: Decode::decode(input)?,
qe_report_signature: Decode::decode(input)?,
qe_auth_data: Decode::decode(input)?,
certification_data: Decode::decode(input)?,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C)]
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,
}
impl AuthDataV4 {
#[allow(missing_docs)]
pub fn into_v3(self) -> AuthDataV3 {
AuthDataV3 {
ecdsa_signature: self.ecdsa_signature,
ecdsa_attestation_key: self.ecdsa_attestation_key,
qe_report: self.qe_report_data.qe_report,
qe_report_signature: self.qe_report_data.qe_report_signature,
qe_auth_data: self.qe_report_data.qe_auth_data,
certification_data: self.qe_report_data.certification_data,
}
}
}
impl Decode for AuthDataV4 {
fn decode<I: Read>(input: &mut I) -> Result<Self, error::QuoteError> {
let ecdsa_signature = Decode::decode(input)?;
let ecdsa_attestation_key = Decode::decode(input)?;
let certification_data: CertificationData = Decode::decode(input)?;
let qe_report_data =
QEReportCertificationData::decode(&mut &certification_data.body.data[..])?;
Ok(AuthDataV4 {
ecdsa_signature,
ecdsa_attestation_key,
certification_data,
qe_report_data,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C)]
pub enum AuthData {
V3(AuthDataV3),
V4(AuthDataV4),
}
impl AuthData {
#[allow(missing_docs)]
pub fn into_v3(self) -> AuthDataV3 {
match self {
AuthData::V3(data) => data,
AuthData::V4(data) => data.into_v3(),
}
}
}
fn decode_auth_data(ver: u16, input: &mut &[u8]) -> Result<AuthData, error::QuoteError> {
match ver {
3 => {
let auth_data = AuthDataV3::decode(input)?;
Ok(AuthData::V3(auth_data))
}
4 => {
let auth_data = AuthDataV4::decode(input)?;
Ok(AuthData::V4(auth_data))
}
_ => Err(error::QuoteError::QuoteVersion),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C)]
#[non_exhaustive]
pub enum Report {
SgxEnclave(EnclaveReport),
TD10(TDReport10),
TD15(TDReport15),
}
impl Report {
#[allow(missing_docs)]
pub fn is_sgx(&self) -> bool {
matches!(self, Report::SgxEnclave(_))
}
#[allow(missing_docs)]
pub fn as_td10(&self) -> Option<&TDReport10> {
match self {
Report::TD10(report) => Some(report),
Report::TD15(report) => Some(&report.base),
_ => None,
}
}
#[allow(missing_docs)]
pub fn as_td15(&self) -> Option<&TDReport15> {
match self {
Report::TD15(report) => Some(report),
_ => None,
}
}
#[allow(missing_docs)]
pub fn as_sgx(&self) -> Option<&EnclaveReport> {
match self {
Report::SgxEnclave(report) => Some(report),
_ => None,
}
}
}
impl Display for Report {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
fn space_or_newline(f: &mut Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
writeln!(f)
} else {
write!(f, " ")
}
}
match self {
Report::SgxEnclave(report_body) => {
write!(f, "mrsigner: {}", hex::encode(report_body.mr_signer))?;
space_or_newline(f)?;
write!(f, "mrenclave: {}", hex::encode(report_body.mr_enclave))?;
space_or_newline(f)?;
write!(
f,
"reportdata: {}",
hex::encode(report_body.report_data.as_slice())
)?;
}
Report::TD10(report_body) => {
write!(f, "mrtd: {}", hex::encode(report_body.mr_td))?;
space_or_newline(f)?;
write!(f, "rtmr0: {}", hex::encode(report_body.rt_mr0))?;
space_or_newline(f)?;
write!(f, "rtmr1: {}", hex::encode(report_body.rt_mr1))?;
space_or_newline(f)?;
write!(f, "rtmr2: {}", hex::encode(report_body.rt_mr2))?;
space_or_newline(f)?;
write!(f, "rtmr3: {}", hex::encode(report_body.rt_mr3))?;
space_or_newline(f)?;
write!(
f,
"reportdata: {}",
hex::encode(report_body.report_data.as_slice())
)?;
}
Report::TD15(report_body) => {
let report_body = &report_body.base;
write!(f, "mrtd: {}", hex::encode(report_body.mr_td))?;
space_or_newline(f)?;
write!(f, "rtmr0: {}", hex::encode(report_body.rt_mr0))?;
space_or_newline(f)?;
write!(f, "rtmr1: {}", hex::encode(report_body.rt_mr1))?;
space_or_newline(f)?;
write!(f, "rtmr2: {}", hex::encode(report_body.rt_mr2))?;
space_or_newline(f)?;
write!(f, "rtmr3: {}", hex::encode(report_body.rt_mr3))?;
space_or_newline(f)?;
write!(
f,
"reportdata: {}",
hex::encode(report_body.report_data.as_slice())
)?;
}
}
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
#[repr(C)]
pub struct Quote {
pub header: Header,
pub report: Report,
pub auth_data: AuthData,
}
impl Decode for Quote {
fn decode<I: Read>(input: &mut I) -> Result<Self, error::QuoteError> {
let header = Header::decode(input)?;
trace!(?header);
let report;
match header.version {
3 => {
if header.tee_type != TEE_TYPE_SGX {
return Err(error::QuoteError::InvalidTeeType);
}
report = Report::SgxEnclave(EnclaveReport::decode(input)?);
}
4 => match header.tee_type {
TEE_TYPE_SGX => {
report = Report::SgxEnclave(EnclaveReport::decode(input)?);
}
TEE_TYPE_TDX => {
report = Report::TD10(TDReport10::decode(input)?);
}
_ => return Err(error::QuoteError::InvalidTeeType),
},
5 => {
let body = Body::decode(input)?;
match body.body_type {
BODY_SGX_ENCLAVE_REPORT_TYPE => {
report = Report::SgxEnclave(EnclaveReport::decode(input)?);
}
BODY_TD_REPORT10_TYPE => {
report = Report::TD10(TDReport10::decode(input)?);
}
BODY_TD_REPORT15_TYPE => {
report = Report::TD15(TDReport15::decode(input)?);
}
_ => return Err(error::QuoteError::UnsupportedBodyType),
}
}
_ => return Err(error::QuoteError::QuoteVersion),
}
let data = Data::<u32>::decode(input)?;
let auth_data = decode_auth_data(header.version, &mut &data.data[..])?;
Ok(Quote {
header,
report,
auth_data,
})
}
}
impl Quote {
/// Parse a TEE quote from a byte slice.
pub fn parse(quote: &[u8]) -> Result<Self, QuoteError> {
let mut input = quote;
let quote = Quote::decode(&mut input)?;
Ok(quote)
}
/// Get the report data
pub fn get_report_data(&self) -> &[u8] {
match &self.report {
Report::SgxEnclave(r) => r.report_data.as_slice(),
Report::TD10(r) => r.report_data.as_slice(),
Report::TD15(r) => r.base.report_data.as_slice(),
}
}
}
/// TEE type
#[non_exhaustive]
pub enum TEEType {
/// Intel SGX
SGX,
/// Intel TDX
TDX,
/// AMD SEV-SNP
SNP,
}
impl Display for TEEType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
TEEType::SGX => "sgx",
TEEType::TDX => "tdx",
TEEType::SNP => "snp",
};
write!(f, "{}", str)
}
}
impl FromStr for TEEType {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
match s.to_lowercase().as_str() {
"sgx" => Ok(TEEType::SGX),
"tdx" => Ok(TEEType::TDX),
"snp" => Ok(TEEType::SNP),
_ => Err("Invalid TEE type".to_string()),
}
}
}
/// Get the attestation quote from a TEE
pub fn get_quote(report_data: &[u8]) -> Result<Box<[u8]>, GetQuoteError> {
pub fn get_quote(report_data: &[u8]) -> Result<(TEEType, Box<[u8]>), QuoteError> {
// check, if we are running in a TEE
if std::fs::metadata("/dev/attestation").is_ok() {
if report_data.len() > 64 {
return Err(GetQuoteError {
msg: "Report data too long".into(),
source: io::Error::new(io::ErrorKind::Other, "Report data too long"),
});
return Err(QuoteError::ReportDataSize);
}
let mut report_data_fixed = [0u8; 64];
report_data_fixed[..report_data.len()].copy_from_slice(report_data);
sgx_gramine_get_quote(&report_data_fixed)
Ok((TEEType::SGX, sgx_gramine_get_quote(&report_data_fixed)?))
} else if std::fs::metadata("/dev/tdx_guest").is_ok() {
if report_data.len() > 64 {
return Err(QuoteError::ReportDataSize);
}
let mut report_data_fixed = [0u8; 64];
report_data_fixed[..report_data.len()].copy_from_slice(report_data);
Ok((TEEType::TDX, tgx_get_quote(&report_data_fixed)?))
} else {
// if not, return an error
Err(GetQuoteError {
msg: "Not running in a TEE".into(),
source: io::Error::new(io::ErrorKind::Other, "Not running in a TEE"),
})
Err(QuoteError::UnknownTee)
}
}
/// The result of the quote verification
pub struct QuoteVerificationResult {
/// the raw result
pub result: sgx_ql_qv_result_t,
/// indicates if the collateral is expired
pub collateral_expired: bool,
/// the earliest expiration date of the collateral
pub earliest_expiration_date: i64,
/// Date of the TCB level
pub tcb_level_date_tag: i64,
/// the advisory string
pub advisories: Vec<String>,
/// the quote
pub quote: Quote,
}
/// Verifies a quote with optional collateral material
pub fn verify_quote_with_collateral(
quote: &[u8],
collateral: Option<&Collateral>,
current_time: i64,
) -> Result<QuoteVerificationResult, QuoteError> {
let mut supp_data: mem::MaybeUninit<sgx_ql_qv_supplemental_t> = mem::MaybeUninit::zeroed();
let mut supp_data_desc = tee_supp_data_descriptor_t {
major_version: 0,
data_size: 0,
p_data: supp_data.as_mut_ptr() as *mut u8,
};
trace!("tee_get_supplemental_data_version_and_size");
let (_, supp_size) =
tee_get_supplemental_data_version_and_size(quote).map_err(|e| QuoteError::Quote3Error {
msg: "tee_get_supplemental_data_version_and_size".into(),
inner: e,
})?;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
if supp_size == mem::size_of::<sgx_ql_qv_supplemental_t>() as u32 {
supp_data_desc.data_size = supp_size;
} else {
supp_data_desc.data_size = 0;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
trace!(
"mem::size_of::<sgx_ql_qv_supplemental_t>(): {}",
mem::size_of::<sgx_ql_qv_supplemental_t>()
);
warn!("Quote supplemental data size is different between DCAP QVL and QvE, please make sure you installed DCAP QVL and QvE from same release.")
}
let p_supplemental_data = match supp_data_desc.data_size {
0 => None,
_ => Some(&mut supp_data_desc),
};
let has_sup = p_supplemental_data.is_some();
trace!("tee_verify_quote");
let (collateral_expiration_status, result) =
tee_verify_quote(quote, collateral, current_time, None, p_supplemental_data)
.context("tee_verify_quote")?;
trace!("tee_verify_quote end");
// check supplemental data if necessary
let (advisories, earliest_expiration_date, tcb_level_date_tag) = if has_sup {
unsafe {
let supp_data = supp_data.assume_init();
// convert to valid UTF-8 string
let ads = CStr::from_bytes_until_nul(cast_slice(&supp_data.sa_list[..]))
.ok()
.and_then(|s| CStr::to_str(s).ok())
.into_iter()
.flat_map(|s| s.split(',').map(str::trim).map(String::from))
.filter(|s| !s.is_empty())
.collect();
(
ads,
supp_data.earliest_expiration_date,
supp_data.tcb_level_date_tag,
)
}
} else {
(vec![], 0, 0)
};
trace!("Quote::parse");
let quote = Quote::parse(quote)?;
let res = QuoteVerificationResult {
collateral_expired: collateral_expiration_status != 0,
earliest_expiration_date,
tcb_level_date_tag,
result,
quote,
advisories,
};
Ok(res)
}

View file

@ -3,17 +3,22 @@
//! Common attestation API for all TEEs
use crate::client::AttestationArgs;
use crate::json::http::AttestationResponse;
use crate::sgx::{
parse_tcb_levels, sgx_gramine_get_quote, tee_qv_get_collateral, verify_quote_with_collateral,
Collateral, EnumSet, QuoteVerificationResult, TcbLevel,
use crate::{
client::AttestationArgs,
json::http::AttestationResponse,
quote::{
error::QuoteContext, get_quote, verify_quote_with_collateral, QuoteVerificationResult,
},
sgx::{parse_tcb_levels, Collateral, EnumSet, TcbLevel},
};
use anyhow::{bail, Context, Result};
use clap::Args;
use intel_tee_quote_verification_rs::tee_qv_get_collateral;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, RwLock};
use std::time::{Duration, UNIX_EPOCH};
use std::{
sync::{Arc, RwLock},
time::{Duration, UNIX_EPOCH},
};
use tracing::{debug, error, info, trace, warn};
struct Attestation {
@ -53,7 +58,7 @@ pub fn get_quote_and_collateral(
}
}
let myquote = sgx_gramine_get_quote(report_data).context("Failed to get own quote")?;
let (_tee_type, myquote) = get_quote(report_data).context("Failed to get own quote")?;
let collateral = tee_qv_get_collateral(&myquote).context("Failed to get own collateral")?;
let QuoteVerificationResult {
@ -89,8 +94,8 @@ pub fn get_quote_and_collateral(
"Earliest expiration in {:?}",
Duration::from_secs((earliest_expiration_date - unix_time) as _)
);
info!("mrsigner: {}", hex::encode(quote.report_body.mrsigner));
info!("mrenclave: {}", hex::encode(quote.report_body.mrenclave));
info!("{:#}", quote.report);
let quote: Arc<[u8]> = Arc::from(myquote);
let collateral = Arc::from(collateral);

View file

@ -3,39 +3,38 @@
//! Create a private key and a signed and self-signed certificates
use crate::quote::get_quote;
use crate::sgx::tee_qv_get_collateral;
pub use crate::sgx::{
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
QuoteVerificationResult, TcbLevel,
};
use crate::quote::{error::QuoteContext, get_quote};
pub use crate::sgx::{parse_tcb_levels, sgx_ql_qv_result_t, EnumSet, TcbLevel};
use anyhow::{Context, Result};
use const_oid::db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH};
use const_oid::AssociatedOid;
use const_oid::{
db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH},
AssociatedOid,
};
use getrandom::getrandom;
use p256::ecdsa::DerSignature;
use p256::pkcs8::EncodePrivateKey;
use intel_tee_quote_verification_rs::tee_qv_get_collateral;
use p256::{ecdsa::DerSignature, pkcs8::EncodePrivateKey};
use pkcs8::der;
use rustls::pki_types::PrivatePkcs8KeyDer;
use sha2::{Digest, Sha256};
use signature::Signer;
use std::str::FromStr;
use std::time::Duration;
use std::{str::FromStr, time::Duration};
use tracing::debug;
use x509_cert::builder::{Builder, CertificateBuilder, Profile};
use x509_cert::der::pem::LineEnding;
use x509_cert::der::{asn1::OctetString, Encode as _, EncodePem as _, Length};
use x509_cert::ext::pkix::name::GeneralNames;
use x509_cert::ext::pkix::{ExtendedKeyUsage, SubjectAltName};
use x509_cert::ext::{AsExtension, Extension};
use x509_cert::name::{Name, RdnSequence};
use x509_cert::serial_number::SerialNumber;
use x509_cert::spki::{
DynSignatureAlgorithmIdentifier, EncodePublicKey, ObjectIdentifier, SignatureBitStringEncoding,
SubjectPublicKeyInfoOwned,
use x509_cert::{
builder::{Builder, CertificateBuilder, Profile},
der::{asn1::OctetString, pem::LineEnding, Encode as _, EncodePem as _, Length},
ext::{
pkix::{name::GeneralNames, ExtendedKeyUsage, SubjectAltName},
AsExtension, Extension,
},
name::{Name, RdnSequence},
serial_number::SerialNumber,
spki::{
DynSignatureAlgorithmIdentifier, EncodePublicKey, ObjectIdentifier,
SignatureBitStringEncoding, SubjectPublicKeyInfoOwned,
},
time::Validity,
Certificate,
};
use x509_cert::time::Validity;
use x509_cert::Certificate;
use zeroize::Zeroizing;
/// The OID for the `gramine-ra-tls` quote extension
@ -148,7 +147,7 @@ pub fn make_self_signed_cert(
let hash = Sha256::digest(verifying_key_der.as_bytes());
key_hash[..32].copy_from_slice(&hash);
let quote = get_quote(&key_hash)?;
let (_tee_type, quote) = get_quote(&key_hash)?;
debug!("quote.len: {:?}", quote.len());
// Create a relative distinguished name.
let rdns = RdnSequence::from_str(dn)?;
@ -185,6 +184,7 @@ pub fn make_self_signed_cert(
.context("failed to add SubjectAltName")?;
}
// FIXME: OID for tee_type
builder
.add_extension(&RaTlsQuoteExtension {
quote: quote.to_vec(),
@ -234,7 +234,7 @@ where
let hash = Sha256::digest(verifying_key_der.as_bytes());
key_hash[..32].copy_from_slice(&hash);
let quote = get_quote(&key_hash).context("Failed to get own quote")?;
let (_tee_type, quote) = get_quote(&key_hash).context("Failed to get own quote")?;
// Create a relative distinguished name.
let subject = Name::from_str(dn)?;
@ -269,6 +269,7 @@ where
.context("failed to add SubjectAltName")?;
}
// FIXME: oid according to tee_type
builder
.add_extension(&RaTlsQuoteExtension {
quote: quote.to_vec(),

View file

@ -1,50 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023 Matter Labs
//! Intel SGX Enclave error wrapper
use bytemuck::PodCastError;
use intel_tee_quote_verification_rs::quote3_error_t;
use std::fmt::Formatter;
/// Wrapper for the quote verification Error
#[derive(Copy, Clone)]
pub struct Quote3Error {
/// error message
pub msg: &'static str,
/// raw error code
pub inner: quote3_error_t,
}
impl std::fmt::Display for Quote3Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {:?}", self.msg, self.inner)
}
}
impl std::fmt::Debug for Quote3Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {:?}", self.msg, self.inner)
}
}
impl std::error::Error for Quote3Error {}
impl From<quote3_error_t> for Quote3Error {
fn from(inner: quote3_error_t) -> Self {
Self {
msg: "Generic",
inner,
}
}
}
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum QuoteFromError {
#[error(transparent)]
PodCastError(#[from] PodCastError),
#[error("Quote version is invalid")]
InvalidVersion,
}

View file

@ -5,24 +5,18 @@
//! Intel SGX Enclave report structures.
pub mod error;
pub mod sign;
pub mod tcblevel;
use bytemuck::{cast_slice, try_from_bytes, AnyBitPattern, PodCastError};
use intel_tee_quote_verification_rs::{
quote3_error_t, sgx_ql_qv_supplemental_t, tee_get_supplemental_data_version_and_size,
tee_supp_data_descriptor_t, tee_verify_quote,
};
use std::ffi::CStr;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::mem;
use tracing::{trace, warn};
use crate::quote::GetQuoteError;
pub use error::{Quote3Error, QuoteFromError};
use crate::quote::error::QuoteContext;
pub use crate::quote::error::QuoteError;
use bytemuck::{try_from_bytes, AnyBitPattern, PodCastError};
pub use intel_tee_quote_verification_rs::{sgx_ql_qv_result_t, Collateral};
use std::{
fs::OpenOptions,
io::{Read, Write},
mem,
};
pub use tcblevel::{parse_tcb_levels, EnumSet, TcbLevel};
/// Structure of a quote
@ -43,13 +37,13 @@ pub struct Quote {
impl Quote {
/// Creates a quote from a byte slice
pub fn try_from_bytes(bytes: &[u8]) -> Result<&Self, QuoteFromError> {
pub fn try_from_bytes(bytes: &[u8]) -> Result<&Self, QuoteError> {
if bytes.len() < mem::size_of::<Self>() {
return Err(PodCastError::SizeMismatch.into());
}
let this: &Self = try_from_bytes(&bytes[..mem::size_of::<Self>()])?;
if this.version() != 3 {
return Err(QuoteFromError::InvalidVersion);
return Err(QuoteError::InvalidVersion);
}
Ok(this)
}
@ -98,152 +92,26 @@ pub struct ReportBody {
pub reportdata: [u8; 64],
}
/// The result of the quote verification
pub struct QuoteVerificationResult<'a> {
/// the raw result
pub result: sgx_ql_qv_result_t,
/// indicates if the collateral is expired
pub collateral_expired: bool,
/// the earliest expiration date of the collateral
pub earliest_expiration_date: i64,
/// Date of the TCB level
pub tcb_level_date_tag: i64,
/// the advisory string
pub advisories: Vec<String>,
/// the quote
pub quote: &'a Quote,
}
/// Verifies a quote with optional collateral material
pub fn verify_quote_with_collateral<'a>(
quote: &'a [u8],
collateral: Option<&Collateral>,
current_time: i64,
) -> Result<QuoteVerificationResult<'a>, Quote3Error> {
let mut supp_data: mem::MaybeUninit<sgx_ql_qv_supplemental_t> = mem::MaybeUninit::zeroed();
let mut supp_data_desc = tee_supp_data_descriptor_t {
major_version: 0,
data_size: 0,
p_data: supp_data.as_mut_ptr() as *mut u8,
};
trace!("tee_get_supplemental_data_version_and_size");
let (_, supp_size) =
tee_get_supplemental_data_version_and_size(quote).map_err(|e| Quote3Error {
msg: "tee_get_supplemental_data_version_and_size",
inner: e,
})?;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
if supp_size == mem::size_of::<sgx_ql_qv_supplemental_t>() as u32 {
supp_data_desc.data_size = supp_size;
} else {
supp_data_desc.data_size = 0;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
trace!(
"mem::size_of::<sgx_ql_qv_supplemental_t>(): {}",
mem::size_of::<sgx_ql_qv_supplemental_t>()
);
warn!("Quote supplemental data size is different between DCAP QVL and QvE, please make sure you installed DCAP QVL and QvE from same release.")
}
let p_supplemental_data = match supp_data_desc.data_size {
0 => None,
_ => Some(&mut supp_data_desc),
};
let has_sup = p_supplemental_data.is_some();
trace!("tee_verify_quote");
let (collateral_expiration_status, result) =
tee_verify_quote(quote, collateral, current_time, None, p_supplemental_data).map_err(
|e| Quote3Error {
msg: "tee_verify_quote",
inner: e,
},
)?;
// check supplemental data if necessary
let (advisories, earliest_expiration_date, tcb_level_date_tag) = if has_sup {
unsafe {
let supp_data = supp_data.assume_init();
// convert to valid UTF-8 string
let ads = CStr::from_bytes_until_nul(cast_slice(&supp_data.sa_list[..]))
.ok()
.and_then(|s| CStr::to_str(s).ok())
.into_iter()
.flat_map(|s| s.split(',').map(str::trim).map(String::from))
.filter(|s| !s.is_empty())
.collect();
(
ads,
supp_data.earliest_expiration_date,
supp_data.tcb_level_date_tag,
)
}
} else {
(vec![], 0, 0)
};
let quote = Quote::try_from_bytes(quote).map_err(|_| Quote3Error {
msg: "Quote::try_from_bytes",
inner: quote3_error_t::SGX_QL_QUOTE_FORMAT_UNSUPPORTED,
})?;
let res = QuoteVerificationResult {
collateral_expired: collateral_expiration_status != 0,
earliest_expiration_date,
tcb_level_date_tag,
result,
quote,
advisories,
};
Ok(res)
}
/// Get the attestation report in a Gramine enclave
pub fn sgx_gramine_get_quote(report_data: &[u8; 64]) -> Result<Box<[u8]>, GetQuoteError> {
pub fn sgx_gramine_get_quote(report_data: &[u8; 64]) -> Result<Box<[u8]>, QuoteError> {
let mut file = OpenOptions::new()
.write(true)
.open("/dev/attestation/user_report_data")
.map_err(|e| GetQuoteError {
msg: "Failed to open `/dev/attestation/user_report_data`".into(),
source: e,
})?;
.context("opening `/dev/attestation/user_report_data`")?;
file.write(report_data).map_err(|e| GetQuoteError {
msg: "Failed to write `/dev/attestation/user_report_data`".into(),
source: e,
})?;
file.write(report_data)
.context("writing `/dev/attestation/user_report_data`")?;
drop(file);
let mut file = OpenOptions::new()
.read(true)
.open("/dev/attestation/quote")
.map_err(|e| GetQuoteError {
msg: "Failed to open `/dev/attestation/quote`".into(),
source: e,
})?;
.context("opening `/dev/attestation/quote`")?;
let mut quote = Vec::new();
file.read_to_end(&mut quote).map_err(|e| GetQuoteError {
msg: "Failed to read `/dev/attestation/quote`".into(),
source: e,
})?;
file.read_to_end(&mut quote)
.context("reading `/dev/attestation/quote`")?;
Ok(quote.into_boxed_slice())
}
/// Wrapper func for error
/// TODO: move to intel_tee_quote_verification_rs
pub fn tee_qv_get_collateral(quote: &[u8]) -> Result<Collateral, Quote3Error> {
intel_tee_quote_verification_rs::tee_qv_get_collateral(quote).map_err(Into::into)
}

View file

@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2024 Matter Labs
//! Intel TDX helper functions.
pub use crate::sgx::tcblevel::{parse_tcb_levels, EnumSet, TcbLevel};
use crate::sgx::QuoteError;
pub use intel_tee_quote_verification_rs::Collateral;
use tdx_attest_rs::{tdx_att_get_quote, tdx_attest_error_t, tdx_report_data_t};
/// Get a TDX quote
pub fn tgx_get_quote(report_data_bytes: &[u8; 64]) -> Result<Box<[u8]>, QuoteError> {
let mut tdx_report_data = tdx_report_data_t { d: [0; 64usize] };
tdx_report_data.d.copy_from_slice(report_data_bytes);
let (error, quote) = tdx_att_get_quote(Some(&tdx_report_data), None, None, 0);
if error == tdx_attest_error_t::TDX_ATTEST_SUCCESS {
if let Some(quote) = quote {
Ok(quote.into())
} else {
Err(QuoteError::TdxAttGetQuote {
msg: "tdx_att_get_quote: No quote returned".into(),
inner: error,
})
}
} else {
Err(error.into())
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,12 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ teepotCrate }: teepotCrate.craneLib.buildPackage (
{ lib, pkgs, makeWrapper, teepotCrate }: teepotCrate.craneLib.buildPackage (
teepotCrate.commonArgs // {
pname = "teepot";
inherit (teepotCrate) cargoArtifacts;
nativeBuildInputs = teepotCrate.commonArgs.nativeBuildInputs ++ [ makeWrapper ];
passthru = {
inherit (teepotCrate) rustPlatform
rustVersion
@ -28,6 +30,7 @@
"verify_attestation"
"verify_era_proof_attestation"
];
postInstall = ''
removeReferencesToVendoredSources "$out" "$cargoVendorDir"
removeReferencesToVendoredSources "$out" "${teepotCrate.rustVersion}/lib/rustlib/"
@ -38,6 +41,11 @@
echo -n "''${!i} " >> $out/nix-support/propagated-user-env-packages
binname=''${i//_/-}
mv "$out/bin/$binname" "''${!i}/bin/"
makeWrapper "''${!i}/bin/$binname" "''${!i}/bin/$binname-dcap" \
--prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath [ pkgs.nixsgx.sgx-dcap.quote_verify pkgs.nixsgx.sgx-dcap.default_qpl pkgs.curl ]}" \
--set-default QCNL_CONF_PATH "${pkgs.nixsgx.sgx-dcap.default_qpl}/etc/sgx_default_qcnl.conf"
done
rmdir "$out/bin"
'';