virtfw-igvm-tools 0.1.6

igvm related linux applications
Documentation
//!
//! add kernel image to (ovmf) igvm
//!
use clap::Parser;
use log::{debug, error, info};
use std::process::ExitCode;

use authenticode::authenticode_digest;
use object::read::pe;
use sha2::{Digest, Sha256};

use virtfw_libefi::efivar::auth::esl_to_auth;
use virtfw_libefi::efivar::sigdb::EfiSigDB;
use virtfw_libefi::guids;
use virtfw_libefi::sb::certs::*;
use virtfw_libefi::sb::dbx::*;
use virtfw_libefi::types::EfiTime;

use igvm::IgvmFile;
use virtfw_igvm_tools::builder::Builder;
use virtfw_igvm_tools::efidata::{EfiIgvmDataList, EfiIgvmDataType};
use virtfw_igvm_tools::inspect::{find_resetvector, inspect_igvm};
use virtfw_igvm_tools::ovmfmeta::{OvmfMeta, OvmfRegionType};

#[derive(Parser, Debug)]
#[command(version, about = "igvm-kernel -- add kernel to igvm file", long_about = None)]
struct Args {
    /// input igvm image
    #[arg(short, long, value_name = "FILE")]
    input: String,

    /// input shim image (optional)
    #[arg(short, long, value_name = "SHIM")]
    shim: Option<String>,

    /// input kernel image (can be any efi image)
    #[arg(short, long, value_name = "KERNEL")]
    kernel: Option<String>,

    /// add microsoft certificates to 'db'
    #[arg(long)]
    add_cert_ms: bool,

    /// add authenticode sha256 hashes for shim and kernel to 'db'
    #[arg(long, visible_alias = "add-hash")]
    add_hash_sha256: bool,

    /// inspect updated igvm
    #[arg(long)]
    inspect: bool,

    /// output igvm image
    #[arg(short, long, value_name = "FILE")]
    output: Option<String>,
}

fn get_sha256(binary: &[u8]) -> [u8; 32] {
    let pefile = pe::PeFile64::parse(binary).expect("efi binary parse error");
    let mut hasher = Sha256::new();
    authenticode_digest(&pefile, &mut hasher).expect("calculating authenticode hash failed");
    hasher.finalize().into()
}

fn siglist_dummy() -> Vec<[u8; 32]> {
    let hasher = Sha256::new();
    let dummy = hasher.finalize().into();
    vec![dummy]
}

fn igvm_add_kernel(
    cfg: &Args,
    igvm: &IgvmFile,
    shim_blob: Option<&[u8]>,
    kernel_blob: Option<&[u8]>,
) -> IgvmFile {
    let mut builder = Builder::from(igvm);
    let mut hoblist = EfiIgvmDataList::new(0x20000000); // start at 512 MB
    let mut shalist = Vec::new();

    // shim + kernel
    if cfg.add_hash_sha256 {
        if let Some(b) = shim_blob {
            info!("calc shim authenticode sha256 hash");
            shalist.push(get_sha256(b));
        }
        if let Some(b) = kernel_blob {
            info!("calc kernel authenticode sha256 hash");
            shalist.push(get_sha256(b));
        }
    }

    // PK
    let mut pk = EfiSigDB::new();
    pk.add_x509_from_der(&guids::MicrosoftVendor, REDHAT_PK_KEK_2014);
    let pk_ts = pk.get_x509_mtime().unwrap();
    let pk_blob = esl_to_auth(&pk_ts, &Vec::<u8>::from(&pk));

    // KEK
    let mut kek = EfiSigDB::new();
    kek.add_x509_from_der(&guids::MicrosoftVendor, MICROSOFT_KEK_2011);
    kek.add_x509_from_der(&guids::MicrosoftVendor, MICROSOFT_KEK_2023);
    let kek_ts = kek.get_x509_mtime().unwrap();
    let kek_blob = esl_to_auth(&kek_ts, &Vec::<u8>::from(&kek));

    // db
    let mut db = EfiSigDB::new();
    if cfg.add_cert_ms {
        info!("add microsoft uefi certificates to 'db'");
        db.add_x509_from_der(&guids::MicrosoftVendor, MICROSOFT_DB_UEFI_2011);
        db.add_x509_from_der(&guids::MicrosoftVendor, MICROSOFT_DB_UEFI_2023);
    }
    if cfg.add_hash_sha256 && !shalist.is_empty() {
        info!("add authenticode sha256 hash(es) to 'db'");
        db.add_sha256_from_list(&guids::OvmfEnrollDefaultKeys, shalist);
    }
    if db.siglists.is_empty() {
        info!("add dummy authenticode sha256 hash to 'db'");
        db.add_sha256_from_list(&guids::OvmfEnrollDefaultKeys, siglist_dummy());
    }
    let db_ts = if let Some(ts) = db.get_x509_mtime() {
        ts
    } else {
        // FIXME: need something better when using hashes ...
        EfiTime::initial()
    };
    let db_blob = esl_to_auth(&db_ts, &Vec::<u8>::from(&db));

    // dbx
    let dbx_blob = DBX_X86_64;

    // calculate load addresses + create hobs
    hoblist.add(&pk_blob, EfiIgvmDataType::Pk, true);
    hoblist.add(&kek_blob, EfiIgvmDataType::Kek, true);
    hoblist.add(&db_blob, EfiIgvmDataType::Db, true);
    hoblist.add(dbx_blob, EfiIgvmDataType::Dbx, true);
    if let Some(b) = shim_blob {
        hoblist.add(b, EfiIgvmDataType::Shim, false);
    }
    if let Some(b) = kernel_blob {
        hoblist.add(b, EfiIgvmDataType::Kernel, false);
    }

    let resetvector = igvm
        .directives()
        .iter()
        .filter_map(find_resetvector)
        .next()
        .expect("igvm: reset vector not found");
    let ovmfmeta = OvmfMeta::new(resetvector).expect("igvm: no ovmf metadata found");
    let hobarea = &ovmfmeta
        .regions
        .iter()
        .find(|r| r.etype == OvmfRegionType::IgvmHobArea)
        .expect("igvm: no hob area found");

    // add igvm data index hob
    debug!("{hobarea}");
    let hobs_blob = hoblist.hobs();
    builder.add_data_pages(hobarea.memory.0, &hobs_blob);

    // add data
    for (addr, blob) in hoblist.blobs(true) {
        builder.add_data_pages(addr, blob);
    }
    for (addr, blob) in hoblist.blobs(false) {
        builder.add_data_pages_unmeasured(addr, blob);
    }

    // build updated igvm
    builder.finalize().expect("build updated igvm")
}

fn main() -> ExitCode {
    let cfg = Args::parse();

    stderrlog::new()
        .module(module_path!())
        .module("virtfw_igvm_tools::efidata")
        .verbosity(stderrlog::LogLevelNum::Debug)
        .init()
        .unwrap();

    info!("reading {}", &cfg.input);
    let blob = std::fs::read(&cfg.input).expect("igvm: file read error");
    let igvm = IgvmFile::new_from_binary(&blob, None).expect("igvm: file parse error");

    // shim
    let shim_blob = if let Some(shim_name) = &cfg.shim {
        info!("reading {}", &shim_name);
        let data = std::fs::read(shim_name).expect("shim: file read error");
        Some(data)
    } else {
        None
    };

    // kernel
    let kernel_blob = if let Some(kernel_name) = &cfg.kernel {
        info!("reading {}", &kernel_name);
        let data = std::fs::read(kernel_name).expect("kernel: file read error");
        Some(data)
    } else {
        None
    };

    // merge
    let new_igvm = igvm_add_kernel(&cfg, &igvm, shim_blob.as_deref(), kernel_blob.as_deref());
    if cfg.inspect {
        println!("updated IGVM image");
        inspect_igvm(&new_igvm);
    }

    if let Some(outfile) = cfg.output {
        let mut blob = Vec::new();
        let res = new_igvm.serialize(&mut blob);
        if let Err(e) = res {
            error!("serialize igvm: {e}");
            return ExitCode::from(1);
        }

        info!("writing {}", &outfile);
        let res = std::fs::write(&outfile, blob);
        if let Err(e) = res {
            error!("write {}: {}", &outfile, e);
            return ExitCode::from(1);
        }
    } else {
        info!("igvm ok");
    }

    ExitCode::from(0)
}