#![allow(clippy::doc_markdown)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use anyhow::{Result, anyhow, bail};
use openssl::hash::MessageDigest;
use openssl::pkcs7::{Pkcs7, Pkcs7Flags};
use openssl::pkey::{PKey, PKeyRef, Private};
use openssl::sha::{Sha256, sha256};
use openssl::sign::Signer;
use openssl::stack::Stack;
use openssl::x509::X509;
use crate::pipeline::{TARGET_EFI_MOUNT, TARGET_MOUNT};
use crate::subproc::LogFn;
const OID_SPC_INDIRECT_DATA: &[u32] = &[1, 3, 6, 1, 4, 1, 311, 2, 1, 4];
const OID_SPC_PE_IMAGE_DATA: &[u32] = &[1, 3, 6, 1, 4, 1, 311, 2, 1, 15];
const OID_SHA256: &[u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 1];
const OID_RSA_ENCRYPTION: &[u32] = &[1, 2, 840, 113_549, 1, 1, 1];
const OID_SIGNED_DATA: &[u32] = &[1, 2, 840, 113_549, 1, 7, 2];
const OID_CONTENT_TYPE: &[u32] = &[1, 2, 840, 113_549, 1, 9, 3];
const OID_MESSAGE_DIGEST: &[u32] = &[1, 2, 840, 113_549, 1, 9, 4];
const WIN_CERT_REVISION_2_0: u16 = 0x0200;
const WIN_CERT_TYPE_PKCS_SIGNED_DATA: u16 = 0x0002;
const SPC_PE_IMAGE_DATA_VALUE: &[u8] = &[
0x30, 0x25, 0x03, 0x01, 0x00, 0xa0, 0x20, 0xa2, 0x1e, 0x80, 0x1c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x4f, 0x00, 0x62, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c,
0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x3e,
];
fn der_len(len: usize) -> Vec<u8> {
if len < 0x80 {
return vec![len as u8];
}
let bytes = len.to_be_bytes();
let start = bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(bytes.len() - 1);
let body = &bytes[start..];
let mut out = Vec::with_capacity(1 + body.len());
out.push(0x80 | body.len() as u8);
out.extend_from_slice(body);
out
}
fn der_tlv(tag: u8, contents: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + contents.len());
out.push(tag);
out.extend_from_slice(&der_len(contents.len()));
out.extend_from_slice(contents);
out
}
fn der_seq(elems: &[Vec<u8>]) -> Vec<u8> {
der_tlv(0x30, &elems.concat())
}
fn der_set(elems: &[Vec<u8>]) -> Vec<u8> {
der_tlv(0x31, &elems.concat())
}
fn der_octet(bytes: &[u8]) -> Vec<u8> {
der_tlv(0x04, bytes)
}
fn der_int_u8(value: u8) -> Vec<u8> {
der_tlv(0x02, &[value])
}
fn der_null() -> Vec<u8> {
vec![0x05, 0x00]
}
fn der_oid(arcs: &[u32]) -> Vec<u8> {
let mut body = vec![(arcs[0] * 40 + arcs[1]) as u8];
for &arc in &arcs[2..] {
let mut group = vec![(arc & 0x7f) as u8];
let mut rest = arc >> 7;
while rest > 0 {
group.push(0x80 | (rest & 0x7f) as u8);
rest >>= 7;
}
group.reverse();
body.extend_from_slice(&group);
}
der_tlv(0x06, &body)
}
fn algorithm_identifier(oid: &[u32]) -> Vec<u8> {
der_seq(&[der_oid(oid), der_null()])
}
struct Tlv<'a> {
tag: u8,
contents: &'a [u8],
total: &'a [u8],
}
fn der_read(data: &[u8], pos: usize) -> Result<(Tlv<'_>, usize)> {
let tag = *data.get(pos).ok_or_else(|| anyhow!("truncated DER tag"))?;
let first = *data
.get(pos + 1)
.ok_or_else(|| anyhow!("truncated DER length"))?;
let (header, len) = if first < 0x80 {
(2, first as usize)
} else {
let n = (first & 0x7f) as usize;
let mut len = 0usize;
for i in 0..n {
let byte = *data
.get(pos + 2 + i)
.ok_or_else(|| anyhow!("truncated DER length"))?;
len = (len << 8) | byte as usize;
}
(2 + n, len)
};
let start = pos + header;
let end = start
.checked_add(len)
.filter(|&e| e <= data.len())
.ok_or_else(|| anyhow!("DER element exceeds buffer"))?;
Ok((
Tlv {
tag,
contents: &data[start..end],
total: &data[pos..end],
},
end,
))
}
fn issuer_and_serial(cert_der: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
let (cert, _) = der_read(cert_der, 0)?;
if cert.tag != 0x30 {
bail!("certificate is not a SEQUENCE");
}
let (tbs, _) = der_read(cert.contents, 0)?;
if tbs.tag != 0x30 {
bail!("tbsCertificate is not a SEQUENCE");
}
let body = tbs.contents;
let (first, mut pos) = der_read(body, 0)?;
if first.tag != 0xa0 {
pos = 0;
}
let (serial, after_serial) = der_read(body, pos)?;
if serial.tag != 0x02 {
bail!("serialNumber is not an INTEGER");
}
let (_signature, after_sig) = der_read(body, after_serial)?;
let (issuer, _) = der_read(body, after_sig)?;
if issuer.tag != 0x30 {
bail!("issuer is not a Name SEQUENCE");
}
Ok((issuer.total.to_vec(), serial.total.to_vec()))
}
fn parse_authenticode(pkcs7: &[u8]) -> Option<(Vec<u8>, Vec<Vec<u8>>)> {
let (content_info, _) = der_read(pkcs7, 0).ok()?;
if content_info.tag != 0x30 {
return None;
}
let (_content_type, after_type) = der_read(content_info.contents, 0).ok()?;
let (content, _) = der_read(content_info.contents, after_type).ok()?;
if content.tag != 0xa0 {
return None;
}
let (signed_data, _) = der_read(content.contents, 0).ok()?;
if signed_data.tag != 0x30 {
return None;
}
let body = signed_data.contents;
let (_version, mut pos) = der_read(body, 0).ok()?;
let (_digest_algs, next) = der_read(body, pos).ok()?;
pos = next;
let (encap, next) = der_read(body, pos).ok()?;
pos = next;
let (_econtent_type, e1) = der_read(encap.contents, 0).ok()?;
let (econtent, _) = der_read(encap.contents, e1).ok()?;
if econtent.tag != 0xa0 {
return None;
}
let (spc, _) = der_read(econtent.contents, 0).ok()?;
let (_spc_data, s1) = der_read(spc.contents, 0).ok()?;
let (digest_info, _) = der_read(spc.contents, s1).ok()?;
let (_alg, d1) = der_read(digest_info.contents, 0).ok()?;
let (digest, _) = der_read(digest_info.contents, d1).ok()?;
if digest.tag != 0x04 {
return None;
}
let mut certs = Vec::new();
while pos < body.len() {
let (elem, next) = der_read(body, pos).ok()?;
pos = next;
if elem.tag == 0xa0 {
let mut cpos = 0;
while cpos < elem.contents.len() {
let (cert, cnext) = der_read(elem.contents, cpos).ok()?;
certs.push(cert.total.to_vec());
cpos = cnext;
}
}
}
Some((digest.contents.to_vec(), certs))
}
fn load_key_and_cert(key_pem: &str, cert_pem: &str) -> Result<(PKey<Private>, X509)> {
let key = PKey::private_key_from_pem(key_pem.as_bytes())?;
let cert = X509::from_pem(cert_pem.as_bytes())?;
Ok((key, cert))
}
fn build_pkcs7(digest: &[u8], key: &PKeyRef<Private>, cert: &X509) -> Result<Vec<u8>> {
let cert_der = cert.to_der()?;
let (issuer, serial) = issuer_and_serial(&cert_der)?;
let spc_attr = der_seq(&[
der_oid(OID_SPC_PE_IMAGE_DATA),
SPC_PE_IMAGE_DATA_VALUE.to_vec(),
]);
let spc_digest = der_seq(&[algorithm_identifier(OID_SHA256), der_octet(digest)]);
let spc = der_seq(&[spc_attr, spc_digest]);
let content_digest = sha256(&spc);
let ct_attr = der_seq(&[
der_oid(OID_CONTENT_TYPE),
der_set(&[der_oid(OID_SPC_INDIRECT_DATA)]),
]);
let md_attr = der_seq(&[
der_oid(OID_MESSAGE_DIGEST),
der_set(&[der_octet(&content_digest)]),
]);
let mut attr_elems = [ct_attr, md_attr];
attr_elems.sort_unstable();
let attrs_contents = attr_elems.concat();
let signed_attrs_to_sign = der_tlv(0x31, &attrs_contents);
let mut signer = Signer::new(MessageDigest::sha256(), key)?;
signer.update(&signed_attrs_to_sign)?;
let signature = signer.sign_to_vec()?;
let issuer_and_serial = der_seq(&[issuer, serial]);
let signed_attrs_implicit = der_tlv(0xa0, &attrs_contents);
let signer_info = der_seq(&[
der_int_u8(1), issuer_and_serial,
algorithm_identifier(OID_SHA256),
signed_attrs_implicit,
algorithm_identifier(OID_RSA_ENCRYPTION),
der_octet(&signature),
]);
let econtent = der_tlv(0xa0, &spc); let encap = der_seq(&[der_oid(OID_SPC_INDIRECT_DATA), econtent]);
let certificates = der_tlv(0xa0, &cert_der); let signed_data = der_seq(&[
der_int_u8(1), der_set(&[algorithm_identifier(OID_SHA256)]),
encap,
certificates,
der_set(&[signer_info]),
]);
let content = der_tlv(0xa0, &signed_data);
Ok(der_seq(&[der_oid(OID_SIGNED_DATA), content]))
}
fn read_u32_le(data: &[u8], off: usize) -> Result<u32> {
let bytes: [u8; 4] = data
.get(off..off + 4)
.ok_or_else(|| anyhow!("truncated PE at offset {off:#x}"))?
.try_into()
.map_err(|_| anyhow!("slice length mismatch reading PE"))?;
Ok(u32::from_le_bytes(bytes))
}
fn read_u16_le(data: &[u8], off: usize) -> Result<u16> {
let bytes: [u8; 2] = data
.get(off..off + 2)
.ok_or_else(|| anyhow!("truncated PE at offset {off:#x}"))?
.try_into()
.map_err(|_| anyhow!("slice length mismatch reading PE"))?;
Ok(u16::from_le_bytes(bytes))
}
fn optional_header_offset(data: &[u8]) -> Result<usize> {
Ok(read_u32_le(data, 0x3c)? as usize + 24)
}
fn security_dir_offset(data: &[u8]) -> Result<usize> {
let opt = optional_header_offset(data)?;
let magic = read_u16_le(data, opt)?;
let dir_start = match magic {
0x10b => opt + 96, 0x20b => opt + 112, other => bail!("unsupported PE optional header magic {other:#x}"),
};
Ok(dir_start + 4 * 8) }
fn authenticode_sha256(image: &[u8]) -> Result<[u8; 32]> {
let e_lfanew = read_u32_le(image, 0x3c)? as usize;
let opt = optional_header_offset(image)?;
let check_sum = opt + 64;
let after_check_sum = check_sum + 4;
let sec_dir = security_dir_offset(image)?;
let after_sec_dir = sec_dir + 8;
let size_of_headers = read_u32_le(image, opt + 60)? as usize;
let slice = |start: usize, end: usize| -> Result<&[u8]> {
image
.get(start..end)
.ok_or_else(|| anyhow!("PE region {start:#x}..{end:#x} out of bounds"))
};
let mut hasher = Sha256::new();
hasher.update(slice(0, check_sum)?);
hasher.update(slice(after_check_sum, sec_dir)?);
hasher.update(slice(after_sec_dir, size_of_headers)?);
let num_sections = read_u16_le(image, e_lfanew + 6)? as usize;
let size_of_optional_header = read_u16_le(image, e_lfanew + 20)? as usize;
let section_table = opt + size_of_optional_header;
let mut ranges = Vec::with_capacity(num_sections);
for i in 0..num_sections {
let header = section_table + i * 40;
let ptr = read_u32_le(image, header + 20)? as usize;
let size = read_u32_le(image, header + 16)? as usize;
ranges.push((ptr, ptr + size));
}
ranges.sort_unstable_by_key(|r| r.0);
let mut bytes_hashed = size_of_headers;
for (start, end) in ranges {
hasher.update(slice(start, end)?);
bytes_hashed += end - start;
}
let cert_table_size = read_u32_le(image, sec_dir + 4)? as usize;
let extra = image
.len()
.checked_sub(bytes_hashed)
.and_then(|n| n.checked_sub(cert_table_size))
.ok_or_else(|| anyhow!("invalid PE layout while hashing trailing data"))?;
hasher.update(slice(bytes_hashed, bytes_hashed + extra)?);
Ok(hasher.finish())
}
fn attribute_certificates(image: &[u8]) -> Result<Vec<&[u8]>> {
let sec_dir = security_dir_offset(image)?;
let table_off = read_u32_le(image, sec_dir)? as usize;
let table_size = read_u32_le(image, sec_dir + 4)? as usize;
if table_size == 0 {
return Ok(Vec::new());
}
let table_end = table_off + table_size;
let mut out = Vec::new();
let mut pos = table_off;
while pos + 8 <= table_end {
let cert_size = read_u32_le(image, pos)? as usize;
let revision = read_u16_le(image, pos + 4)?;
let cert_type = read_u16_le(image, pos + 6)?;
if cert_size < 8 {
break;
}
let data = image
.get(pos + 8..pos + cert_size)
.ok_or_else(|| anyhow!("certificate entry exceeds table"))?;
if revision == WIN_CERT_REVISION_2_0 && cert_type == WIN_CERT_TYPE_PKCS_SIGNED_DATA {
out.push(data);
}
pos += (cert_size + 7) & !7; }
Ok(out)
}
pub fn sign_pe(image: &[u8], key_pem: &str, cert_pem: &str) -> Result<Vec<u8>> {
let (key, cert) = load_key_and_cert(key_pem, cert_pem)?;
let digest = authenticode_sha256(image)?;
let pkcs7 = build_pkcs7(&digest, &key, &cert)?;
let cert_len = 8 + pkcs7.len();
let mut entry = Vec::with_capacity((cert_len + 7) & !7);
entry.extend_from_slice(&(cert_len as u32).to_le_bytes());
entry.extend_from_slice(&WIN_CERT_REVISION_2_0.to_le_bytes());
entry.extend_from_slice(&WIN_CERT_TYPE_PKCS_SIGNED_DATA.to_le_bytes());
entry.extend_from_slice(&pkcs7);
while entry.len() % 8 != 0 {
entry.push(0);
}
let mut out = image.to_vec();
while out.len() % 8 != 0 {
out.push(0);
}
let cert_offset = out.len();
out.extend_from_slice(&entry);
let dir = security_dir_offset(image)?;
out[dir..dir + 4].copy_from_slice(&(cert_offset as u32).to_le_bytes());
out[dir + 4..dir + 8].copy_from_slice(&(entry.len() as u32).to_le_bytes());
Ok(out)
}
pub fn verify_pe(image: &[u8], cert_pem: &str) -> Result<bool> {
let Ok(cert) = X509::from_pem(cert_pem.as_bytes()) else {
return Ok(false);
};
let cert_der = cert.to_der()?;
let expected = authenticode_sha256(image)?;
for pkcs7 in attribute_certificates(image)? {
let Some((digest, certs)) = parse_authenticode(pkcs7) else {
continue;
};
if digest != expected {
continue;
}
if certs.iter().any(|c| c == &cert_der) {
return Ok(true);
}
}
Ok(false)
}
const EFI_CERT_X509_GUID: [u8; 16] = [
0xa1, 0x59, 0xc0, 0xa5, 0xe4, 0x94, 0xa7, 0x4a, 0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72,
];
const EFI_CERT_TYPE_PKCS7_GUID: [u8; 16] = [
0x9d, 0xd2, 0xaf, 0x4a, 0xdf, 0x68, 0xee, 0x49, 0x8a, 0xa9, 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7,
];
const EFI_GLOBAL_VARIABLE_GUID: [u8; 16] = [
0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c,
];
const EFI_IMAGE_SECURITY_DATABASE_GUID: [u8; 16] = [
0xcb, 0xb2, 0x19, 0xd7, 0x3a, 0x3d, 0x96, 0x45, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f,
];
const AUTH_VAR_ATTRS: u32 = 0x1 | 0x2 | 0x4 | 0x20;
const WIN_CERT_TYPE_EFI_GUID: u16 = 0x0ef1;
const EFIVARS_DIR: &str = "/sys/firmware/efi/efivars";
fn signature_list(cert_der: &[u8], owner_guid: &[u8; 16]) -> Vec<u8> {
let signature_size = 16 + cert_der.len();
let list_size = 28 + signature_size;
let mut out = Vec::with_capacity(list_size);
out.extend_from_slice(&EFI_CERT_X509_GUID);
out.extend_from_slice(&(list_size as u32).to_le_bytes());
out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&(signature_size as u32).to_le_bytes());
out.extend_from_slice(owner_guid);
out.extend_from_slice(cert_der);
out
}
fn efi_time_zero() -> [u8; 16] {
[0u8; 16]
}
fn auth_message(name: &str, vendor_guid: &[u8; 16], data: &[u8]) -> Vec<u8> {
let mut msg = Vec::new();
for unit in name.encode_utf16() {
msg.extend_from_slice(&unit.to_le_bytes());
}
msg.extend_from_slice(vendor_guid);
msg.extend_from_slice(&AUTH_VAR_ATTRS.to_le_bytes());
msg.extend_from_slice(&efi_time_zero());
msg.extend_from_slice(data);
msg
}
fn sign_detached_pkcs7(
message: &[u8],
signer_key: &PKeyRef<Private>,
signer_cert: &X509,
) -> Result<Vec<u8>> {
let certs = Stack::new()?;
let pkcs7 = Pkcs7::sign(
signer_cert,
signer_key,
&certs,
message,
Pkcs7Flags::DETACHED | Pkcs7Flags::BINARY | Pkcs7Flags::NOATTR,
)?;
Ok(pkcs7.to_der()?)
}
fn authenticated_payload(
name: &str,
vendor_guid: &[u8; 16],
esl: &[u8],
signer_key: &PKeyRef<Private>,
signer_cert: &X509,
) -> Result<Vec<u8>> {
let message = auth_message(name, vendor_guid, esl);
let pkcs7 = sign_detached_pkcs7(&message, signer_key, signer_cert)?;
let mut out = Vec::new();
out.extend_from_slice(&efi_time_zero()); let win_cert_len = 8 + 16 + pkcs7.len();
out.extend_from_slice(&(win_cert_len as u32).to_le_bytes()); out.extend_from_slice(&WIN_CERT_REVISION_2_0.to_le_bytes());
out.extend_from_slice(&WIN_CERT_TYPE_EFI_GUID.to_le_bytes());
out.extend_from_slice(&EFI_CERT_TYPE_PKCS7_GUID);
out.extend_from_slice(&pkcs7);
out.extend_from_slice(esl);
Ok(out)
}
fn write_auth_variable(name: &str, vendor_guid: &[u8; 16], payload: &[u8]) -> Result<()> {
use std::io::Write;
let guid_str = format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
vendor_guid[3],
vendor_guid[2],
vendor_guid[1],
vendor_guid[0],
vendor_guid[5],
vendor_guid[4],
vendor_guid[7],
vendor_guid[6],
vendor_guid[8],
vendor_guid[9],
vendor_guid[10],
vendor_guid[11],
vendor_guid[12],
vendor_guid[13],
vendor_guid[14],
vendor_guid[15],
);
let path = format!("{EFIVARS_DIR}/{name}-{guid_str}");
let mut buf = Vec::with_capacity(4 + payload.len());
buf.extend_from_slice(&AUTH_VAR_ATTRS.to_le_bytes());
buf.extend_from_slice(payload);
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(false)
.open(&path)
.map_err(|e| anyhow!("failed to open {path}: {e}"))?;
file.write_all(&buf)
.map_err(|e| anyhow!("failed to write {path}: {e}"))?;
Ok(())
}
pub fn enroll_keys(keydir: &std::path::Path, owner_guid: &[u8; 16]) -> Result<()> {
let read_pair = |kind: &str| -> Result<(PKey<Private>, X509)> {
let dir = keydir.join(kind);
let key_pem = std::fs::read_to_string(dir.join(format!("{kind}.key")))
.map_err(|e| anyhow!("failed to read {kind} key: {e}"))?;
let cert_pem = std::fs::read_to_string(dir.join(format!("{kind}.pem")))
.map_err(|e| anyhow!("failed to read {kind} certificate: {e}"))?;
load_key_and_cert(&key_pem, &cert_pem)
};
let (pk_key, pk_cert) = read_pair("PK")?;
let (kek_key, kek_cert) = read_pair("KEK")?;
let (_db_key, db_cert) = read_pair("db")?;
let cert_der = |c: &X509| -> Result<Vec<u8>> { Ok(c.to_der()?) };
let esl = signature_list(&cert_der(&db_cert)?, owner_guid);
let payload = authenticated_payload(
"db",
&EFI_IMAGE_SECURITY_DATABASE_GUID,
&esl,
&kek_key,
&kek_cert,
)?;
write_auth_variable("db", &EFI_IMAGE_SECURITY_DATABASE_GUID, &payload)?;
let esl = signature_list(&cert_der(&kek_cert)?, owner_guid);
let payload = authenticated_payload("KEK", &EFI_GLOBAL_VARIABLE_GUID, &esl, &pk_key, &pk_cert)?;
write_auth_variable("KEK", &EFI_GLOBAL_VARIABLE_GUID, &payload)?;
let esl = signature_list(&cert_der(&pk_cert)?, owner_guid);
let payload = authenticated_payload("PK", &EFI_GLOBAL_VARIABLE_GUID, &esl, &pk_key, &pk_cert)?;
write_auth_variable("PK", &EFI_GLOBAL_VARIABLE_GUID, &payload)?;
Ok(())
}
const SBCTL_KEY_PATHS: &[&str] = &[
"var/lib/sbctl/keys/PK/PK.key",
"var/lib/sbctl/keys/KEK/KEK.key",
"var/lib/sbctl/keys/db/db.key",
];
fn target_sbctl_keys_exist() -> bool {
let target = Path::new(TARGET_MOUNT);
SBCTL_KEY_PATHS.iter().all(|p| target.join(p).is_file())
}
fn target_sbctl_dir() -> PathBuf {
let path = Path::new(TARGET_MOUNT).join("var/lib/sbctl");
let _ = std::fs::create_dir_all(&path);
path
}
pub fn sbctl_keydir() -> PathBuf {
target_sbctl_dir().join("keys")
}
pub fn sbctl_owner_guid() -> [u8; 16] {
let contents = std::fs::read_to_string(target_sbctl_dir().join("GUID")).unwrap_or_default();
uuid::Uuid::parse_str(contents.trim())
.unwrap_or(uuid::Uuid::nil())
.to_bytes_le()
}
pub fn target_efi_binaries() -> Vec<PathBuf> {
let esp = Path::new(TARGET_EFI_MOUNT);
let mut paths: Vec<PathBuf> = Vec::new();
for pattern in &["*.efi", "*.EFI"] {
for entry in walkdir_like(esp, pattern) {
if entry.is_file() {
paths.push(entry);
}
}
}
paths.sort();
paths
}
fn walkdir_like(root: &Path, glob: &str) -> Vec<PathBuf> {
let mut results = Vec::new();
let ext_lower = glob.trim_start_matches('*').to_lowercase();
#[allow(clippy::items_after_statements)]
fn recurse(dir: &Path, ext: &str, results: &mut Vec<PathBuf>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
recurse(&path, ext, results);
} else if path
.extension()
.and_then(|e| e.to_str())
.is_some_and(|e| e.to_lowercase() == ext)
{
results.push(path);
}
}
}
}
recurse(root, &ext_lower, &mut results);
results
}
fn random_uuid() -> Result<String> {
let mut buf = [0u8; 16];
openssl::rand::rand_bytes(&mut buf)?;
Ok(uuid::Builder::from_random_bytes(buf)
.into_uuid()
.to_string())
}
pub(crate) fn generate_sb_key(common_name: &str) -> Result<(String, String)> {
use openssl::asn1::Asn1Time;
use openssl::bn::{BigNum, MsbOption};
use openssl::rsa::Rsa;
use openssl::x509::X509NameBuilder;
let rsa = Rsa::generate(2048)?;
let pkey = PKey::from_rsa(rsa)?;
let mut name = X509NameBuilder::new()?;
name.append_entry_by_text("CN", common_name)?;
let name = name.build();
let mut builder = X509::builder()?;
builder.set_version(2)?; let mut serial = BigNum::new()?;
serial.rand(159, MsbOption::MAYBE_ZERO, false)?;
let serial = serial.to_asn1_integer()?;
builder.set_serial_number(&serial)?;
builder.set_subject_name(&name)?;
builder.set_issuer_name(&name)?;
builder.set_pubkey(&pkey)?;
let not_before = Asn1Time::days_from_now(0)?;
let not_after = Asn1Time::days_from_now(3650)?;
builder.set_not_before(¬_before)?;
builder.set_not_after(¬_after)?;
builder.sign(&pkey, MessageDigest::sha256())?;
let cert = builder.build();
let key_pem = String::from_utf8(pkey.private_key_to_pem_pkcs8()?)?;
let cert_pem = String::from_utf8(cert.to_pem()?)?;
Ok((key_pem, cert_pem))
}
pub fn ensure_sbctl_keys(log: &mut LogFn) -> Result<()> {
if target_sbctl_keys_exist() {
log("Secure Boot signing keys already exist; reusing them".to_string());
return Ok(());
}
let keydir = target_sbctl_dir().join("keys");
for kind in ["PK", "KEK", "db"] {
let dir = keydir.join(kind);
std::fs::create_dir_all(&dir)?;
let (key_pem, cert_pem) = generate_sb_key(kind)?;
let key_path = dir.join(format!("{kind}.key"));
std::fs::write(&key_path, key_pem)?;
std::fs::set_permissions(&key_path, PermissionsExt::from_mode(0o600))?;
std::fs::write(dir.join(format!("{kind}.pem")), cert_pem)?;
}
let guid_path = target_sbctl_dir().join("GUID");
if !guid_path.exists() {
std::fs::write(&guid_path, format!("{}\n", random_uuid()?))?;
}
log("generated Secure Boot PK/KEK/db keys".to_string());
Ok(())
}