diff --git a/doc/release-notes/rl-2511.section.md b/doc/release-notes/rl-2511.section.md index ba2d9f256d34..da42ae4326de 100644 --- a/doc/release-notes/rl-2511.section.md +++ b/doc/release-notes/rl-2511.section.md @@ -34,4 +34,3 @@ ### Additions and Improvements {#sec-nixpkgs-release-25.11-lib-additions-improvements} - Create the first release note entry in this section! - diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md index 794c0dc6a633..65bac7327ed9 100644 --- a/nixos/doc/manual/release-notes/rl-2511.section.md +++ b/nixos/doc/manual/release-notes/rl-2511.section.md @@ -4,7 +4,7 @@ -- Create the first release note entry in this section! +- Secure boot support can now be enabled for the Limine bootloader through {option}`boot.loader.limine.secureBoot.enable`. Bootloader install script signs the bootloader, then kernels are hashed during system rebuild and written to a config. This allows Limine to boot only the kernels installed through NixOS system. ## New Modules {#sec-release-25.11-new-modules} diff --git a/nixos/modules/system/boot/loader/limine/limine-install.py b/nixos/modules/system/boot/loader/limine/limine-install.py index 9279db7f58f0..94d47a3169c1 100644 --- a/nixos/modules/system/boot/loader/limine/limine-install.py +++ b/nixos/modules/system/boot/loader/limine/limine-install.py @@ -249,6 +249,10 @@ def main(): partition formatted as FAT. ''')) + if config('secureBoot')['enable'] and not config('secureBoot')['createAndEnrollKeys'] and not os.path.exists("/var/lib/sbctl"): + print("There are no sbctl secure boot keys present. Please generate some.") + sys.exit(1) + if not os.path.exists(limine_dir): os.makedirs(limine_dir) else: @@ -352,6 +356,28 @@ def main(): print('error: failed to enroll limine config.', file=sys.stderr) sys.exit(1) + if config('secureBoot')['enable']: + sbctl = os.path.join(config('secureBoot')['sbctl'], 'bin', 'sbctl') + if config('secureBoot')['createAndEnrollKeys']: + print("TEST MODE: creating and enrolling keys") + try: + subprocess.run([sbctl, 'create-keys']) + except: + print('error: failed to create keys', file=sys.stderr) + sys.exit(1) + try: + subprocess.run([sbctl, 'enroll-keys', '--yes-this-might-brick-my-machine']) + except: + print('error: failed to enroll keys', file=sys.stderr) + sys.exit(1) + + print('signing limine...') + try: + subprocess.run([sbctl, 'sign', dest_path]) + except: + print('error: failed to sign limine', file=sys.stderr) + sys.exit(1) + if not config('efiRemovable') and not config('canTouchEfiVariables'): print('warning: boot.loader.efi.canTouchEfiVariables is set to false while boot.loader.limine.efiInstallAsRemovable.\n This may render the system unbootable.') diff --git a/nixos/modules/system/boot/loader/limine/limine.nix b/nixos/modules/system/boot/loader/limine/limine.nix index 14aa49ae579d..50a000380287 100644 --- a/nixos/modules/system/boot/loader/limine/limine.nix +++ b/nixos/modules/system/boot/loader/limine/limine.nix @@ -18,6 +18,7 @@ let canTouchEfiVariables = efi.canTouchEfiVariables; efiSupport = cfg.efiSupport; efiRemovable = cfg.efiInstallAsRemovable; + secureBoot = cfg.secureBoot; biosSupport = cfg.biosSupport; biosDevice = cfg.biosDevice; partitionIndex = cfg.partitionIndex; @@ -177,6 +178,41 @@ in ''; }; + secureBoot = { + enable = lib.mkEnableOption null // { + description = '' + Whether to use sign the limine binary with sbctl. + + ::: {.note} + This requires you to already have generated the keys and enrolled them with {command}`sbctl`. + + To create keys use {command}`sbctl create-keys`. + + To enroll them first reset secure boot to "Setup Mode". This is device specific. + Then enroll them using {command}`sbctl enroll-keys -m -f`. + + You can now rebuild your system with this option enabled. + + Afterwards turn setup mode off and enable secure boot. + ::: + ''; + }; + + createAndEnrollKeys = lib.mkEnableOption null // { + internal = true; + description = '' + Creates secure boot signing keys and enrolls them during bootloader installation. + + ::: {.note} + This is used for automated nixos tests. + NOT INTENDED to be used on a real system. + ::: + ''; + }; + + sbctl = lib.mkPackageOption pkgs "sbctl" { }; + }; + style = { wallpapers = lib.mkOption { default = [ ]; @@ -368,5 +404,57 @@ in }; }; }) + (lib.mkIf (cfg.enable && cfg.secureBoot.enable) { + assertions = [ + { + assertion = cfg.enrollConfig; + message = "Disabling enrollConfig allows bypassing secure boot."; + } + { + assertion = cfg.validateChecksums; + message = "Disabling validateChecksums allows bypassing secure boot."; + } + { + assertion = cfg.panicOnChecksumMismatch; + message = "Disabling panicOnChecksumMismatch allows bypassing secure boot."; + } + { + assertion = cfg.efiSupport; + message = "Secure boot is only supported on EFI systems."; + } + ]; + + boot.loader.limine.enrollConfig = true; + boot.loader.limine.validateChecksums = true; + boot.loader.limine.panicOnChecksumMismatch = true; + }) + + # Fwupd binary needs to be signed in secure boot mode + (lib.mkIf (cfg.enable && cfg.secureBoot.enable && config.services.fwupd.enable) { + systemd.services.fwupd = { + environment.FWUPD_EFIAPPDIR = "/run/fwupd-efi"; + }; + + systemd.services.fwupd-efi = { + description = "Sign fwupd EFI app for secure boot"; + wantedBy = [ "fwupd.service" ]; + partOf = [ "fwupd.service" ]; + before = [ "fwupd.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + RuntimeDirectory = "fwupd-efi"; + }; + script = '' + cp ${config.services.fwupd.package.fwupd-efi}/libexec/fwupd/efi/fwupd*.efi /run/fwupd-efi/ + chmod +w /run/fwupd-efi/fwupd*.efi + ${lib.getExe pkgs.sbctl} sign /run/fwupd-efi/fwupd*.efi + ''; + }; + + services.fwupd.uefiCapsuleSettings = { + DisableShimForSecureBoot = true; + }; + }) ]; } diff --git a/nixos/tests/limine/default.nix b/nixos/tests/limine/default.nix index 7458b9641633..9497e06a18f6 100644 --- a/nixos/tests/limine/default.nix +++ b/nixos/tests/limine/default.nix @@ -4,5 +4,6 @@ }: { checksum = runTest ./checksum.nix; + secureBoot = runTest ./secure-boot.nix; uefi = runTest ./uefi.nix; } diff --git a/nixos/tests/limine/secure-boot.nix b/nixos/tests/limine/secure-boot.nix new file mode 100644 index 000000000000..9f7969e626a0 --- /dev/null +++ b/nixos/tests/limine/secure-boot.nix @@ -0,0 +1,34 @@ +{ lib, ... }: +{ + name = "secureBoot"; + meta.maintainers = with lib.maintainers; [ + programmerlexi + ]; + meta.platforms = [ + "aarch64-linux" + "i686-linux" + "x86_64-linux" + ]; + nodes.machine = + { pkgs, ... }: + { + virtualisation.useBootLoader = true; + virtualisation.useEFIBoot = true; + virtualisation.useSecureBoot = true; + virtualisation.efi.OVMF = pkgs.OVMFFull.fd; + virtualisation.efi.keepVariables = true; + + boot.loader.efi.canTouchEfiVariables = true; + + boot.loader.limine.enable = true; + boot.loader.limine.efiSupport = true; + boot.loader.limine.secureBoot.enable = true; + boot.loader.limine.secureBoot.createAndEnrollKeys = true; + boot.loader.timeout = 0; + }; + + testScript = '' + machine.start() + assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") + ''; +}