1#![expect(missing_docs)]
5#![forbid(unsafe_code)]
6
7mod storage_backend;
8#[cfg(feature = "test_helpers")]
9mod test;
10mod uefi_nvram;
11mod vmgs_json;
12
13#[cfg(feature = "test_helpers")]
14use crate::test::TestOperation;
15use anyhow::Result;
16use clap::Args;
17use clap::Parser;
18use disk_backend::Disk;
19use disk_vhd1::Vhd1Disk;
20use fs_err::File;
21use pal_async::DefaultPool;
22use std::io::prelude::*;
23use std::path::Path;
24use std::path::PathBuf;
25use thiserror::Error;
26use uefi_nvram::UefiNvramOperation;
27use vmgs::Error as VmgsError;
28use vmgs::GspType;
29use vmgs::Vmgs;
30use vmgs::vmgs_helpers::get_active_header;
31use vmgs::vmgs_helpers::read_headers;
32use vmgs::vmgs_helpers::validate_header;
33use vmgs_format::EncryptionAlgorithm;
34use vmgs_format::FileId;
35use vmgs_format::VMGS_BYTES_PER_BLOCK;
36use vmgs_format::VMGS_DEFAULT_CAPACITY;
37use vmgs_format::VMGS_ENCRYPTION_KEY_SIZE;
38use vmgs_format::VmgsHeader;
39
40const ONE_MEGA_BYTE: u64 = 1024 * 1024;
41const ONE_GIGA_BYTE: u64 = ONE_MEGA_BYTE * 1024;
42const VHD_DISK_FOOTER_PACKED_SIZE: u64 = 512;
43
44#[derive(Debug, Error)]
45enum Error {
46 #[error("VMGS file IO")]
47 VmgsFile(#[source] std::io::Error),
48 #[error("VHD file error")]
49 Vhd1(#[source] disk_vhd1::OpenError),
50 #[error("invalid disk")]
51 InvalidDisk(#[source] disk_backend::InvalidDisk),
52 #[error("VMGS format")]
53 Vmgs(#[source] vmgs::Error),
54 #[error("VMGS file already exists")]
55 FileExists,
56 #[cfg(with_encryption)]
57 #[error("Adding encryption key")]
58 EncryptionKey(#[source] vmgs::Error),
59 #[error("Data file / STDOUT IO")]
60 DataFile(#[source] std::io::Error),
61 #[error("The VMGS file has zero size")]
62 ZeroSize,
63 #[error("The VMGS file has a non zero size but the contents are empty")]
64 EmptyFile,
65 #[error("Invalid VMGS file size: {0} {1}")]
66 InvalidVmgsFileSize(u64, String),
67 #[error("Key file IO")]
68 KeyFile(#[source] std::io::Error),
69 #[error("Key must be {0} bytes long, is {1} bytes instead")]
70 InvalidKeySize(u64, u64),
71 #[error("File is not encrypted")]
72 NotEncrypted,
73 #[error("File is VMGSv1 format")]
74 V1Format,
75 #[error("VmgsStorageBackend")]
76 VmgsStorageBackend(#[from] storage_backend::EncryptionNotSupported),
77 #[error("NVRAM storage")]
78 NvramStorage(#[from] uefi_nvram_storage::NvramStorageError),
79 #[error("UEFI NVRAM variable parsing")]
80 NvramParsing(#[from] uefi_nvram_specvars::ParseError),
81 #[error("NVRAM entry not found: {0}")]
82 MissingNvramEntry(ucs2::Ucs2LeVec),
83 #[error("GUID parsing")]
84 Guid(#[from] guid::ParseError),
85 #[error("JSON parsing")]
86 SerdeJson(#[from] serde_json::Error),
87 #[error("Bad JSON contents: {0}")]
88 Json(String),
89 #[error("File ID {0:?} already exists. Use `--allow-overwrite` to ignore.")]
90 FileIdExists(FileId),
91 #[error("VMGS file is encrypted using GspById")]
92 GspByIdEncryption,
93 #[error("VMGS file is encrypted using an unknown encryption scheme")]
94 GspUnknown,
95 #[error("VMGS file is using an unknown encryption algorithm")]
96 EncryptionUnknown,
97}
98
99impl From<vmgs::Error> for Error {
100 fn from(value: vmgs::Error) -> Self {
101 match value {
102 vmgs::Error::EmptyFile => Error::EmptyFile,
103 vmgs::Error::V1Format => Error::V1Format,
104 e => Error::Vmgs(e),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy)]
117#[repr(i32)]
118enum ExitCode {
119 Error = 1,
120 ErrorNotEncrypted = 2,
121 ErrorEmpty = 3,
122 ErrorNotFound = 4,
123 ErrorV1 = 5,
124 ErrorGspById = 6,
125 ErrorGspUnknown = 7,
126}
127
128#[derive(Args)]
129struct FilePathArg {
130 #[clap(short = 'f', long, alias = "filepath")]
132 file_path: PathBuf,
133}
134
135#[derive(Args)]
136struct KeyPathArg {
137 #[clap(short = 'k', long, alias = "keypath")]
139 key_path: Option<PathBuf>,
140}
141
142#[derive(Args)]
143struct FileIdArg {
144 #[clap(short = 'i', long, alias = "fileid", value_parser = parse_file_id)]
146 file_id: FileId,
147}
148
149#[derive(Parser)]
150#[clap(name = "vmgstool", about = "Tool to interact with VMGS files.")]
151enum Options {
152 Create {
157 #[command(flatten)]
158 file_path: FilePathArg,
159 #[clap(short = 's', long, alias = "filesize")]
161 file_size: Option<u64>,
162 #[clap(
166 short = 'k',
167 long,
168 alias = "keypath",
169 requires = "encryption_algorithm"
170 )]
171 key_path: Option<PathBuf>,
172 #[clap(short = 'e', long, alias = "encryptionalgorithm", requires = "key_path", value_parser = parse_encryption_algorithm)]
176 encryption_algorithm: Option<EncryptionAlgorithm>,
177 #[clap(long, alias = "forcecreate")]
180 force_create: bool,
181 },
182 Write {
186 #[command(flatten)]
187 file_path: FilePathArg,
188 #[clap(short = 'd', long, alias = "datapath")]
190 data_path: PathBuf,
191 #[command(flatten)]
192 file_id: FileIdArg,
193 #[command(flatten)]
194 key_path: KeyPathArg,
195 #[clap(long, alias = "allowoverwrite")]
197 allow_overwrite: bool,
198 },
199 Dump {
205 #[command(flatten)]
206 file_path: FilePathArg,
207 #[clap(short = 'd', long, alias = "datapath")]
209 data_path: Option<PathBuf>,
210 #[command(flatten)]
211 file_id: FileIdArg,
212 #[command(flatten)]
213 key_path: KeyPathArg,
214 #[clap(long, conflicts_with = "data_path")]
216 raw_stdout: bool,
217 },
218 DumpHeaders {
220 #[command(flatten)]
221 file_path: FilePathArg,
222 },
223 QuerySize {
225 #[command(flatten)]
226 file_path: FilePathArg,
227 #[command(flatten)]
228 file_id: FileIdArg,
229 },
230 UpdateKey {
234 #[command(flatten)]
235 file_path: FilePathArg,
236 #[clap(short = 'k', long, alias = "keypath")]
238 key_path: PathBuf,
239 #[clap(short = 'n', long, alias = "newkeypath")]
241 new_key_path: PathBuf,
242 #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
244 encryption_algorithm: EncryptionAlgorithm,
245 },
246 Encrypt {
248 #[command(flatten)]
249 file_path: FilePathArg,
250 #[clap(short = 'k', long, alias = "keypath")]
252 key_path: PathBuf,
253 #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
255 encryption_algorithm: EncryptionAlgorithm,
256 },
257 QueryEncryption {
259 #[command(flatten)]
260 file_path: FilePathArg,
261 },
262 UefiNvram {
264 #[clap(subcommand)]
265 operation: UefiNvramOperation,
266 },
267 #[cfg(feature = "test_helpers")]
268 Test {
270 #[clap(subcommand)]
271 operation: TestOperation,
272 },
273}
274
275fn parse_file_id(file_id: &str) -> Result<FileId, std::num::ParseIntError> {
276 Ok(match file_id {
277 "FILE_TABLE" => FileId::FILE_TABLE,
278 "BIOS_NVRAM" => FileId::BIOS_NVRAM,
279 "TPM_PPI" => FileId::TPM_PPI,
280 "TPM_NVRAM" => FileId::TPM_NVRAM,
281 "RTC_SKEW" => FileId::RTC_SKEW,
282 "ATTEST" => FileId::ATTEST,
283 "KEY_PROTECTOR" => FileId::KEY_PROTECTOR,
284 "VM_UNIQUE_ID" => FileId::VM_UNIQUE_ID,
285 "GUEST_FIRMWARE" => FileId::GUEST_FIRMWARE,
286 "CUSTOM_UEFI" => FileId::CUSTOM_UEFI,
287 "GUEST_WATCHDOG" => FileId::GUEST_WATCHDOG,
288 "HW_KEY_PROTECTOR" => FileId::HW_KEY_PROTECTOR,
289 "GUEST_SECRET_KEY" => FileId::GUEST_SECRET_KEY,
290 "EXTENDED_FILE_TABLE" => FileId::EXTENDED_FILE_TABLE,
291 v => FileId(v.parse::<u32>()?),
292 })
293}
294
295fn parse_encryption_algorithm(algorithm: &str) -> Result<EncryptionAlgorithm, &'static str> {
296 match algorithm {
297 "AES_GCM" => Ok(EncryptionAlgorithm::AES_GCM),
298 _ => Err("Encryption algorithm not supported"),
299 }
300}
301
302fn extract_version(ver: u32) -> String {
303 let major = (ver >> 16) & 0xFF;
304 let minor = ver & 0xFF;
305 format!("{major}.{minor}")
306}
307
308fn parse_legacy_args() -> Vec<String> {
309 use std::env;
310 let mut args: Vec<String> = env::args().collect();
311 if let Some(cmd) = args.get(1) {
312 let cmd_lower = cmd.to_ascii_lowercase();
313 let new_cmd = match &cmd_lower[..] {
314 "-c" | "-create" => Some("create"),
315 "-w" | "-write" => Some("write"),
316 "-r" | "-dump" => Some("dump"),
317 "-rh" | "-dumpheaders" => Some("dump-headers"),
318 "-qs" | "-querysize" => Some("query-size"),
319 "-uk" | "-updatekey" => Some("update-key"),
320 "-e" | "-encrypt" => Some("encrypt"),
321 _ => None,
322 };
323
324 if let Some(new_cmd) = new_cmd {
325 eprintln!("Warning: Using legacy arguments. Please migrate to the new syntax.");
326 args[1] = new_cmd.to_string();
327
328 let mut index = 2;
329 while let Some(arg) = args.get(index) {
330 let arg_lower = arg.to_ascii_lowercase();
331 if let Some(new_arg) = match &arg_lower[..] {
332 "-f" | "-filepath" => Some("--file-path"),
333 "-s" | "-filesize" => Some("--file-size"),
334 "-i" | "-fileid" => Some("--file-id"),
335 "-d" | "-datapath" => Some("--data-path"),
336 "-ow" | "-allowoverwrite" => Some("--allow-overwrite"),
337 "-k" | "-keypath" => Some("--key-path"),
338 "-n" | "-newkeypath" => Some("--new-key-path"),
339 "-ea" | "-encryptionalgorithm" => Some("--encryption-algorithm"),
340 "-fc" | "-forcecreate" => Some("--force-create"),
341 _ => None,
342 } {
343 args[index] = new_arg.to_string();
344 }
345 index += 1;
346 }
347 }
348 }
349 args
350}
351
352fn main() {
353 DefaultPool::run_with(async |_| match do_main().await {
354 Ok(_) => eprintln!("The operation completed successfully."),
355 Err(e) => {
356 let exit_code = match e {
357 Error::NotEncrypted => ExitCode::ErrorNotEncrypted,
358 Error::EmptyFile => ExitCode::ErrorEmpty,
359 Error::ZeroSize => ExitCode::ErrorEmpty,
360 Error::Vmgs(VmgsError::FileInfoNotAllocated) => ExitCode::ErrorNotFound,
361 Error::V1Format => ExitCode::ErrorV1,
362 Error::GspByIdEncryption => ExitCode::ErrorGspById,
363 Error::GspUnknown => ExitCode::ErrorGspUnknown,
364 _ => ExitCode::Error,
365 };
366
367 eprintln!("EXIT CODE: {} ({:?})", exit_code as i32, exit_code);
368 eprintln!("ERROR: {}", e);
369 let mut error_source = std::error::Error::source(&e);
370 while let Some(e2) = error_source {
371 eprintln!("- {}", e2);
372 error_source = e2.source();
373 }
374
375 std::process::exit(exit_code as i32);
376 }
377 })
378}
379
380async fn do_main() -> Result<(), Error> {
381 let opt = Options::parse_from(parse_legacy_args());
382
383 match opt {
384 Options::Create {
385 file_path,
386 file_size,
387 key_path,
388 encryption_algorithm,
389 force_create,
390 } => {
391 let encryption_alg_key = encryption_algorithm.map(|x| (x, key_path.unwrap()));
392 vmgs_file_create(
393 file_path.file_path,
394 file_size,
395 force_create,
396 encryption_alg_key,
397 )
398 .await
399 .map(|_| ())
400 }
401 Options::Dump {
402 file_path,
403 data_path,
404 file_id,
405 key_path,
406 raw_stdout,
407 } => {
408 vmgs_file_read(
409 file_path.file_path,
410 data_path,
411 file_id.file_id,
412 key_path.key_path,
413 raw_stdout,
414 )
415 .await
416 }
417 Options::Write {
418 file_path,
419 data_path,
420 file_id,
421 key_path,
422 allow_overwrite,
423 } => {
424 vmgs_file_write(
425 file_path.file_path,
426 data_path,
427 file_id.file_id,
428 key_path.key_path,
429 allow_overwrite,
430 )
431 .await
432 }
433 Options::DumpHeaders { file_path } => vmgs_file_dump_headers(file_path.file_path).await,
434 Options::QuerySize { file_path, file_id } => {
435 vmgs_file_query_file_size(file_path.file_path, file_id.file_id).await
436 }
437 Options::UpdateKey {
438 file_path,
439 key_path,
440 new_key_path,
441 encryption_algorithm,
442 } => {
443 vmgs_file_update_key(
444 file_path.file_path,
445 encryption_algorithm,
446 Some(key_path),
447 new_key_path,
448 )
449 .await
450 }
451 Options::Encrypt {
452 file_path,
453 key_path,
454 encryption_algorithm,
455 } => {
456 vmgs_file_update_key(
457 file_path.file_path,
458 encryption_algorithm,
459 None as Option<PathBuf>,
460 key_path,
461 )
462 .await
463 }
464 Options::QueryEncryption { file_path } => {
465 vmgs_file_query_encryption(file_path.file_path).await
466 }
467 Options::UefiNvram { operation } => uefi_nvram::do_command(operation).await,
468 #[cfg(feature = "test_helpers")]
469 Options::Test { operation } => test::do_command(operation).await,
470 }
471}
472
473async fn vmgs_file_update_key(
474 file_path: impl AsRef<Path>,
475 encryption_alg: EncryptionAlgorithm,
476 key_path: Option<impl AsRef<Path>>,
477 new_key_path: impl AsRef<Path>,
478) -> Result<(), Error> {
479 let new_encryption_key = read_key_path(new_key_path)?;
480 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWrite).await?;
481
482 vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key.as_ref()).await
483}
484
485#[cfg_attr(not(with_encryption), expect(unused_variables))]
486async fn vmgs_update_key(
487 vmgs: &mut Vmgs,
488 encryption_alg: EncryptionAlgorithm,
489 new_encryption_key: &[u8],
490) -> Result<(), Error> {
491 #[cfg(not(with_encryption))]
492 unreachable!("encryption requires the encryption feature");
493 #[cfg(with_encryption)]
494 {
495 eprintln!("Updating encryption key");
496 vmgs.update_encryption_key(new_encryption_key, encryption_alg)
497 .await
498 .map_err(Error::EncryptionKey)?;
499
500 Ok(())
501 }
502}
503
504async fn vmgs_file_create(
505 path: impl AsRef<Path>,
506 file_size: Option<u64>,
507 force_create: bool,
508 encryption_alg_key: Option<(EncryptionAlgorithm, impl AsRef<Path>)>,
509) -> Result<Vmgs, Error> {
510 let disk = vhdfiledisk_create(path, file_size, force_create)?;
511
512 let encryption_key = encryption_alg_key
513 .as_ref()
514 .map(|(_, key_path)| read_key_path(key_path))
515 .transpose()?;
516 let encryption_alg_key =
517 encryption_alg_key.map(|(alg, _)| (alg, encryption_key.as_deref().unwrap()));
518
519 let vmgs = vmgs_create(disk, encryption_alg_key).await?;
520
521 Ok(vmgs)
522}
523
524fn vhdfiledisk_create(
525 path: impl AsRef<Path>,
526 req_file_size: Option<u64>,
527 force_create: bool,
528) -> Result<Disk, Error> {
529 const MIN_VMGS_FILE_SIZE: u64 = 4 * VMGS_BYTES_PER_BLOCK as u64;
530 const SECTOR_SIZE: u64 = 512;
531
532 let file_size = req_file_size.unwrap_or(VMGS_DEFAULT_CAPACITY);
534 if file_size < MIN_VMGS_FILE_SIZE || !file_size.is_multiple_of(SECTOR_SIZE) {
535 return Err(Error::InvalidVmgsFileSize(
536 file_size,
537 format!(
538 "Must be a multiple of {} and at least {}",
539 SECTOR_SIZE, MIN_VMGS_FILE_SIZE
540 ),
541 ));
542 }
543
544 let exists = Path::new(path.as_ref()).exists();
547
548 eprintln!("Creating file: {}", path.as_ref().display());
550 let file = match fs_err::OpenOptions::new()
551 .read(true)
552 .write(true)
553 .create(true)
554 .create_new(!force_create)
555 .open(path.as_ref())
556 {
557 Ok(file) => file,
558 Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
559 return Err(Error::FileExists);
560 }
561 Err(err) => return Err(Error::VmgsFile(err)),
562 };
563
564 let existing_size = exists
566 .then(|| {
567 Ok(file
568 .metadata()?
569 .len()
570 .checked_sub(VHD_DISK_FOOTER_PACKED_SIZE))
571 })
572 .transpose()
573 .map_err(Error::VmgsFile)?
574 .flatten();
575 let needs_resize =
576 !exists || existing_size.is_none_or(|existing_size| file_size != existing_size);
577
578 let default_label = if file_size == VMGS_DEFAULT_CAPACITY {
580 " (default)"
581 } else {
582 ""
583 };
584 if needs_resize {
585 eprintln!(
586 "Setting file size to {}{}{}",
587 file_size,
588 default_label,
589 existing_size
590 .map(|s| format!(" (previous size: {s})"))
591 .unwrap_or_default(),
592 );
593 file.set_len(file_size).map_err(Error::VmgsFile)?;
594 } else {
595 eprintln!(
596 "File size is already {}{}, skipping resize",
597 file_size, default_label
598 );
599 }
600
601 let disk = if needs_resize {
603 None
604 } else {
605 Vhd1Disk::open_fixed(file.try_clone().map_err(Error::VmgsFile)?.into(), false)
606 .inspect_err(|e| eprintln!("No valid VHD header found in existing file: {e:#}"))
607 .ok()
608 };
609
610 let disk = match disk {
612 Some(disk) => {
613 eprintln!("Valid VHD footer already exists, skipping VHD format");
614 disk
615 }
616 None => {
617 eprintln!("Formatting VHD");
618 Vhd1Disk::make_fixed(file.file()).map_err(Error::Vhd1)?;
619 Vhd1Disk::open_fixed(file.into(), false).map_err(Error::Vhd1)?
620 }
621 };
622
623 Disk::new(disk).map_err(Error::InvalidDisk)
624}
625
626#[cfg_attr(not(with_encryption), expect(unused_mut), expect(unused_variables))]
627async fn vmgs_create(
628 disk: Disk,
629 encryption_alg_key: Option<(EncryptionAlgorithm, &[u8])>,
630) -> Result<Vmgs, Error> {
631 eprintln!("Formatting VMGS");
632 let mut vmgs = Vmgs::format_new(disk, None).await?;
633
634 if let Some((algorithm, encryption_key)) = encryption_alg_key {
635 eprintln!("Adding encryption key");
636 #[cfg(with_encryption)]
637 vmgs.update_encryption_key(encryption_key, algorithm)
638 .await
639 .map_err(Error::EncryptionKey)?;
640 #[cfg(not(with_encryption))]
641 unreachable!("Encryption requires the encryption feature");
642 }
643
644 Ok(vmgs)
645}
646
647async fn vmgs_file_write(
648 file_path: impl AsRef<Path>,
649 data_path: impl AsRef<Path>,
650 file_id: FileId,
651 key_path: Option<impl AsRef<Path>>,
652 allow_overwrite: bool,
653) -> Result<(), Error> {
654 eprintln!(
655 "Opening source (raw data file): {}",
656 data_path.as_ref().display()
657 );
658
659 let mut file = File::open(data_path.as_ref()).map_err(Error::DataFile)?;
660 let mut buf = Vec::new();
661
662 file.read_to_end(&mut buf).map_err(Error::DataFile)?;
665
666 eprintln!("Read {} bytes", buf.len());
667
668 let encrypt = key_path.is_some();
669 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWrite).await?;
670
671 vmgs_write(&mut vmgs, file_id, &buf, encrypt, allow_overwrite).await?;
672
673 Ok(())
674}
675
676async fn vmgs_write(
677 vmgs: &mut Vmgs,
678 file_id: FileId,
679 data: &[u8],
680 encrypt: bool,
681 allow_overwrite: bool,
682) -> Result<(), Error> {
683 eprintln!("Writing File ID {} ({:?})", file_id.0, file_id);
684
685 if let Ok(info) = vmgs.get_file_info(file_id) {
686 if !allow_overwrite && info.valid_bytes > 0 {
687 return Err(Error::FileIdExists(file_id));
688 }
689 if !encrypt && info.encrypted {
690 eprintln!("Warning: overwriting encrypted file with plaintext data")
691 }
692 }
693
694 if encrypt {
695 #[cfg(with_encryption)]
696 vmgs.write_file_encrypted(file_id, data).await?;
697 #[cfg(not(with_encryption))]
698 unreachable!("Encryption requires the encryption feature");
699 } else {
700 vmgs.write_file_allow_overwrite_encrypted(file_id, data)
701 .await?;
702 }
703
704 Ok(())
705}
706
707async fn vmgs_file_read(
709 file_path: impl AsRef<Path>,
710 data_path: Option<impl AsRef<Path>>,
711 file_id: FileId,
712 key_path: Option<impl AsRef<Path>>,
713 raw_stdout: bool,
714) -> Result<(), Error> {
715 let decrypt = key_path.is_some();
716 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadOnly).await?;
717
718 let file_info = vmgs.get_file_info(file_id)?;
719 if !decrypt && file_info.encrypted {
720 eprintln!("Warning: Reading encrypted file without decrypting");
721 }
722
723 let buf = vmgs_read(&mut vmgs, file_id, decrypt).await?;
724
725 eprintln!("Read {} bytes", buf.len());
726 if buf.len() != file_info.valid_bytes as usize {
727 eprintln!("Warning: Bytes read from VMGS doesn't match file info");
728 }
729
730 if let Some(path) = data_path {
731 eprintln!("Writing contents to {}", path.as_ref().display());
732 let mut file = File::create(path.as_ref()).map_err(Error::DataFile)?;
733 file.write_all(&buf).map_err(Error::DataFile)?;
734 } else {
735 eprintln!("Writing contents to stdout");
736 if raw_stdout {
737 let mut stdout = std::io::stdout();
738 stdout.write_all(&buf).map_err(Error::DataFile)?;
739 } else {
740 for c in buf.chunks(16) {
741 for b in c {
742 print!("0x{:02x},", b);
743 }
744 println!(
745 "{:missing$}// {}",
746 ' ',
747 c.iter()
748 .map(|c| if c.is_ascii_graphic() {
749 *c as char
750 } else {
751 '.'
752 })
753 .collect::<String>(),
754 missing = (16 - c.len()) * 5 + 1
755 );
756 }
757 }
758 }
759
760 Ok(())
761}
762
763async fn vmgs_read(vmgs: &mut Vmgs, file_id: FileId, decrypt: bool) -> Result<Vec<u8>, Error> {
764 eprintln!("Reading File ID {} ({:?})", file_id.0, file_id);
765 Ok(if decrypt {
766 vmgs.read_file(file_id).await?
767 } else {
768 vmgs.read_file_raw(file_id).await?
769 })
770}
771
772async fn vmgs_file_dump_headers(file_path: impl AsRef<Path>) -> Result<(), Error> {
773 let file = File::open(file_path.as_ref()).map_err(Error::VmgsFile)?;
774 let disk = vhdfiledisk_open(file, OpenMode::ReadOnly)?;
775
776 let (headers, res0) = match read_headers(disk).await {
777 Ok(headers) => (Some(headers), Ok(())),
778 Err((e, headers)) => (headers, Err(e.into())),
779 };
780
781 if let Some(headers) = headers {
782 let res1 = vmgs_dump_headers(&headers.0, &headers.1);
783 if res0.is_err() { res0 } else { res1 }
784 } else {
785 res0
786 }
787}
788
789fn vmgs_dump_headers(header1: &VmgsHeader, header2: &VmgsHeader) -> Result<(), Error> {
790 println!("FILE HEADERS");
791 println!("{0:<23} {1:^70} {2:^70}", "Field", "Header 1", "Header 2");
792 println!("{} {} {}", "-".repeat(23), "-".repeat(70), "-".repeat(70));
793
794 let signature1 = format!("{:#018x}", header1.signature);
795 let signature2 = format!("{:#018x}", header2.signature);
796 println!(
797 "{0:<23} {1:>70} {2:>70}",
798 "Signature:", signature1, signature2
799 );
800
801 println!(
802 "{0:<23} {1:>70} {2:>70}",
803 "Version:",
804 extract_version(header1.version),
805 extract_version(header2.version)
806 );
807 println!(
808 "{0:<23} {1:>70x} {2:>70x}",
809 "Checksum:", header1.checksum, header2.checksum
810 );
811 println!(
812 "{0:<23} {1:>70} {2:>70}",
813 "Sequence:", header1.sequence, header2.sequence
814 );
815 println!(
816 "{0:<23} {1:>70} {2:>70}",
817 "HeaderSize:", header1.header_size, header2.header_size
818 );
819
820 let file_table_offset1 = format!("{:#010x}", header1.file_table_offset);
821 let file_table_offset2 = format!("{:#010x}", header2.file_table_offset);
822 println!(
823 "{0:<23} {1:>70} {2:>70}",
824 "FileTableOffset:", file_table_offset1, file_table_offset2
825 );
826
827 println!(
828 "{0:<23} {1:>70} {2:>70}",
829 "FileTableSize:", header1.file_table_size, header2.file_table_size
830 );
831
832 let encryption_algorithm1 = format!("{:#06x}", header1.encryption_algorithm.0);
833 let encryption_algorithm2 = format!("{:#06x}", header2.encryption_algorithm.0);
834 println!(
835 "{0:<23} {1:>70} {2:>70}",
836 "EncryptionAlgorithm:", encryption_algorithm1, encryption_algorithm2
837 );
838
839 let markers1 = format!("{:#06x}", header1.markers.into_bits());
840 let markers2 = format!("{:#06x}", header2.markers.into_bits());
841
842 println!("{0:<23} {1:>70} {2:>70}", "Markers:", markers1, markers2);
843
844 println!("{0:<23}", "MetadataKey1:");
845
846 let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[0].nonce));
847 let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[0].nonce));
848 println!(
849 " {0:<19} {1:>70} {2:>70}",
850 "Nonce:", key1_nonce, key2_nonce
851 );
852
853 let key1_reserved = format!("{:#010x}", header1.metadata_keys[0].reserved);
854 let key2_reserved = format!("{:#010x}", header2.metadata_keys[0].reserved);
855 println!(
856 " {0:<19} {1:>70} {2:>70}",
857 "Reserved:", key1_reserved, key2_reserved
858 );
859
860 let key1_auth_tag = format!(
861 "0x{}",
862 hex::encode(header1.metadata_keys[0].authentication_tag)
863 );
864 let key2_auth_tag = format!(
865 "0x{}",
866 hex::encode(header2.metadata_keys[0].authentication_tag)
867 );
868 println!(
869 " {0:<19} {1:>70} {2:>70}",
870 "AuthenticationTag:", key1_auth_tag, key2_auth_tag
871 );
872
873 let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[0].encryption_key));
874 let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[0].encryption_key));
875 println!(
876 " {0:<19} {1:>70} {2:>70}",
877 "EncryptionKey:", key1_encryption_key, key2_encryption_key
878 );
879
880 println!("{0:<23}", "MetadataKey2:");
881 let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[1].nonce));
882 let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[1].nonce));
883 println!(
884 " {0:<19} {1:>70} {2:>70}",
885 "Nonce:", key1_nonce, key2_nonce
886 );
887
888 let key1_reserved = format!("0x{:#010x}", header1.metadata_keys[1].reserved);
889 let key2_reserved = format!("0x{:#010x}", header2.metadata_keys[1].reserved);
890 println!(
891 " {0:<19} {1:>70} {2:>70}",
892 "Reserved:", key1_reserved, key2_reserved
893 );
894
895 let key1_auth_tag = format!(
896 "0x{}",
897 hex::encode(header1.metadata_keys[1].authentication_tag)
898 );
899 let key2_auth_tag = format!(
900 "0x{}",
901 hex::encode(header2.metadata_keys[1].authentication_tag)
902 );
903 println!(
904 " {0:<19} {1:>70} {2:>70}",
905 "AuthenticationTag:", key1_auth_tag, key2_auth_tag
906 );
907
908 let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[1].encryption_key));
909 let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[1].encryption_key));
910 println!(
911 " {0:<19} {1:>70} {2:>70}",
912 "EncryptionKey:", key1_encryption_key, key2_encryption_key
913 );
914
915 let key1_reserved1 = format!("0x{:#010x}", header1.reserved_1);
916 let key2_reserved1 = format!("0x{:#010x}", header2.reserved_1);
917 println!(
918 "{0:<23} {1:>70} {2:>70}",
919 "Reserved:", key1_reserved1, key2_reserved1
920 );
921
922 println!("{} {} {}\n", "-".repeat(23), "-".repeat(70), "-".repeat(70));
923
924 print!("Verifying header 1... ");
925 let header1_result = validate_header(header1);
926 match &header1_result {
927 Ok(_) => println!("[VALID]"),
928 Err(e) => println!("[INVALID] Error: {}", e),
929 }
930
931 print!("Verifying header 2... ");
932 let header2_result = validate_header(header2);
933 match &header2_result {
934 Ok(_) => println!("[VALID]"),
935 Err(e) => println!("[INVALID] Error: {}", e),
936 }
937
938 match get_active_header(header1_result, header2_result) {
939 Ok(active_index) => match active_index {
940 0 => println!("Active header is 1"),
941 1 => println!("Active header is 2"),
942 _ => unreachable!(),
943 },
944 Err(e) => {
945 println!("Unable to determine active header");
946 return Err(Error::Vmgs(e));
947 }
948 }
949
950 Ok(())
951}
952
953#[derive(Copy, Clone, Debug, PartialEq, Eq)]
954enum OpenMode {
955 ReadOnly,
956 ReadWrite,
957}
958
959async fn vmgs_file_open(
960 file_path: impl AsRef<Path>,
961 key_path: Option<impl AsRef<Path>>,
962 open_mode: OpenMode,
963) -> Result<Vmgs, Error> {
964 eprintln!("Opening VMGS File: {}", file_path.as_ref().display());
965 let file = fs_err::OpenOptions::new()
966 .read(true)
967 .write(open_mode == OpenMode::ReadWrite)
968 .open(file_path.as_ref())
969 .map_err(Error::VmgsFile)?;
970
971 let disk = vhdfiledisk_open(file, open_mode)?;
972
973 let encryption_key = key_path.map(read_key_path).transpose()?;
974
975 let res = vmgs_open(disk, encryption_key.as_deref()).await;
976
977 if matches!(
978 res,
979 Err(Error::Vmgs(VmgsError::InvalidFormat(_)))
980 | Err(Error::Vmgs(VmgsError::CorruptFormat(_)))
981 ) {
982 eprintln!("VMGS is corrupted or invalid. Dumping headers.");
983 let _ = vmgs_file_dump_headers(file_path.as_ref()).await;
984 }
985
986 res
987}
988
989#[cfg_attr(not(with_encryption), expect(unused_mut), expect(unused_variables))]
990async fn vmgs_open(disk: Disk, encryption_key: Option<&[u8]>) -> Result<Vmgs, Error> {
991 let mut vmgs: Vmgs = Vmgs::open(disk, None).await?;
992
993 if let Some(encryption_key) = encryption_key {
994 #[cfg(with_encryption)]
995 if vmgs.is_encrypted() {
996 vmgs.unlock_with_encryption_key(encryption_key).await?;
997 } else {
998 return Err(Error::NotEncrypted);
999 }
1000 #[cfg(not(with_encryption))]
1001 unreachable!("Encryption requires the encryption feature");
1002 }
1003
1004 Ok(vmgs)
1005}
1006
1007fn read_key_path(path: impl AsRef<Path>) -> Result<Vec<u8>, Error> {
1008 eprintln!("Reading encryption key: {}", path.as_ref().display());
1009 let metadata = fs_err::metadata(&path).map_err(Error::KeyFile)?;
1010 if metadata.len() != VMGS_ENCRYPTION_KEY_SIZE as u64 {
1011 return Err(Error::InvalidKeySize(
1012 VMGS_ENCRYPTION_KEY_SIZE as u64,
1013 metadata.len(),
1014 ));
1015 }
1016
1017 let bytes = fs_err::read(&path).map_err(Error::KeyFile)?;
1018 if bytes.len() != metadata.len() as usize {
1019 return Err(Error::InvalidKeySize(
1020 VMGS_ENCRYPTION_KEY_SIZE as u64,
1021 bytes.len() as u64,
1022 ));
1023 }
1024
1025 Ok(bytes)
1026}
1027
1028async fn vmgs_file_query_file_size(
1029 file_path: impl AsRef<Path>,
1030 file_id: FileId,
1031) -> Result<(), Error> {
1032 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnly).await?;
1033
1034 let file_size = vmgs_query_file_size(&vmgs, file_id)?;
1035
1036 println!(
1037 "File ID {} ({:?}) has a size of {}",
1038 file_id.0, file_id, file_size
1039 );
1040
1041 Ok(())
1042}
1043
1044fn vmgs_query_file_size(vmgs: &Vmgs, file_id: FileId) -> Result<u64, Error> {
1045 Ok(vmgs.get_file_info(file_id)?.valid_bytes)
1046}
1047
1048async fn vmgs_file_query_encryption(file_path: impl AsRef<Path>) -> Result<(), Error> {
1049 print!("{} is ", file_path.as_ref().display());
1050
1051 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnly).await?;
1052
1053 match (vmgs.get_encryption_algorithm(), vmgs_get_gsp_type(&vmgs)) {
1054 (EncryptionAlgorithm::NONE, scheme) => {
1055 println!("not encrypted (encryption scheme: {scheme:?})");
1056 Err(Error::NotEncrypted)
1057 }
1058 (EncryptionAlgorithm::AES_GCM, GspType::GspKey) => {
1059 println!("encrypted with AES GCM encryption algorithm using GspKey");
1060 Ok(())
1061 }
1062 (EncryptionAlgorithm::AES_GCM, GspType::GspById) => {
1063 println!("encrypted with AES GCM encryption algorithm using GspById");
1064 Err(Error::GspByIdEncryption)
1065 }
1066 (EncryptionAlgorithm::AES_GCM, GspType::None) => {
1067 println!(
1068 "encrypted with AES GCM encryption algorithm using an unknown encryption scheme"
1069 );
1070 Err(Error::GspUnknown)
1071 }
1072 (alg, scheme) => {
1073 println!(
1074 "using an unknown encryption algorithm: {alg:?} (encryption scheme: {scheme:?})"
1075 );
1076 Err(Error::EncryptionUnknown)
1077 }
1078 }
1079}
1080
1081fn vmgs_get_gsp_type(vmgs: &Vmgs) -> GspType {
1082 if vmgs_query_file_size(vmgs, FileId::KEY_PROTECTOR).is_ok() {
1083 GspType::GspKey
1084 } else if vmgs_query_file_size(vmgs, FileId::VM_UNIQUE_ID).is_ok() {
1085 GspType::GspById
1086 } else {
1087 GspType::None
1088 }
1089}
1090
1091fn vhdfiledisk_open(file: File, open_mode: OpenMode) -> Result<Disk, Error> {
1092 let file_size = file.metadata().map_err(Error::VmgsFile)?.len();
1093 validate_size(file_size)?;
1094
1095 let disk = Disk::new(
1096 Vhd1Disk::open_fixed(file.into(), open_mode == OpenMode::ReadOnly).map_err(Error::Vhd1)?,
1097 )
1098 .map_err(Error::InvalidDisk)?;
1099
1100 Ok(disk)
1101}
1102
1103fn validate_size(file_size: u64) -> Result<(), Error> {
1104 const MAX_VMGS_FILE_SIZE: u64 = 4 * ONE_GIGA_BYTE;
1105
1106 if file_size > MAX_VMGS_FILE_SIZE {
1107 return Err(Error::InvalidVmgsFileSize(
1108 file_size,
1109 format!("Must be less than {}", MAX_VMGS_FILE_SIZE),
1110 ));
1111 }
1112
1113 if file_size == 0 {
1114 return Err(Error::ZeroSize);
1115 }
1116
1117 if file_size < VHD_DISK_FOOTER_PACKED_SIZE {
1118 return Err(Error::InvalidVmgsFileSize(
1119 file_size,
1120 format!("Must be greater than {}", VHD_DISK_FOOTER_PACKED_SIZE),
1121 ));
1122 }
1123
1124 Ok(())
1125}
1126
1127#[cfg(test)]
1128mod tests {
1129 use super::*;
1130 use pal_async::async_test;
1131 use tempfile::tempdir;
1132
1133 async fn test_vmgs_create(
1134 path: impl AsRef<Path>,
1135 file_size: Option<u64>,
1136 force_create: bool,
1137 encryption_alg_key: Option<(EncryptionAlgorithm, &[u8])>,
1138 ) -> Result<(), Error> {
1139 let disk = vhdfiledisk_create(path, file_size, force_create)?;
1140 let _ = vmgs_create(disk, encryption_alg_key).await?;
1141 Ok(())
1142 }
1143
1144 async fn test_vmgs_open(
1145 path: impl AsRef<Path>,
1146 open_mode: OpenMode,
1147 encryption_key: Option<&[u8]>,
1148 ) -> Result<Vmgs, Error> {
1149 let file = fs_err::OpenOptions::new()
1150 .read(true)
1151 .write(open_mode == OpenMode::ReadWrite)
1152 .open(path.as_ref())
1153 .map_err(Error::VmgsFile)?;
1154 let disk = vhdfiledisk_open(file, open_mode)?;
1155 let vmgs = vmgs_open(disk, encryption_key).await?;
1156 Ok(vmgs)
1157 }
1158
1159 async fn test_vmgs_query_file_size(
1160 file_path: impl AsRef<Path>,
1161 file_id: FileId,
1162 ) -> Result<u64, Error> {
1163 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnly).await?;
1164
1165 vmgs_query_file_size(&vmgs, file_id)
1166 }
1167
1168 #[cfg(with_encryption)]
1169 async fn test_vmgs_query_encryption(
1170 file_path: impl AsRef<Path>,
1171 ) -> Result<EncryptionAlgorithm, Error> {
1172 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnly).await?;
1173
1174 Ok(vmgs.get_encryption_algorithm())
1175 }
1176
1177 #[cfg(with_encryption)]
1178 async fn test_vmgs_update_key(
1179 file_path: impl AsRef<Path>,
1180 encryption_alg: EncryptionAlgorithm,
1181 encryption_key: Option<&[u8]>,
1182 new_encryption_key: &[u8],
1183 ) -> Result<(), Error> {
1184 let mut vmgs = test_vmgs_open(file_path, OpenMode::ReadWrite, encryption_key).await?;
1185
1186 vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key).await
1187 }
1188
1189 fn new_path() -> (tempfile::TempDir, PathBuf) {
1191 let dir = tempdir().unwrap();
1192 let file_path = dir.path().join("test.vmgs");
1193 (dir, file_path)
1194 }
1195
1196 #[async_test]
1197 async fn read_invalid_file() {
1198 let (_dir, path) = new_path();
1199
1200 let result = test_vmgs_open(path, OpenMode::ReadOnly, None).await;
1201
1202 assert!(result.is_err());
1203 }
1204
1205 #[async_test]
1206 async fn read_empty_file() {
1207 let (_dir, path) = new_path();
1208
1209 test_vmgs_create(&path, None, false, None).await.unwrap();
1210
1211 let mut vmgs = test_vmgs_open(path, OpenMode::ReadOnly, None)
1212 .await
1213 .unwrap();
1214 let result = vmgs_read(&mut vmgs, FileId::FILE_TABLE, false).await;
1215 assert!(result.is_err());
1216 }
1217
1218 #[async_test]
1219 async fn read_write_file() {
1220 let (_dir, path) = new_path();
1221 let buf = b"Plain text data".to_vec();
1222
1223 test_vmgs_create(&path, None, false, None).await.unwrap();
1224
1225 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWrite, None)
1226 .await
1227 .unwrap();
1228
1229 vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1230 .await
1231 .unwrap();
1232 let read_buf = vmgs_read(&mut vmgs, FileId::ATTEST, false).await.unwrap();
1233
1234 assert_eq!(buf, read_buf);
1235 }
1236
1237 #[async_test]
1238 async fn multiple_write_file() {
1239 let (_dir, path) = new_path();
1240 let buf_1 = b"Random super sensitive data".to_vec();
1241 let buf_2 = b"Other super secret data".to_vec();
1242 let buf_3 = b"I'm storing so much data".to_vec();
1243
1244 test_vmgs_create(&path, None, false, None).await.unwrap();
1245
1246 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWrite, None)
1247 .await
1248 .unwrap();
1249
1250 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, false, false)
1251 .await
1252 .unwrap();
1253 let read_buf_1 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1254 .await
1255 .unwrap();
1256
1257 assert_eq!(buf_1, read_buf_1);
1258
1259 vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_2, false, false)
1260 .await
1261 .unwrap();
1262 let read_buf_2 = vmgs_read(&mut vmgs, FileId::TPM_PPI, false).await.unwrap();
1263
1264 assert_eq!(buf_2, read_buf_2);
1265
1266 let result = vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, false).await;
1267 assert!(result.is_err());
1268
1269 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, true)
1270 .await
1271 .unwrap();
1272 let read_buf_3 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1273 .await
1274 .unwrap();
1275
1276 assert_eq!(buf_2, read_buf_2);
1277 assert_eq!(buf_3, read_buf_3);
1278 }
1279
1280 #[cfg(with_encryption)]
1281 #[async_test]
1282 async fn read_write_encrypted_file() {
1283 let (_dir, path) = new_path();
1284 let encryption_key = vec![5; 32];
1285 let buf_1 = b"123".to_vec();
1286
1287 test_vmgs_create(
1288 &path,
1289 None,
1290 false,
1291 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1292 )
1293 .await
1294 .unwrap();
1295
1296 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWrite, Some(&encryption_key))
1297 .await
1298 .unwrap();
1299
1300 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1301 .await
1302 .unwrap();
1303 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1304 .await
1305 .unwrap();
1306
1307 assert!(read_buf == buf_1);
1308
1309 vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_1, false, false)
1311 .await
1312 .unwrap();
1313
1314 let _encrypted_read = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1316 .await
1317 .unwrap();
1318 }
1319
1320 #[cfg(with_encryption)]
1321 #[async_test]
1322 async fn encrypted_read_write_plain_file() {
1323 let (_dir, path) = new_path();
1326 let encryption_key = vec![5; VMGS_ENCRYPTION_KEY_SIZE];
1327
1328 test_vmgs_create(&path, None, false, None).await.unwrap();
1329
1330 let result = test_vmgs_open(path, OpenMode::ReadWrite, Some(&encryption_key)).await;
1331
1332 assert!(result.is_err());
1333 }
1334
1335 #[cfg(with_encryption)]
1336 #[async_test]
1337 async fn plain_read_write_encrypted_file() {
1338 let (_dir, path) = new_path();
1339 let encryption_key = vec![5; 32];
1340 let buf_1 = b"123".to_vec();
1341
1342 test_vmgs_create(
1343 &path,
1344 None,
1345 false,
1346 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1347 )
1348 .await
1349 .unwrap();
1350
1351 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWrite, None)
1352 .await
1353 .unwrap();
1354
1355 vmgs_write(&mut vmgs, FileId::VM_UNIQUE_ID, &buf_1, false, false)
1356 .await
1357 .unwrap();
1358 let read_buf = vmgs_read(&mut vmgs, FileId::VM_UNIQUE_ID, false)
1359 .await
1360 .unwrap();
1361
1362 assert!(read_buf == buf_1);
1363 }
1364
1365 #[async_test]
1366 async fn query_size() {
1367 let (_dir, path) = new_path();
1368 let buf = b"Plain text data".to_vec();
1369
1370 test_vmgs_create(&path, None, false, None).await.unwrap();
1371
1372 {
1373 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWrite, None)
1374 .await
1375 .unwrap();
1376
1377 vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1378 .await
1379 .unwrap();
1380 }
1381
1382 let file_size = test_vmgs_query_file_size(&path, FileId::ATTEST)
1383 .await
1384 .unwrap();
1385 assert_eq!(file_size, buf.len() as u64);
1386 }
1387
1388 #[cfg(with_encryption)]
1389 #[async_test]
1390 async fn query_encrypted_file() {
1391 let (_dir, path) = new_path();
1392 let encryption_key = vec![5; 32];
1393 let buf_1 = b"123".to_vec();
1394
1395 test_vmgs_create(
1396 &path,
1397 None,
1398 false,
1399 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1400 )
1401 .await
1402 .unwrap();
1403
1404 {
1405 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWrite, Some(&encryption_key))
1406 .await
1407 .unwrap();
1408
1409 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1410 .await
1411 .unwrap();
1412 }
1413
1414 let file_size = test_vmgs_query_file_size(&path, FileId::BIOS_NVRAM)
1415 .await
1416 .unwrap();
1417 assert_eq!(file_size, buf_1.len() as u64);
1418 }
1419
1420 #[async_test]
1421 async fn test_validate_vmgs_file_not_empty() {
1422 let buf: Vec<u8> = (0..255).collect();
1423 let (_dir, path) = new_path();
1424
1425 {
1427 fs_err::OpenOptions::new()
1428 .write(true)
1429 .create_new(true)
1430 .open(&path)
1431 .unwrap();
1432 }
1433
1434 {
1436 let result = test_vmgs_open(&path, OpenMode::ReadOnly, None).await;
1437 assert!(matches!(result, Err(Error::ZeroSize)));
1438 }
1439
1440 {
1442 vhdfiledisk_create(&path, None, true).unwrap();
1443 }
1444
1445 {
1447 let result = test_vmgs_open(&path, OpenMode::ReadOnly, None).await;
1448 assert!(matches!(result, Err(Error::EmptyFile)));
1449 }
1450
1451 {
1453 let mut file = fs_err::OpenOptions::new()
1454 .read(true)
1455 .write(true)
1456 .open(&path)
1457 .unwrap();
1458 file.seek(std::io::SeekFrom::Start(1024)).unwrap();
1459 file.write_all(&buf).unwrap();
1460 }
1461
1462 {
1464 let result = test_vmgs_open(&path, OpenMode::ReadOnly, None).await;
1465 matches!(result, Err(Error::Vmgs(vmgs::Error::CorruptFormat(_))));
1466 }
1467
1468 {
1470 test_vmgs_create(&path, None, true, None).await.unwrap();
1471 }
1472
1473 {
1475 test_vmgs_open(&path, OpenMode::ReadOnly, None)
1476 .await
1477 .unwrap();
1478 }
1479 }
1480
1481 #[async_test]
1482 async fn test_misaligned_size() {
1483 let (_dir, path) = new_path();
1484 let result = test_vmgs_create(&path, Some(65537), false, None).await;
1486 assert!(result.is_err());
1487 assert!(!path.exists());
1488 }
1489
1490 #[async_test]
1491 async fn test_forcecreate() {
1492 let (_dir, path) = new_path();
1493 let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1494 assert!(result.is_ok());
1495 let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1497 assert!(result.is_err());
1498 let result = test_vmgs_create(&path, Some(8388608), true, None).await;
1500 assert!(result.is_ok());
1501 }
1502
1503 #[cfg(with_encryption)]
1504 #[async_test]
1505 async fn test_update_encryption_key() {
1506 let (_dir, path) = new_path();
1507 let encryption_key = vec![5; 32];
1508 let new_encryption_key = vec![6; 32];
1509 let buf_1 = b"123".to_vec();
1510
1511 test_vmgs_create(
1512 &path,
1513 None,
1514 false,
1515 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1516 )
1517 .await
1518 .unwrap();
1519
1520 {
1521 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWrite, 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 }
1529
1530 test_vmgs_update_key(
1531 &path,
1532 EncryptionAlgorithm::AES_GCM,
1533 Some(&encryption_key),
1534 &new_encryption_key,
1535 )
1536 .await
1537 .unwrap();
1538
1539 {
1540 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadOnly, Some(&new_encryption_key))
1541 .await
1542 .unwrap();
1543
1544 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1545 .await
1546 .unwrap();
1547 assert!(read_buf == buf_1);
1548 }
1549
1550 let result = test_vmgs_open(&path, OpenMode::ReadOnly, Some(&encryption_key)).await;
1552 assert!(result.is_err());
1553 }
1554
1555 #[cfg(with_encryption)]
1556 #[async_test]
1557 async fn test_add_encryption_key() {
1558 let (_dir, path) = new_path();
1559 let encryption_key = vec![5; 32];
1560 let buf_1 = b"123".to_vec();
1561
1562 test_vmgs_create(&path, None, false, None).await.unwrap();
1563
1564 test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1565 .await
1566 .unwrap();
1567
1568 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWrite, Some(&encryption_key))
1569 .await
1570 .unwrap();
1571
1572 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1573 .await
1574 .unwrap();
1575
1576 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1577 .await
1578 .unwrap();
1579
1580 assert!(read_buf == buf_1);
1581 }
1582
1583 #[cfg(with_encryption)]
1584 #[async_test]
1585 async fn test_query_encryption_update() {
1586 let (_dir, path) = new_path();
1587 let encryption_key = vec![5; 32];
1588
1589 test_vmgs_create(&path, None, false, None).await.unwrap();
1590
1591 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1592 assert_eq!(encryption_algorithm, EncryptionAlgorithm::NONE);
1593
1594 test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1595 .await
1596 .unwrap();
1597
1598 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1599 assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1600 }
1601
1602 #[cfg(with_encryption)]
1603 #[async_test]
1604 async fn test_query_encryption_new() {
1605 let (_dir, path) = new_path();
1606 let encryption_key = vec![5; 32];
1607
1608 test_vmgs_create(
1609 &path,
1610 None,
1611 false,
1612 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1613 )
1614 .await
1615 .unwrap();
1616
1617 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1618 assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1619 }
1620}