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