3
0
forked from pool/shim

Add a pretrans script to verify that the necessary certificate is in the UEFI db #1

Manually merged
joeyli merged 1 commits from joeyli/shim:16.1-pretrans-script into main 2025-10-13 18:58:10 +02:00
2 changed files with 192 additions and 0 deletions

View File

@@ -1,3 +1,12 @@
-------------------------------------------------------------------
Mon Oct 13 16:31:45 UTC 2025 - Joey Lee <jlee@suse.com>
- Add a pretrans scripti to verify that the UEFI db should have the
necessary certificate to allow the shim binary to boot. The installation
will be aborted if the db is missing the target certificate. To proceed,
the user must enroll the target certificate in the db or disable UEFI
Secure Boot.
-------------------------------------------------------------------
Tue Aug 19 07:48:52 UTC 2025 - Joey Lee <jlee@suse.com>

183
shim.spec
View File

@@ -385,6 +385,189 @@ cp -r source/* %{buildroot}/usr/src/debug/%{name}-%{version}
%clean
%{?buildroot:%__rm -rf "%{buildroot}"}
%pretrans -p <lua>
-- Using Lua
print("INFO: Current Lua Version: " .. tostring(_VERSION))
-- ==========================================================================================
-- This pretrans script verifies that the UEFI db should have the necessary certificate to
-- allow the shim binary to boot.
-- The installation will be aborted if the db is missing the target certificate. To proceed,
-- the user must enroll the target certificate in the db or disable UEFI Secure Boot.
-- ==========================================================================================
local db_filename = "/sys/firmware/efi/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f"
-- The db file existence check
-- Use pcall to execute rpm.open to prevent errors from being thrown when
-- the file cannot be found, causing RPM to fail.
local success, result = pcall(rpm.open, db_filename, "rb")
local f_check = nil
if not success then
-- pcall catches errors (e.g. "No such file or directory")
print("WARNING: Attempt to open db EFI variable file failed. Error message: " .. tostring(result))
print("WARNING: This usually means the system is not booted in UEFI mode. Skipping all db check steps.")
return 0
else
-- If pcall succeeds, result may be an archive handle or nil (depending on the behavior of rpm.open)
f_check = result
if not f_check then
-- The archive does not exist, but rpm.open returns nil
print("WARNING: db EFI variable file does not exist (rpm.open returned nil). Skipping db check steps.")
return 0
else
-- If the file exists and is successfully opened,
-- close the handle immediately so that subsequent code can open it again.
f_check:close()
end
end
-- ==========================================================================================
-- This is the hardcoded target certificate content used to check for its existence.
-- HEX_CONTENT=$(xxd -p taget_certificate.der | tr -d '\n') && echo "$HEX_CONTENT"
-- ==========================================================================================
-- Only the DER format is supported
local TARGET_CERT_HEXES = {
-- Certificate #1, Microsoft Corporation UEFI CA 2011
"30820610308203f8a003020102020a6108d3c4000000000004300d06092a864886f70d01010b0500308191310b3009060355040613025553311330110603550408130a57617368696e67746f6e3110300e060355040713075265646d6f6e64311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e313b3039060355040313324d6963726f736f667420436f72706f726174696f6e205468697264205061727479204d61726b6574706c61636520526f6f74301e170d3131303632373231323234355a170d3236303632373231333234355a308181310b3009060355040613025553311330110603550408130a57617368696e67746f6e3110300e060355040713075265646d6f6e64311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e312b3029060355040313224d6963726f736f667420436f72706f726174696f6e2055454649204341203230313130820122300d06092a864886f70d01010105000382010f003082010a0282010100a5086c4cc745096a4b0ca4c0877f06750c43015464e0167f07ed927d0bb273bf0c0ac64a4561a0c5162d96d3f52ba0fb4d499b4180903cb954fde6bcd19dc4a4188a7f418a5c59836832bb8c47c9ee71bc214f9a8a7cff443f8d8f32b22648ae75b5eec94c1e4a197ee4829a1d78774d0cb0bdf60fd316d3bcfa2ba551385df5fbbadb7802dbffec0a1b96d583b81913e9b6c07b407be11f2827c9faef565e1ce67e947ec0f044b27939e5dab2628b4dbf3870e2682414c933a40837d558695ed37cedc1045308e74eb02a876308616f631559eab22b79d70c61678a5bfd5ead877fba86674f71581222042222ce8bef547100ce503558769508ee6ab1a201d50203010001a382017630820172301206092b060104018237150104050203010001302306092b060104018237150204160414f8c16bb77f77534af325371d4ea1267b0f207080301d0603551d0e0416041413adbf4309bd82709c8cd54f316ed522988a1bd4301906092b0601040182371402040c1e0a00530075006200430041300b0603551d0f040403020186300f0603551d130101ff040530030101ff301f0603551d2304183016801445665243e17e5811bfd64e9e2355083b3a226aa8305c0603551d1f045530533051a04fa04d864b687474703a2f2f63726c2e6d6963726f736f66742e636f6d2f706b692f63726c2f70726f64756374732f4d6963436f725468695061724d6172526f6f5f323031302d31302d30352e63726c306006082b0601050507010104543052305006082b060105050730028644687474703a2f2f7777772e6d6963726f736f66742e636f6d2f706b692f63657274732f4d6963436f725468695061724d6172526f6f5f323031302d31302d30352e637274300d06092a864886f70d01010b05000382020100350842ff30cccef7760cad1068583529463276277cef124127421b4aaa6d813848591355f3e95834a6160b82aa5dad82da808341068fb41df203b9f31a5d1bf15090f9b3558442281c20bdb2ae5114c5c0ac9795211c90db0ffc779e95739188cabdbd52b905500ddf579ea061ed0de56d25d9400f1740c8cea34ac24daf9a121d08548fbdc7bcb92b3d492b1f32fc6a21694f9bc87e4234fc3606178b8f2040c0b39a257527cdc903a3f65dd1e736547ab950b5d312d107bfbb74dfdc1e8f80d5ed18f42f14166b2fde668cb023e5c784d8edeac13382ad564b182df1689507cdcff072f0aebbdd8685982c214c332bf00f4af06887b592553275a16a826a3ca32511a4edadd704aecbd84059a084d1954c6291221a741d8c3d470e44a6e4b09b3435b1fab653a82c81eca40571c89db8bae81b4466e447540e8e567fb39f1698b286d0683e9023b52f5e8f50858dc68d825f41a1f42e0de099d26c75e4b669b52186fa07d1f6e24dd1daad2c77531e253237c76c52729586b0f135616a19f5b23b815056a6322dfea289f94286271855a182ca5a9bf830985414a64796252fc826e441941a5c023fe596e3855b3c3e3fbb47167255e22522b1d97be703062aa3f71e9046c3000dd61989e30e352762037115a6efd027a0a0593760f83894b8e07870f8ba4c868794f6e0ae0245ee65c2b6a37e69167507929bf5a6bc598358",
-- Certificate #2, openSUSE Secure Boot CA 2013
"308204743082035ca003020102020101300d06092a864886f70d01010b05003081813120301e06035504030c176f70656e535553452053656375726520426f6f74204341310b30090603550406130244453112301006035504070c094e7572656d6265726731193017060355040a0c106f70656e535553452050726f6a6563743121301f06092a864886f70d01090116126275696c64406f70656e737573652e6f7267301e170d3133303832363136313230375a170d3335303732323136313230375a3081813120301e06035504030c176f70656e535553452053656375726520426f6f74204341310b30090603550406130244453112301006035504070c094e7572656d6265726731193017060355040a0c106f70656e535553452050726f6a6563743121301f06092a864886f70d01090116126275696c64406f70656e737573652e6f726730820122300d06092a864886f70d01010105000382010f003082010a0282010100dedf61927aa4fe83d17d3b680eb1a7f04e9293fc473e702d4e88dc9a9efa33b4a6db0e23c10da8c1d565048404ff3a48184f3932e4ca4ef9049e9f0fcd205d61aba700d8a5ff2b7fbee847c32f5b02c8bbde8e1ae946d386efff889990eb1089b88b3f3ea807c6557a6ed35ffc833c3d16ed26c5137392b1701e2295c8006c257646f1a2d9d0b098680fa72db10d6789ca944aea12c59155767f6c7a2ef918899ff8f42443d5356acb000e2eed4be25d09d81b9770999e5a6fa681a89da958767d697182d3ba3a96439bf0da15c64ee9c815b9e9cbc7e471ceea101b6bc42a7001a952b417de0052cf7de4fd0f4d0318b29028d46fc4ae56bc366049468b6b0b0203010001a381f43081f1300f0603551d130101ff040530030101ff301d0603551d0e041604146842600de22c4c477e95be23dfea9513e59717623081ae0603551d230481a63081a380146842600de22c4c477e95be23dfea9513e5971762a18187a481843081813120301e06035504030c176f70656e535553452053656375726520426f6f74204341310b30090603550406130244453112301006035504070c094e7572656d6265726731193017060355040a0c106f70656e535553452050726f6a6563743121301f06092a864886f70d01090116126275696c64406f70656e737573652e6f7267820101300e0603551d0f0101ff040403020186300d06092a864886f70d01010b050003820101008aa389c28ed9f9820bf333cee9191717a36580cd33ae06515629b638877bf49dfc288eaae053120e3a60c706d83a61763b7708f494a48c7c473a99d8849b17cc20622ee276e4c6360d26e92e53350afb3a359345c39382c10bf308e9571f5937a9d06c69fb68ea7f3bafd3f759278ed4c79673f40c0af73ee4af6c8cc77a6f0979f4411fe36f11fb3e6cb1a07be492b7caf932f5dec3b0737de3b3825dcdec61dcfe0c3ec6b5e76c2d5d9273ffedaa6aa99b669e5e3a6d70b031c0cedf2f2110680c87f377a033310a0f15f6ee3288c59a5371cd0d1aa12889d0bff656ac4b3b36062b01c5ebe5dc72833d94ac288313fbc15d279c13f6325ff61f4ab73e538a",
}
-- Check if the TARGET_CERT_HEXES array is empty
if #TARGET_CERT_HEXES == 0 then
print("INFO: certificate list is empty. Skipping certificate check.")
-- Exiting safely as the certificate list is empty.
return 0
else
-- Check if the Hex string for certificate is valid
for i, cert_hex in ipairs(TARGET_CERT_HEXES) do
if #cert_hex % 2 ~= 0 then
print("Error: The length of hard-coded hex string for certificate #" .. i .. " must be an even number.")
error("The Hex string is invalid. The transaction is being aborted in the pretrans script.")
end
end
end
-- =========================================================================
-- Helper functions
-- =========================================================================
-- Convert hexadecimal string to original binary string
local function hex_to_binary(hex)
local binary = ""
for i = 1, #hex, 2 do
local byte_hex = hex:sub(i, i + 1)
binary = binary .. string.char(tonumber(byte_hex, 16))
end
return binary
end
-- =========================================================================
-- Main logic for checking if the db has any target certificate
-- =========================================================================
-- Read existing db contents
local db_content = ""
do
-- The db file is now confirmed to exist, open it again to read the contents
local f = rpm.open(db_filename, "rb")
if f then
local chunks = {}
local CHUNK_SIZE = 4096
local raw_content = ""
local chunk = f:read(CHUNK_SIZE)
while chunk do
-- If an empty string is read, it means EOF has been reached and the loop is exited.
if chunk == "" then
break
end
table.insert(chunks, chunk)
chunk = f:read(CHUNK_SIZE)
end
raw_content = table.concat(chunks)
f:close()
-- Skip the first 4 bytes (EFI attributes)
if #raw_content > 4 then
-- Truncate from the 5th byte to the end
db_content = string.sub(raw_content, 5)
print("INFO: Successfully read existing db content")
else
-- The file is too small or only has attributes, so it is considered blank.
db_content = ""
print("WARNING: db file content length is abnormal (<= 4 bytes). Treated as blank.")
end
end
end
-- Check all target certificates
for i, cert_hex in ipairs(TARGET_CERT_HEXES) do
local target_binary_content = hex_to_binary(cert_hex)
-- Perform binary string matching
local start_pos, end_pos = db_content:find(target_binary_content, 1, true)
if start_pos then
-- Success: Certificate exist in db
-- Return 0 to allow the RPM transaction to continue
print("Target certificate #" .. i .. " was found in the db variable. Proceed with install.")
return 0
end
end
-- Certificate not present in db
print("WARNING: The target certificate binary was not found in the db variable.")
print("Please add the appropriate certificate to the db or disable UEFI secure boot.")
-- Secure Boot status check: We only proceed with installation if the certificate is not present in the db and Secure Boot is disabled.
local sb_filename = "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c"
local success_sb, result_sb = pcall(rpm.open, sb_filename, "rb")
if not success_sb or not result_sb then
-- If the file is missing, it typically means the system is not UEFI, or Secure Boot is disabled/the variable is absent.
print("WARNING: SecureBoot EFI variable file does not exist. Proceed with install.")
else
local f_sb = result_sb
local raw_content_sb = ""
local sb_status = 0
-- Read file contents
local chunk_sb = f_sb:read(4096)
while chunk_sb do
if chunk_sb == "" then break end
raw_content_sb = raw_content_sb .. chunk_sb
chunk_sb = f_sb:read(4096)
end
f_sb:close()
-- SecureBoot status check
if #raw_content_sb >= 5 then
-- Skip the first 4-byte attribute header and read the 5th byte (status byte)
sb_status = string.byte(raw_content_sb, 5)
if sb_status == 0x00 then
print("INFO: Since Secure Boot is DISABLED, proceed with install.")
return 0
elseif sb_status == 0x01 then
error("Fatal error: Secure Boot is ENABLED (status = 0x01), but the target certificate was not found in the db. Aborting installation.")
else
error("Fatal error: Secure Boot status is unrecognized (0x" .. string.format("%02x", sb_status) .. "). Aborting installation.")
end
else
error("Fatal error: SecureBoot variable content is too short to determine status. Aborting installation.")
end
end
%post
%if 0%{?fde_tpm_update_post:1}
%fde_tpm_update_post shim