Base
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
output/
|
||||
build/
|
||||
iso_root/
|
||||
keys/
|
||||
input/*.efi
|
||||
input/autoexec.ipxe
|
||||
input/boot.ipxe
|
||||
input/shim/*
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "includes/deps/gnu-efi"]
|
||||
path = includes/deps/gnu-efi
|
||||
url = https://github.com/rhboot/gnu-efi.git
|
||||
294
Makefile
Normal file
294
Makefile
Normal file
@@ -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)"
|
||||
110
gen-efi-media.sh
Executable file
110
gen-efi-media.sh
Executable file
@@ -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"
|
||||
14
gen-key.sh
Executable file
14
gen-key.sh
Executable file
@@ -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
|
||||
|
||||
1
includes/deps/gnu-efi
Submodule
1
includes/deps/gnu-efi
Submodule
Submodule includes/deps/gnu-efi added at 74bd9b60ba
43
includes/mok.h
Normal file
43
includes/mok.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef MOK_H
|
||||
#define MOK_H
|
||||
|
||||
#include <efi.h>
|
||||
|
||||
/* 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 */
|
||||
27
openssl.cnf
Normal file
27
openssl.cnf
Normal file
@@ -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"
|
||||
|
||||
473
src/enroll.c
Normal file
473
src/enroll.c
Normal file
@@ -0,0 +1,473 @@
|
||||
#include <efi.h>
|
||||
#include <efilib.h>
|
||||
#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);
|
||||
}
|
||||
46
src/loader.c
Normal file
46
src/loader.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <efi.h>
|
||||
#include <efilib.h>
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
102
src/stub.c
Normal file
102
src/stub.c
Normal file
@@ -0,0 +1,102 @@
|
||||
#include <efi.h>
|
||||
#include <efilib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user