Secure Boot: technical details
This page covers the mechanics behind Secure Boot signing that aren’t specific to any one enrollment route: how the signed shim chain is served, how FOS kernels actually get signed, and how to sign your own FOS builds. For the concepts, start at Secure Boot signing. For enrolling a certificate on a client, see MOK enrollment or Setup Mode enrollment.
Serve the signed chain
Set the DHCP boot file for Secure Boot clients to
secureboot/snponly-shimx64.efi. Nothing in this step needs signing by you —
both binaries already carry signatures the firmware and shim trust.
This is the same driver model FOG already uses
snponlybinds only the firmware’s own UEFI network protocol instead of replacing the NIC driver, which is exactly why FOG servessnponly.efito every other client. So a Secure Boot machine now behaves like the rest of your estate rather than being a special case — the only difference is the shim in front and the signature on the kernel.
Because upstream’s snponly.efi has no boot script compiled in, it fetches one
over TFTP, and where it looks is not a single fixed path. In FOG 1.6.0
this is true of every UEFI binary FOG ships, not just upstream’s — none of
them embed a script any more. iPXE asks for the
bare name autoexec.ipxe and tries two locations in order:
- relative to its current working URI — the TFTP directory the running
.efiwas itself downloaded from, i.e.secureboot/autoexec.ipxe - absolute at the TFTP root —
/autoexec.ipxe
You can watch both attempts on the client console:
autoexec.ipxe... Not found
/autoexec.ipxe... Not found
The installer satisfies both. It hard-links autoexec.ipxe into every
directory a script-less binary can be booted from — the TFTP root,
i386-efi/, arm64-efi/, secureboot/ and secureboot/arm64-efi/. All five
are one file, so editing any of them changes all of them and there is no copy
left quietly running the old script.
A hard link rather than a symlink because some TFTP daemons refuse to follow symlinks, while a hard link is indistinguishable from a regular file to all of them. A hard link rather than a copy so the paths cannot drift apart.
If a link has been broken — an older install, or the file was replaced by an editor that writes-and-renames — re-running the installer restores it, or:
sudo ln -f /tftpboot/autoexec.ipxe /tftpboot/secureboot/autoexec.ipxeYou can check they really are one file: every copy should report the same inode and a link count of 5.
find /tftpboot -name autoexec.ipxe -printf '%i links=%n %p\n'If nothing seems to happen
Watch the TFTP server’s log during a boot — it tells you exactly which filenames the client asked for and whether they were served, which beats guessing every time.
Your existing clients are unaffected — FOG’s own unsigned snponly.efi stays
at the TFTP root, and non-Secure-Boot machines keep booting it. The signed copy
lives under secureboot/ and is reached only by machines you point there.
Both files are called
snponly.efi, and that is fine
/tftpboot/snponly.efiis FOG’s own build./tftpboot/secureboot/snponly.efiis upstream’s, signed by the iPXE project. Both read their script fromautoexec.ipxe; what differs is who vouches for the binary — upstream’s is trusted through shim and Microsoft’s key, FOG’s through the MOK this server publishes. That is why the signed one gets its own directory rather than replacing the other, and which you point DHCP at decides which trust root the client has to have enrolled.
If the chain loads but the network never comes up
Shim runs, iPXE starts, and then there is no link or no DHCP. That points at
the firmware’s own UEFI network stack, not at anything you signed. Set the DHCP
boot file to secureboot/ipxe-shimx64.efi instead — on arm64,
secureboot/arm64-efi/ipxe-shimaa64.efi.
That chain runs the all-drivers ipxe.efi, which replaces the firmware’s NIC
driver with iPXE’s own rather than binding the firmware’s UEFI network
protocol. It recovers machines whose firmware SNP is broken or absent, and it
is the more invasive option — on hardware where the takeover fails, it hangs
instead. So try snponly first and move to this only on the symptom above.
Everything about the rest of this step is unchanged: both binaries are staged
for you, both are already signed, and autoexec.ipxe is hard-linked into
secureboot/ for either one. Nothing needs renaming server-side — the shim
picks its second stage from its own filename, so the two chains sit side by
side in one directory and DHCP alone decides which runs.
Signing the FOS kernels
This is the part that is genuinely yours to sign, and the installer has already done it — there is no step here unless you supplied your own key, in which case bringing your own key covers passing it.
Every install and upgrade re-signs the kernels, and it has to: the FOS binaries are re-copied into place unsigned on every run, so the signature is removed and immediately re-applied in the same pass. That is what stops an upgrade silently leaving you with kernels your clients will not boot — which is the single most common way this setup breaks.
Verify — and note the certificate must be the PEM, because sbverify will
not read DER:
sbverify --cert /opt/fog/pki/secureboot/leaf/sign.pem \
/var/www/fog/service/ipxe/bzImage
# Signature verification OKThe full chain, including the intermediate:
$ sbverify --list /var/www/fog/service/ipxe/bzImage
- subject: /CN=FOG Project Secure Boot Signing
issuer: /CN=FOG Secure Boot CA
- subject: /CN=FOG Secure Boot CA
issuer: /CN=FOG Server CAThe signer shown should match the fingerprint on the FOG Configuration → Secure Boot page; that page’s fingerprint is the digest of the certificate your clients enroll — the CA, not the leaf.
The installer keeps a .unsigned copy of each kernel beside the signed one,
because sbsign will not cleanly re-sign an already-signed image. Leave them
alone; they are refreshed on every download.
The web Kernel Update page is covered too
Downloading a kernel from FOG Configuration → Kernel Update signs it before it is sent to the TFTP server, so that route cannot leave you with an unsigned kernel either. It signs through a small root-only helper (
$fogprogramdir/bin/fog-sign-kernel,/opt/fog/bin/…by default) rather than in the web server itself, so the web server never gets read access to your private key. If signing fails the update is refused outright rather than quietly installing a kernel your clients will not boot.Be aware of the limit of that protection: anyone who can already run code as your web server can ask the helper to sign a kernel of their choosing. What they cannot do is walk off with the key.
If
sbsigntoolcould not be installedThe installer adds
sbsigntool(sbsigntoolson RHEL/Arch) to its baseline package set, but if neither name exists in your distribution’s repositories it skips the package, then warns and carries on unsigned rather than aborting the whole install. Read the installer output — an unsigned kernel only announces itself at a client, as aSecurity Policy Violation.
Signing your own FOS builds
If you build FOS yourself rather than using the released kernels, build.sh
can sign as part of the build, so the published .sha256 covers the signed
image:
./build.sh -nka x64 \
--sign-key /root/fog-secureboot/MOK.priv \
--sign-cert /root/fog-secureboot/MOK.pemIf you are using FOG’s own auto-generated key, that is
--sign-key /opt/fog/pki/secureboot/leaf/sign.key --sign-cert /opt/fog/pki/secureboot/leaf/sign.pem.
--sign-cert must be the PEM here — build.sh hands it straight to
sbsign, which cannot read DER.
FOS_SIGN_KEY and FOS_SIGN_CERT work too, which is easier in CI. With
neither set the build is byte-for-byte what it always was.
Verify end to end
Take one enrolled machine and PXE boot it with Secure Boot on:
- iPXE loads and shows its banner — the shim accepted
snponly.efi, so the upstream signed binaries are working. - The FOG menu appears —
autoexec.ipxeis being served and read. - Selecting a task boots FOS rather than failing — your signature is accepted.
If it stops at the third step with a security violation, the kernel signature is not being accepted. In order of likelihood:
| Symptom | Cause |
|---|---|
Security Policy Violation | Certificate not enrolled on this machine, or the leaf was signed under a different CA than what’s enrolled |
Security Policy Violation, but the certificate is listed by mokutil --list-enrolled | The key carries the Module-signing only OID — see Bringing your own key |
| Fails on every machine, including enrolled ones | Shim is not in the boot chain — see the chain |
| Worked yesterday, fails today | Something replaced the kernels without re-signing them. The installer always re-signs on install/upgrade — suspect anything that copies into service/ipxe/ outside it — check with sbverify and re-run the installer |
| Every machine stops working after a change | Either the CA was regenerated, or you switched to a different admin-supplied flat key. Enrollment is per-CA (or per-flat-key), so all clients need re-enrolling — see Rotating or removing a key |
| Complains about format, not signature | Kernel lacks CONFIG_EFI_STUB |