1#![forbid(unsafe_code)]
5#![expect(missing_docs)]
6
7#[cfg(all(not(test), feature = "encryption"))]
8crypto::ensure_single_backend!();
9
10mod storage_backend;
15#[cfg(feature = "test_helpers")]
16mod test;
17mod uefi_nvram;
18mod vmgs_json;
19
20#[cfg(feature = "test_helpers")]
21use crate::test::TestOperation;
22use anyhow::Result;
23use clap::Args;
24use clap::Parser;
25use clap::Subcommand;
26use disk_backend::Disk;
27use disk_vhd1::Vhd1Disk;
28use fs_err::File;
29use pal_async::DefaultPool;
30use std::io::prelude::*;
31use std::path::Path;
32use std::path::PathBuf;
33use thiserror::Error;
34use uefi_nvram::UefiNvramOperation;
35use vmgs::Error as VmgsError;
36use vmgs::GspType;
37use vmgs::Vmgs;
38use vmgs::vmgs_helpers::get_active_header;
39use vmgs::vmgs_helpers::read_headers;
40use vmgs::vmgs_helpers::validate_header;
41use vmgs_format::EncryptionAlgorithm;
42use vmgs_format::FileId;
43use vmgs_format::VMGS_BYTES_PER_BLOCK;
44use vmgs_format::VMGS_DEFAULT_CAPACITY;
45use vmgs_format::VMGS_ENCRYPTION_KEY_SIZE;
46use vmgs_format::VmgsHeader;
47
48const ONE_MEGA_BYTE: u64 = 1024 * 1024;
49const ONE_GIGA_BYTE: u64 = ONE_MEGA_BYTE * 1024;
50const VHD_DISK_FOOTER_PACKED_SIZE: u64 = 512;
51
52#[derive(Debug, Error)]
53pub(crate) enum Error {
54 #[error("VMGS file IO")]
55 VmgsFile(#[source] std::io::Error),
56 #[error("VHD file error")]
57 Vhd1(#[source] disk_vhd1::OpenError),
58 #[error("Invalid disk")]
59 InvalidDisk(#[source] disk_backend::InvalidDisk),
60 #[error("Internal VMGS error")]
61 Vmgs(#[from] VmgsError),
62 #[error("VMGS file already exists")]
63 FileExists,
64 #[cfg(feature = "encryption")]
65 #[error("Adding encryption key")]
66 EncryptionKey(#[source] VmgsError),
67 #[error("Data file / STDOUT IO")]
68 DataFile(#[source] std::io::Error),
69 #[error("The VMGS file has zero size")]
70 ZeroSize,
71 #[error("Invalid VMGS file size: {0} {1}")]
72 InvalidVmgsFileSize(u64, String),
73 #[error("Key file IO")]
74 KeyFile(#[source] std::io::Error),
75 #[error("Key must be {0} bytes long, is {1} bytes instead")]
76 InvalidKeySize(u64, u64),
77 #[error("File is not encrypted")]
78 NotEncrypted,
79 #[error("File must be decrypted to perform this operation but no key was provided")]
80 EncryptedNoKey,
81 #[error("VmgsStorageBackend")]
82 VmgsStorageBackend(#[from] storage_backend::EncryptionNotSupported),
83 #[error("NVRAM storage")]
84 NvramStorage(#[from] uefi_nvram_storage::NvramStorageError),
85 #[error("UEFI NVRAM variable parsing")]
86 NvramParsing(#[from] uefi_nvram_specvars::ParseError),
87 #[error("NVRAM entry not found: {0}")]
88 MissingNvramEntry(ucs2::Ucs2LeVec),
89 #[error("GUID parsing")]
90 Guid(#[from] guid::ParseError),
91 #[error("JSON parsing")]
92 SerdeJson(#[from] serde_json::Error),
93 #[error("Bad JSON contents: {0}")]
94 Json(String),
95 #[error("File ID {0:?} already exists. Use `--allow-overwrite` to ignore.")]
96 FileIdExists(FileId),
97 #[error("VMGS file is encrypted using GspById")]
98 GspByIdEncryption,
99 #[error("VMGS file is encrypted using an unknown encryption scheme")]
100 GspUnknown,
101 #[error("VMGS file is using an unknown encryption algorithm")]
102 EncryptionUnknown,
103 #[cfg(feature = "test_helpers")]
104 #[error("Unable to parse IGVM file")]
105 IgvmFile(#[source] anyhow::Error),
106}
107
108#[derive(Debug, Clone, Copy)]
118#[repr(i32)]
119enum ExitCode {
120 Error = 1,
121 NotEncrypted = 2,
122 Empty = 3,
123 NotFound = 4,
124 V1Format = 5,
125 GspById = 6,
126 GspUnknown = 7,
127}
128
129#[derive(Args)]
130struct FilePathArg {
131 #[clap(short = 'f', long, alias = "filepath")]
133 file_path: PathBuf,
134}
135
136#[derive(Args)]
137struct KeyPathArg {
138 #[clap(short = 'k', long, alias = "keypath")]
140 key_path: Option<PathBuf>,
141}
142
143#[derive(Args)]
144struct FileIdArg {
145 #[clap(short = 'i', long, alias = "fileid", value_parser = parse_file_id)]
147 file_id: FileId,
148}
149
150#[derive(Parser)]
151#[clap(name = "vmgstool", about = "Tool to interact with VMGS files.")]
152#[clap(long_about = r#"Tool to interact with VMGS files.
153
154Unless otherwise noted, everything written to STDOUT and STDERR is unstable
155and subject to change. Automated consumers of VmgsTool should generally parse
156only the exit code. In some cases, the STDOUT of specific subcommands may be
157made stable (ex: query-size). STDERR is for human-readable debug messages and
158is never stable."#)]
159struct CliArgs {
160 #[clap(short = 'v', long)]
163 verbose: bool,
164
165 #[clap(subcommand)]
166 opt: Options,
167}
168
169#[derive(Subcommand)]
170enum Options {
171 Create {
176 #[command(flatten)]
177 file_path: FilePathArg,
178 #[clap(short = 's', long, alias = "filesize")]
180 file_size: Option<u64>,
181 #[clap(
185 short = 'k',
186 long,
187 alias = "keypath",
188 requires = "encryption_algorithm"
189 )]
190 key_path: Option<PathBuf>,
191 #[clap(short = 'e', long, alias = "encryptionalgorithm", requires = "key_path", value_parser = parse_encryption_algorithm)]
195 encryption_algorithm: Option<EncryptionAlgorithm>,
196 #[clap(long, alias = "forcecreate")]
199 force_create: bool,
200 },
201 Write {
205 #[command(flatten)]
206 file_path: FilePathArg,
207 #[clap(short = 'd', long, alias = "datapath")]
209 data_path: PathBuf,
210 #[command(flatten)]
211 file_id: FileIdArg,
212 #[command(flatten)]
213 key_path: KeyPathArg,
214 #[clap(long, alias = "allowoverwrite")]
216 allow_overwrite: bool,
217 },
218 Dump {
224 #[command(flatten)]
225 file_path: FilePathArg,
226 #[clap(short = 'd', long, alias = "datapath")]
228 data_path: Option<PathBuf>,
229 #[command(flatten)]
230 file_id: FileIdArg,
231 #[command(flatten)]
232 key_path: KeyPathArg,
233 #[clap(long, conflicts_with = "data_path")]
235 raw_stdout: bool,
236 },
237 DumpHeaders {
239 #[command(flatten)]
240 file_path: FilePathArg,
241 },
242 QuerySize {
246 #[command(flatten)]
247 file_path: FilePathArg,
248 #[command(flatten)]
249 file_id: FileIdArg,
250 },
251 UpdateKey {
255 #[command(flatten)]
256 file_path: FilePathArg,
257 #[clap(short = 'k', long, alias = "keypath")]
259 key_path: PathBuf,
260 #[clap(short = 'n', long, alias = "newkeypath")]
262 new_key_path: PathBuf,
263 #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
265 encryption_algorithm: EncryptionAlgorithm,
266 },
267 Encrypt {
269 #[command(flatten)]
270 file_path: FilePathArg,
271 #[clap(short = 'k', long, alias = "keypath")]
273 key_path: PathBuf,
274 #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
276 encryption_algorithm: EncryptionAlgorithm,
277 },
278 QueryEncryption {
280 #[command(flatten)]
281 file_path: FilePathArg,
282 },
283 Move {
285 #[command(flatten)]
286 file_path: FilePathArg,
287 #[clap(long, alias = "src", value_parser = parse_file_id)]
289 src_file_id: FileId,
290 #[clap(long, alias = "dst", value_parser = parse_file_id)]
292 dst_file_id: FileId,
293 #[command(flatten)]
294 key_path: KeyPathArg,
295 #[clap(long, alias = "allowoverwrite")]
297 allow_overwrite: bool,
298 },
299 Delete {
301 #[command(flatten)]
302 file_path: FilePathArg,
303 #[command(flatten)]
304 file_id: FileIdArg,
305 },
306 DumpFileTable {
308 #[command(flatten)]
309 file_path: FilePathArg,
310 #[command(flatten)]
311 key_path: KeyPathArg,
312 },
313 UefiNvram {
315 #[clap(subcommand)]
316 operation: UefiNvramOperation,
317 },
318 #[cfg(feature = "test_helpers")]
319 Test {
321 #[clap(subcommand)]
322 operation: TestOperation,
323 },
324}
325
326fn parse_file_id(file_id: &str) -> Result<FileId, std::num::ParseIntError> {
327 Ok(match file_id {
328 "FILE_TABLE" => FileId::FILE_TABLE,
329 "BIOS_NVRAM" => FileId::BIOS_NVRAM,
330 "TPM_PPI" => FileId::TPM_PPI,
331 "TPM_NVRAM" => FileId::TPM_NVRAM,
332 "RTC_SKEW" => FileId::RTC_SKEW,
333 "ATTEST" => FileId::ATTEST,
334 "KEY_PROTECTOR" => FileId::KEY_PROTECTOR,
335 "VM_UNIQUE_ID" => FileId::VM_UNIQUE_ID,
336 "GUEST_FIRMWARE" => FileId::GUEST_FIRMWARE,
337 "CUSTOM_UEFI" => FileId::CUSTOM_UEFI,
338 "GUEST_WATCHDOG" => FileId::GUEST_WATCHDOG,
339 "HW_KEY_PROTECTOR" => FileId::HW_KEY_PROTECTOR,
340 "GUEST_SECRET_KEY" => FileId::GUEST_SECRET_KEY,
341 "HIBERNATION_FIRMWARE" => FileId::HIBERNATION_FIRMWARE,
342 "PLATFORM_SEED" => FileId::PLATFORM_SEED,
343 "PROVENANCE_DOC" => FileId::PROVENANCE_DOC,
344 "TPM_NVRAM_BACKUP" => FileId::TPM_NVRAM_BACKUP,
345 "EXTENDED_FILE_TABLE" => FileId::EXTENDED_FILE_TABLE,
346 v => FileId(v.parse::<u32>()?),
347 })
348}
349
350fn parse_encryption_algorithm(algorithm: &str) -> Result<EncryptionAlgorithm, &'static str> {
351 match algorithm {
352 "AES_GCM" => Ok(EncryptionAlgorithm::AES_GCM),
353 _ => Err("Encryption algorithm not supported"),
354 }
355}
356
357fn extract_version(ver: u32) -> String {
358 let major = (ver >> 16) & 0xFF;
359 let minor = ver & 0xFF;
360 format!("{major}.{minor}")
361}
362
363fn parse_legacy_args() -> Vec<String> {
364 use std::env;
365 let mut args: Vec<String> = env::args().collect();
366 if let Some(cmd) = args.get(1) {
367 let cmd_lower = cmd.to_ascii_lowercase();
368 let new_cmd = match &cmd_lower[..] {
369 "-c" | "-create" => Some("create"),
370 "-w" | "-write" => Some("write"),
371 "-r" | "-dump" => Some("dump"),
372 "-rh" | "-dumpheaders" => Some("dump-headers"),
373 "-qs" | "-querysize" => Some("query-size"),
374 "-uk" | "-updatekey" => Some("update-key"),
375 "-e" | "-encrypt" => Some("encrypt"),
376 _ => None,
377 };
378
379 if let Some(new_cmd) = new_cmd {
380 eprintln!("Warning: Using legacy arguments. Please migrate to the new syntax.");
382 args[1] = new_cmd.to_string();
383
384 let mut index = 2;
385 while let Some(arg) = args.get(index) {
386 let arg_lower = arg.to_ascii_lowercase();
387 if let Some(new_arg) = match &arg_lower[..] {
388 "-f" | "-filepath" => Some("--file-path"),
389 "-s" | "-filesize" => Some("--file-size"),
390 "-i" | "-fileid" => Some("--file-id"),
391 "-d" | "-datapath" => Some("--data-path"),
392 "-ow" | "-allowoverwrite" => Some("--allow-overwrite"),
393 "-k" | "-keypath" => Some("--key-path"),
394 "-n" | "-newkeypath" => Some("--new-key-path"),
395 "-ea" | "-encryptionalgorithm" => Some("--encryption-algorithm"),
396 "-fc" | "-forcecreate" => Some("--force-create"),
397 _ => None,
398 } {
399 args[index] = new_arg.to_string();
400 }
401 index += 1;
402 }
403 }
404 }
405 args
406}
407
408pub fn init_tracing(verbose: bool) {
410 use tracing::level_filters::LevelFilter;
411 use tracing_subscriber::filter::Targets;
412 use tracing_subscriber::layer::SubscriberExt;
413 use tracing_subscriber::util::SubscriberInitExt;
414
415 let targets = if verbose {
416 Targets::new().with_default(LevelFilter::TRACE)
417 } else {
418 Targets::new()
419 .with_default(LevelFilter::OFF)
420 .with_target("vmgstool", LevelFilter::INFO)
421 };
422
423 tracing_subscriber::fmt()
424 .with_ansi(false)
425 .log_internal_errors(true)
426 .with_writer(std::io::stderr)
427 .with_max_level(LevelFilter::TRACE)
428 .finish()
429 .with(targets)
430 .init();
431}
432
433fn main() {
434 DefaultPool::run_with(async |_| match do_main().await {
435 Ok(_) => tracing::info!("The operation completed successfully."),
436 Err(e) => {
437 let exit_code = match e {
438 Error::NotEncrypted => ExitCode::NotEncrypted,
439 Error::GspByIdEncryption => ExitCode::GspById,
440 Error::GspUnknown => ExitCode::GspUnknown,
441 Error::Vmgs(VmgsError::EmptyFile) | Error::ZeroSize => ExitCode::Empty,
442 Error::Vmgs(VmgsError::FileInfoNotAllocated(_)) => ExitCode::NotFound,
443 Error::Vmgs(VmgsError::V1Format) => ExitCode::V1Format,
444 _ => ExitCode::Error,
445 };
446
447 match e {
448 Error::NotEncrypted | Error::GspByIdEncryption | Error::GspUnknown => {}
450 Error::Vmgs(inner)
452 if matches!(
453 inner,
454 VmgsError::EmptyFile | VmgsError::FileInfoNotAllocated(_)
455 ) =>
456 {
457 tracing::info!("{}", inner)
458 }
459 e => {
461 tracing::error!("{}", e);
462 let mut error_source = std::error::Error::source(&e);
463 while let Some(e2) = error_source {
464 tracing::error!("{}", e2);
465 error_source = e2.source();
466 }
467 }
468 };
469
470 tracing::info!(
471 "The operation completed with exit code: {} ({:?})",
472 exit_code as i32,
473 exit_code
474 );
475
476 std::process::exit(exit_code as i32);
477 }
478 })
479}
480
481async fn do_main() -> Result<(), Error> {
482 let args = CliArgs::parse_from(parse_legacy_args());
483 init_tracing(args.verbose);
484
485 match args.opt {
486 Options::Create {
487 file_path,
488 file_size,
489 key_path,
490 encryption_algorithm,
491 force_create,
492 } => {
493 let encryption_alg_key = encryption_algorithm.map(|x| (x, key_path.unwrap()));
494 vmgs_file_create(
495 file_path.file_path,
496 file_size,
497 force_create,
498 encryption_alg_key,
499 )
500 .await
501 .map(|_| ())
502 }
503 Options::Dump {
504 file_path,
505 data_path,
506 file_id,
507 key_path,
508 raw_stdout,
509 } => {
510 vmgs_file_read(
511 file_path.file_path,
512 data_path,
513 file_id.file_id,
514 key_path.key_path,
515 raw_stdout,
516 )
517 .await
518 }
519 Options::Write {
520 file_path,
521 data_path,
522 file_id,
523 key_path,
524 allow_overwrite,
525 } => {
526 vmgs_file_write(
527 file_path.file_path,
528 data_path,
529 file_id.file_id,
530 key_path.key_path,
531 allow_overwrite,
532 )
533 .await
534 }
535 Options::DumpHeaders { file_path } => vmgs_file_dump_headers(file_path.file_path).await,
536 Options::QuerySize { file_path, file_id } => {
537 vmgs_file_query_file_size(file_path.file_path, file_id.file_id)
538 .await
539 .map(|_| ())
540 }
541 Options::UpdateKey {
542 file_path,
543 key_path,
544 new_key_path,
545 encryption_algorithm,
546 } => {
547 vmgs_file_update_key(
548 file_path.file_path,
549 encryption_algorithm,
550 Some(key_path),
551 new_key_path,
552 )
553 .await
554 }
555 Options::Encrypt {
556 file_path,
557 key_path,
558 encryption_algorithm,
559 } => {
560 vmgs_file_update_key(
561 file_path.file_path,
562 encryption_algorithm,
563 None as Option<PathBuf>,
564 key_path,
565 )
566 .await
567 }
568 Options::QueryEncryption { file_path } => {
569 vmgs_file_query_encryption(file_path.file_path).await
570 }
571 Options::Move {
572 file_path,
573 src_file_id,
574 dst_file_id,
575 key_path,
576 allow_overwrite,
577 } => {
578 vmgs_file_move(
579 file_path.file_path,
580 src_file_id,
581 dst_file_id,
582 key_path.key_path,
583 allow_overwrite,
584 )
585 .await
586 }
587 Options::Delete { file_path, file_id } => {
588 vmgs_file_delete(file_path.file_path, file_id.file_id).await
589 }
590 Options::DumpFileTable {
591 file_path,
592 key_path,
593 } => vmgs_file_dump_file_table(file_path.file_path, key_path.key_path).await,
594 Options::UefiNvram { operation } => uefi_nvram::do_command(operation).await,
595 #[cfg(feature = "test_helpers")]
596 Options::Test { operation } => test::do_command(operation).await,
597 }
598}
599
600async fn vmgs_file_update_key(
601 file_path: impl AsRef<Path>,
602 encryption_alg: EncryptionAlgorithm,
603 key_path: Option<impl AsRef<Path>>,
604 new_key_path: impl AsRef<Path>,
605) -> Result<(), Error> {
606 let new_encryption_key = read_key_path(new_key_path)?;
607 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteRequire).await?;
608
609 vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key.as_ref()).await
610}
611
612#[cfg_attr(not(feature = "encryption"), expect(unused_variables))]
613async fn vmgs_update_key(
614 vmgs: &mut Vmgs,
615 encryption_alg: EncryptionAlgorithm,
616 new_encryption_key: &[u8],
617) -> Result<(), Error> {
618 #[cfg(not(feature = "encryption"))]
619 unreachable!("encryption requires the encryption feature");
620 #[cfg(feature = "encryption")]
621 {
622 tracing::info!("Updating encryption key");
623 vmgs.update_encryption_key(new_encryption_key, encryption_alg)
624 .await
625 .map_err(Error::EncryptionKey)?;
626
627 Ok(())
628 }
629}
630
631async fn vmgs_file_create(
632 path: impl AsRef<Path>,
633 file_size: Option<u64>,
634 force_create: bool,
635 encryption_alg_key: Option<(EncryptionAlgorithm, impl AsRef<Path>)>,
636) -> Result<Vmgs, Error> {
637 let disk = vhdfiledisk_create(path, file_size, force_create)?;
638
639 let encryption_key = encryption_alg_key
640 .as_ref()
641 .map(|(_, key_path)| read_key_path(key_path))
642 .transpose()?;
643 let encryption_alg_key =
644 encryption_alg_key.map(|(alg, _)| (alg, encryption_key.as_ref().unwrap()));
645
646 let vmgs = vmgs_create(disk, encryption_alg_key).await?;
647
648 Ok(vmgs)
649}
650
651fn vhdfiledisk_create(
652 path: impl AsRef<Path>,
653 req_file_size: Option<u64>,
654 force_create: bool,
655) -> Result<Disk, Error> {
656 const MIN_VMGS_FILE_SIZE: u64 = 4 * VMGS_BYTES_PER_BLOCK as u64;
657 const SECTOR_SIZE: u64 = 512;
658
659 let file_size = req_file_size.unwrap_or(VMGS_DEFAULT_CAPACITY);
661 if file_size < MIN_VMGS_FILE_SIZE || !file_size.is_multiple_of(SECTOR_SIZE) {
662 return Err(Error::InvalidVmgsFileSize(
663 file_size,
664 format!(
665 "Must be a multiple of {} and at least {}",
666 SECTOR_SIZE, MIN_VMGS_FILE_SIZE
667 ),
668 ));
669 }
670
671 let exists = Path::new(path.as_ref()).exists();
674
675 tracing::info!("Creating file: {}", path.as_ref().display());
677 let file = match fs_err::OpenOptions::new()
678 .read(true)
679 .write(true)
680 .create(true)
681 .create_new(!force_create)
682 .open(path.as_ref())
683 {
684 Ok(file) => file,
685 Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
686 return Err(Error::FileExists);
687 }
688 Err(err) => return Err(Error::VmgsFile(err)),
689 };
690
691 let existing_size = exists
693 .then(|| {
694 Ok(file
695 .metadata()?
696 .len()
697 .checked_sub(VHD_DISK_FOOTER_PACKED_SIZE))
698 })
699 .transpose()
700 .map_err(Error::VmgsFile)?
701 .flatten();
702 let needs_resize =
703 !exists || existing_size.is_none_or(|existing_size| file_size != existing_size);
704
705 let default_label = if file_size == VMGS_DEFAULT_CAPACITY {
707 " (default)"
708 } else {
709 ""
710 };
711 if needs_resize {
712 tracing::info!(
713 "Setting file size to {}{}{}",
714 file_size,
715 default_label,
716 existing_size
717 .map(|s| format!(" (previous size: {s})"))
718 .unwrap_or_default(),
719 );
720 file.set_len(file_size).map_err(Error::VmgsFile)?;
721 } else {
722 tracing::info!(
723 "File size is already {}{}, skipping resize",
724 file_size,
725 default_label
726 );
727 }
728
729 let disk = if needs_resize {
731 None
732 } else {
733 Vhd1Disk::open_fixed(file.try_clone().map_err(Error::VmgsFile)?.into(), false)
734 .inspect_err(|e| tracing::info!("No valid VHD header found in existing file: {e:#}"))
735 .ok()
736 };
737
738 let disk = match disk {
740 Some(disk) => {
741 tracing::info!("Valid VHD footer already exists, skipping VHD format");
742 disk
743 }
744 None => {
745 tracing::info!("Formatting VHD");
746 Vhd1Disk::make_fixed(file.file()).map_err(Error::Vhd1)?;
747 Vhd1Disk::open_fixed(file.into(), false).map_err(Error::Vhd1)?
748 }
749 };
750
751 Disk::new(disk).map_err(Error::InvalidDisk)
752}
753
754#[cfg_attr(
755 not(feature = "encryption"),
756 expect(unused_mut),
757 expect(unused_variables)
758)]
759async fn vmgs_create(
760 disk: Disk,
761 encryption_alg_key: Option<(EncryptionAlgorithm, &[u8; VMGS_ENCRYPTION_KEY_SIZE])>,
762) -> Result<Vmgs, Error> {
763 tracing::info!("Formatting VMGS");
764 let mut vmgs = Vmgs::format_new(disk, None).await?;
765
766 if let Some((algorithm, encryption_key)) = encryption_alg_key {
767 tracing::info!("Adding encryption key");
768 #[cfg(feature = "encryption")]
769 vmgs.update_encryption_key(encryption_key, algorithm)
770 .await
771 .map_err(Error::EncryptionKey)?;
772 #[cfg(not(feature = "encryption"))]
773 unreachable!("Encryption requires the encryption feature");
774 }
775
776 Ok(vmgs)
777}
778
779async fn vmgs_file_write(
780 file_path: impl AsRef<Path>,
781 data_path: impl AsRef<Path>,
782 file_id: FileId,
783 key_path: Option<impl AsRef<Path>>,
784 allow_overwrite: bool,
785) -> Result<(), Error> {
786 tracing::info!(
787 "Opening source (raw data file): {}",
788 data_path.as_ref().display()
789 );
790
791 let mut file = File::open(data_path.as_ref()).map_err(Error::DataFile)?;
792 let mut buf = Vec::new();
793
794 file.read_to_end(&mut buf).map_err(Error::DataFile)?;
795
796 tracing::info!("Read {} bytes", buf.len());
797
798 let encrypt = key_path.is_some();
799 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteIgnore).await?;
800
801 vmgs_write(&mut vmgs, file_id, &buf, encrypt, allow_overwrite).await?;
802
803 Ok(())
804}
805
806async fn vmgs_write(
807 vmgs: &mut Vmgs,
808 file_id: FileId,
809 data: &[u8],
810 encrypt: bool,
811 allow_overwrite: bool,
812) -> Result<(), Error> {
813 tracing::info!("Writing {}", file_id);
814
815 if let Ok(info) = vmgs.get_file_info(file_id) {
816 if !allow_overwrite && info.valid_bytes > 0 {
817 return Err(Error::FileIdExists(file_id));
818 }
819 if !encrypt && info.encrypted {
820 tracing::warn!("Overwriting encrypted file with plaintext data")
821 }
822 }
823
824 if encrypt {
825 #[cfg(feature = "encryption")]
826 vmgs.write_file_encrypted(file_id, data).await?;
827 #[cfg(not(feature = "encryption"))]
828 unreachable!("Encryption requires the encryption feature");
829 } else {
830 vmgs.write_file_allow_overwrite_encrypted(file_id, data)
831 .await?;
832 }
833
834 Ok(())
835}
836
837async fn vmgs_file_read(
839 file_path: impl AsRef<Path>,
840 data_path: Option<impl AsRef<Path>>,
841 file_id: FileId,
842 key_path: Option<impl AsRef<Path>>,
843 raw_stdout: bool,
844) -> Result<(), Error> {
845 let decrypt = key_path.is_some();
846 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadOnlyWarn).await?;
847
848 let file_info = vmgs.get_file_info(file_id)?;
849 if !decrypt && file_info.encrypted {
850 tracing::warn!("Reading encrypted file without decrypting");
851 }
852
853 let buf = vmgs_read(&mut vmgs, file_id, decrypt).await?;
854
855 tracing::info!("Read {} bytes", buf.len());
856 if buf.len() != file_info.valid_bytes as usize {
857 tracing::warn!("Bytes read from VMGS doesn't match file info");
858 }
859
860 if let Some(path) = data_path {
861 tracing::info!("Writing contents to {}", path.as_ref().display());
862 let mut file = File::create(path.as_ref()).map_err(Error::DataFile)?;
863 file.write_all(&buf).map_err(Error::DataFile)?;
864 } else {
865 tracing::info!("Writing contents to stdout");
866 if raw_stdout {
867 let mut stdout = std::io::stdout();
868 stdout.write_all(&buf).map_err(Error::DataFile)?;
869 } else {
870 for c in buf.chunks(16) {
871 for b in c {
872 print!("0x{:02x},", b);
873 }
874 println!(
875 "{:missing$}// {}",
876 ' ',
877 c.iter()
878 .map(|c| if c.is_ascii_graphic() {
879 *c as char
880 } else {
881 '.'
882 })
883 .collect::<String>(),
884 missing = (16 - c.len()) * 5 + 1
885 );
886 }
887 }
888 }
889
890 Ok(())
891}
892
893async fn vmgs_read(vmgs: &mut Vmgs, file_id: FileId, decrypt: bool) -> Result<Vec<u8>, Error> {
894 tracing::info!("Reading {}", file_id);
895 Ok(if decrypt {
896 vmgs.read_file(file_id).await?
897 } else {
898 vmgs.read_file_raw(file_id).await?
899 })
900}
901
902async fn vmgs_file_move(
903 file_path: impl AsRef<Path>,
904 src: FileId,
905 dst: FileId,
906 key_path: Option<impl AsRef<Path>>,
907 allow_overwrite: bool,
908) -> Result<(), Error> {
909 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteRequire).await?;
910
911 vmgs_move(&mut vmgs, src, dst, allow_overwrite).await
912}
913
914async fn vmgs_move(
915 vmgs: &mut Vmgs,
916 src: FileId,
917 dst: FileId,
918 allow_overwrite: bool,
919) -> Result<(), Error> {
920 tracing::info!("Moving {} to {}", src, dst);
921
922 vmgs.move_file(src, dst, allow_overwrite).await?;
923
924 Ok(())
925}
926
927async fn vmgs_file_delete(file_path: impl AsRef<Path>, file_id: FileId) -> Result<(), Error> {
928 let mut vmgs = vmgs_file_open(
929 file_path,
930 None as Option<PathBuf>,
931 OpenMode::ReadWriteIgnore,
932 )
933 .await?;
934
935 vmgs_delete(&mut vmgs, file_id).await
936}
937
938async fn vmgs_delete(vmgs: &mut Vmgs, file_id: FileId) -> Result<(), Error> {
939 tracing::info!("Deleting {}", file_id);
940
941 vmgs.delete_file(file_id).await?;
942
943 Ok(())
944}
945
946async fn vmgs_file_dump_file_table(
947 file_path: impl AsRef<Path>,
948 key_path: Option<impl AsRef<Path>>,
949) -> Result<(), Error> {
950 let vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadOnlyWarn).await?;
951
952 vmgs_dump_file_table(&vmgs)
953}
954
955fn vmgs_dump_file_table(vmgs: &Vmgs) -> Result<(), Error> {
956 println!("FILE TABLE");
957 println!(
958 "{0:^7} {1:^25} {2:^9} {3:^9} {4:^9}",
959 "File ID", "File Name", "Allocated", "Valid", "Encrypted",
960 );
961 println!(
962 "{} {} {} {} {}",
963 "-".repeat(7),
964 "-".repeat(25),
965 "-".repeat(9),
966 "-".repeat(9),
967 "-".repeat(9),
968 );
969 for (file_id, file_info) in vmgs.dump_file_table() {
970 println!(
971 "{0:>7} {1:^25?} {2:>9} {3:>9} {4:^9}",
972 file_id.0,
973 file_id,
974 file_info.allocated_bytes,
975 file_info.valid_bytes,
976 file_info.encrypted,
977 );
978 }
979
980 Ok(())
981}
982
983async fn vmgs_file_dump_headers(file_path: impl AsRef<Path>) -> Result<(), Error> {
984 tracing::info!("Opening VMGS File: {}", file_path.as_ref().display());
985
986 let file = File::open(file_path.as_ref()).map_err(Error::VmgsFile)?;
987 let disk = vhdfiledisk_open(file, OpenMode::ReadOnlyIgnore)?;
988
989 let (headers, res0) = match read_headers(disk).await {
990 Ok(headers) => (Some(headers), Ok(())),
991 Err((e, headers)) => (headers, Err(e.into())),
992 };
993
994 if let Some(headers) = headers {
995 let res1 = vmgs_dump_headers(&headers.0, &headers.1);
996 if res0.is_err() { res0 } else { res1 }
997 } else {
998 res0
999 }
1000}
1001
1002fn vmgs_dump_headers(header1: &VmgsHeader, header2: &VmgsHeader) -> Result<(), Error> {
1003 println!("FILE HEADERS");
1004 println!("{0:<23} {1:^70} {2:^70}", "Field", "Header 1", "Header 2");
1005 println!("{} {} {}", "-".repeat(23), "-".repeat(70), "-".repeat(70));
1006
1007 let signature1 = format!("{:#018x}", header1.signature);
1008 let signature2 = format!("{:#018x}", header2.signature);
1009 println!(
1010 "{0:<23} {1:>70} {2:>70}",
1011 "Signature:", signature1, signature2
1012 );
1013
1014 println!(
1015 "{0:<23} {1:>70} {2:>70}",
1016 "Version:",
1017 extract_version(header1.version),
1018 extract_version(header2.version)
1019 );
1020 println!(
1021 "{0:<23} {1:>70x} {2:>70x}",
1022 "Checksum:", header1.checksum, header2.checksum
1023 );
1024 println!(
1025 "{0:<23} {1:>70} {2:>70}",
1026 "Sequence:", header1.sequence, header2.sequence
1027 );
1028 println!(
1029 "{0:<23} {1:>70} {2:>70}",
1030 "HeaderSize:", header1.header_size, header2.header_size
1031 );
1032
1033 let file_table_offset1 = format!("{:#010x}", header1.file_table_offset);
1034 let file_table_offset2 = format!("{:#010x}", header2.file_table_offset);
1035 println!(
1036 "{0:<23} {1:>70} {2:>70}",
1037 "FileTableOffset:", file_table_offset1, file_table_offset2
1038 );
1039
1040 println!(
1041 "{0:<23} {1:>70} {2:>70}",
1042 "FileTableSize:", header1.file_table_size, header2.file_table_size
1043 );
1044
1045 let encryption_algorithm1 = format!("{:#06x}", header1.encryption_algorithm.0);
1046 let encryption_algorithm2 = format!("{:#06x}", header2.encryption_algorithm.0);
1047 println!(
1048 "{0:<23} {1:>70} {2:>70}",
1049 "EncryptionAlgorithm:", encryption_algorithm1, encryption_algorithm2
1050 );
1051
1052 let markers1 = format!("{:#06x}", header1.markers.into_bits());
1053 let markers2 = format!("{:#06x}", header2.markers.into_bits());
1054
1055 println!("{0:<23} {1:>70} {2:>70}", "Markers:", markers1, markers2);
1056
1057 println!("{0:<23}", "MetadataKey1:");
1058
1059 let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[0].nonce));
1060 let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[0].nonce));
1061 println!(
1062 " {0:<19} {1:>70} {2:>70}",
1063 "Nonce:", key1_nonce, key2_nonce
1064 );
1065
1066 let key1_reserved = format!("{:#010x}", header1.metadata_keys[0].reserved);
1067 let key2_reserved = format!("{:#010x}", header2.metadata_keys[0].reserved);
1068 println!(
1069 " {0:<19} {1:>70} {2:>70}",
1070 "Reserved:", key1_reserved, key2_reserved
1071 );
1072
1073 let key1_auth_tag = format!(
1074 "0x{}",
1075 hex::encode(header1.metadata_keys[0].authentication_tag)
1076 );
1077 let key2_auth_tag = format!(
1078 "0x{}",
1079 hex::encode(header2.metadata_keys[0].authentication_tag)
1080 );
1081 println!(
1082 " {0:<19} {1:>70} {2:>70}",
1083 "AuthenticationTag:", key1_auth_tag, key2_auth_tag
1084 );
1085
1086 let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[0].encryption_key));
1087 let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[0].encryption_key));
1088 println!(
1089 " {0:<19} {1:>70} {2:>70}",
1090 "EncryptionKey:", key1_encryption_key, key2_encryption_key
1091 );
1092
1093 println!("{0:<23}", "MetadataKey2:");
1094 let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[1].nonce));
1095 let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[1].nonce));
1096 println!(
1097 " {0:<19} {1:>70} {2:>70}",
1098 "Nonce:", key1_nonce, key2_nonce
1099 );
1100
1101 let key1_reserved = format!("0x{:#010x}", header1.metadata_keys[1].reserved);
1102 let key2_reserved = format!("0x{:#010x}", header2.metadata_keys[1].reserved);
1103 println!(
1104 " {0:<19} {1:>70} {2:>70}",
1105 "Reserved:", key1_reserved, key2_reserved
1106 );
1107
1108 let key1_auth_tag = format!(
1109 "0x{}",
1110 hex::encode(header1.metadata_keys[1].authentication_tag)
1111 );
1112 let key2_auth_tag = format!(
1113 "0x{}",
1114 hex::encode(header2.metadata_keys[1].authentication_tag)
1115 );
1116 println!(
1117 " {0:<19} {1:>70} {2:>70}",
1118 "AuthenticationTag:", key1_auth_tag, key2_auth_tag
1119 );
1120
1121 let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[1].encryption_key));
1122 let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[1].encryption_key));
1123 println!(
1124 " {0:<19} {1:>70} {2:>70}",
1125 "EncryptionKey:", key1_encryption_key, key2_encryption_key
1126 );
1127
1128 let key1_reserved1 = format!("0x{:#010x}", header1.reserved_1);
1129 let key2_reserved1 = format!("0x{:#010x}", header2.reserved_1);
1130 println!(
1131 "{0:<23} {1:>70} {2:>70}",
1132 "Reserved:", key1_reserved1, key2_reserved1
1133 );
1134
1135 println!("{} {} {}\n", "-".repeat(23), "-".repeat(70), "-".repeat(70));
1136
1137 print!("Verifying header 1... ");
1138 let header1_result = validate_header(header1);
1139 match &header1_result {
1140 Ok(_) => println!("[VALID]"),
1141 Err(e) => println!("[INVALID] Error: {}", e),
1142 }
1143
1144 print!("Verifying header 2... ");
1145 let header2_result = validate_header(header2);
1146 match &header2_result {
1147 Ok(_) => println!("[VALID]"),
1148 Err(e) => println!("[INVALID] Error: {}", e),
1149 }
1150
1151 match get_active_header(header1_result, header2_result) {
1152 Ok(active_index) => match active_index {
1153 0 => println!("Active header is 1"),
1154 1 => println!("Active header is 2"),
1155 _ => unreachable!(),
1156 },
1157 Err(e) => {
1158 println!("Unable to determine active header");
1159 return Err(Error::Vmgs(e));
1160 }
1161 }
1162
1163 Ok(())
1164}
1165
1166#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1167#[expect(clippy::enum_variant_names)]
1168enum OpenMode {
1169 ReadOnlyIgnore,
1171 ReadOnlyWarn,
1173 ReadWriteIgnore,
1175 ReadWriteRequire,
1177}
1178
1179impl OpenMode {
1180 fn write(&self) -> bool {
1181 match self {
1182 OpenMode::ReadOnlyIgnore | OpenMode::ReadOnlyWarn => false,
1183 OpenMode::ReadWriteIgnore | OpenMode::ReadWriteRequire => true,
1184 }
1185 }
1186}
1187
1188async fn vmgs_file_open(
1189 file_path: impl AsRef<Path>,
1190 key_path: Option<impl AsRef<Path>>,
1191 open_mode: OpenMode,
1192) -> Result<Vmgs, Error> {
1193 tracing::info!("Opening VMGS File: {}", file_path.as_ref().display());
1194 let file = fs_err::OpenOptions::new()
1195 .read(true)
1196 .write(open_mode.write())
1197 .open(file_path.as_ref())
1198 .map_err(Error::VmgsFile)?;
1199
1200 let disk = vhdfiledisk_open(file, open_mode)?;
1201
1202 let encryption_key = key_path.map(read_key_path).transpose()?;
1203
1204 let res = vmgs_open(disk, encryption_key.as_ref(), open_mode).await;
1205
1206 if matches!(
1207 res,
1208 Err(Error::Vmgs(VmgsError::InvalidFormat(_)))
1209 | Err(Error::Vmgs(VmgsError::CorruptFormat(_)))
1210 ) {
1211 tracing::error!("VMGS is corrupted or invalid. Dumping headers.");
1212 let _ = vmgs_file_dump_headers(file_path.as_ref()).await;
1213 }
1214
1215 res
1216}
1217
1218#[cfg_attr(
1219 not(feature = "encryption"),
1220 expect(unused_mut),
1221 expect(unused_variables)
1222)]
1223async fn vmgs_open(
1224 disk: Disk,
1225 encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1226 open_mode: OpenMode,
1227) -> Result<Vmgs, Error> {
1228 let mut vmgs: Vmgs = Vmgs::open(disk, None).await?;
1229
1230 if let Some(encryption_key) = encryption_key {
1231 #[cfg(feature = "encryption")]
1232 vmgs.unlock_with_encryption_key(encryption_key).await?;
1233 #[cfg(not(feature = "encryption"))]
1234 unreachable!("Encryption requires the encryption feature");
1235 } else if vmgs.encrypted() {
1236 match open_mode {
1237 OpenMode::ReadWriteRequire => return Err(Error::EncryptedNoKey),
1238 OpenMode::ReadOnlyWarn => tracing::warn!(
1239 "Opening encrypted VMGS file without decrypting. File ID encryption status may be inaccurate."
1240 ),
1241 OpenMode::ReadOnlyIgnore | OpenMode::ReadWriteIgnore => {}
1242 }
1243 }
1244
1245 Ok(vmgs)
1246}
1247
1248fn read_key_path(path: impl AsRef<Path>) -> Result<[u8; VMGS_ENCRYPTION_KEY_SIZE], Error> {
1249 tracing::info!("Reading encryption key: {}", path.as_ref().display());
1250 let metadata = fs_err::metadata(&path).map_err(Error::KeyFile)?;
1251 if metadata.len() != VMGS_ENCRYPTION_KEY_SIZE as u64 {
1252 return Err(Error::InvalidKeySize(
1253 VMGS_ENCRYPTION_KEY_SIZE as u64,
1254 metadata.len(),
1255 ));
1256 }
1257
1258 let bytes = fs_err::read(&path).map_err(Error::KeyFile)?;
1259 let bytes_sized = bytes.try_into().map_err(|bytes: Vec<u8>| {
1260 Error::InvalidKeySize(VMGS_ENCRYPTION_KEY_SIZE as u64, bytes.len() as u64)
1261 })?;
1262 Ok(bytes_sized)
1263}
1264
1265async fn vmgs_file_query_file_size(
1266 file_path: impl AsRef<Path>,
1267 file_id: FileId,
1268) -> Result<u64, Error> {
1269 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1270
1271 vmgs_query_file_size(&vmgs, file_id)
1272}
1273
1274fn vmgs_query_file_size(vmgs: &Vmgs, file_id: FileId) -> Result<u64, Error> {
1275 let file_size = vmgs.get_file_info(file_id)?.valid_bytes;
1276
1277 tracing::info!("{} has a size of {}", file_id, file_size);
1278
1279 println!("{file_size}");
1281
1282 Ok(file_size)
1283}
1284
1285async fn vmgs_file_query_encryption(file_path: impl AsRef<Path>) -> Result<(), Error> {
1286 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1287
1288 let encryption_alg = vmgs.get_encryption_algorithm();
1289 tracing::info!("Encryption algorithm: {:?}", encryption_alg);
1290 let gsp_type = vmgs_get_gsp_type(&vmgs);
1291 tracing::info!("Guest state protection type: {:?}", gsp_type);
1292
1293 match (encryption_alg, gsp_type) {
1294 (EncryptionAlgorithm::NONE, _) => Err(Error::NotEncrypted),
1295 (EncryptionAlgorithm::AES_GCM, GspType::GspKey) => Ok(()),
1296 (EncryptionAlgorithm::AES_GCM, GspType::GspById) => Err(Error::GspByIdEncryption),
1297 (EncryptionAlgorithm::AES_GCM, GspType::None) => Err(Error::GspUnknown),
1298 _ => Err(Error::EncryptionUnknown),
1299 }
1300}
1301
1302fn vmgs_get_gsp_type(vmgs: &Vmgs) -> GspType {
1303 if vmgs.check_file_allocated(FileId::KEY_PROTECTOR) {
1304 GspType::GspKey
1305 } else if vmgs.check_file_allocated(FileId::VM_UNIQUE_ID) {
1306 GspType::GspById
1307 } else {
1308 GspType::None
1309 }
1310}
1311
1312fn vhdfiledisk_open(file: File, open_mode: OpenMode) -> Result<Disk, Error> {
1313 let file_size = file.metadata().map_err(Error::VmgsFile)?.len();
1314 validate_size(file_size)?;
1315
1316 let disk = Disk::new(
1317 Vhd1Disk::open_fixed(file.into(), open_mode == OpenMode::ReadOnlyWarn)
1318 .map_err(Error::Vhd1)?,
1319 )
1320 .map_err(Error::InvalidDisk)?;
1321
1322 Ok(disk)
1323}
1324
1325fn validate_size(file_size: u64) -> Result<(), Error> {
1326 const MAX_VMGS_FILE_SIZE: u64 = 4 * ONE_GIGA_BYTE;
1327
1328 if file_size > MAX_VMGS_FILE_SIZE {
1329 return Err(Error::InvalidVmgsFileSize(
1330 file_size,
1331 format!("Must be less than {}", MAX_VMGS_FILE_SIZE),
1332 ));
1333 }
1334
1335 if file_size == 0 {
1336 return Err(Error::ZeroSize);
1337 }
1338
1339 if file_size < VHD_DISK_FOOTER_PACKED_SIZE {
1340 return Err(Error::InvalidVmgsFileSize(
1341 file_size,
1342 format!("Must be greater than {}", VHD_DISK_FOOTER_PACKED_SIZE),
1343 ));
1344 }
1345
1346 Ok(())
1347}
1348
1349#[cfg(test)]
1350mod tests {
1351 use super::*;
1352 use pal_async::async_test;
1353 use tempfile::tempdir;
1354
1355 pub(crate) async fn test_vmgs_create(
1356 path: impl AsRef<Path>,
1357 file_size: Option<u64>,
1358 force_create: bool,
1359 encryption_alg_key: Option<(EncryptionAlgorithm, &[u8; VMGS_ENCRYPTION_KEY_SIZE])>,
1360 ) -> Result<(), Error> {
1361 let disk = vhdfiledisk_create(path, file_size, force_create)?;
1362 let _ = vmgs_create(disk, encryption_alg_key).await?;
1363 Ok(())
1364 }
1365
1366 pub(crate) async fn test_vmgs_open(
1367 path: impl AsRef<Path>,
1368 open_mode: OpenMode,
1369 encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1370 ) -> Result<Vmgs, Error> {
1371 let file = fs_err::OpenOptions::new()
1372 .read(true)
1373 .write(open_mode.write())
1374 .open(path.as_ref())
1375 .map_err(Error::VmgsFile)?;
1376 let disk = vhdfiledisk_open(file, open_mode)?;
1377 let vmgs = vmgs_open(disk, encryption_key, open_mode).await?;
1378 Ok(vmgs)
1379 }
1380
1381 async fn test_vmgs_query_file_size(
1382 file_path: impl AsRef<Path>,
1383 file_id: FileId,
1384 ) -> Result<u64, Error> {
1385 let vmgs =
1386 vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1387
1388 vmgs_query_file_size(&vmgs, file_id)
1389 }
1390
1391 #[cfg(feature = "encryption")]
1392 async fn test_vmgs_query_encryption(
1393 file_path: impl AsRef<Path>,
1394 ) -> Result<EncryptionAlgorithm, Error> {
1395 let vmgs =
1396 vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1397
1398 Ok(vmgs.get_encryption_algorithm())
1399 }
1400
1401 #[cfg(feature = "encryption")]
1402 async fn test_vmgs_update_key(
1403 file_path: impl AsRef<Path>,
1404 encryption_alg: EncryptionAlgorithm,
1405 encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1406 new_encryption_key: &[u8; VMGS_ENCRYPTION_KEY_SIZE],
1407 ) -> Result<(), Error> {
1408 let mut vmgs =
1409 test_vmgs_open(file_path, OpenMode::ReadWriteRequire, encryption_key).await?;
1410
1411 vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key).await
1412 }
1413
1414 fn new_path() -> (tempfile::TempDir, PathBuf) {
1416 let dir = tempdir().unwrap();
1417 let file_path = dir.path().join("test.vmgs");
1418 (dir, file_path)
1419 }
1420
1421 #[async_test]
1422 async fn read_invalid_file() {
1423 let (_dir, path) = new_path();
1424
1425 let result = test_vmgs_open(path, OpenMode::ReadOnlyWarn, None).await;
1426
1427 assert!(result.is_err());
1428 }
1429
1430 #[async_test]
1431 async fn read_empty_file() {
1432 let (_dir, path) = new_path();
1433
1434 test_vmgs_create(&path, None, false, None).await.unwrap();
1435
1436 let mut vmgs = test_vmgs_open(path, OpenMode::ReadOnlyWarn, None)
1437 .await
1438 .unwrap();
1439 let result = vmgs_read(&mut vmgs, FileId::FILE_TABLE, false).await;
1440 assert!(result.is_err());
1441 }
1442
1443 #[async_test]
1444 async fn read_write_file() {
1445 let (_dir, path) = new_path();
1446 let buf = b"Plain text data".to_vec();
1447
1448 test_vmgs_create(&path, None, false, None).await.unwrap();
1449
1450 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1451 .await
1452 .unwrap();
1453
1454 vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1455 .await
1456 .unwrap();
1457 let read_buf = vmgs_read(&mut vmgs, FileId::ATTEST, false).await.unwrap();
1458
1459 assert_eq!(buf, read_buf);
1460 }
1461
1462 #[async_test]
1463 async fn multiple_write_file() {
1464 let (_dir, path) = new_path();
1465 let buf_1 = b"Random super sensitive data".to_vec();
1466 let buf_2 = b"Other super secret data".to_vec();
1467 let buf_3 = b"I'm storing so much data".to_vec();
1468
1469 test_vmgs_create(&path, None, false, None).await.unwrap();
1470
1471 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1472 .await
1473 .unwrap();
1474
1475 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, false, false)
1476 .await
1477 .unwrap();
1478 let read_buf_1 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1479 .await
1480 .unwrap();
1481
1482 assert_eq!(buf_1, read_buf_1);
1483
1484 vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_2, false, false)
1485 .await
1486 .unwrap();
1487 let read_buf_2 = vmgs_read(&mut vmgs, FileId::TPM_PPI, false).await.unwrap();
1488
1489 assert_eq!(buf_2, read_buf_2);
1490
1491 let result = vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, false).await;
1492 assert!(result.is_err());
1493
1494 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, true)
1495 .await
1496 .unwrap();
1497 let read_buf_3 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1498 .await
1499 .unwrap();
1500
1501 assert_eq!(buf_2, read_buf_2);
1502 assert_eq!(buf_3, read_buf_3);
1503 }
1504
1505 #[cfg(feature = "encryption")]
1506 #[async_test]
1507 async fn read_write_encrypted_file() {
1508 let (_dir, path) = new_path();
1509 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1510 let buf_1 = b"123".to_vec();
1511
1512 test_vmgs_create(
1513 &path,
1514 None,
1515 false,
1516 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1517 )
1518 .await
1519 .unwrap();
1520
1521 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1522 .await
1523 .unwrap();
1524
1525 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1526 .await
1527 .unwrap();
1528 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1529 .await
1530 .unwrap();
1531
1532 assert!(read_buf == buf_1);
1533
1534 vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_1, false, false)
1536 .await
1537 .unwrap();
1538
1539 let _encrypted_read = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1541 .await
1542 .unwrap();
1543 }
1544
1545 #[cfg(feature = "encryption")]
1546 #[async_test]
1547 async fn encrypted_read_write_plain_file() {
1548 let (_dir, path) = new_path();
1551 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1552
1553 test_vmgs_create(&path, None, false, None).await.unwrap();
1554
1555 let result = test_vmgs_open(path, OpenMode::ReadWriteRequire, Some(&encryption_key)).await;
1556
1557 assert!(result.is_err());
1558 }
1559
1560 #[cfg(feature = "encryption")]
1561 #[async_test]
1562 async fn plain_read_write_encrypted_file() {
1563 let (_dir, path) = new_path();
1564 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1565 let buf_1 = b"123".to_vec();
1566
1567 test_vmgs_create(
1568 &path,
1569 None,
1570 false,
1571 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1572 )
1573 .await
1574 .unwrap();
1575
1576 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteIgnore, None)
1577 .await
1578 .unwrap();
1579
1580 vmgs_write(&mut vmgs, FileId::VM_UNIQUE_ID, &buf_1, false, false)
1581 .await
1582 .unwrap();
1583 let read_buf = vmgs_read(&mut vmgs, FileId::VM_UNIQUE_ID, false)
1584 .await
1585 .unwrap();
1586
1587 assert!(read_buf == buf_1);
1588 }
1589
1590 #[async_test]
1591 async fn query_size() {
1592 let (_dir, path) = new_path();
1593 let buf = b"Plain text data".to_vec();
1594
1595 test_vmgs_create(&path, None, false, None).await.unwrap();
1596
1597 {
1598 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, None)
1599 .await
1600 .unwrap();
1601
1602 vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1603 .await
1604 .unwrap();
1605 }
1606
1607 let file_size = test_vmgs_query_file_size(&path, FileId::ATTEST)
1608 .await
1609 .unwrap();
1610 assert_eq!(file_size, buf.len() as u64);
1611 }
1612
1613 #[cfg(feature = "encryption")]
1614 #[async_test]
1615 async fn query_encrypted_file() {
1616 let (_dir, path) = new_path();
1617 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1618 let buf_1 = b"123".to_vec();
1619
1620 test_vmgs_create(
1621 &path,
1622 None,
1623 false,
1624 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1625 )
1626 .await
1627 .unwrap();
1628
1629 {
1630 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1631 .await
1632 .unwrap();
1633
1634 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1635 .await
1636 .unwrap();
1637 }
1638
1639 let file_size = test_vmgs_query_file_size(&path, FileId::BIOS_NVRAM)
1640 .await
1641 .unwrap();
1642 assert_eq!(file_size, buf_1.len() as u64);
1643 }
1644
1645 #[async_test]
1646 async fn test_validate_vmgs_file_not_empty() {
1647 let buf: Vec<u8> = (0..255).collect();
1648 let (_dir, path) = new_path();
1649
1650 {
1652 fs_err::OpenOptions::new()
1653 .write(true)
1654 .create_new(true)
1655 .open(&path)
1656 .unwrap();
1657 }
1658
1659 {
1661 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1662 assert!(matches!(result, Err(Error::ZeroSize)));
1663 }
1664
1665 {
1667 vhdfiledisk_create(&path, None, true).unwrap();
1668 }
1669
1670 {
1672 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1673 assert!(matches!(result, Err(Error::Vmgs(VmgsError::EmptyFile))));
1674 }
1675
1676 {
1678 let mut file = fs_err::OpenOptions::new()
1679 .read(true)
1680 .write(true)
1681 .open(&path)
1682 .unwrap();
1683 file.seek(std::io::SeekFrom::Start(1024)).unwrap();
1684 file.write_all(&buf).unwrap();
1685 }
1686
1687 {
1689 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1690 matches!(result, Err(Error::Vmgs(VmgsError::CorruptFormat(_))));
1691 }
1692
1693 {
1695 test_vmgs_create(&path, None, true, None).await.unwrap();
1696 }
1697
1698 {
1700 test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None)
1701 .await
1702 .unwrap();
1703 }
1704 }
1705
1706 #[async_test]
1707 async fn test_misaligned_size() {
1708 let (_dir, path) = new_path();
1709 let result = test_vmgs_create(&path, Some(65537), false, None).await;
1711 assert!(result.is_err());
1712 assert!(!path.exists());
1713 }
1714
1715 #[async_test]
1716 async fn test_forcecreate() {
1717 let (_dir, path) = new_path();
1718 let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1719 assert!(result.is_ok());
1720 let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1722 assert!(result.is_err());
1723 let result = test_vmgs_create(&path, Some(8388608), true, None).await;
1725 assert!(result.is_ok());
1726 }
1727
1728 #[cfg(feature = "encryption")]
1729 #[async_test]
1730 async fn test_update_encryption_key() {
1731 let (_dir, path) = new_path();
1732 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1733 let new_encryption_key = [6; VMGS_ENCRYPTION_KEY_SIZE];
1734 let buf_1 = b"123".to_vec();
1735
1736 test_vmgs_create(
1737 &path,
1738 None,
1739 false,
1740 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1741 )
1742 .await
1743 .unwrap();
1744
1745 {
1746 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1747 .await
1748 .unwrap();
1749
1750 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1751 .await
1752 .unwrap();
1753 }
1754
1755 test_vmgs_update_key(
1756 &path,
1757 EncryptionAlgorithm::AES_GCM,
1758 Some(&encryption_key),
1759 &new_encryption_key,
1760 )
1761 .await
1762 .unwrap();
1763
1764 {
1765 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, Some(&new_encryption_key))
1766 .await
1767 .unwrap();
1768
1769 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1770 .await
1771 .unwrap();
1772 assert!(read_buf == buf_1);
1773 }
1774
1775 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, Some(&encryption_key)).await;
1777 assert!(result.is_err());
1778 }
1779
1780 #[cfg(feature = "encryption")]
1781 #[async_test]
1782 async fn test_add_encryption_key() {
1783 let (_dir, path) = new_path();
1784 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1785 let buf_1 = b"123".to_vec();
1786
1787 test_vmgs_create(&path, None, false, None).await.unwrap();
1788
1789 test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1790 .await
1791 .unwrap();
1792
1793 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1794 .await
1795 .unwrap();
1796
1797 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1798 .await
1799 .unwrap();
1800
1801 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1802 .await
1803 .unwrap();
1804
1805 assert!(read_buf == buf_1);
1806 }
1807
1808 #[cfg(feature = "encryption")]
1809 #[async_test]
1810 async fn test_query_encryption_update() {
1811 let (_dir, path) = new_path();
1812 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1813
1814 test_vmgs_create(&path, None, false, None).await.unwrap();
1815
1816 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1817 assert_eq!(encryption_algorithm, EncryptionAlgorithm::NONE);
1818
1819 test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1820 .await
1821 .unwrap();
1822
1823 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1824 assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1825 }
1826
1827 #[cfg(feature = "encryption")]
1828 #[async_test]
1829 async fn test_query_encryption_new() {
1830 let (_dir, path) = new_path();
1831 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1832
1833 test_vmgs_create(
1834 &path,
1835 None,
1836 false,
1837 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1838 )
1839 .await
1840 .unwrap();
1841
1842 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1843 assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1844 }
1845
1846 #[async_test]
1847 async fn move_delete_file() {
1848 let (_dir, path) = new_path();
1849 let buf = b"Plain text data".to_vec();
1850
1851 test_vmgs_create(&path, None, false, None).await.unwrap();
1852
1853 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1854 .await
1855 .unwrap();
1856
1857 vmgs_write(&mut vmgs, FileId::TPM_NVRAM, &buf, false, false)
1858 .await
1859 .unwrap();
1860 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM, false)
1861 .await
1862 .unwrap();
1863 assert_eq!(buf, read_buf);
1864
1865 vmgs_move(
1866 &mut vmgs,
1867 FileId::TPM_NVRAM,
1868 FileId::TPM_NVRAM_BACKUP,
1869 false,
1870 )
1871 .await
1872 .unwrap();
1873 vmgs_read(&mut vmgs, FileId::TPM_NVRAM, false)
1874 .await
1875 .unwrap_err();
1876 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
1877 .await
1878 .unwrap();
1879 assert_eq!(buf, read_buf);
1880 vmgs_delete(&mut vmgs, FileId::TPM_NVRAM_BACKUP)
1881 .await
1882 .unwrap();
1883 vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
1884 .await
1885 .unwrap_err();
1886 }
1887
1888 #[cfg(feature = "encryption")]
1889 #[async_test]
1890 async fn move_delete_file_encrypted() {
1891 let (_dir, path) = new_path();
1892 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1893 let buf_1 = b"123".to_vec();
1894 let buf_2 = b"456".to_vec();
1895
1896 test_vmgs_create(
1897 &path,
1898 None,
1899 false,
1900 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1901 )
1902 .await
1903 .unwrap();
1904
1905 {
1906 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1907 .await
1908 .unwrap();
1909
1910 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_2, true, false)
1911 .await
1912 .unwrap();
1913 vmgs_write(&mut vmgs, FileId::TPM_NVRAM, &buf_1, true, false)
1914 .await
1915 .unwrap();
1916 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM, true).await.unwrap();
1917 assert!(read_buf == buf_1);
1918
1919 vmgs_move(
1920 &mut vmgs,
1921 FileId::TPM_NVRAM,
1922 FileId::TPM_NVRAM_BACKUP,
1923 false,
1924 )
1925 .await
1926 .unwrap();
1927 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, true)
1928 .await
1929 .unwrap();
1930 assert!(read_buf == buf_1);
1931 }
1932
1933 {
1935 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteIgnore, None)
1936 .await
1937 .unwrap();
1938 vmgs_delete(&mut vmgs, FileId::TPM_NVRAM_BACKUP)
1939 .await
1940 .unwrap();
1941 vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
1942 .await
1943 .unwrap_err();
1944 }
1945
1946 {
1948 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1949 .await
1950 .unwrap();
1951 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1952 .await
1953 .unwrap();
1954 assert!(read_buf == buf_2);
1955 }
1956 }
1957}