Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 50 additions & 1 deletion crates/enclave-contract/contracts/UpgradeOperator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ contract UpgradeOperator {
// Keep track of all tags for enumeration if needed
bytes32[] public acceptedTags;
bytes32[] public deprecatedTags;
bool public initialIsDeprecated = false;

// Track if a tag exists to prevent duplicates
mapping(bytes32 => bool) public tagExists;
Expand Down Expand Up @@ -128,7 +129,14 @@ contract UpgradeOperator {
* @param measurementHash Hash of the measurements to check
*/
function isAccepted(bytes32 measurementHash) external view returns (bool) {
return bytes(acceptedMeasurements[measurementHash].tag).length > 0;
if (
!initialIsDeprecated &&
measurementHash == _getMeasurementHash(getInitialMeasurements())
) {
return true;
} else {
return bytes(acceptedMeasurements[measurementHash].tag).length > 0;
}
}

/**
Expand All @@ -147,6 +155,17 @@ contract UpgradeOperator {
function getAcceptedMeasurement(
bytes32 measurementHash
) external view returns (Measurements memory) {
if (
!initialIsDeprecated &&
measurementHash == _getMeasurementHash(getInitialMeasurements())
) {
Measurements memory initialMeasurements = getInitialMeasurements();

if (_getMeasurementHash(initialMeasurements) == measurementHash) {
return initialMeasurements;
}
}

require(
bytes(acceptedMeasurements[measurementHash].tag).length > 0,
"Measurement not found"
Expand Down Expand Up @@ -196,4 +215,34 @@ contract UpgradeOperator {
)
);
}

function getInitialMeasurements()
public
pure
returns (Measurements memory)
{
Measurements memory m;

m.tag = "Initial";

m
.mrtd = hex"f858414aef26d52a3b21614bab4bafab13b3ed62ebdd9d46a6be799228c2e27bc0d025cc6e4e90daff827cbe0316bbd9";

m
.mrseam = hex"49b66faa451d19ebbdbe89371b8daf2b65aa3984ec90110343e9e2eec116af08850fa20e3b1aa9a874d77a65380ee7e6";

m.registrar_slots = new uint8[](4);
m.registrar_slots[0] = 0;
m.registrar_slots[1] = 1;
m.registrar_slots[2] = 2;
m.registrar_slots[3] = 3;

m.registrar_values = new bytes[](4);
m.registrar_values[0] = new bytes(48); // All zeros by default
m.registrar_values[1] = new bytes(48);
m.registrar_values[2] = new bytes(48);
m.registrar_values[3] = new bytes(48);

return m;
}
}
31 changes: 31 additions & 0 deletions crates/enclave-contract/tests/MultisigUpgradeOperator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,37 @@ contract MultisigUpgradeOperatorTest is Test {
assertFalse(upgradeOperator.isDeprecated(measurement1Hash));
}

function testInitialMeasurments() public view {
// Setup test measurements
UpgradeOperator.Measurements memory m;

m.tag = "Initial";

m
.mrtd = hex"f858414aef26d52a3b21614bab4bafab13b3ed62ebdd9d46a6be799228c2e27bc0d025cc6e4e90daff827cbe0316bbd9";

m
.mrseam = hex"49b66faa451d19ebbdbe89371b8daf2b65aa3984ec90110343e9e2eec116af08850fa20e3b1aa9a874d77a65380ee7e6";

m.registrar_slots = new uint8[](4);
m.registrar_slots[0] = 0;
m.registrar_slots[1] = 1;
m.registrar_slots[2] = 2;
m.registrar_slots[3] = 3;

m.registrar_values = new bytes[](4);
m.registrar_values[0] = new bytes(48); // All zeros by default
m.registrar_values[1] = new bytes(48);
m.registrar_values[2] = new bytes(48);
m.registrar_values[3] = new bytes(48);

console.logBytes(m.mrtd);
console.logBytes(m.mrseam);
bytes32 measurementHash = upgradeOperator.getMeasurementHash(m);

assertTrue(upgradeOperator.isAccepted(measurementHash));
}

// Test get vote status
function testGetVoteStatus() public {
vm.prank(signer1);
Expand Down
1 change: 0 additions & 1 deletion crates/enclave-contract/tests/multisig_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ pub async fn test_multisig_upgrade_operator_workflow() -> Result<(), anyhow::Err
// Wait a bit for the transaction to be processed
sleep(Duration::from_secs(2));

// Test data for proposal
let params = Measurements {
tag: "AzureV1".to_string(),
mrtd: [
Expand Down
5 changes: 5 additions & 0 deletions crates/enclave-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ sha2.workspace = true

[dev-dependencies]
serial_test = "3.2.0"
tempfile = "3.17.1"

[features]
default = []
systemctl = []

3 changes: 2 additions & 1 deletion crates/enclave-server/src/key_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const PURPOSE_DERIVE_SALT: &[u8] = b"seismic-purpose-derive-salt";
/// Prefix used in domain separation when deriving purpose-specific keys.
const PREFIX: &str = "seismic-purpose";

#[derive(Zeroize, ZeroizeOnDrop)]
#[derive(Zeroize, ZeroizeOnDrop, Clone)]
pub struct Key([u8; 32]);

impl AsRef<[u8]> for Key {
Expand All @@ -18,6 +18,7 @@ impl AsRef<[u8]> for Key {
}
}

#[derive(Clone)]
pub struct KeyManager {
root_key: Key,
}
Expand Down
9 changes: 8 additions & 1 deletion crates/enclave-server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
mod attestation;
mod key_manager;
mod server;
mod snapshot;
mod summit;
pub mod utils;

const ENCLAVE_DEFAULT_ENDPOINT_IP: &str = "127.0.0.1";
const DEFAULT_RETH_RPC: &str = "127.0.0.1:8545";
pub const ENCLAVE_DEFAULT_ENDPOINT_PORT: u16 = 7878;
const DEFAULT_ENCLAVE_SUMMIT_SOCKET: &str = "/tmp/reth_enclave_socket.ipc";

use anyhow::Result;
use clap::Parser;
Expand All @@ -32,6 +35,10 @@ pub struct Args {
#[arg(long)]
pub peers: Vec<String>,

/// path to unix socket used to communicate with Summit
#[arg(long, default_value_t = DEFAULT_ENCLAVE_SUMMIT_SOCKET.to_string())]
pub summit_socket: String,

#[arg(long, default_value_t =DEFAULT_RETH_RPC.to_string())]
pub reth_rpc_url: String,

Expand All @@ -48,7 +55,7 @@ impl Args {
if self.mock {
start_mock_server(addr).await
} else {
server::start_server(addr, self.genesis_node, self.peers).await
server::start_server(addr, self).await
}
}
}
141 changes: 129 additions & 12 deletions crates/enclave-server/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use crate::{attestation::AttestationAgent, key_manager::KeyManager, utils::anyhow_to_rpc_error};
use crate::snapshot::{DATA_DISK_DIR, SNAPSHOT_FILE_PREFIX, restore_from_encrypted_snapshot};
use crate::{
Args,
attestation::AttestationAgent,
key_manager::KeyManager,
summit::run_summit_socket,
utils::{anyhow_to_rpc_error, string_to_rpc_error},
};
use dcap_rs::types::quotes::version_4::QuoteV4;
use jsonrpsee::{
core::{RpcResult, async_trait},
Expand All @@ -9,7 +16,10 @@ use seismic_enclave::{
AttestationGetEvidenceResponse, GetPurposeKeysResponse, ShareRootKeyResponse,
TdxQuoteRpcClient as _, api::TdxQuoteRpcServer,
};
use std::fs;
use std::path::Path;
use std::{net::SocketAddr, time::Duration};
use tokio::io::AsyncWriteExt as _;
use tracing::{info, warn};

pub struct TdxQuoteServer {
Expand Down Expand Up @@ -79,36 +89,143 @@ impl TdxQuoteRpcServer for TdxQuoteServer {
}

/// Prepares an encrypted snapshot
async fn prepare_encrypted_snapshot(&self) -> RpcResult<()> {
todo!()
async fn download_encrypted_snapshot(&self, epoch: u64, url: String) -> RpcResult<()> {
// Download the file
let response = reqwest::get(&url)
.await
.map_err(|e| string_to_rpc_error(format!("Failed to download snapshot: {}", e)))?;

if !response.status().is_success() {
return Err(string_to_rpc_error(format!(
"HTTP error: {}",
response.status()
)));
}

let bytes = response
.bytes()
.await
.map_err(|e| string_to_rpc_error(format!("Failed to read response body: {}", e)))?;

// Create the filename
let filename = format!("{SNAPSHOT_FILE_PREFIX}-{epoch}.tar.lz4.enc");

// Write to file
let mut file = tokio::fs::File::create(format!("{DATA_DISK_DIR}/{filename}"))
.await
.map_err(|e| {
string_to_rpc_error(format!("Failed to create file {}: {}", filename, e))
})?;

file.write_all(&bytes).await.map_err(|e| {
string_to_rpc_error(format!("Failed to write to file {}: {}", filename, e))
})?;

Ok(())
}

/// Restores from an encrypted snapshot
async fn restore_from_encrypted_snapshot(&self) -> RpcResult<()> {
todo!()
async fn restore_from_encrypted_snapshot(&self, epoch: u64) -> RpcResult<()> {
restore_from_encrypted_snapshot(
&self.key_manager,
epoch,
format!("{DATA_DISK_DIR}/{epoch}-snapshot.tar.lz4.enc"),
)
.await
.map_err(|e| string_to_rpc_error(format!("Failed to restore from checkpoint: {e}")))
}

/// Get an encrypted snapshot from this servers database
async fn get_encrypted_snapshot(&self, epoch: u64) -> RpcResult<Vec<u8>> {
let snapshot_path = format!(
"{}/{}-{}.tar.lz4.enc",
DATA_DISK_DIR, SNAPSHOT_FILE_PREFIX, epoch
);

if !fs::exists(&snapshot_path).unwrap_or_default() {
return Err(string_to_rpc_error(format!(
"No snapshot for epoch {epoch} stored"
)));
}

fs::read(snapshot_path).map_err(|e| {
string_to_rpc_error(format!(
"Failed to read snapshot for epoch {}: {}",
epoch, e
))
})
}

/// List all encrypted snapshots stored in this enclave
async fn list_all_encrypted_snapshots(&self) -> RpcResult<Vec<u64>> {
let dir_path = Path::new(DATA_DISK_DIR);

let entries = fs::read_dir(dir_path).map_err(|e| {
string_to_rpc_error(format!("Failed to read snapshots directory: {}", e))
})?;

let mut epochs = Vec::new();
let prefix = format!("{}-", SNAPSHOT_FILE_PREFIX);
let suffix = ".tar.lz4.enc";

for entry in entries {
let entry = entry.map_err(|e| {
string_to_rpc_error(format!("Failed to read directory entry: {}", e))
})?;

if let Some(filename) = entry.file_name().to_str() {
if filename.starts_with(&prefix) && filename.ends_with(suffix) {
// Extract epoch from filename
let epoch_str = filename
.strip_prefix(&prefix)
.and_then(|s| s.strip_suffix(suffix));

if let Some(epoch_str) = epoch_str {
if let Ok(epoch) = epoch_str.parse::<u64>() {
epochs.push(epoch);
}
}
}
}
}

epochs.sort_unstable();
Ok(epochs)
}

/// List all encrypted snapshots stored in this enclave
async fn list_latest_encrypted_snapshots(&self) -> RpcResult<u64> {
let all_snapshots = self.list_all_encrypted_snapshots().await?;

all_snapshots
.into_iter()
.max()
.ok_or_else(|| string_to_rpc_error("No snapshots found".to_string()))
}
}

pub async fn start_server(
addr: SocketAddr,
genesis_node: bool,
peers: Vec<String>,
) -> anyhow::Result<()> {
pub async fn start_server(addr: SocketAddr, args: Args) -> anyhow::Result<()> {
let attestation_agent = AttestationAgent::new().unwrap();

let key_manager = if genesis_node {
let key_manager = if args.genesis_node {
KeyManager::new_as_genesis()?
} else {
fetch_root_key_from_peers(peers, &attestation_agent).await
fetch_root_key_from_peers(args.peers, &attestation_agent).await
};

let summit_handle = tokio::spawn(run_summit_socket(args.summit_socket, key_manager.clone()));

let server = ServerBuilder::default().build(addr).await?;

let handle = server.start(TdxQuoteServer::new(attestation_agent, key_manager).into_rpc());

println!("TDX Quote JSON-RPC Server started at {}", addr);

handle.stopped().await;

// server stopped abort the summit socket
summit_handle.abort();

Ok(())
}

Expand Down
Loading
Loading