diff --git a/src/backup/manifest.rs b/src/backup/manifest.rs index ec7d3104..0dbd6558 100644 --- a/src/backup/manifest.rs +++ b/src/backup/manifest.rs @@ -4,7 +4,7 @@ use std::path::Path; use serde_json::{json, Value}; -use crate::backup::{BackupDir, CryptMode}; +use crate::backup::{BackupDir, CryptMode, CryptConfig}; pub const MANIFEST_BLOB_NAME: &str = "index.json.blob"; pub const CLIENT_LOG_BLOB_NAME: &str = "client.log.blob"; @@ -84,8 +84,41 @@ impl BackupManifest { Ok(()) } - pub fn into_json(self) -> Value { - json!({ + pub fn signature(&self, crypt_config: &CryptConfig) -> [u8; 32] { + + let mut data = String::new(); + + data.push_str(self.snapshot.group().backup_type()); + data.push('\n'); + data.push_str(self.snapshot.group().backup_id()); + data.push('\n'); + data.push_str(&format!("{}", self.snapshot.backup_time().timestamp())); + data.push('\n'); + data.push('\n'); + + for info in self.files.iter() { + data.push_str(&info.filename); + data.push('\n'); + data.push_str(match info.crypt_mode { + CryptMode::None => "None", + CryptMode::SignOnly => "SignOnly", + CryptMode::Encrypt => "Encrypt", + }); + data.push('\n'); + data.push_str(&format!("{}", info.size)); + data.push('\n'); + data.push_str(&proxmox::tools::digest_to_hex(&info.csum)); + data.push('\n'); + + data.push('\n'); + } + + crypt_config.compute_auth_tag(data.as_bytes()) + } + + pub fn into_json(self, crypt_config: Option<&CryptConfig>) -> Value { + + let mut manifest = json!({ "backup-type": self.snapshot.group().backup_type(), "backup-id": self.snapshot.group().backup_id(), "backup-time": self.snapshot.backup_time().timestamp(), @@ -99,7 +132,14 @@ impl BackupManifest { })); acc }) - }) + }); + + if let Some(crypt_config) = crypt_config { + let sig = self.signature(crypt_config); + manifest["signature"] = proxmox::tools::digest_to_hex(&sig).into(); + } + + manifest } } diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index a5dd2146..6794068b 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1081,7 +1081,7 @@ async fn create_backup( } // create manifest (index.json) - let manifest = manifest.into_json(); + let manifest = manifest.into_json(crypt_config.as_ref().map(Arc::as_ref)); println!("Upload index.json to '{:?}'", repo); let manifest = serde_json::to_string_pretty(&manifest)?.into(); @@ -1272,18 +1272,17 @@ async fn restore(param: Value) -> Result { true, ).await?; - let manifest = client.download_manifest().await?; + let (manifest, backup_index_data) = client.download_manifest().await?; let (archive_name, archive_type) = parse_archive_type(archive_name); if archive_name == MANIFEST_BLOB_NAME { - let backup_index_data = manifest.into_json().to_string(); if let Some(target) = target { - replace_file(target, backup_index_data.as_bytes(), CreateOptions::new())?; + replace_file(target, &backup_index_data, CreateOptions::new())?; } else { let stdout = std::io::stdout(); let mut writer = stdout.lock(); - writer.write_all(backup_index_data.as_bytes()) + writer.write_all(&backup_index_data) .map_err(|err| format_err!("unable to pipe data - {}", err))?; } diff --git a/src/bin/proxmox_backup_client/catalog.rs b/src/bin/proxmox_backup_client/catalog.rs index ecc1278b..73ad6ad5 100644 --- a/src/bin/proxmox_backup_client/catalog.rs +++ b/src/bin/proxmox_backup_client/catalog.rs @@ -82,7 +82,7 @@ async fn dump_catalog(param: Value) -> Result { true, ).await?; - let manifest = client.download_manifest().await?; + let (manifest, _) = client.download_manifest().await?; let index = client.download_dynamic_index(&manifest, CATALOG_NAME).await?; @@ -181,7 +181,7 @@ async fn catalog_shell(param: Value) -> Result<(), Error> { .custom_flags(libc::O_TMPFILE) .open("/tmp")?; - let manifest = client.download_manifest().await?; + let (manifest, _) = client.download_manifest().await?; let index = client.download_dynamic_index(&manifest, &server_archive_name).await?; let most_used = index.find_most_used_chunks(8); diff --git a/src/bin/proxmox_backup_client/mount.rs b/src/bin/proxmox_backup_client/mount.rs index 15cd663c..73bb8d4c 100644 --- a/src/bin/proxmox_backup_client/mount.rs +++ b/src/bin/proxmox_backup_client/mount.rs @@ -139,7 +139,7 @@ async fn mount_do(param: Value, pipe: Option) -> Result { true, ).await?; - let manifest = client.download_manifest().await?; + let (manifest, _) = client.download_manifest().await?; if server_archive_name.ends_with(".didx") { let index = client.download_dynamic_index(&manifest, &server_archive_name).await?; diff --git a/src/client/backup_reader.rs b/src/client/backup_reader.rs index 9c9e14d1..0f121b4f 100644 --- a/src/client/backup_reader.rs +++ b/src/client/backup_reader.rs @@ -1,4 +1,4 @@ -use anyhow::{format_err, Error}; +use anyhow::{bail, format_err, Error}; use std::io::{Read, Write, Seek, SeekFrom}; use std::fs::File; use std::sync::Arc; @@ -123,7 +123,9 @@ impl BackupReader { } /// Download backup manifest (index.json) - pub async fn download_manifest(&self) -> Result { + /// + /// The manifest signature is verified if we have a crypt_config. + pub async fn download_manifest(&self) -> Result<(BackupManifest, Vec), Error> { use std::convert::TryFrom; @@ -131,10 +133,25 @@ impl BackupReader { self.download(MANIFEST_BLOB_NAME, &mut raw_data).await?; let blob = DataBlob::from_raw(raw_data)?; blob.verify_crc()?; - let data = blob.decode(self.crypt_config.as_ref().map(Arc::as_ref))?; + let data = blob.decode(None)?; let json: Value = serde_json::from_slice(&data[..])?; - BackupManifest::try_from(json) + let signature = json["signature"].as_str().map(String::from); + + let manifest = BackupManifest::try_from(json)?; + + if let Some(ref crypt_config) = self.crypt_config { + if let Some(signature) = signature { + let expected_signature = proxmox::tools::digest_to_hex(&manifest.signature(crypt_config)); + if signature != expected_signature { + bail!("wrong signature in manifest"); + } + } else { + // warn/fail? + } + } + + Ok((manifest, data)) } /// Download a .blob file