diff --git a/shim.changes b/shim.changes index dc88a33..84dc137 100644 --- a/shim.changes +++ b/shim.changes @@ -1,3 +1,12 @@ +------------------------------------------------------------------- +Mon Oct 13 16:31:45 UTC 2025 - Joey Lee + +- 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 diff --git a/shim.spec b/shim.spec index eb4f761..861bdd7 100644 --- a/shim.spec +++ b/shim.spec @@ -385,6 +385,189 @@ cp -r source/* %{buildroot}/usr/src/debug/%{name}-%{version} %clean %{?buildroot:%__rm -rf "%{buildroot}"} +%pretrans -p +-- 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