Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
- run: nix run nixpkgs#taplo -- fmt --check
6 changes: 3 additions & 3 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
Expand All @@ -37,7 +37,7 @@ jobs:
runs-on: [ matterlabs-default-infra-runners ]
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
Expand Down Expand Up @@ -75,7 +75,7 @@ jobs:
- { nixpackage: 'container-verify-era-proof-attestation-sgx' }
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
# teepot

Key Value store in a TEE with Remote Attestation for Authentication

## Introduction

This project is a key-value store that runs in a Trusted Execution Environment (TEE) and uses Remote Attestation for
Authentication.
The key-value store is implemented using Hashicorp Vault running in an Intel SGX enclave via the Gramine runtime.

## Parts of this project

- `teepot`: The main rust crate that abstracts TEEs and key-value stores.
Expand All @@ -22,6 +14,18 @@ The key-value store is implemented using Hashicorp Vault running in an Intel SGX
- `verify-attestation`: A client utility that verifies the attestation of an enclave.
- `tee-key-preexec`: A pre-exec utility that generates a p256 secret key and passes it as an environment variable to the
enclave along with the attestation quote containing the hash of the public key.
- `tdx_google`: A base VM running on Google Cloud TDX. It receives a container URL via the instance metadata,
measures the sha384 of the URL to RTMR3 and launches the container.
- `tdx-extend`: A utility to extend an RTMR register with a hash value.
- `rtmr-calc`: A utility to calculate RTMR1 and RTMR2 from a GPT disk, the linux kernel, the linux initrd
and a UKI (unified kernel image).
- `sha384-extend`: A utility to calculate RTMR registers after extending them with a digest.

## Vault

Part of this project is a key-value store that runs in a Trusted Execution Environment (TEE) and uses Remote Attestation
for Authentication. The key-value store is implemented using Hashicorp Vault running in an Intel SGX enclave via the
Gramine runtime.

## Development

Expand Down Expand Up @@ -96,3 +100,9 @@ Attributes:
isv_svn: 0
debug_enclave: False
```

### TDX VM testing

```shell
nixos-rebuild -L --flake .#tdxtest build-vm && ./result/bin/run-tdxtest-vm
```
45 changes: 45 additions & 0 deletions assets/gcloud-deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash

#
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Matter Labs
#

set -ex

NO=${NO:-1}

nix build -L .#tdx_google

gsutil cp result/tdx_base_1.vmdk gs://tdx_vms/

gcloud migration vms image-imports create \
--location=us-central1 \
--target-project=tdx-pilot \
--project=tdx-pilot \
--skip-os-adaptation \
--source-file=gs://tdx_vms/tdx_base_1.vmdk \
tdx-img-pre-"${NO}"

gcloud compute instances stop tdx-pilot --zone us-central1-c --project tdx-pilot || :
gcloud compute instances delete tdx-pilot --zone us-central1-c --project tdx-pilot || :

while gcloud migration vms image-imports list --location=us-central1 --project=tdx-pilot | grep -F RUNNING; do
sleep 1
done

gcloud compute images create \
--project tdx-pilot \
--guest-os-features=UEFI_COMPATIBLE,TDX_CAPABLE,GVNIC,VIRTIO_SCSI_MULTIQUEUE \
--storage-location=us-central1 \
--source-image=tdx-img-pre-"${NO}" \
tdx-img-f-"${NO}"

gcloud compute instances create tdx-pilot \
--machine-type c3-standard-4 --zone us-central1-c \
--confidential-compute-type=TDX \
--maintenance-policy=TERMINATE \
--image-project=tdx-pilot \
--project tdx-pilot \
--metadata=container_hub="docker.io",container_image="amd64/hello-world@sha256:e2fc4e5012d16e7fe466f5291c476431beaa1f9b90a5c2125b493ed28e2aba57" \
--image tdx-img-f-"${NO}"
87 changes: 53 additions & 34 deletions bin/verify-era-proof-attestation/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,65 @@
// Copyright (c) 2023-2024 Matter Labs

use crate::{args::AttestationPolicyArgs, client::JsonRpcClient};
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use hex::encode;
use secp256k1::Message;
use secp256k1::{ecdsa::Signature, Message};
use teepot::{
client::TcbLevel,
ethereum::recover_signer,
prover::reportdata::ReportData,
quote::{
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
QuoteVerificationResult, Report,
},
};
use tracing::{debug, info, warn};
use zksync_basic_types::L1BatchNumber;
use zksync_basic_types::{L1BatchNumber, H256};

struct TeeProof {
report: ReportData,
root_hash: H256,
signature: Vec<u8>,
}

impl TeeProof {
pub fn new(report: ReportData, root_hash: H256, signature: Vec<u8>) -> Self {
Self {
report,
root_hash,
signature,
}
}

pub fn verify(&self) -> Result<bool> {
match &self.report {
ReportData::V0(report) => {
let signature = Signature::from_compact(&self.signature)?;
let root_hash_msg = Message::from_digest_slice(&self.root_hash.0)?;
Ok(signature.verify(&root_hash_msg, &report.pubkey).is_ok())
}
ReportData::V1(report) => {
let ethereum_address_from_report = report.ethereum_addr;
let root_hash_msg = Message::from_digest_slice(self.root_hash.as_bytes())?;
let signature_bytes: [u8; 65] = self
.signature
.clone()
.try_into()
.map_err(|e| anyhow!("{:?}", e))?;
let ethereum_address_from_signature =
recover_signer(&signature_bytes, &root_hash_msg)?;
debug!(
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
self.root_hash,
encode(ethereum_address_from_report),
encode(ethereum_address_from_signature),
);
Ok(ethereum_address_from_signature == ethereum_address_from_report)
}
ReportData::Unknown(_) => Ok(false),
}
}
}

pub async fn verify_batch_proof(
quote_verification_result: &QuoteVerificationResult,
Expand All @@ -27,38 +73,11 @@ pub async fn verify_batch_proof(
return Ok(false);
}

let batch_no = batch_number.0;
let root_hash = node_client.get_root_hash(batch_number).await?;
let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
let signature_bytes: &[u8; 65] = signature.try_into()?;
let root_hash_bytes = root_hash.as_bytes();
let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
let ethereum_address_from_signature = recover_signer(signature_bytes, &root_hash_msg)?;
let verification_successful = ethereum_address_from_signature == ethereum_address_from_quote;
debug!(
batch_no,
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
root_hash,
encode(ethereum_address_from_quote),
encode(ethereum_address_from_signature),
);

if verification_successful {
info!(
batch_no,
signature = encode(signature),
ethereum_address = encode(ethereum_address_from_quote),
"Signature verified successfully."
);
} else {
warn!(
batch_no,
signature = encode(signature),
ethereum_address_from_signature = encode(ethereum_address_from_signature),
ethereum_address_from_quote = encode(ethereum_address_from_quote),
"Failed to verify signature!"
);
}
let report_data_bytes = quote_verification_result.quote.get_report_data();
let report_data = ReportData::try_from(report_data_bytes)?;
let tee_proof = TeeProof::new(report_data, root_hash, signature.to_vec());
let verification_successful = tee_proof.verify().is_ok();
Ok(verification_successful)
}

Expand Down
123 changes: 62 additions & 61 deletions crates/teepot-tee-quote-verification-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024 Matter Labs
// Copyright (c) 2024-2025 Matter Labs

// SPDX-License-Identifier: BSD-3-Clause
/*
Expand Down Expand Up @@ -39,22 +39,14 @@
//! This is a safe wrapper for **sgx-dcap-quoteverify-sys**.

use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::slice;
use std::{marker::PhantomData, ops::Deref, slice};

use intel_tee_quote_verification_sys as qvl_sys;

pub use qvl_sys::quote3_error_t;
pub use qvl_sys::sgx_ql_qe_report_info_t;
pub use qvl_sys::sgx_ql_qv_result_t;
pub use qvl_sys::sgx_ql_qv_supplemental_t;
pub use qvl_sys::sgx_ql_qve_collateral_t;
pub use qvl_sys::sgx_ql_request_policy_t;
pub use qvl_sys::sgx_qv_path_type_t;
pub use qvl_sys::tdx_ql_qve_collateral_t;
pub use qvl_sys::tee_supp_data_descriptor_t;
pub use qvl_sys::{
quote3_error_t, sgx_ql_qe_report_info_t, sgx_ql_qv_result_t, sgx_ql_qv_supplemental_t,
sgx_ql_qve_collateral_t, sgx_ql_request_policy_t, sgx_qv_path_type_t, tdx_ql_qve_collateral_t,
tee_qv_free_collateral, tee_supp_data_descriptor_t,
};

/// When the Quoting Verification Library is linked to a process, it needs to know the proper enclave loading policy.
/// The library may be linked with a long lived process, such as a service, where it can load the enclaves and leave
Expand Down Expand Up @@ -328,43 +320,6 @@ pub struct Collateral {
pub qe_identity: Box<[u8]>,
}

impl TryFrom<&sgx_ql_qve_collateral_t> for Collateral {
type Error = ();

fn try_from(value: &sgx_ql_qve_collateral_t) -> Result<Self, Self::Error> {
fn to_boxed_slice(p: *mut ::std::os::raw::c_char, size: u32) -> Result<Box<[u8]>, ()> {
if p.is_null() {
return Err(());
}
Ok(Box::from(unsafe {
slice::from_raw_parts(p as _, size as _)
}))
}

Ok(Collateral {
major_version: unsafe { value.__bindgen_anon_1.__bindgen_anon_1.major_version },
minor_version: unsafe { value.__bindgen_anon_1.__bindgen_anon_1.minor_version },
tee_type: value.tee_type,
pck_crl_issuer_chain: to_boxed_slice(
value.pck_crl_issuer_chain,
value.pck_crl_issuer_chain_size,
)?,
root_ca_crl: to_boxed_slice(value.root_ca_crl, value.root_ca_crl_size)?,
pck_crl: to_boxed_slice(value.pck_crl, value.pck_crl_size)?,
tcb_info_issuer_chain: to_boxed_slice(
value.tcb_info_issuer_chain,
value.tcb_info_issuer_chain_size,
)?,
tcb_info: to_boxed_slice(value.tcb_info, value.tcb_info_size)?,
qe_identity_issuer_chain: to_boxed_slice(
value.qe_identity_issuer_chain,
value.qe_identity_issuer_chain_size,
)?,
qe_identity: to_boxed_slice(value.qe_identity, value.qe_identity_size)?,
})
}
}

// referential struct
struct SgxQlQveCollateralT<'a> {
inner: sgx_ql_qve_collateral_t,
Expand Down Expand Up @@ -432,22 +387,68 @@ impl Deref for SgxQlQveCollateralT<'_> {
/// - *SGX_QL_ERROR_UNEXPECTED*
///
pub fn tee_qv_get_collateral(quote: &[u8]) -> Result<Collateral, quote3_error_t> {
fn try_into_collateral(
buf: *const sgx_ql_qve_collateral_t,
buf_len: u32,
) -> Result<Collateral, quote3_error_t> {
fn try_into_boxed_slice(
p: *mut ::std::os::raw::c_char,
size: u32,
) -> Result<Box<[u8]>, quote3_error_t> {
if p.is_null() || !p.is_aligned() {
return Err(quote3_error_t::SGX_QL_ERROR_MAX);
}
Ok(Box::from(unsafe {
slice::from_raw_parts(p as _, size as _)
}))
}

if buf.is_null()
|| (buf_len as usize) < size_of::<sgx_ql_qve_collateral_t>()
|| !buf.is_aligned()
{
return Err(quote3_error_t::SGX_QL_ERROR_MAX);
}

// SAFETY: buf is not null, buf_len is not zero, and buf is aligned.
let collateral = unsafe { *buf };

Ok(Collateral {
major_version: unsafe { collateral.__bindgen_anon_1.__bindgen_anon_1.major_version },
minor_version: unsafe { collateral.__bindgen_anon_1.__bindgen_anon_1.minor_version },
tee_type: collateral.tee_type,
pck_crl_issuer_chain: try_into_boxed_slice(
collateral.pck_crl_issuer_chain,
collateral.pck_crl_issuer_chain_size,
)?,
root_ca_crl: try_into_boxed_slice(collateral.root_ca_crl, collateral.root_ca_crl_size)?,
pck_crl: try_into_boxed_slice(collateral.pck_crl, collateral.pck_crl_size)?,
tcb_info_issuer_chain: try_into_boxed_slice(
collateral.tcb_info_issuer_chain,
collateral.tcb_info_issuer_chain_size,
)?,
tcb_info: try_into_boxed_slice(collateral.tcb_info, collateral.tcb_info_size)?,
qe_identity_issuer_chain: try_into_boxed_slice(
collateral.qe_identity_issuer_chain,
collateral.qe_identity_issuer_chain_size,
)?,
qe_identity: try_into_boxed_slice(collateral.qe_identity, collateral.qe_identity_size)?,
})
}

let mut buf = std::ptr::null_mut();
let mut buf_len = 0u32;

match unsafe {
qvl_sys::tee_qv_get_collateral(quote.as_ptr(), quote.len() as u32, &mut buf, &mut buf_len)
} {
quote3_error_t::SGX_QL_SUCCESS => {
assert!(!buf.is_null());
assert!(buf_len > 0);
assert_eq!(
(buf as usize) % mem::align_of::<sgx_ql_qve_collateral_t>(),
0
);
// SAFETY: buf is not null, buf_len is not zero, and buf is aligned.
let orig_collateral = &unsafe { *(buf as *const sgx_ql_qve_collateral_t) };
Collateral::try_from(orig_collateral).map_err(|_| quote3_error_t::SGX_QL_ERROR_MAX)
let collateral = try_into_collateral(buf as _, buf_len);

match unsafe { tee_qv_free_collateral(buf) } {
quote3_error_t::SGX_QL_SUCCESS => collateral,
error_code => Err(error_code),
}
}
error_code => Err(error_code),
}
Expand Down
Loading
Loading