use clap::{CommandFactory, Parser, ValueEnum};
use log::error;
use std::process::ExitCode;
use der::{DecodePem, Encode};
use x509_cert::Certificate;
use virtfw_libefi::arch::EfiArch;
use virtfw_libefi::efivar::auth::auth_to_esl;
use virtfw_libefi::efivar::sigdb::EfiSigDB;
use virtfw_libefi::guids;
use virtfw_libefi::sb::certs::profiles::SecureBootProfile;
use virtfw_libefi::sb::dbx::*;
use virtfw_varstore::store::EfiVarStore;
#[derive(Debug, Clone, ValueEnum)]
pub enum PkEnroll {
#[value(name = "mgmt", help = "EFI_CERT_EXTERNAL_MANAGEMENT_GUID")]
ExtMgmt,
#[value(name = "redhat", help = "Red Hat Secure Boot (PK/KEK key 1)")]
RedHat,
}
#[derive(Parser, Debug)]
#[command(version, author, name = "virt-fw-vars-setup",
about = "generate uefi variable store in json format",
long_about = None)]
struct Args {
#[arg(long, value_name = "FILE")]
output: Option<String>,
#[arg(long, hide = true)]
manpage: bool,
#[arg(
long,
value_name = "NAME",
help_heading = "Quick setup",
required_unless_present = "manpage"
)]
profile: Option<SecureBootProfile>,
#[arg(long, value_name = "MODE", help_heading = "Quick setup",
value_enum, default_value_t = PkEnroll::ExtMgmt)]
enroll: PkEnroll,
#[arg(long, value_name = "ARCH", help_heading = "Quick setup")]
arch: Option<EfiArch>,
#[arg(long, value_name = "FILE", help_heading = "Specify details")]
set_pk_cert: Option<String>,
#[arg(long, value_name = "FILE", help_heading = "Specify details")]
add_kek_cert: Vec<String>,
#[arg(long, value_name = "FILE", help_heading = "Specify details")]
add_db_cert: Vec<String>,
#[arg(long, value_name = "FILE", help_heading = "Specify details")]
set_dbx_update: Option<String>,
#[arg(long, help_heading = "Specify details")]
disable_secure_boot: bool,
}
fn loginit() {
let levelfilter = log::LevelFilter::Info;
env_logger::Builder::from_default_env()
.filter_module(module_path!(), levelfilter)
.format_timestamp(None)
.format_target(false)
.init();
}
fn cert_is_pem(data: &[u8]) -> bool {
let Ok(s) = std::str::from_utf8(data) else {
return false;
};
s.contains("-----BEGIN")
}
fn cert_load(filename: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let data = std::fs::read(filename)?;
if cert_is_pem(&data) {
let cert = Certificate::from_pem(&data)?;
Ok(cert.to_der().expect("serialize cert"))
} else {
Ok(data)
}
}
fn main() -> ExitCode {
let cfg = Args::parse();
loginit();
if cfg.manpage {
let man = clap_mangen::Man::new(Args::command());
man.render(&mut std::io::stdout()).expect("render manpage");
return 0.into();
}
let mut store = EfiVarStore::new();
let mut db = cfg.profile.unwrap().sigdb();
for filename in cfg.add_db_cert {
let der = cert_load(&filename);
if let Err(e) = der {
error!("{filename}: {e}");
return 1.into();
}
db.add_x509_from_der(&guids::OvmfEnrollDefaultKeys, &der.unwrap());
}
store.enroll_db(db.get_x509_mtime(), &db);
let auth = if let Some(filename) = cfg.set_dbx_update {
match std::fs::read(&filename) {
Err(e) => {
error!("{filename}: {e}");
return 1.into();
}
Ok(a) => a,
}
} else {
get_dbx_opt(cfg.arch).into()
};
let (dbx_ts, esl) = auth_to_esl(&auth).unwrap();
let dbx = EfiSigDB::new_from_bytes(esl).unwrap();
store.enroll_dbx(Some(dbx_ts), &dbx);
if cfg.add_kek_cert.is_empty() {
store.enroll_kek_microsoft();
} else {
let mut kek = EfiSigDB::new();
for filename in cfg.add_kek_cert {
let der = cert_load(&filename);
if let Err(e) = der {
error!("{filename}: {e}");
return 1.into();
}
kek.add_x509_from_der(&guids::OvmfEnrollDefaultKeys, &der.unwrap());
}
store.enroll_kek(kek.get_x509_mtime(), &kek);
}
if let Some(filename) = cfg.set_pk_cert {
let mut pk = EfiSigDB::new();
let der = cert_load(&filename);
if let Err(e) = der {
error!("{filename}: {e}");
return 1.into();
}
pk.add_x509_from_der(&guids::OvmfEnrollDefaultKeys, &der.unwrap());
store.enroll_pk(pk.get_x509_mtime(), &pk);
} else {
match cfg.enroll {
PkEnroll::RedHat => store.enroll_pk_redhat(),
PkEnroll::ExtMgmt => store.enroll_pk_mgmt(),
}
}
store.set_secure_boot_enable(!cfg.disable_secure_boot);
let jstore = store.json_export();
let json = serde_json::to_string(&jstore).expect("serialize to json failed");
if let Some(filename) = cfg.output {
if let Err(e) = std::fs::write(&filename, json) {
error!("{filename}: {e}");
return 1.into();
}
} else {
println!("{json}");
};
0.into()
}