Skip to content

Commit 24a6ac6

Browse files
authored
Merge pull request #4 from osservatorionessuno/improve-backups
improve backups
2 parents 95ca410 + bb19a78 commit 24a6ac6

File tree

6 files changed

+260
-128
lines changed

6 files changed

+260
-128
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
with:
3535
toolchain: stable
3636
components: rustfmt, clippy
37+
- uses: Swatinem/rust-cache@v2
3738
- name: Cargo fmt
3839
uses: actions-rs/cargo@v1
3940
with:
@@ -76,6 +77,7 @@ jobs:
7677
with:
7778
toolchain: stable
7879
components: rustfmt, clippy
80+
- uses: Swatinem/rust-cache@v2
7981
- name: Install sqlx-cli
8082
uses: actions-rs/cargo@v1
8183
with:

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ reqwest = { version = "0.12.24", default-features = false, features = ["json", "
2626
rtnetlink = "0.18"
2727
rustls-pemfile = "2.2.0"
2828
serde = { version = "1.0.228", features = ["derive"] }
29+
serde_bytes = "0.11.19"
30+
serde_json = "1.0.145"
2931
sha2 = { version = "0.11.0-rc.2", features = ["oid"] }
3032
sysinfo = { version = "0.37", features = ["serde"] }
3133
systemctl = "0.5.0"

client/src/lib.rs

Lines changed: 42 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use rtnetlink::{
1919
link::{LinkAttribute, LinkExtentMask},
2020
},
2121
};
22+
use serde::{Deserialize, Serialize};
2223
use std::{
2324
collections::BTreeMap,
24-
ffi::CStr,
25-
ffi::CString,
25+
ffi::{CStr, CString},
2626
fs,
2727
net::{IpAddr, Ipv4Addr, Ipv6Addr},
2828
os::unix::fs::chown,
@@ -40,6 +40,7 @@ const MANGLE_CHAIN_NAME: &CStr = c"mangle";
4040
const MANGLE_CHAIN_PRIORITY: i32 = libc::NF_IP_PRI_MANGLE;
4141
const NAT_CHAIN_NAME: &CStr = c"nat";
4242
pub const TOR_INSTANCE_LIB_DIR: &str = "/var/lib/tor-instances";
43+
const TOR_MASTER_KEY_NAME: &str = "ed25519_master_id_secret_key";
4344

4445
pub fn collect_specs() -> anyhow::Result<HwSpecs> {
4546
let sys = System::new_all();
@@ -420,92 +421,68 @@ fn send_and_process(batch: &FinalizedBatch) -> anyhow::Result<()> {
420421
Ok(())
421422
}
422423

423-
// Tor keys are stored in TPM NV storage
424-
// Serialization format: <instance-name>\0<key-bytes>\0<instance-name>\0<key-bytes>...
425-
fn serialize_relay_keys(relays: &[ResolvedRelayRecord]) -> anyhow::Result<Vec<u8>> {
426-
let mut buffer = Vec::new();
424+
#[derive(Debug, Serialize, Deserialize)]
425+
pub struct RelayKeyData {
426+
pub instance_name: String,
427+
#[serde(with = "serde_bytes")]
428+
pub ed25519_master_id_secret_key: Vec<u8>,
429+
}
430+
431+
pub fn serialize_relay_keys(relays: &[ResolvedRelayRecord]) -> anyhow::Result<Vec<u8>> {
432+
let mut relay_keys = Vec::new();
427433

428434
for relay in relays {
429435
let keys_dir = Path::new(&TOR_INSTANCE_LIB_DIR)
430436
.join(&relay.name)
431437
.join("keys");
432438

433-
let key_path = keys_dir.join("ed25519_master_id_secret_key");
439+
let key_path = keys_dir.join(TOR_MASTER_KEY_NAME);
434440
let key_data = fs::read(&key_path)
435441
.map_err(|e| anyhow::anyhow!("Failed to read key for relay {}: {}", relay.name, e))?;
436442

437-
buffer.extend_from_slice(relay.name.as_bytes());
438-
buffer.push(0);
439-
440-
buffer.extend_from_slice(&key_data);
441-
buffer.push(0);
443+
relay_keys.push(RelayKeyData {
444+
instance_name: relay.name.clone(),
445+
ed25519_master_id_secret_key: key_data,
446+
});
442447
}
448+
let json = serde_json::to_string(&relay_keys)
449+
.map_err(|e| anyhow::anyhow!("Failed to serialize backup to JSON: {}", e))?;
443450

444-
// Pad to NV_SIZE if needed
445-
if buffer.len() > NV_SIZE {
446-
anyhow::bail!(
447-
"Serialized keys exceed NV_SIZE: {} > {}",
448-
buffer.len(),
449-
NV_SIZE
450-
);
451-
}
452-
buffer.resize(NV_SIZE, 0);
451+
let bytes = json.into_bytes();
453452

454-
Ok(buffer)
453+
Ok(bytes)
455454
}
456455

457-
fn deserialize_relay_keys(data: &[u8]) -> anyhow::Result<Vec<(String, Vec<u8>)>> {
458-
let mut result = Vec::new();
459-
let mut pos = 0;
460-
461-
while pos < data.len() {
462-
// Skip padding zeros at the end
463-
if data[pos] == 0 {
464-
break;
465-
}
466-
467-
// Read instance name until null terminator
468-
let name_end = data[pos..]
469-
.iter()
470-
.position(|&b| b == 0)
471-
.ok_or_else(|| anyhow::anyhow!("Missing null terminator for instance name"))?;
472-
473-
let name = String::from_utf8(data[pos..pos + name_end].to_vec())
474-
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in instance name: {}", e))?;
475-
pos += name_end + 1; // Skip null terminator
456+
pub fn deserialize_relay_keys(data: &[u8]) -> anyhow::Result<Vec<RelayKeyData>> {
457+
let trimmed = data
458+
.iter()
459+
.rposition(|&b| b != 0)
460+
.map(|pos| &data[..=pos])
461+
.unwrap_or(data);
476462

477-
if pos >= data.len() {
478-
anyhow::bail!("Truncated key data for instance {}", name);
479-
}
463+
let json_str = std::str::from_utf8(trimmed)
464+
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in backup data: {}", e))?;
480465

481-
// Read key data until null terminator
482-
let key_end = data[pos..]
483-
.iter()
484-
.position(|&b| b == 0)
485-
.ok_or_else(|| anyhow::anyhow!("Missing null terminator for key data"))?;
486-
487-
let key_data = data[pos..pos + key_end].to_vec();
488-
pos += key_end + 1; // Skip null terminator
489-
490-
result.push((name, key_data));
491-
}
466+
let backup: Vec<RelayKeyData> = serde_json::from_str(json_str)
467+
.map_err(|e| anyhow::anyhow!("Failed to deserialize backup data: {}", e))?;
492468

493-
Ok(result)
469+
Ok(backup)
494470
}
495471

496472
pub fn backup_tor_keys_to_tpm(
497473
ctx: &mut TpmContext,
498474
nv_handle: NvIndexHandle,
475+
nv_size: usize,
499476
relays: &[ResolvedRelayRecord],
500477
) -> anyhow::Result<()> {
501478
println!("Backing up Tor relay keys to TPM...");
502479

503480
let serialized = serialize_relay_keys(relays)?;
504-
let key_array: [u8; NV_SIZE] = serialized
505-
.try_into()
506-
.map_err(|_| anyhow::anyhow!("Failed to convert to array"))?;
481+
// Pad data to nv_size
482+
let mut padded_data = serialized;
483+
padded_data.resize(nv_size, 0);
507484

508-
nv_write_key(ctx, nv_handle, &key_array)?;
485+
nv_write_data(ctx, nv_handle, &padded_data)?;
509486

510487
println!("Successfully backed up {} relay keys to TPM", relays.len());
511488
Ok(())
@@ -514,18 +491,19 @@ pub fn backup_tor_keys_to_tpm(
514491
pub fn restore_tor_keys_from_tpm(
515492
ctx: &mut TpmContext,
516493
nv_handle: NvIndexHandle,
494+
_nv_size: usize,
517495
relays: &[ResolvedRelayRecord],
518496
) -> anyhow::Result<()> {
519497
println!("Restoring Tor relay keys from TPM");
520498

521-
let data = nv_read_key(ctx, nv_handle)?;
499+
let data = nv_read_data(ctx, nv_handle)?;
522500
let restored_keys = deserialize_relay_keys(&data)?;
523501

524502
for relay in relays {
525503
let key_data = restored_keys
526504
.iter()
527-
.find(|(name, _)| name == &relay.name)
528-
.map(|(_, key)| key)
505+
.find(|rk| rk.instance_name == relay.name)
506+
.map(|rk| rk.ed25519_master_id_secret_key.clone())
529507
.ok_or_else(|| anyhow::anyhow!("No backup found for relay {}", relay.name))?;
530508

531509
let keys_dir = Path::new(&TOR_INSTANCE_LIB_DIR)
@@ -534,7 +512,7 @@ pub fn restore_tor_keys_from_tpm(
534512

535513
fs::create_dir_all(&keys_dir)?;
536514

537-
let key_path = keys_dir.join("ed25519_master_id_secret_key");
515+
let key_path = keys_dir.join(TOR_MASTER_KEY_NAME);
538516
fs::write(&key_path, key_data)
539517
.map_err(|e| anyhow::anyhow!("Failed to write key for relay {}: {}", relay.name, e))?;
540518

client/src/main.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ async fn cmd_start(
131131
let client = build_client(insecure).await?;
132132

133133
// Get NV handle, ensuring index is created if not existing
134-
let nv_handle = get_nv_index_handle(context)?;
134+
let (nv_handle, nv_size) = get_nv_index_handle(context)?;
135135

136136
// Get the AK name for the challenge
137137
let (_ak_pub, ak_name, _qualified_name) = context.read_public(ak_ecc)?;
@@ -371,7 +371,7 @@ async fn cmd_start(
371371

372372
if !is_first_time && !skip_restore {
373373
println!("Fetch tor keys backup");
374-
restore_tor_keys_from_tpm(context, nv_handle, &relays)?;
374+
restore_tor_keys_from_tpm(context, nv_handle, nv_size, &relays)?;
375375
}
376376

377377
let systemctl = SystemCtl::default();
@@ -387,7 +387,7 @@ async fn cmd_start(
387387

388388
tokio::time::sleep(Duration::from_secs(wait_seconds)).await;
389389

390-
backup_tor_keys_to_tpm(context, nv_handle, &relays)?;
390+
backup_tor_keys_to_tpm(context, nv_handle, nv_size, &relays)?;
391391

392392
Ok(())
393393
}
@@ -423,16 +423,17 @@ async fn cmd_tpm(config: TpmCommands, tpm2: Option<String>) -> anyhow::Result<()
423423
);
424424
}
425425
TpmCommands::NvWrite => {
426-
let plain_text = "A".repeat(NV_SIZE);
427-
let array_ref: &[u8; NV_SIZE] = plain_text.as_bytes().try_into().unwrap();
428-
let nv_index_handle = get_nv_index_handle(context).unwrap();
429-
nv_write_key(context, nv_index_handle, array_ref).unwrap();
426+
let (nv_index_handle, size) = get_nv_index_handle(context).unwrap();
427+
let plain_text = "A".repeat(size);
428+
let bytes: Vec<u8> = plain_text.as_bytes().into();
429+
430+
nv_write_data(context, nv_index_handle, &bytes).unwrap();
430431

431432
println!("=== Data successfully written to TPM NV ===");
432433
}
433434
TpmCommands::NvRead => {
434-
let nv_index_handle = get_nv_index_handle(context).unwrap();
435-
let bytes = nv_read_key(context, nv_index_handle).unwrap();
435+
let (nv_index_handle, _size) = get_nv_index_handle(context).unwrap();
436+
let bytes = nv_read_data(context, nv_index_handle).unwrap();
436437
let text = std::str::from_utf8(&bytes).unwrap();
437438
println!("=== {} read from TPM NV ===", text);
438439
}

0 commit comments

Comments
 (0)