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