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