commit 7c08b986d4ad2dfa03b4a2ec5ffa088f97974469 Author: oxmc7769 Date: Tue May 26 03:27:31 2026 -0700 Base diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce0bc32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +output/ +build/ +iso_root/ +keys/ +input/*.efi +input/autoexec.ipxe +input/boot.ipxe +input/shim/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..02539f8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "includes/deps/gnu-efi"] + path = includes/deps/gnu-efi + url = https://github.com/rhboot/gnu-efi.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c1ab8d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,294 @@ +PROJECT_NAME = ipxe-stub +SRC_DIR = src +BUILD_DIR = build +GNU_EFI_DIR = includes/deps/gnu-efi +OUTPUT_DIR = output +KEYS_DIR = keys +SHIM_DIR = input/shim +OPENSSL_CNF = openssl.cnf + +SIGNING_KEY = $(KEYS_DIR)/signing.key +SIGNING_CERT = $(KEYS_DIR)/signing.crt +SIGNING_DER = $(KEYS_DIR)/signing.der + +HOST_ARCH := $(shell uname -m) +ARCH ?= $(HOST_ARCH) + +ifeq ($(ARCH),x86_64) + TARGET_ARCH = x86_64 + EFI_BINARY = BOOTX64.EFI + STUB_BINARY = stubx64.efi + ENROLL_BINARY = enroll-x64.efi + ARCH_CFLAGS = -mno-red-zone +else ifeq ($(ARCH),aarch64) + TARGET_ARCH = aarch64 + EFI_BINARY = BOOTAA64.EFI + STUB_BINARY = stubaa64.efi + ENROLL_BINARY = enroll-aa64.efi + ARCH_CFLAGS = +else ifeq ($(ARCH),ia32) + TARGET_ARCH = ia32 + EFI_BINARY = BOOTIA32.EFI + STUB_BINARY = stubia32.efi + ENROLL_BINARY = enroll-ia32.efi + ARCH_CFLAGS = +else + $(error Unsupported ARCH=$(ARCH). Use x86_64, aarch64, or ia32) +endif + +ifeq ($(HOST_ARCH),x86_64) + HOST_ARCH_NORM = x86_64 +else ifeq ($(HOST_ARCH),aarch64) + HOST_ARCH_NORM = aarch64 +else ifeq ($(HOST_ARCH),i686) + HOST_ARCH_NORM = ia32 +else + HOST_ARCH_NORM = unknown +endif + +ifneq ($(HOST_ARCH_NORM),$(TARGET_ARCH)) + CROSS = 1 +else + CROSS = 0 +endif + +ifeq ($(CROSS),1) + ifeq ($(TARGET_ARCH),x86_64) + CROSS_PREFIX = x86_64-linux-gnu- + else ifeq ($(TARGET_ARCH),aarch64) + CROSS_PREFIX = aarch64-linux-gnu- + else ifeq ($(TARGET_ARCH),ia32) + CROSS_PREFIX = i686-linux-gnu- + endif + CC = $(CROSS_PREFIX)gcc + LD = $(CROSS_PREFIX)ld + AR = $(CROSS_PREFIX)ar + OBJCOPY = $(CROSS_PREFIX)objcopy +else + CC = gcc + LD = ld + AR = ar + OBJCOPY = objcopy +endif + +GNU_EFI_INC = $(GNU_EFI_DIR)/inc +GNU_EFI_LIB_DIR = $(GNU_EFI_DIR)/$(TARGET_ARCH)/lib +GNU_EFI_GNUEFI = $(GNU_EFI_DIR)/gnuefi + +ARCH_BUILD_DIR = $(BUILD_DIR)/$(TARGET_ARCH) +EFI_BUILD_DIR = $(ARCH_BUILD_DIR)/efi +EFI_CRT0 = $(EFI_BUILD_DIR)/crt0-efi-$(TARGET_ARCH).o +EFI_RELOC = $(EFI_BUILD_DIR)/reloc_$(TARGET_ARCH).o +EFI_LIBEFI = $(GNU_EFI_LIB_DIR)/libefi.a +EFI_LIBGNUEFI = $(EFI_BUILD_DIR)/libgnuefi.a +EFI_LDS = $(GNU_EFI_GNUEFI)/elf_$(TARGET_ARCH)_efi.lds + +CFLAGS = -I$(GNU_EFI_INC) \ + -I$(GNU_EFI_INC)/$(TARGET_ARCH) \ + -I$(GNU_EFI_INC)/protocol \ + -Iincludes \ + -fno-stack-protector \ + -fpic \ + -fshort-wchar \ + $(ARCH_CFLAGS) \ + -DEFI_FUNCTION_WRAPPER + +LDFLAGS = -nostdlib \ + -znocombreloc \ + -T $(EFI_LDS) \ + -shared \ + -Bsymbolic \ + -L$(GNU_EFI_LIB_DIR) \ + -L$(EFI_BUILD_DIR) \ + $(EFI_CRT0) + +LIBS = -l:libgnuefi.a -l:libefi.a + +OBJCOPY_FLAGS = -j .text \ + -j .sdata \ + -j .data \ + -j .rodata \ + -j .dynamic \ + -j .dynsym \ + -j .rel \ + -j .rela \ + -j .reloc \ + --target=efi-app-$(TARGET_ARCH) + +LOADER_OBJ = $(ARCH_BUILD_DIR)/loader.o +LOADER_SO = $(ARCH_BUILD_DIR)/loader.so +TARGET_EFI = $(OUTPUT_DIR)/$(EFI_BINARY) + +STUB_OBJ = $(ARCH_BUILD_DIR)/stub.o +STUB_SO = $(ARCH_BUILD_DIR)/stub.so +TARGET_STUB = $(OUTPUT_DIR)/$(STUB_BINARY) + +ENROLL_OBJ = $(ARCH_BUILD_DIR)/enroll.o +ENROLL_SO = $(ARCH_BUILD_DIR)/enroll.so +TARGET_ENROLL = $(OUTPUT_DIR)/$(ENROLL_BINARY) + +.PHONY: all enroll clean distclean all-arches check-deps test-efi info keygen sign iso + +all: check-deps $(TARGET_EFI) $(TARGET_STUB) $(TARGET_ENROLL) + @echo "✓ Built: $(TARGET_EFI) (loader → $(STUB_BINARY))" + @echo "✓ Built: $(TARGET_STUB)" + @echo "✓ Built: $(TARGET_ENROLL) (→ mm$(TARGET_ARCH).efi on ISO)" + +all-arches: + $(MAKE) all ARCH=x86_64 + $(MAKE) all ARCH=aarch64 + $(MAKE) all ARCH=ia32 + +enroll: check-deps $(TARGET_ENROLL) + @echo "✓ Built: $(TARGET_ENROLL)" + +check-deps: + @echo "Host: $(HOST_ARCH) Target: $(TARGET_ARCH) Mode: $(if $(filter 1,$(CROSS)),cross,native)" +ifeq ($(CROSS),1) + @which $(CC) > /dev/null 2>&1 || \ + (echo "Install: sudo apt install gcc-$(TARGET_ARCH)-linux-gnu binutils-$(TARGET_ARCH)-linux-gnu" && exit 1) +endif + +$(EFI_LIBEFI): + @echo "→ Building libefi.a" + @if [ "$(CROSS)" = "1" ]; then \ + $(MAKE) -C $(GNU_EFI_DIR) ARCH=$(TARGET_ARCH) CROSS_COMPILE=$(CROSS_PREFIX) lib; \ + else \ + $(MAKE) -C $(GNU_EFI_DIR) ARCH=$(TARGET_ARCH) lib; \ + fi + +$(EFI_CRT0): | $(EFI_BUILD_DIR) + $(CC) -I$(GNU_EFI_INC) -I$(GNU_EFI_INC)/$(TARGET_ARCH) \ + -fno-stack-protector -fpic \ + -c -o $@ $(GNU_EFI_GNUEFI)/crt0-efi-$(TARGET_ARCH).S + +$(EFI_RELOC): | $(EFI_BUILD_DIR) + $(CC) -I$(GNU_EFI_INC) -I$(GNU_EFI_INC)/$(TARGET_ARCH) \ + -fno-stack-protector -fpic -fshort-wchar $(ARCH_CFLAGS) \ + -c -o $@ $(GNU_EFI_GNUEFI)/reloc_$(TARGET_ARCH).c + +$(EFI_LIBGNUEFI): $(EFI_RELOC) | $(EFI_BUILD_DIR) + $(AR) rcs $@ $(EFI_RELOC) + +$(BUILD_DIR) $(ARCH_BUILD_DIR) $(OUTPUT_DIR) $(EFI_BUILD_DIR): + @mkdir -p $@ + +$(LOADER_OBJ): src/loader.c | $(ARCH_BUILD_DIR) + $(CC) $(CFLAGS) -DSTUB_NAME='"$(STUB_BINARY)"' -c $< -o $@ + +$(LOADER_SO): $(LOADER_OBJ) $(EFI_CRT0) $(EFI_LIBGNUEFI) $(EFI_LIBEFI) + $(LD) $(LDFLAGS) $(LOADER_OBJ) -o $@ $(LIBS) + +$(TARGET_EFI): $(LOADER_SO) | $(OUTPUT_DIR) + $(OBJCOPY) $(OBJCOPY_FLAGS) $(LOADER_SO) $@ + +$(STUB_OBJ): src/stub.c | $(ARCH_BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(STUB_SO): $(STUB_OBJ) $(EFI_CRT0) $(EFI_LIBGNUEFI) $(EFI_LIBEFI) + $(LD) $(LDFLAGS) $(STUB_OBJ) -o $@ $(LIBS) + +$(TARGET_STUB): $(STUB_SO) | $(OUTPUT_DIR) + $(OBJCOPY) $(OBJCOPY_FLAGS) $(STUB_SO) $@ + +$(ENROLL_OBJ): src/enroll.c | $(ARCH_BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(ENROLL_SO): $(ENROLL_OBJ) $(EFI_CRT0) $(EFI_LIBGNUEFI) $(EFI_LIBEFI) + $(LD) $(LDFLAGS) $(ENROLL_OBJ) -o $@ $(LIBS) + +$(TARGET_ENROLL): $(ENROLL_SO) | $(OUTPUT_DIR) + $(OBJCOPY) $(OBJCOPY_FLAGS) $(ENROLL_SO) $@ + +test-efi: + @if [ -f "$(TARGET_EFI)" ]; then \ + echo -n " Type: "; file $(TARGET_EFI) | grep -oE "PE32\+? executable.*EFI" || echo "unknown"; \ + echo -n " Size: "; ls -lh $(TARGET_EFI) | awk '{print $$5}'; \ + fi + +keygen: + @if [ -f $(SIGNING_KEY) ]; then \ + echo "Keys already exist. Delete keys/ to regenerate."; exit 1; \ + fi + bash gen-key.sh + @echo " openssl.cnf — config used for key generation" + @echo "✓ Keys generated in $(KEYS_DIR)/" + @echo " signing.key — keep private" + @echo " signing.crt — PEM cert" + @echo " signing.der — DER cert (place at EFI/BOOT/cert.der on ISO)" + +sign: all-arches + @if [ ! -f $(SIGNING_KEY) ]; then \ + echo "No signing key found. Run: make keygen"; exit 1; \ + fi + @which sbsign > /dev/null 2>&1 || \ + (echo "Install: sudo apt install sbsigntool" && exit 1) + + @for efi in $(OUTPUT_DIR)/BOOT*.EFI $(OUTPUT_DIR)/stub*.efi; do \ + [ -f "$$efi" ] || continue; \ + sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \ + --output "$$efi" "$$efi" 2>&1 | grep -v "^warning:"; \ + echo "✓ Signed: $$efi"; \ + done + + @[ -f $(OUTPUT_DIR)/enroll-x64.efi ] && \ + sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \ + --output $(OUTPUT_DIR)/mmx64.efi $(OUTPUT_DIR)/enroll-x64.efi 2>&1 | grep -v "^warning:" && \ + echo "✓ Signed: $(OUTPUT_DIR)/mmx64.efi" || true + + @[ -f $(OUTPUT_DIR)/enroll-aa64.efi ] && \ + sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \ + --output $(OUTPUT_DIR)/mmaa64.efi $(OUTPUT_DIR)/enroll-aa64.efi 2>&1 | grep -v "^warning:" && \ + echo "✓ Signed: $(OUTPUT_DIR)/mmaa64.efi" || true + + @[ -f $(OUTPUT_DIR)/enroll-ia32.efi ] && \ + sbsign --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \ + --output $(OUTPUT_DIR)/mmia32.efi $(OUTPUT_DIR)/enroll-ia32.efi 2>&1 | grep -v "^warning:" && \ + echo "✓ Signed: $(OUTPUT_DIR)/mmia32.efi" || true + +iso: sign + @which xorriso > /dev/null 2>&1 || \ + (echo "Install: sudo apt install xorriso" && exit 1) + @if [ ! -f $(SIGNING_DER) ]; then \ + echo "No signing.der found. Run: make keygen"; exit 1; \ + fi + @rm -rf iso_root + @mkdir -p iso_root/EFI/BOOT iso_root/IPXE + @# Our signed stubs — firmware picks the right arch automatically + cp $(OUTPUT_DIR)/BOOTX64.EFI iso_root/EFI/BOOT/BOOTX64.EFI + @[ -f $(OUTPUT_DIR)/BOOTAA64.EFI ] && \ + cp $(OUTPUT_DIR)/BOOTAA64.EFI iso_root/EFI/BOOT/BOOTAA64.EFI || true + @[ -f $(OUTPUT_DIR)/BOOTIA32.EFI ] && \ + cp $(OUTPUT_DIR)/BOOTIA32.EFI iso_root/EFI/BOOT/BOOTIA32.EFI || true + @# stub.efi — arch-neutral, loaded by all BOOT*.EFI stubs + cp $(OUTPUT_DIR)/stub.efi iso_root/EFI/BOOT/stub.efi + @# Pre-signed + renamed MOK manager binaries + @[ -f $(OUTPUT_DIR)/mmx64.efi ] && \ + cp $(OUTPUT_DIR)/mmx64.efi iso_root/EFI/BOOT/mmx64.efi || true + @[ -f $(OUTPUT_DIR)/mmaa64.efi ] && \ + cp $(OUTPUT_DIR)/mmaa64.efi iso_root/EFI/BOOT/mmaa64.efi || true + @[ -f $(OUTPUT_DIR)/mmia32.efi ] && \ + cp $(OUTPUT_DIR)/mmia32.efi iso_root/EFI/BOOT/mmia32.efi || true + @# Signing cert — enroll.efi reads this; also enrollable via UEFI setup + cp $(SIGNING_DER) iso_root/EFI/BOOT/cert.der + @# iPXE binaries + @if [ -f ipxe/ipxe.efi ]; then cp ipxe/ipxe.efi iso_root/IPXE/ipxe.efi; \ + else echo "Warning: ipxe/ipxe.efi not found — IPXE/ will be empty"; fi + @if [ -f ipxe/ipxe-snp.efi ]; then cp ipxe/ipxe-snp.efi iso_root/IPXE/ipxe-snp.efi; \ + else echo "Warning: ipxe/ipxe-snp.efi not found"; fi + xorriso -as mkisofs \ + -o output/ipxe-boot.iso \ + -e EFI/BOOT/BOOTX64.EFI \ + -no-emul-boot \ + -isohybrid-gpt-basdat \ + iso_root + @echo "✓ ISO: output/ipxe-boot.iso" + +clean: + rm -rf $(BUILD_DIR) $(OUTPUT_DIR) iso_root + +distclean: clean + $(MAKE) -C $(GNU_EFI_DIR) clean > /dev/null 2>&1 || true + +info: + @echo "Target: $(TARGET_ARCH) Binary: $(EFI_BINARY) Compiler: $(CC)" diff --git a/gen-efi-media.sh b/gen-efi-media.sh new file mode 100755 index 0000000..f853d60 --- /dev/null +++ b/gen-efi-media.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +IN="input" +OUT="output" +ISO_ROOT="iso_root" +IMG="$OUT/efiboot.img" +ISO="$OUT/ipxe.iso" + +rm -rf "$ISO_ROOT" "$IMG" "$ISO" +mkdir -p "$ISO_ROOT/EFI/BOOT" "$ISO_ROOT/IPXE" "$OUT" + +# stub.efi for direct loading by BOOT*.EFI and future direct loading by firmware (and future shim flow) +cp "$OUT/stubx64.efi" "$ISO_ROOT/EFI/BOOT/stubx64.efi" +[ -f "$OUT/stubaa64.efi" ] && cp "$OUT/stubaa64.efi" "$ISO_ROOT/EFI/BOOT/" +[ -f "$OUT/stubia32.efi" ] && cp "$OUT/stubia32.efi" "$ISO_ROOT/EFI/BOOT/" + +# Current flow: direct EFI boot +cp "$OUT/BOOTX64.EFI" "$ISO_ROOT/EFI/BOOT/BOOTX64.EFI" +[ -f "$OUT/BOOTAA64.EFI" ] && cp "$OUT/BOOTAA64.EFI" "$ISO_ROOT/EFI/BOOT/" +[ -f "$OUT/BOOTIA32.EFI" ] && cp "$OUT/BOOTIA32.EFI" "$ISO_ROOT/EFI/BOOT/" + +# Future flow: shim → grub*.efi/BOOT*.EFI → stub*.efi +# cp "input/shim/shimx64.efi" "$ISO_ROOT/EFI/BOOT/BOOTX64.EFI" +# cp "$OUT/BOOTX64.EFI" "$ISO_ROOT/EFI/BOOT/grubx64.efi" + +# MOK manager binaries for direct loading by shim and future direct loading by firmware (and future shim flow) +[ -f "$OUT/mmx64.efi" ] && cp "$OUT/mmx64.efi" "$ISO_ROOT/EFI/BOOT/" +[ -f "$OUT/mmaa64.efi" ] && cp "$OUT/mmaa64.efi" "$ISO_ROOT/EFI/BOOT/" +[ -f "$OUT/mmia32.efi" ] && cp "$OUT/mmia32.efi" "$ISO_ROOT/EFI/BOOT/" + +# Signing cert for direct loading by shim and future direct loading by firmware (and future shim flow) +cp keys/signing.der "$ISO_ROOT/EFI/BOOT/cert.der" + +# iPXE binaries for direct loading by firmware and future shim flow +[ -f "$IN/ipxe-snp.efi" ] && cp "$IN/ipxe-snp.efi" "$ISO_ROOT/IPXE/ipxe-snp.efi" +[ -f "$IN/ipxe.efi" ] && cp "$IN/ipxe.efi" "$ISO_ROOT/IPXE/ipxe.efi" + +# If autoexec.ipxe file is present, copy it over to the ISO root +[ -f "$IN/autoexec.ipxe" ] && cp "$IN/autoexec.ipxe" "$ISO_ROOT/autoexec.ipxe" + +# Build EFI boot image +dd if=/dev/zero of="$IMG" bs=1M count=8 +mkfs.vfat -F 12 "$IMG" + +mmd -i "$IMG" ::/EFI +mmd -i "$IMG" ::/EFI/BOOT + +# Copy BOOT*.EFI to the EFI System Partition for direct loading by shim (and future direct loading by firmware) +mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/BOOTX64.EFI" ::/EFI/BOOT/BOOTX64.EFI + +# Copy stub*.efi to the EFI System Partition +mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/stubx64.efi" ::/EFI/BOOT/stubx64.efi + +[ -f "$ISO_ROOT/EFI/BOOT/stubaa64.efi" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/stubaa64.efi" ::/EFI/BOOT/stubaa64.efi + +[ -f "$ISO_ROOT/EFI/BOOT/stubia32.efi" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/stubia32.efi" ::/EFI/BOOT/stubia32.efi + +# Copy BOOT*.EFI, cert.der, and MOK manager binaries to the EFI System Partition for direct loading by firmware (and future shim flow) +[ -f "$ISO_ROOT/EFI/BOOT/BOOTAA64.EFI" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/BOOTAA64.EFI" ::/EFI/BOOT/BOOTAA64.EFI + +[ -f "$ISO_ROOT/EFI/BOOT/BOOTIA32.EFI" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/BOOTIA32.EFI" ::/EFI/BOOT/BOOTIA32.EFI + +[ -f "$ISO_ROOT/EFI/BOOT/cert.der" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/cert.der" ::/EFI/BOOT/cert.der + +[ -f "$ISO_ROOT/EFI/BOOT/mmx64.efi" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/mmx64.efi" ::/EFI/BOOT/mmx64.efi + +[ -f "$ISO_ROOT/EFI/BOOT/mmaa64.efi" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/mmaa64.efi" ::/EFI/BOOT/mmaa64.efi + +[ -f "$ISO_ROOT/EFI/BOOT/mmia32.efi" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/EFI/BOOT/mmia32.efi" ::/EFI/BOOT/mmia32.efi + +# Copy iPXE binaries to the EFI System Partition for direct loading by firmware (and future shim flow) +mmd -i "$IMG" ::/IPXE +[ -f "$ISO_ROOT/IPXE/ipxe-snp.efi" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/IPXE/ipxe-snp.efi" ::/IPXE/ipxe-snp.efi + +[ -f "$ISO_ROOT/IPXE/ipxe.efi" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/IPXE/ipxe.efi" ::/IPXE/ipxe.efi + +# If autoexec.ipxe file is present, copy it over to the EFI System Partition +[ -f "$ISO_ROOT/IPXE/autoexec.ipxe" ] && \ + mcopy -i "$IMG" "$ISO_ROOT/autoexec.ipxe" ::/IPXE/autoexec.ipxe + +# build EFI System Partition image for El Torito booting +cp "$IMG" "$ISO_ROOT/efiboot.img" + +# Build ISO +xorriso -as mkisofs \ + -iso-level 3 \ + -full-iso9660-filenames \ + -volid "IPXE_BOOT" \ + -eltorito-platform efi \ + -eltorito-boot efiboot.img \ + -no-emul-boot \ + -append_partition 2 0xef "$IMG" \ + -appended_part_as_gpt \ + -output "$ISO" \ + "$ISO_ROOT" + +xorriso -indev "$ISO" -report_el_torito plain + +echo "Created $ISO" \ No newline at end of file diff --git a/gen-key.sh b/gen-key.sh new file mode 100755 index 0000000..d559079 --- /dev/null +++ b/gen-key.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +mkdir -p keys + +openssl req -newkey rsa:2048 -nodes \ + -keyout keys/signing.key \ + -new -x509 -days 3650 \ + -config openssl.cnf \ + -subj "/CN=iPXE Boot Signing Key" \ + -out keys/signing.crt + +# DER format — this is the cert.der enroll.efi reads +openssl x509 -in keys/signing.crt -outform DER -out keys/signing.der + diff --git a/includes/deps/gnu-efi b/includes/deps/gnu-efi new file mode 160000 index 0000000..74bd9b6 --- /dev/null +++ b/includes/deps/gnu-efi @@ -0,0 +1 @@ +Subproject commit 74bd9b60ba4b59117490ffd54b9fb68bbe91d6b8 diff --git a/includes/mok.h b/includes/mok.h new file mode 100644 index 0000000..62d8e79 --- /dev/null +++ b/includes/mok.h @@ -0,0 +1,43 @@ +#ifndef MOK_H +#define MOK_H + +#include + +/* MOK variables GUID */ +#define MOK_GUID \ + { 0x605dab50, 0xe046, 0x4300, \ + {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} } + +/* EFI X.509 certificate type GUID (used in EFI_SIGNATURE_LIST) */ +#define EFI_CERT_X509_GUID \ + { 0xa5c059a1, 0x94e4, 0x4aa7, \ + {0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72} } + +/* + * Owner GUID embedded in EFI_SIGNATURE_DATA when enrolling. + * Spells "NETIPXEBOOT" in ASCII — arbitrary but stable identifier. + */ +#define MOK_OWNER_GUID \ + { 0x4e455449, 0x504f, 0x5854, \ + {0x49, 0x50, 0x58, 0x45, 0x42, 0x4f, 0x4f, 0x54} } + +#define MOK_ATTRS \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) + +#pragma pack(1) +typedef struct { + EFI_GUID SignatureType; + UINT32 SignatureListSize; + UINT32 SignatureHeaderSize; + UINT32 SignatureSize; +} MOK_SIG_LIST; + +typedef struct { + EFI_GUID SignatureOwner; + /* followed by variable-length cert/data bytes */ +} MOK_SIG_DATA; +#pragma pack() + +#endif /* MOK_H */ diff --git a/openssl.cnf b/openssl.cnf new file mode 100644 index 0000000..9f9d8f7 --- /dev/null +++ b/openssl.cnf @@ -0,0 +1,27 @@ +HOME = keys +RANDFILE = keys/.rnd + +[ req ] +distinguished_name = req_distinguished_name +x509_extensions = v3_ca +string_mask = utf8only + +[ req_distinguished_name ] + +[ v3_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical,CA:FALSE + +# We use extended key usage information to limit what this auto-generated +# key can be used for. +# +# codeSigning: specifies that this key is used to sign code. +# +# 1.3.6.1.4.1.2312.16.1.2: defines this key as used for module signing +# only. See https://lkml.org/lkml/2015/8/26/741. +# +extendedKeyUsage = codeSigning,1.3.6.1.4.1.2312.16.1.2 + +nsComment = "OpenSSL Generated Certificate" + diff --git a/src/enroll.c b/src/enroll.c new file mode 100644 index 0000000..33df7e6 --- /dev/null +++ b/src/enroll.c @@ -0,0 +1,473 @@ +#include +#include +#include "mok.h" + +/* ── Minimal freestanding SHA-256 ───────────────────────────────────────── */ + +#define SHA256_BLOCK 64 +#define SHA256_DIGEST 32 + +typedef struct { + UINT32 state[8]; + UINT64 count; + UINT8 buf[SHA256_BLOCK]; +} SHA256_CTX; + +static const UINT32 sha256_k[64] = { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5, + 0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3, + 0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc, + 0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7, + 0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13, + 0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3, + 0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5, + 0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208, + 0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 +}; + +#define ROR32(v,n) (((v) >> (n)) | ((v) << (32-(n)))) +#define CH(x,y,z) (((x)&(y))^(~(x)&(z))) +#define MAJ(x,y,z) (((x)&(y))^((x)&(z))^((y)&(z))) +#define S0(x) (ROR32(x,2) ^ROR32(x,13)^ROR32(x,22)) +#define S1(x) (ROR32(x,6) ^ROR32(x,11)^ROR32(x,25)) +#define G0(x) (ROR32(x,7) ^ROR32(x,18)^((x)>>3)) +#define G1(x) (ROR32(x,17)^ROR32(x,19)^((x)>>10)) + +static void sha256_transform(SHA256_CTX *ctx, const UINT8 *data) { + UINT32 a,b,c,d,e,f,g,h,t1,t2,w[64]; + UINTN i; + + for (i = 0; i < 16; i++) + w[i] = ((UINT32)data[i*4]<<24)|((UINT32)data[i*4+1]<<16)| + ((UINT32)data[i*4+2]<<8)|(UINT32)data[i*4+3]; + for (i = 16; i < 64; i++) + w[i] = G1(w[i-2])+w[i-7]+G0(w[i-15])+w[i-16]; + + a=ctx->state[0]; b=ctx->state[1]; c=ctx->state[2]; d=ctx->state[3]; + e=ctx->state[4]; f=ctx->state[5]; g=ctx->state[6]; h=ctx->state[7]; + + for (i = 0; i < 64; i++) { + t1 = h + S1(e) + CH(e,f,g) + sha256_k[i] + w[i]; + t2 = S0(a) + MAJ(a,b,c); + h=g; g=f; f=e; e=d+t1; + d=c; c=b; b=a; a=t1+t2; + } + + ctx->state[0]+=a; ctx->state[1]+=b; ctx->state[2]+=c; ctx->state[3]+=d; + ctx->state[4]+=e; ctx->state[5]+=f; ctx->state[6]+=g; ctx->state[7]+=h; +} + +static void sha256_init(SHA256_CTX *ctx) { + ctx->count = 0; + ctx->state[0] = 0x6a09e667; ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; ctx->state[7] = 0x5be0cd19; +} + +static void sha256_update(SHA256_CTX *ctx, const UINT8 *data, UINTN len) { + UINTN used = (UINTN)(ctx->count & 63); + ctx->count += len; + if (used) { + UINTN free = SHA256_BLOCK - used; + if (len < free) { CopyMem(ctx->buf + used, (void *)data, len); return; } + CopyMem(ctx->buf + used, (void *)data, free); + sha256_transform(ctx, ctx->buf); + data += free; len -= free; + } + while (len >= SHA256_BLOCK) { + sha256_transform(ctx, data); + data += SHA256_BLOCK; len -= SHA256_BLOCK; + } + CopyMem(ctx->buf, (void *)data, len); +} + +static void sha256_final(SHA256_CTX *ctx, UINT8 *digest) { + UINT64 bits = ctx->count * 8; + UINTN used = (UINTN)(ctx->count & 63); + UINTN i; + ctx->buf[used++] = 0x80; + if (used > 56) { + SetMem(ctx->buf + used, SHA256_BLOCK - used, 0); + sha256_transform(ctx, ctx->buf); + used = 0; + } + SetMem(ctx->buf + used, 56 - used, 0); + for (i = 0; i < 8; i++) + ctx->buf[56+i] = (UINT8)(bits >> (56 - i*8)); + sha256_transform(ctx, ctx->buf); + for (i = 0; i < 8; i++) { + digest[i*4] = (UINT8)(ctx->state[i] >> 24); + digest[i*4+1] = (UINT8)(ctx->state[i] >> 16); + digest[i*4+2] = (UINT8)(ctx->state[i] >> 8); + digest[i*4+3] = (UINT8)(ctx->state[i]); + } +} + +static void sha256(const UINT8 *data, UINTN len, UINT8 *digest) { + SHA256_CTX ctx; + sha256_init(&ctx); + sha256_update(&ctx, data, len); + sha256_final(&ctx, digest); +} + +/* ── UI helpers ─────────────────────────────────────────────────────────── */ + +static void print_line(UINTN width) { + UINTN i; + for (i = 0; i < width; i++) Print(L"─"); + Print(L"\n"); +} + +static void print_hex_line(const UINT8 *d, UINTN offset, UINTN count) { + UINTN i; + for (i = 0; i < count; i++) { + if (i) Print(L":"); + Print(L"%02x", d[offset + i]); + } +} + +static void print_fingerprint(const UINT8 *fp) { + Print(L" Fingerprint (SHA-256):\n"); + Print(L" "); print_hex_line(fp, 0, 16); Print(L"\n"); + Print(L" "); print_hex_line(fp, 16, 16); Print(L"\n"); +} + +/* Read a masked password. Returns length, buffer is NOT null-terminated. */ +static UINTN read_password(UINT8 *buf, UINTN max) { + UINTN len = 0; + EFI_INPUT_KEY key; + + while (1) { + uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key); + if (key.UnicodeChar == 0) continue; + + if (key.UnicodeChar == CHAR_CARRIAGE_RETURN) break; + + if (key.UnicodeChar == CHAR_BACKSPACE) { + if (len) { len--; Print(L"\b \b"); } + continue; + } + + if (len < max) { + buf[len++] = (UINT8)key.UnicodeChar; + Print(L"*"); + } + } + Print(L"\n"); + return len; +} + +/* Wait for one of the listed unicode chars, return which one. */ +static CHAR16 wait_for_key(const CHAR16 *allowed) { + EFI_INPUT_KEY key; + while (1) { + uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key); + if (!key.UnicodeChar) continue; + const CHAR16 *p = allowed; + while (*p) { if (*p == key.UnicodeChar) return *p; p++; } + } +} + +/* ── File reading ───────────────────────────────────────────────────────── */ + +static EFI_STATUS read_file(EFI_HANDLE image, CHAR16 *path, + UINT8 **out, UINTN *out_len) { + EFI_STATUS status; + EFI_LOADED_IMAGE *loaded = NULL; + EFI_FILE_IO_INTERFACE *fs = NULL; + EFI_FILE_HANDLE root = NULL, file = NULL; + EFI_FILE_INFO *info = NULL; + UINTN info_size; + UINT8 *buf = NULL; + UINTN size; + + EFI_GUID fs_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; + EFI_GUID info_guid = EFI_FILE_INFO_ID; + + status = uefi_call_wrapper(BS->HandleProtocol, 3, + image, &LoadedImageProtocol, (void **)&loaded); + if (EFI_ERROR(status)) return status; + + status = uefi_call_wrapper(BS->HandleProtocol, 3, + loaded->DeviceHandle, &fs_guid, (void **)&fs); + if (EFI_ERROR(status)) return status; + + status = uefi_call_wrapper(fs->OpenVolume, 2, fs, &root); + if (EFI_ERROR(status)) return status; + + status = uefi_call_wrapper(root->Open, 5, + root, &file, path, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(status)) { root->Close(root); return status; } + + info_size = sizeof(EFI_FILE_INFO) + 256; + status = uefi_call_wrapper(BS->AllocatePool, 3, + EfiLoaderData, info_size, (void **)&info); + if (EFI_ERROR(status)) goto out; + + status = uefi_call_wrapper(file->GetInfo, 4, + file, &info_guid, &info_size, info); + if (EFI_ERROR(status)) goto out; + + size = (UINTN)info->FileSize; + status = uefi_call_wrapper(BS->AllocatePool, 3, + EfiLoaderData, size, (void **)&buf); + if (EFI_ERROR(status)) goto out; + + status = uefi_call_wrapper(file->Read, 3, file, &size, buf); + if (EFI_ERROR(status)) { + uefi_call_wrapper(BS->FreePool, 1, buf); + goto out; + } + + *out = buf; + *out_len = size; + +out: + if (info) uefi_call_wrapper(BS->FreePool, 1, info); + uefi_call_wrapper(file->Close, 1, file); + uefi_call_wrapper(root->Close, 1, root); + return status; +} + +/* ── MOK variable helpers ───────────────────────────────────────────────── */ + +static EFI_GUID mok_guid = MOK_GUID; +static EFI_GUID efi_cert_x509_guid = EFI_CERT_X509_GUID; +static EFI_GUID owner_guid = MOK_OWNER_GUID; + +static EFI_STATUS write_mok_new(const UINT8 *cert, UINTN cert_len) { + UINTN list_size = sizeof(MOK_SIG_LIST) + + sizeof(MOK_SIG_DATA) + cert_len; + UINT8 *list; + EFI_STATUS status = uefi_call_wrapper(BS->AllocatePool, 3, + EfiLoaderData, list_size, (void **)&list); + if (EFI_ERROR(status)) return status; + + MOK_SIG_LIST *hdr = (MOK_SIG_LIST *)list; + CopyMem(&hdr->SignatureType, (void *)&efi_cert_x509_guid, sizeof(EFI_GUID)); + hdr->SignatureListSize = (UINT32)list_size; + hdr->SignatureHeaderSize = 0; + hdr->SignatureSize = (UINT32)(sizeof(MOK_SIG_DATA) + cert_len); + + MOK_SIG_DATA *sig = (MOK_SIG_DATA *)(list + sizeof(MOK_SIG_LIST)); + CopyMem(&sig->SignatureOwner, (void *)&owner_guid, sizeof(EFI_GUID)); + CopyMem((UINT8 *)sig + sizeof(MOK_SIG_DATA), (void *)cert, cert_len); + + status = uefi_call_wrapper(RT->SetVariable, 5, + L"MokNew", &mok_guid, MOK_ATTRS, list_size, list); + + uefi_call_wrapper(BS->FreePool, 1, list); + return status; +} + +static EFI_STATUS write_mok_auth(const UINT8 *hash) { + return uefi_call_wrapper(RT->SetVariable, 5, + L"MokAuth", &mok_guid, MOK_ATTRS, + SHA256_DIGEST, (void *)hash); +} + +static BOOLEAN mok_new_exists(void) { + UINTN size = 0; + UINT32 attrs = 0; + EFI_STATUS s = uefi_call_wrapper(RT->GetVariable, 5, + L"MokNew", &mok_guid, &attrs, &size, NULL); + return (s == EFI_BUFFER_TOO_SMALL); +} + +static EFI_STATUS delete_mok_auth(void) { + return uefi_call_wrapper(RT->SetVariable, 5, + L"MokAuth", &mok_guid, MOK_ATTRS, 0, NULL); +} + +/* ── Phase 1: initial enrollment request ───────────────────────────────── */ + +static EFI_STATUS phase_enroll(EFI_HANDLE image) { + EFI_STATUS status; + UINT8 *cert = NULL; + UINTN cert_len = 0; + UINT8 fp[SHA256_DIGEST]; + UINT8 pw1[128], pw2[128]; + UINTN pw1_len, pw2_len; + UINT8 pw_hash[SHA256_DIGEST]; + UINTN i; + + status = read_file(image, L"\\EFI\\BOOT\\cert.der", &cert, &cert_len); + if (EFI_ERROR(status)) { + Print(L" Error: cert.der not found (\\EFI\\BOOT\\cert.der): %r\n", status); + return status; + } + + sha256(cert, cert_len, fp); + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + print_line(60); + Print(L" iPXE Boot - Secure Boot Key Enrollment\n"); + print_line(60); + Print(L"\n"); + Print(L" Certificate: \\EFI\\BOOT\\cert.der (%d bytes)\n", cert_len); + Print(L"\n"); + print_fingerprint(fp); + Print(L"\n"); + Print(L" This key will be enrolled as a Machine Owner Key (MOK).\n"); + Print(L" Binaries signed with it will be trusted under Secure Boot.\n"); + Print(L"\n"); + Print(L" Press ENTER to continue, ESC to cancel.\n"); + Print(L"\n"); + + if (wait_for_key(L"\r\x1b") == L'\x1b') { + Print(L" Cancelled.\n"); + uefi_call_wrapper(BS->FreePool, 1, cert); + return EFI_ABORTED; + } + + /* Password */ + while (1) { + Print(L" Enter password (remember this for next boot): "); + pw1_len = read_password(pw1, sizeof(pw1)); + + Print(L" Confirm password: "); + pw2_len = read_password(pw2, sizeof(pw2)); + + if (pw1_len != pw2_len) { + Print(L" Passwords do not match. Try again.\n\n"); + continue; + } + + BOOLEAN match = TRUE; + for (i = 0; i < pw1_len; i++) { + if (pw1[i] != pw2[i]) { match = FALSE; break; } + } + if (!match) { + Print(L" Passwords do not match. Try again.\n\n"); + continue; + } + break; + } + + sha256(pw1, pw1_len, pw_hash); + + Print(L"\n Writing MokNew... "); + status = write_mok_new(cert, cert_len); + uefi_call_wrapper(BS->FreePool, 1, cert); + if (EFI_ERROR(status)) { + Print(L"failed: %r\n", status); + return status; + } + Print(L"OK\n"); + + Print(L" Writing MokAuth... "); + status = write_mok_auth(pw_hash); + if (EFI_ERROR(status)) { + Print(L"failed: %r\n", status); + return status; + } + Print(L"OK\n"); + + Print(L"\n"); + print_line(60); + Print(L" Enrollment queued. On next boot, shim will launch this\n"); + Print(L" app again to confirm. Enter the same password to complete.\n"); + print_line(60); + Print(L"\n"); + Print(L" Press R to reboot, ESC to exit.\n"); + + if (wait_for_key(L"rR\x1b") != L'\x1b') { + uefi_call_wrapper(RT->ResetSystem, 4, + EfiResetWarm, EFI_SUCCESS, 0, NULL); + } + + return EFI_SUCCESS; +} + +/* ── Phase 2: confirmation (launched by shim as mmx64.efi) ─────────────── */ + +static EFI_STATUS phase_confirm(EFI_HANDLE image) { + UINT8 stored_hash[SHA256_DIGEST]; + UINTN stored_size = SHA256_DIGEST; + UINT32 attrs = 0; + UINT8 pw[128]; + UINTN pw_len; + UINT8 pw_hash[SHA256_DIGEST]; + UINTN i; + + /* Read stored MokAuth */ + EFI_STATUS status = uefi_call_wrapper(RT->GetVariable, 5, + L"MokAuth", &mok_guid, &attrs, &stored_size, stored_hash); + if (EFI_ERROR(status) || stored_size != SHA256_DIGEST) { + Print(L" Error: MokAuth not found or wrong size.\n"); + return EFI_NOT_FOUND; + } + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + print_line(60); + Print(L" iPXE Boot - Confirm MOK Enrollment\n"); + print_line(60); + Print(L"\n"); + Print(L" A Machine Owner Key is pending enrollment.\n"); + Print(L" Enter the password you set during enrollment to confirm.\n"); + Print(L"\n"); + + /* Allow 3 attempts */ + for (i = 0; i < 3; i++) { + Print(L" Password: "); + pw_len = read_password(pw, sizeof(pw)); + sha256(pw, pw_len, pw_hash); + + BOOLEAN match = TRUE; + UINTN j; + for (j = 0; j < SHA256_DIGEST; j++) { + if (pw_hash[j] != stored_hash[j]) { match = FALSE; break; } + } + + if (match) { + delete_mok_auth(); + Print(L"\n Password verified. MOK will be enrolled.\n"); + Print(L"\n"); + print_line(60); + Print(L" Shim will now finalize enrollment and reboot.\n"); + print_line(60); + Print(L"\n Press any key...\n"); + EFI_INPUT_KEY key; + while (uefi_call_wrapper(ST->ConIn->ReadKeyStroke, + 2, ST->ConIn, &key) == EFI_NOT_READY) {} + return EFI_SUCCESS; + } + + Print(L" Incorrect password. %d attempt(s) remaining.\n\n", + (int)(2 - i)); + } + + Print(L" Too many failed attempts. Enrollment cancelled.\n"); + /* Delete MokNew + MokAuth to cancel */ + uefi_call_wrapper(RT->SetVariable, 5, + L"MokNew", &mok_guid, MOK_ATTRS, 0, NULL); + delete_mok_auth(); + return EFI_ACCESS_DENIED; +} + +/* ── Entry point ────────────────────────────────────────────────────────── */ + +EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { + InitializeLib(ImageHandle, SystemTable); + + uefi_call_wrapper(ST->BootServices->SetWatchdogTimer, 4, 0, 0, 0, NULL); + + /* If MokNew already exists we're in phase 2 (shim called us to confirm) */ + if (mok_new_exists()) { + EFI_STATUS s = phase_confirm(ImageHandle); + if (EFI_ERROR(s)) { + uefi_call_wrapper(BS->Stall, 1, 3000000); + } + return s; + } + + return phase_enroll(ImageHandle); +} diff --git a/src/loader.c b/src/loader.c new file mode 100644 index 0000000..cbc79cf --- /dev/null +++ b/src/loader.c @@ -0,0 +1,46 @@ +#include +#include + +/* + * STUB_NAME is defined by the Makefile per-arch (e.g. "stubx64.efi"). + * When shim is introduced, BOOT{ARCH}.EFI is replaced by shim and this + * file is retired — shim loads stub{arch}.efi directly. + */ +#ifndef STUB_NAME +#define STUB_NAME "stubx64.efi" /* default to x64 */ +#endif + +#define WIDEN_(x) L##x +#define WIDEN(x) WIDEN_(x) +#define STUB_PATH (L"\\EFI\\BOOT\\" WIDEN(STUB_NAME)) + +static EFI_STATUS chainload(EFI_HANDLE ImageHandle, CHAR16 *path) { + EFI_STATUS status; + EFI_HANDLE child = NULL; + EFI_LOADED_IMAGE *loaded = NULL; + EFI_DEVICE_PATH *devpath = NULL; + + status = uefi_call_wrapper(BS->HandleProtocol, 3, + ImageHandle, &LoadedImageProtocol, (void **)&loaded); + if (EFI_ERROR(status)) return status; + + devpath = FileDevicePath(loaded->DeviceHandle, path); + if (!devpath) return EFI_OUT_OF_RESOURCES; + + status = uefi_call_wrapper(BS->LoadImage, 6, + FALSE, ImageHandle, devpath, NULL, 0, &child); + if (EFI_ERROR(status)) { + Print(L"loader: failed to load %s (%r)\n", path, status); + return status; + } + + status = uefi_call_wrapper(BS->StartImage, 3, child, NULL, NULL); + + uefi_call_wrapper(BS->UnloadImage, 1, child); + return status; +} + +EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { + InitializeLib(ImageHandle, SystemTable); + return chainload(ImageHandle, STUB_PATH); +} diff --git a/src/stub.c b/src/stub.c new file mode 100644 index 0000000..f7e6c90 --- /dev/null +++ b/src/stub.c @@ -0,0 +1,102 @@ +#include +#include + +#define IPXE_PATH L"\\IPXE\\ipxe.efi" +#define IPXE_SNP_PATH L"\\IPXE\\ipxe-snp.efi" + +/* ── SMBIOS ─────────────────────────────────────────────────────────────── */ + +static BOOLEAN ascii_equals(const CHAR8 *a, const CHAR16 *b) { + if (!a) return FALSE; + while (*b) { + if ((CHAR16)(UINT8)*a != *b) return FALSE; + a++; b++; + } + return *a == 0; +} + +static BOOLEAN is_apple(void) { + void *table = NULL; + UINT8 *structs = NULL; + UINT32 len = 0; + + if (!EFI_ERROR(LibGetSystemConfigurationTable(&SMBIOS3TableGuid, &table)) && table) { + SMBIOS3_STRUCTURE_TABLE *e = (SMBIOS3_STRUCTURE_TABLE *)table; + structs = (UINT8 *)(UINTN)e->TableAddress; + len = e->TableMaximumSize; + } else if (!EFI_ERROR(LibGetSystemConfigurationTable(&SMBIOSTableGuid, &table)) && table) { + SMBIOS_STRUCTURE_TABLE *e = (SMBIOS_STRUCTURE_TABLE *)table; + structs = (UINT8 *)(UINTN)e->TableAddress; + len = e->TableLength; + } else { + return FALSE; + } + + UINT8 *p = structs; + UINT8 *end = structs + len; + + while (p + sizeof(SMBIOS_HEADER) <= end) { + SMBIOS_STRUCTURE_POINTER sp; + sp.Raw = p; + + if (sp.Hdr->Type == 127) break; + + if (sp.Hdr->Type == 0 && sp.Hdr->Length >= sizeof(SMBIOS_TYPE0)) { + CHAR8 *vendor = LibGetSmbiosString(&sp, sp.Type0->Vendor); + return ascii_equals(vendor, L"Apple Inc."); + } + + UINT8 *s = p + sp.Hdr->Length; + while (s + 1 < end) { + if (s[0] == 0 && s[1] == 0) { s += 2; break; } + s++; + } + p = s; + } + + return FALSE; +} + +/* ── Chainloader ────────────────────────────────────────────────────────── */ + +static EFI_STATUS chainload(EFI_HANDLE ImageHandle, CHAR16 *path) { + EFI_STATUS status; + EFI_HANDLE child = NULL; + EFI_LOADED_IMAGE *loaded = NULL; + EFI_DEVICE_PATH *devpath = NULL; + + status = uefi_call_wrapper(BS->HandleProtocol, 3, + ImageHandle, &LoadedImageProtocol, (void **)&loaded); + if (EFI_ERROR(status)) return status; + + devpath = FileDevicePath(loaded->DeviceHandle, path); + if (!devpath) return EFI_OUT_OF_RESOURCES; + + status = uefi_call_wrapper(BS->LoadImage, 6, + FALSE, ImageHandle, devpath, NULL, 0, &child); + if (EFI_ERROR(status)) { + Print(L"stub: failed to load %s (%r)\n", path, status); + return status; + } + + status = uefi_call_wrapper(BS->StartImage, 3, child, NULL, NULL); + + uefi_call_wrapper(BS->UnloadImage, 1, child); + return status; +} + +/* ── Entry point ────────────────────────────────────────────────────────── */ + +EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { + InitializeLib(ImageHandle, SystemTable); + + CHAR16 *target = is_apple() ? IPXE_SNP_PATH : IPXE_PATH; + + EFI_STATUS status = chainload(ImageHandle, target); + if (EFI_ERROR(status)) { + Print(L"stub: chainload failed (%r) — halting\n", status); + uefi_call_wrapper(BS->Stall, 1, 5000000); + } + + return status; +}