vmgstool/
main.rs

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