vmgstool/
main.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![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/// Automation requires certain exit codes to be guaranteed
110/// main matches Error enum to ExitCode
111///
112/// query-encryption must return ErrorNotEncrypted if file is not encrypted
113/// dump-headers must return ErrorEmpty when file is blank
114/// query-size must return ErrorNotFound when file id is uninitialized
115/// ExitCode::Error returned for all other errors
116#[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    /// VMGS file path
131    #[clap(short = 'f', long, alias = "filepath")]
132    file_path: PathBuf,
133}
134
135#[derive(Args)]
136struct KeyPathArg {
137    /// Encryption key file path. The file must contain a key that is 32 bytes long.
138    #[clap(short = 'k', long, alias = "keypath")]
139    key_path: Option<PathBuf>,
140}
141
142#[derive(Args)]
143struct FileIdArg {
144    /// VMGS File ID
145    #[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 and initialize `filepath` as a VMGS file of size `filesize`.
153    ///
154    /// `keypath` and `encryptionalgorithm` must both be specified if encrypted
155    /// guest state is required.
156    Create {
157        #[command(flatten)]
158        file_path: FilePathArg,
159        /// VMGS file size, default = 4194816 (~4MB)
160        #[clap(short = 's', long, alias = "filesize")]
161        file_size: Option<u64>,
162        /// Encryption key file path. The file must contain a key that is 32 bytes long.
163        ///
164        /// `encryptionalgorithm` must also be specified when using this flag.
165        #[clap(
166            short = 'k',
167            long,
168            alias = "keypath",
169            requires = "encryption_algorithm"
170        )]
171        key_path: Option<PathBuf>,
172        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
173        ///
174        /// `keypath` must also be specified when using this flag.
175        #[clap(short = 'e', long, alias = "encryptionalgorithm", requires = "key_path", value_parser = parse_encryption_algorithm)]
176        encryption_algorithm: Option<EncryptionAlgorithm>,
177        /// Force creation of the VMGS file. If the VMGS filepath already exists,
178        /// this flag allows an existing file to be overwritten.
179        #[clap(long, alias = "forcecreate")]
180        force_create: bool,
181    },
182    /// Write data into the specified file ID of the VMGS file.
183    ///
184    /// The proper key file must be specified to write encrypted data.
185    Write {
186        #[command(flatten)]
187        file_path: FilePathArg,
188        /// Data file path to read
189        #[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        /// Overwrite the VMGS data at `fileid`, even if it already exists with nonzero size
196        #[clap(long, alias = "allowoverwrite")]
197        allow_overwrite: bool,
198    },
199    /// Dump/read data from the specified file ID of the VMGS file.
200    ///
201    /// The proper key file must be specified to read encrypted data. If the data
202    /// is encrypted and no key is specified, the data will be dumped without
203    /// decrypting.
204    Dump {
205        #[command(flatten)]
206        file_path: FilePathArg,
207        /// Data file path to write
208        #[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        /// When dumping to stdout, dump data as raw bytes instead of ASCII hex
215        #[clap(long, conflicts_with = "data_path")]
216        raw_stdout: bool,
217    },
218    /// Dump headers of the VMGS file at `filepath` to the console.
219    DumpHeaders {
220        #[command(flatten)]
221        file_path: FilePathArg,
222    },
223    /// Get the size of the specified `fileid` within the VMGS file
224    QuerySize {
225        #[command(flatten)]
226        file_path: FilePathArg,
227        #[command(flatten)]
228        file_id: FileIdArg,
229    },
230    /// Replace the current encryption key with a new provided key
231    ///
232    /// Both key files must contain a key that is 32 bytes long.
233    UpdateKey {
234        #[command(flatten)]
235        file_path: FilePathArg,
236        /// Current encryption key file path.
237        #[clap(short = 'k', long, alias = "keypath")]
238        key_path: PathBuf,
239        /// New encryption key file path.
240        #[clap(short = 'n', long, alias = "newkeypath")]
241        new_key_path: PathBuf,
242        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
243        #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
244        encryption_algorithm: EncryptionAlgorithm,
245    },
246    /// Encrypt an existing VMGS file
247    Encrypt {
248        #[command(flatten)]
249        file_path: FilePathArg,
250        /// Encryption key file path. The file must contain a key that is 32 bytes long.
251        #[clap(short = 'k', long, alias = "keypath")]
252        key_path: PathBuf,
253        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
254        #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
255        encryption_algorithm: EncryptionAlgorithm,
256    },
257    /// Query whether a VMGS file is encrypted
258    QueryEncryption {
259        #[command(flatten)]
260        file_path: FilePathArg,
261    },
262    /// UEFI NVRAM operations
263    UefiNvram {
264        #[clap(subcommand)]
265        operation: UefiNvramOperation,
266    },
267    #[cfg(feature = "test_helpers")]
268    /// Create a test VMGS file
269    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    // validate the VHD size
533    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    // check if the file already exists so we know whether to try to preserve
545    // the size and footer later
546    let exists = Path::new(path.as_ref()).exists();
547
548    // open/create the file
549    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    // determine if a resize is necessary
565    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    // resize the file if necessary
579    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    // attempt to open the VHD file if it already existed
602    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    // format the VHD if necessary
611    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    // manually allow, since we want to differentiate between the file not being
663    // accessible, and a read operation failing
664    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
707/// Get data from VMGS file, and write to `data_path`.
708async 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    // Create a new test file path.
1190    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        // try to normal write encrypted VMGs
1310        vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_1, false, false)
1311            .await
1312            .unwrap();
1313
1314        // try to normal read encrypted FileId
1315        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        // You shouldn't be able to use encryption if you create the VMGS
1324        // file without encryption.
1325        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        // create an empty (zero-length) file
1426        {
1427            fs_err::OpenOptions::new()
1428                .write(true)
1429                .create_new(true)
1430                .open(&path)
1431                .unwrap();
1432        }
1433
1434        // verify the file is zero size
1435        {
1436            let result = test_vmgs_open(&path, OpenMode::ReadOnly, None).await;
1437            assert!(matches!(result, Err(Error::ZeroSize)));
1438        }
1439
1440        // create an empty vhd of default size
1441        {
1442            vhdfiledisk_create(&path, None, true).unwrap();
1443        }
1444
1445        // verify the file is empty (with non-zero size)
1446        {
1447            let result = test_vmgs_open(&path, OpenMode::ReadOnly, None).await;
1448            assert!(matches!(result, Err(Error::EmptyFile)));
1449        }
1450
1451        // write some invalid data to the file
1452        {
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        // verify the vmgs is identified as corrupted
1463        {
1464            let result = test_vmgs_open(&path, OpenMode::ReadOnly, None).await;
1465            matches!(result, Err(Error::Vmgs(vmgs::Error::CorruptFormat(_))));
1466        }
1467
1468        // create a valid vmgs
1469        {
1470            test_vmgs_create(&path, None, true, None).await.unwrap();
1471        }
1472
1473        // sanity check that the positive case works
1474        {
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        //File size must be % 512 to be valid, should produce error and file should not be created
1485        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        // Recreating file should fail without force create flag
1496        let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1497        assert!(result.is_err());
1498        // Should be able to resize the file when force create is passed in
1499        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        // Old key should no longer work
1551        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}