Skip to content

secureboot: enroll EFI keys on first boot#1692

Merged
brianmcgillion merged 1 commit intotiiuae:mainfrom
vadika:secureboot-firstboot-enroll
Jan 22, 2026
Merged

secureboot: enroll EFI keys on first boot#1692
brianmcgillion merged 1 commit intotiiuae:mainfrom
vadika:secureboot-firstboot-enroll

Conversation

@vadika
Copy link
Copy Markdown
Contributor

@vadika vadika commented Jan 21, 2026

Secure Boot: first-boot EFI key enrollment + enable on X1 Carbon and System76

Secure Boot enrollment is implemented in the ghaf-installer -s .
Keys are enrolled during install in Setup Mode.

What’s included

  • New secure boot module modules/secureboot/secureboot.nix that:
    • Stages public keys into /etc/ghaf/secureboot/keys.
    • Runs a oneshot first-boot systemd service that enrolls keys with
      efi-updatevar.
    • Checks SetupMode and writes /persist/.secureboot-enrolled to avoid
      repeats.
  • Public key artifacts added under modules/secureboot/keys (PK.auth,
    KEK.crt, db.crt + README).
  • Module wired into common security imports.
  • Secure boot enabled for Lenovo X1 Carbon gen10–gen13 and System76
    Darp11-b (debug + release) targets.
  • Added an installer flag (-s) and gated Secure Boot enrollment on an installer
    marker so it won’t run by default.

This way secure boot enrollment only happens when the installer is
explicitly run with -s.

Behavior

  • Enrollment only happens if EFI vars are available and SetupMode is 0x1.

  • The service is skipped if the marker /persist/.secureboot-enrolled exists.

  • Uses efi-updatevar to write db, KEK, and PK
    Notes

  • Keys are public; private material is not included.

Testing
Boot in SetupMode, confirm EFI vars updated, then verify /persist/.secureboot-enrolled prevents re-run.

Description of Changes

Type of Change

  • New Feature
  • Bug Fix
  • Improvement / Refactor

Related Issues / Tickets

Checklist

  • Clear summary in PR description
  • Detailed and meaningful commit message(s)
  • Commits are logically organized and squashed if appropriate
  • Contribution guidelines followed
  • Ghaf documentation updated with the commit - https://tiiuae.github.io/ghaf/
  • Author has run make-checks and it passes
  • All automatic GitHub Action checks pass - see actions
  • Author has added reviewers and removed PR draft status

Testing Instructions

Applicable Targets

  • Orin AGX aarch64
  • Orin NX aarch64
  • Lenovo X1 x86_64
  • Dell Latitude x86_64
  • System 76 x86_64

Installation Method

  • Requires full re-installation
  • Can be updated with nixos-rebuild ... switch
  • Other:

Test Steps To Verify:

  1. Take the signed image from CI/CD
  2. Clear secure boot keys in BIOS setup in secure boot section, disable secure boot, enable secure boot Setup Mode.
  3. Install it with -s flag on the target device,
  4. The SB keys will be stored to EFI vars.
  5. Turn on SB and reboot device
  6. SB should be now enabled and device is booted successfuly.

@brianmcgillion
Copy link
Copy Markdown
Collaborator

we need a flag in the installer so as not to turn on secure boot by default for all, otherwise we will have non functional devices once you build your own local image.

@vadika
Copy link
Copy Markdown
Contributor Author

vadika commented Jan 21, 2026

we need a flag in the installer so as not to turn on secure boot by default for all, otherwise we will have non functional devices once you build your own local image.

done!

@milva-unikie
Copy link
Copy Markdown

Tested on signed Darter Pro installer

  • Signed image does not boot.
  • secureboot-first-boot.service fails on host during the first boot
Jan 21 10:18:40 ghaf-host systemd[1]: Starting Enroll Secure Boot keys on first boot...
Jan 21 10:18:40 ghaf-host ghaf-secureboot-enroll[902]: /nix/store/j86mkyw606aqf3mi9hpyqj053pdaivd1-ghaf-secureboot-enroll/bin/ghaf-secureboot-enroll: line 30: awk: command not found
Jan 21 10:18:40 ghaf-host systemd[1]: secureboot-first-boot.service: Main process exited, code=exited, status=127/n/a
Jan 21 10:18:40 ghaf-host systemd[1]: secureboot-first-boot.service: Failed with result 'exit-code'.
Jan 21 10:18:40 ghaf-host systemd[1]: Failed to start Enroll Secure Boot keys on first boot. 

My testing process

  • Boot to BIOS and remove all Secure Boot keys -> Secure Boot turns off
  • Boot to the installer and install Ghaf
  • Boot to Ghaf, create user account and login
  • Boot to BIOS and turn Secure Boot on
    -> Device does not boot anymore, Secure Boot check fails

When I started testing this PR I already had the matching keys enrolled to BIOS. I confirmed that the image I was testing was signed correctly by running the installer and then turning on the Secure Boot. The image booted without issues. The problem is only when trying to enroll the keys with this PR.

@milva-unikie
Copy link
Copy Markdown

Tested on signed Darter Pro installer

Still not working, same result. Device does not boot after Secure Boot has been enabled.

secureboot-first-boot.service says that secure boot enrollment was skipped.

[ghaf@ghaf-host:~]$ systemctl status secureboot-first-boot.service
● secureboot-first-boot.service - Enroll Secure Boot keys on first boot
     Loaded: loaded (/etc/systemd/system/secureboot-first-boot.service; enabled; preset: ignored)
     Active: active (exited) since Wed 2026-01-21 12:39:45 UTC; 1min 39s ago
 Invocation: 65c820ffea9f440eb8ad0c6940724a26
    Process: 818 ExecStart=/nix/store/hs6p60spr1ja298mcjkq0c5nf51xw2fz-ghaf-secureboot-enroll/bin/ghaf-secureboot-enroll (code=exited, status=0/SUCCESS)
   Main PID: 818 (code=exited, status=0/SUCCESS)
         IP: 0B in, 0B out
         IO: 3.8M read, 0B written
   Mem peak: 5.2M
        CPU: 30ms
Jan 21 12:39:45 ghaf-host systemd[1]: Starting Enroll Secure Boot keys on first boot...
Jan 21 12:39:45 ghaf-host ghaf-secureboot-enroll[818]: Installer marker not found. Skipping secure boot enrollment.
Jan 21 12:39:45 ghaf-host systemd[1]: Finished Enroll Secure Boot keys on first boot.

@vadika
Copy link
Copy Markdown
Contributor Author

vadika commented Jan 21, 2026

heavily refactored, moved all key deployment logic to ghaf-installer.
Clean the UEFI keys, switch secure boot to Setup mode and run the installer.

@milva-unikie
Copy link
Copy Markdown

Tested on signed Darter Pro installer

The signed installer image does not boot.

IMG_3950

Non-signed installer image boots normally.

@brianmcgillion
Copy link
Copy Markdown
Collaborator

brianmcgillion commented Jan 22, 2026

Critical Issues

1. Missing .auth Files for KEK and db

The implementation uses .crt (PEM certificate) files for KEK and db, but efi-updatevar -c expects DER-encoded certificates. More importantly, for proper chain-of-trust enrollment, KEK and db should use .auth files (signed EFI signature lists) similar to PK.

Current code (lines 259-261 in ghaf-installer.sh):

efi-updatevar -c "$DB_CRT" db       # Uses .crt
efi-updatevar -c "$KEK_CRT" KEK     # Uses .crt
efi-updatevar -f "$PK_AUTH" PK      # Uses .auth ✓

Recommendation: Generate proper .auth files for KEK and db using cert-to-efi-sig-list and sign-efi-sig-list, or use a tool like sbctl for consistent key generation.


2. Missing chattr -i for PK Variable

The code removes the immutable flag from db-* and KEK-* EFI variables, but not from PK-*.

Current code (lines 250-257):

db_vars=(/sys/firmware/efi/efivars/db-*)
kek_vars=(/sys/firmware/efi/efivars/KEK-*)
# Missing: pk_vars handling

Recommendation: Add PK variable handling:

pk_vars=(/sys/firmware/efi/efivars/PK-*)
if [ ${#pk_vars[@]} -gt 0 ]; then
  chattr -i "${pk_vars[@]}" || true
fi

3. No Error Handling for efi-updatevar

If key enrollment fails, the script continues silently and reports success.

Recommendation: Add error handling:

efi-updatevar -c "$DB_CRT" db || { echo "Failed to enroll db"; exit 1; }
efi-updatevar -c "$KEK_CRT" KEK || { echo "Failed to enroll KEK"; exit 1; }
efi-updatevar -f "$PK_AUTH" PK || { echo "Failed to enroll PK"; exit 1; }

4. Fragile Setup Mode Detection

The current check parses efi-readvar output for "has no entries" text (lines 232-239), which may vary across UEFI implementations.

Recommendation: Check the SetupMode EFI variable directly:

setup_mode_file=$(find /sys/firmware/efi/efivars -name 'SetupMode-*' 2>/dev/null | head -1)
if [ -n "$setup_mode_file" ]; then
  # Last byte indicates mode: 1 = Setup Mode, 0 = User Mode
  setup_mode=$(tail -c1 "$setup_mode_file" | od -An -tu1 | tr -d ' ')
  if [ "$setup_mode" != "1" ]; then
    echo "System is not in Setup Mode. Enable Setup Mode in BIOS first."
    exit 1
  fi
fi

5. No Post-Enrollment Verification

The script doesn't verify that keys were actually enrolled correctly.

Recommendation: Add verification after enrollment:

echo "Verifying Secure Boot key enrollment..."
efi-readvar -v db >/dev/null 2>&1 || { echo "db verification failed"; exit 1; }
efi-readvar -v KEK >/dev/null 2>&1 || { echo "KEK verification failed"; exit 1; }
efi-readvar -v PK >/dev/null 2>&1 || { echo "PK verification failed"; exit 1; }
echo "All keys verified successfully."

Moderate Issues

6. Questionable PK.auth File Format

file modules/secureboot/keys/PK.auth reports DOS 2.0 backup id file, which is unusual for an EFI authentication structure. This may indicate the file was generated incorrectly or is corrupted.

Recommendation: Regenerate keys using sbctl create-keys or verify the file with sbsiglist --list PK.auth.


7. Hardcoded Key Path in Installer

The installer hardcodes /etc/ghaf/secureboot/keys (line 212), but the Nix module has a configurable keysDir option.

Recommendation: Either:

  • Pass the key path as an environment variable from the Nix module, or
  • Document that the path must match the module's default

8. Module Import Path Convention

The secureboot module uses a relative import path in modules/common/security/default.nix:

../../secureboot/secureboot.nix

Recommendation: Register as a proper NixOS module and import via self.nixosModules.secureboot, or move the module to modules/common/security/secureboot/.


9. shopt -s nullglob Inconsistency

nullglob is enabled at line 159 for the raw file glob but never disabled. It's then re-enabled at line 249 and disabled at line 258. This could cause unexpected behavior.

Recommendation: Use subshells or ensure consistent enable/disable pairing:

(
  shopt -s nullglob
  db_vars=(/sys/firmware/efi/efivars/db-*)
  # ... rest of glob operations
)

Minor Issues

10. Documentation Not Updated

docs/src/content/docs/ghaf/overview/arch/secureboot.mdx still contains TODO placeholders and doesn't document this new enrollment process.


11. No Combination Testing Documented

The -e (encryption) and -s (secure boot) flags can be used together, but there's no documentation or testing of this combination.


Summary Checklist

  • Generate proper .auth files for KEK and db
  • Add chattr -i handling for PK variable
  • Add error handling after each efi-updatevar call
  • Improve Setup Mode detection using EFI variable
  • Add post-enrollment verification
  • Verify/regenerate PK.auth file format
  • Consider using sbctl for more robust key management
  • Update secureboot documentation
  • Fix module import path convention
  • Test -e -s combination

@vadika
Copy link
Copy Markdown
Contributor Author

vadika commented Jan 22, 2026

Critical Issues

1. Missing .auth Files for KEK and db

The implementation uses .crt (PEM certificate) files for KEK and db, but efi-updatevar -c expects DER-encoded certificates. More importantly, for proper chain-of-trust enrollment, KEK and db should use .auth files (signed EFI signature lists) similar to PK.

Current code (lines 259-261 in ghaf-installer.sh):

efi-updatevar -c "$DB_CRT" db       # Uses .crt
efi-updatevar -c "$KEK_CRT" KEK     # Uses .crt
efi-updatevar -f "$PK_AUTH" PK      # Uses .auth ✓

Recommendation: Generate proper .auth files for KEK and db using cert-to-efi-sig-list and sign-efi-sig-list, or use a tool like sbctl for consistent key generation.

2. Missing chattr -i for PK Variable

The code removes the immutable flag from db-* and KEK-* EFI variables, but not from PK-*.

Current code (lines 250-257):

db_vars=(/sys/firmware/efi/efivars/db-*)
kek_vars=(/sys/firmware/efi/efivars/KEK-*)
# Missing: pk_vars handling

Recommendation: Add PK variable handling:

pk_vars=(/sys/firmware/efi/efivars/PK-*)
if [ ${#pk_vars[@]} -gt 0 ]; then
  chattr -i "${pk_vars[@]}" || true
fi

3. No Error Handling for efi-updatevar

If key enrollment fails, the script continues silently and reports success.

Recommendation: Add error handling:

efi-updatevar -c "$DB_CRT" db || { echo "Failed to enroll db"; exit 1; }
efi-updatevar -c "$KEK_CRT" KEK || { echo "Failed to enroll KEK"; exit 1; }
efi-updatevar -f "$PK_AUTH" PK || { echo "Failed to enroll PK"; exit 1; }

4. Fragile Setup Mode Detection

The current check parses efi-readvar output for "has no entries" text (lines 232-239), which may vary across UEFI implementations.

Recommendation: Check the SetupMode EFI variable directly:

setup_mode_file=$(find /sys/firmware/efi/efivars -name 'SetupMode-*' 2>/dev/null | head -1)
if [ -n "$setup_mode_file" ]; then
  # Last byte indicates mode: 1 = Setup Mode, 0 = User Mode
  setup_mode=$(tail -c1 "$setup_mode_file" | od -An -tu1 | tr -d ' ')
  if [ "$setup_mode" != "1" ]; then
    echo "System is not in Setup Mode. Enable Setup Mode in BIOS first."
    exit 1
  fi
fi

5. No Post-Enrollment Verification

The script doesn't verify that keys were actually enrolled correctly.

Recommendation: Add verification after enrollment:

echo "Verifying Secure Boot key enrollment..."
efi-readvar -v db >/dev/null 2>&1 || { echo "db verification failed"; exit 1; }
efi-readvar -v KEK >/dev/null 2>&1 || { echo "KEK verification failed"; exit 1; }
efi-readvar -v PK >/dev/null 2>&1 || { echo "PK verification failed"; exit 1; }
echo "All keys verified successfully."

Moderate Issues

6. Questionable PK.auth File Format

file modules/secureboot/keys/PK.auth reports DOS 2.0 backup id file, which is unusual for an EFI authentication structure. This may indicate the file was generated incorrectly or is corrupted.

Recommendation: Regenerate keys using sbctl create-keys or verify the file with sbsiglist --list PK.auth.

7. Hardcoded Key Path in Installer

The installer hardcodes /etc/ghaf/secureboot/keys (line 212), but the Nix module has a configurable keysDir option.

Recommendation: Either:

  • Pass the key path as an environment variable from the Nix module, or
  • Document that the path must match the module's default

8. Module Import Path Convention

The secureboot module uses a relative import path in modules/common/security/default.nix:

../../secureboot/secureboot.nix

Recommendation: Register as a proper NixOS module and import via self.nixosModules.secureboot, or move the module to modules/common/security/secureboot/.

9. shopt -s nullglob Inconsistency

nullglob is enabled at line 159 for the raw file glob but never disabled. It's then re-enabled at line 249 and disabled at line 258. This could cause unexpected behavior.

Recommendation: Use subshells or ensure consistent enable/disable pairing:

(
  shopt -s nullglob
  db_vars=(/sys/firmware/efi/efivars/db-*)
  # ... rest of glob operations
)

Minor Issues

10. Documentation Not Updated

docs/src/content/docs/ghaf/overview/arch/secureboot.mdx still contains TODO placeholders and doesn't document this new enrollment process.

11. No Combination Testing Documented

The -e (encryption) and -s (secure boot) flags can be used together, but there's no documentation or testing of this combination.

Summary Checklist

  • Generate proper .auth files for KEK and db
  • Add chattr -i handling for PK variable
  • Add error handling after each efi-updatevar call
  • Improve Setup Mode detection using EFI variable
  • Add post-enrollment verification
  • Verify/regenerate PK.auth file format
  • Consider using sbctl for more robust key management
  • Update secureboot documentation
  • Fix module import path convention
  • Test -e -s combination
  • Use .auth for KEK/db and enroll with efi-updatevar -f.
  • Added PK immutable flag handling.
  • Added error handling and post‑enrollment verification.
  • Added SetupMode check via SetupMode-* efivar (fallback to PK output only
    if missing).
  • Fixed nullglob scope and added a “no image found” guard.
  • Staged KEK.auth and db.auth into /etc/ghaf/secureboot/keys via the
    secureboot module.

  Secure Boot enrollment is implemented in the ghaf-installer -s .
  Keys are enrolled during install in Setup Mode.

Signed-off-by: vadik likholetov <vadikas@gmail.com>
@milva-unikie
Copy link
Copy Markdown

Tested on signed Darter Pro installer

  • Installation works with the signed installer image (sudo ghaf-installer -s)
  • Secure Boot and full disk encryption work together (sudo ghaf-installer -s -e)

My testing process

  • Boot to BIOS and remove all Secure Boot keys -> Secure Boot turns to setup mode
  • Boot to the installer and install Ghaf
  • Boot to Ghaf, create user account and login
  • SSH to host and check with sbctl that Secure Boot is on

Notes

  • Lenovo X1 should also be tested at some point

@vadika vadika requested a review from vunnyso January 22, 2026 08:46
@brianmcgillion brianmcgillion merged commit 56b2677 into tiiuae:main Jan 22, 2026
31 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants