File shim-posttrans.spec of Package shim-posttrans

#
# spec file for package pesign-obs-integration
#
# Copyright (c) 2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# Please submit bugfixes or comments via https://bugs.opensuse.org/

Name:           shim-posttrans
Version:        0.1
Release:        0
Summary:        Testing lua script for shim posttrans
License:        GPL-2.0-or-later
Group:          Development/Tools/Other
Source0:	Microsoft_Corporation_UEFI_CA_2011.hex	

%description
Testing lua script for shim posttrans

%prep

%build

%install
# install MS certificate hex, just for packaging for testing
install -d %{buildroot}/%{_sysconfdir}/uefi/certs/
install -m 644 %{SOURCE0} %{buildroot}/%{_sysconfdir}/uefi/certs/

%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

%files
%dir %{_sysconfdir}/uefi/
%dir %{_sysconfdir}/uefi/certs/
%{_sysconfdir}/uefi/certs/*.hex

%changelog
openSUSE Build Service is sponsored by