diff options
Diffstat (limited to 'sys/boot')
| -rw-r--r-- | sys/boot/chain.nix | 47 | ||||
| -rw-r--r-- | sys/boot/default.nix | 15 | ||||
| -rw-r--r-- | sys/boot/detached-luks.nix | 117 | ||||
| -rw-r--r-- | sys/boot/efi.nix | 49 | ||||
| -rw-r--r-- | sys/boot/firmware.nix | 33 | ||||
| -rw-r--r-- | sys/boot/fscrypt.nix | 30 | ||||
| -rw-r--r-- | sys/boot/impermanence.nix | 56 | ||||
| -rw-r--r-- | sys/boot/namespaced.nix | 33 | ||||
| -rw-r--r-- | sys/boot/pxe.nix | 187 | ||||
| -rw-r--r-- | sys/boot/secure-boot.nix | 51 | ||||
| -rw-r--r-- | sys/boot/stack/btrfs-toplevel-multidrive.nix | 99 | ||||
| -rw-r--r-- | sys/boot/stack/default.nix | 6 | ||||
| -rw-r--r-- | sys/boot/stack/luks-ext4-fscrypt-impermanence.nix | 98 | ||||
| -rw-r--r-- | sys/boot/tpm.nix | 128 |
14 files changed, 949 insertions, 0 deletions
diff --git a/sys/boot/chain.nix b/sys/boot/chain.nix new file mode 100644 index 0000000..b6a9ab2 --- /dev/null +++ b/sys/boot/chain.nix @@ -0,0 +1,47 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.local.boot; +in { + options.local.boot = { + loader = mkOption { + type = types.enum ["none" "out-of-band" "grub" "systemd-boot"]; + }; + + kernel = mkOption { + type = types.raw; + }; + }; + + config = mkIf (cfg.loader != "none") { + boot = { + kernelPackages = cfg.kernel; + + loader = + if cfg.loader == "none" || cfg.loader == "out-of-band" + then { + grub.enable = false; + } + else if cfg.loader == "grub" + then { + grub = { + enable = true; + device = "nodev"; + efiSupport = true; + }; + } + else if cfg.loader == "systemd-boot" + then { + systemd-boot = { + enable = true; + editor = true; + }; + } + else throw "unexpected config.local.boot.loader setting: ${cfg.loader}"; + }; + }; +} diff --git a/sys/boot/default.nix b/sys/boot/default.nix new file mode 100644 index 0000000..3cbbb6f --- /dev/null +++ b/sys/boot/default.nix @@ -0,0 +1,15 @@ +{ + imports = [ + ./chain.nix + ./detached-luks.nix + ./efi.nix + ./firmware.nix + ./fscrypt.nix + ./impermanence.nix + ./namespaced.nix + ./pxe.nix + ./secure-boot.nix + ./stack + ./tpm.nix + ]; +} diff --git a/sys/boot/detached-luks.nix b/sys/boot/detached-luks.nix new file mode 100644 index 0000000..78ae35c --- /dev/null +++ b/sys/boot/detached-luks.nix @@ -0,0 +1,117 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.local.boot.detachedLuks; + + bootFs = config.fileSystems."/boot"; + tpmInitrd = config.local.boot.tpm.initrd.enable; + + pcrList = concatStringsSep "," (map toString config.local.boot.tpm.initrd.pcrs); +in { + options.local.boot.detachedLuks = { + enable = mkEnableOption "detached LUKS header in initrd"; + + headerFromBoot = mkOption { + type = types.str; + }; + + tpmStorageFromBoot = mkOption { + type = types.str; + default = "tpm-boot"; + }; + + crypt = mkOption { + type = types.str; + }; + + target = mkOption { + type = types.str; + }; + }; + + config = mkIf cfg.enable { + boot.initrd = let + headerPath = "/initrd-boot/${cfg.headerFromBoot}"; + headerPathEscaped = escapeShellArg headerPath; + + tpmPath = escapeShellArg "/initrd-boot/${cfg.tpmStorageFromBoot}"; + hardwareKeyPath = "/tpm/unsealed.luks-key"; + in { + preDeviceCommands = '' + mkdir -p `dirname ${headerPathEscaped}` + touch ${headerPathEscaped} + ''; + + postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' + # Set the system time from the hardware clock to work around a + # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised + # to the *boot time* of the host). + hwclock -s + ''; + + #FIXME: Demasiado vulgar + preLVMCommands = optionalString (config.local.boot.efi.enable && config.local.boot.efi.removable) '' + sleep 2 + ''; + + luks.devices.${cfg.target} = { + device = cfg.crypt; + header = headerPath; + preLVM = false; + + keyFile = mkIf tpmInitrd hardwareKeyPath; + fallbackToPassword = tpmInitrd; + + preOpenCommands = + '' + mount -o ro -t ${bootFs.fsType} ${bootFs.device} /initrd-boot + '' + + optionalString tpmInitrd '' + mkdir /tpm + touch ${escapeShellArg hardwareKeyPath} + + unseal_tpm_key() { + tpm2 createprimary -Q -C owner -g sha256 -G ecc -c /tpm/prim.ctx || return + + tpm2 loadexternal -Q -C owner -G rsa -u ${tpmPath}/signing-key.pub -c /tpm/signing-key.ctx -n /tpm/signing-key.name || return + tpm2 verifysignature -Q -c /tpm/signing-key.ctx -g sha256 -m ${tpmPath}/auth.policy -s ${tpmPath}/auth.sig -t /tpm/verified.ticket -f rsassa || return + + tpm2 startauthsession -Q -S /tpm/session.ctx --policy-session || return + + tpm_resets=`tpm2 readclock | grep reset_count | sed 's/.*: //g'` + tpm2 policycountertimer -Q -S /tpm/session.ctx resets="$tpm_resets" || return + tpm2 policypcr -Q -S /tpm/session.ctx -l sha256:${pcrList} || return + tpm2 policyauthorize -Q -S /tpm/session.ctx -i ${tpmPath}/auth.policy -n /tpm/signing-key.name -t /tpm/verified.ticket || return + + tpm2 load -Q -C /tpm/prim.ctx -u ${tpmPath}/key.pub -r ${tpmPath}/key.priv -c /tpm/key.ctx || return + tpm2 unseal -Q -c /tpm/key.ctx -p session:/tpm/session.ctx -o ${escapeShellArg hardwareKeyPath} || return + + tpm2 flushcontext /tpm/session.ctx + } + + unseal_tpm_key + ''; + + postOpenCommands = mkBefore ('' + umount /initrd-boot + '' + + optionalString tpmInitrd '' + rm -r /tpm + ''); + }; + }; + + local.boot = { + stack = { + btrfsToplevelMultidrive.toplevel.device = "/dev/mapper/${cfg.target}"; + luksExt4FscryptImpermanence = {inherit (cfg) target;}; + }; + + tpm.initrd.enable = mkDefault config.local.boot.tpm.enable; + }; + }; +} diff --git a/sys/boot/efi.nix b/sys/boot/efi.nix new file mode 100644 index 0000000..71c42c8 --- /dev/null +++ b/sys/boot/efi.nix @@ -0,0 +1,49 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.local.boot.efi; +in { + options.local.boot.efi = { + enable = mkEnableOption "EFI with FAT32 system partition"; + + esp = { + mountpoint = mkOption { + type = types.enum ["/boot" "/boot/efi"]; + default = "/boot"; + }; + + uuid = mkOption { + type = types.strMatching "[0-9A-F]{4}-[0-9A-F]{4}"; + }; + }; + + removable = mkOption { + type = types.bool; + }; + }; + + config = mkIf cfg.enable { + boot = { + initrd.supportedFilesystems = ["vfat"]; + + loader = { + efi = { + efiSysMountPoint = cfg.esp.mountpoint; + canTouchEfiVariables = !cfg.removable; + }; + + grub.efiInstallAsRemovable = cfg.removable; + }; + }; + + fileSystems.${cfg.esp.mountpoint} = { + device = "/dev/disk/by-uuid/${cfg.esp.uuid}"; + fsType = "vfat"; + options = ["noatime" "umask=027" "sync"]; + neededForBoot = true; + }; + }; +} diff --git a/sys/boot/firmware.nix b/sys/boot/firmware.nix new file mode 100644 index 0000000..b3598a7 --- /dev/null +++ b/sys/boot/firmware.nix @@ -0,0 +1,33 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.local.boot.firmware; +in { + options.local.boot.firmware = { + mode = mkOption { + type = types.enum ["none" "redistributable" "all"]; + }; + + cpuVendor = mkOption { + type = types.enum ["amd" "intel"]; + }; + }; + + config = mkIf (cfg.mode != "none") { + hardware = { + cpu = { + amd.updateMicrocode = cfg.cpuVendor == "amd"; + intel.updateMicrocode = cfg.cpuVendor == "intel"; + }; + + enableAllFirmware = cfg.mode == "all"; + enableRedistributableFirmware = true; + }; + + services.fwupd.enable = true; + }; +} diff --git a/sys/boot/fscrypt.nix b/sys/boot/fscrypt.nix new file mode 100644 index 0000000..459e02b --- /dev/null +++ b/sys/boot/fscrypt.nix @@ -0,0 +1,30 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.local.boot.fscrypt; +in { + options.local.boot.fscrypt = { + enable = mkEnableOption "fscrypt support"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [pkgs.fscrypt-experimental]; + + local.boot.impermanence = { + directories = [ + { + directory = "/.fscrypt"; + mode = "u=rwx,g=rx,o=rx"; + } + ]; + + files = [ + "/etc/fscrypt.conf" + ]; + }; + }; +} diff --git a/sys/boot/impermanence.nix b/sys/boot/impermanence.nix new file mode 100644 index 0000000..632094b --- /dev/null +++ b/sys/boot/impermanence.nix @@ -0,0 +1,56 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.local.boot.impermanence; +in { + options.local.boot.impermanence = { + enable = mkEnableOption "root fs impermanence"; + + #TODO: type correcto de files, directories? + + directories = mkOption { + type = with lib.types; listOf (either str attrs); + default = []; + }; + + files = mkOption { + type = with lib.types; listOf (either str attrs); + default = []; + }; + }; + + config = mkMerge [ + { + local.boot.impermanence = { + directories = [ + "/etc/lvm" + "/var/lib/nixos" + "/var/log" + ]; + + files = [ + "/etc/machine-id" + "/var/lib/logrotate.status" + ]; + }; + } + (mkIf cfg.enable { + assertions = [ + { + assertion = (config.fileSystems ? "/persist") && config.fileSystems."/persist".neededForBoot; + message = "Impermanence requires /persist to be a neededForBoot mountpoint"; + } + ]; + + environment.persistence."/persist" = { + hideMounts = true; + + files = cfg.files; + directories = cfg.directories; + }; + }) + ]; +} diff --git a/sys/boot/namespaced.nix b/sys/boot/namespaced.nix new file mode 100644 index 0000000..3f95960 --- /dev/null +++ b/sys/boot/namespaced.nix @@ -0,0 +1,33 @@ +{ + config, + lib, + options, + ... +}: +with lib; let + cfg = config.local.boot.namespaced; +in { + options.local.boot.namespaced = { + enable = mkEnableOption "system containerization"; + }; + + config = mkIf cfg.enable { + boot.isContainer = true; + + local.boot = mkMerge ([ + { + loader = mkForce "none"; + + efi.enable = mkForce false; + firmware.mode = mkForce "none"; + secureBoot.enable = mkForce false; + impermanence.enable = mkForce false; + } + ] + ++ map + (name: { + stack.${name}.enable = mkForce false; + }) + (attrNames options.local.boot.stack)); + }; +} diff --git a/sys/boot/pxe.nix b/sys/boot/pxe.nix new file mode 100644 index 0000000..e25ba9d --- /dev/null +++ b/sys/boot/pxe.nix @@ -0,0 +1,187 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.local.boot.pxe; +in { + options.local.boot.pxe = { + enable = mkEnableOption "PXE netboot"; + + setupMode = mkOption { + type = types.bool; + default = false; + }; + + initrdInterface = mkOption { + type = types.str; + }; + + linkLocal6 = mkOption { + type = types.str; + }; + + networkDriver = mkOption { + type = types.str; + }; + + mac = mkOption { + type = types.str; + }; + + panicTimeout = mkOption { + type = types.nullOr types.ints.u16; + default = 30; + }; + }; + + # Based on nixpkgs/nixos/modules/installer/netboot/netboot.nix + config = mkIf cfg.enable { + boot = { + initrd = { + kernelModules = [cfg.networkDriver]; + + network = { + enable = true; + flushBeforeStage2 = true; + ssh = { + enable = true; + ignoreEmptyHostKeys = true; + }; + }; + + preFailCommands = mkIf (!cfg.setupMode) '' + echo "init stage1 failed in unattended mode, rebooting" + reboot -f + ''; + + postResumeCommands = '' + store_img_dir="/nix/.ro-store-img" + store_img="$store_img_dir/nix-store.squashfs" + + mkdir -p "$store_img_dir" + mount -t tmpfs -o mode=0700,nr_inodes=2,huge=within_size,nodev,nosuid tmpfs "$store_img_dir" + + ip link set up ${cfg.initrdInterface} + ip addr add ${cfg.linkLocal6}/64 dev ${cfg.initrdInterface} + + recv_init_timeout=120 + echo -n "Waiting up to ''${recv_init_timeout}s for $store_img" + + t="$recv_init_timeout" + while true; do + echo -n . | nc -u -w1 -s ${cfg.linkLocal6}%${cfg.initrdInterface} fe80::b007:b007%${cfg.initrdInterface} 1792 >/dev/null 2>&1 & + + if [ -e "$store_img.tmp" ]; then + kill -x nc + break + elif [ "$t" -le 0 ]; then + echo "timed out" + fail + fi + + # sleep builtin de ash retorna cuando nc termina (lo cual puede pasar muchas veces debido a IPv6 DAD) + /bin/sleep 1 + echo -n . + t=$((t - 1)) + done + + recv_poll=60 + recv_timeout=1800 + + t=0 + last_mtime="" + poll_time="$recv_poll" + while true; do + if [ -e "$store_img" ]; then + break + elif [ "$t" -ge "$recv_timeout" ]; then + echo "Store image upload timed out" + fail + fi + + sleep 1 + t=$((t + 1)) + poll_time=$((poll_time - 1)) + + if [ "$poll_time" -le 0 ]; then + current_size=$(stat -c '%s' "$store_img.tmp" || echo -) + current_mtime=$(stat -c '%y' "$store_img.tmp" || echo final) + echo "Store upload: $current_mtime: $current_size bytes" + + if [ "$current_mtime" = "$last_mtime" ]; then + echo "Store image upload stalled, image not modified in ''${recv_poll}s" + fail + fi + + last_mtime="$current_mtime" + poll_time="$recv_poll" + fi + done + + sleep 1 + kill -x sshd + + chmod 0500 "$store_img_dir" + chmod 0400 "$store_img" + chattr +i "$store_img" + mount -o remount,ro "$store_img_dir" + + store_size=$(stat -c '%s' "$store_img") + echo "Store image upload complete, final size is $store_size bytes" + + ln -sf "$store_img" /dev/nix-store + ''; + + postMountCommands = '' + mkdir -p "/mnt-root$store_img_dir" + mount --move "$store_img_dir" "/mnt-root$store_img_dir" + ''; + }; + + kernelParams = + optional (cfg.panicTimeout != null) "panic=${toString cfg.panicTimeout}" + ++ optional cfg.setupMode "boot.shell_on_fail"; + }; + + fileSystems = { + "/" = { + fsType = "tmpfs"; + neededForBoot = true; + + options = ["mode=0755"]; + }; + + "/nix/store" = { + fsType = "overlay"; + neededForBoot = true; + + overlay = { + lowerdir = ["/nix/.ro-store"]; + upperdir = "/nix/.rw-store/store"; + workdir = "/nix/.rw-store/work"; + }; + }; + + "/nix/.ro-store" = { + fsType = "squashfs"; + neededForBoot = true; + + device = "/dev/nix-store"; + options = ["loop" "threads=multi"]; + }; + + "/nix/.rw-store" = { + fsType = "tmpfs"; + neededForBoot = true; + + options = ["mode=0755"]; + }; + }; + + local = { + boot.loader = "out-of-band"; + }; + }; +} diff --git a/sys/boot/secure-boot.nix b/sys/boot/secure-boot.nix new file mode 100644 index 0000000..b13ab7c --- /dev/null +++ b/sys/boot/secure-boot.nix @@ -0,0 +1,51 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.local.boot.secureBoot; + + pkiBundle = + if cfg.legacyPath + then "/etc/secureboot" + else "/var/lib/sbctl"; +in { + options.local.boot.secureBoot = { + enable = mkEnableOption "secure boot"; + + legacyPath = mkOption { + type = types.bool; + default = false; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = config.local.boot.efi.enable; + message = "secure boot requires EFI"; + } + { + assertion = config.local.boot.loader == "systemd-boot"; + message = "lanzaboote requires systemd-boot"; + } + ]; + + boot = { + loader.systemd-boot.enable = mkForce false; + + lanzaboote = { + enable = true; + inherit pkiBundle; + }; + }; + + environment.systemPackages = [ + pkgs.sbctl + ]; + + local.boot.impermanence.directories = [pkiBundle]; + }; +} diff --git a/sys/boot/stack/btrfs-toplevel-multidrive.nix b/sys/boot/stack/btrfs-toplevel-multidrive.nix new file mode 100644 index 0000000..52db865 --- /dev/null +++ b/sys/boot/stack/btrfs-toplevel-multidrive.nix @@ -0,0 +1,99 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.local.boot.stack.btrfsToplevelMultidrive; +in { + options.local.boot.stack.btrfsToplevelMultidrive = { + enable = mkEnableOption "filesystem stack: persistent btrfs toplevel with optional hdd drive"; + + toplevel = { + device = mkOption { + type = types.str; + }; + + ssd = mkOption { + type = types.bool; + }; + + snapshot = mkOption { + type = types.bool; + default = false; + }; + + root = mkOption { + type = types.str; + }; + + pivot = mkOption { + type = types.str; + default = "/"; + }; + }; + + secondary = { + device = mkOption { + type = types.str; + }; + + ssd = mkOption { + type = types.bool; + }; + + snapshot = mkOption { + type = types.bool; + default = false; + }; + + home = mkOption { + type = types.str; + }; + + pivot = mkOption { + type = types.str; + default = "/"; + }; + }; + }; + + config = mkIf cfg.enable { + local.btrfs = { + mounts = { + "/" = { + inherit (cfg.toplevel) device ssd; + subvol = cfg.toplevel.root; + }; + + "/toplevel" = { + inherit (cfg.toplevel) device ssd; + subvol = cfg.toplevel.pivot; + }; + + #FIXME: Este nombre es legacy + "/hdd" = { + inherit (cfg.secondary) device ssd; + subvol = cfg.secondary.pivot; + }; + + "/home" = { + inherit (cfg.secondary) device ssd; + subvol = cfg.secondary.home; + }; + }; + + snapper = + optionalAttrs cfg.toplevel.snapshot + { + root = "/"; + } + // optionalAttrs cfg.secondary.snapshot { + home = "/home"; + }; + }; + + # Asegura que /hdd sea descifrado antes de intentar montar /home + fileSystems."/home".depends = ["/hdd"]; + }; +} diff --git a/sys/boot/stack/default.nix b/sys/boot/stack/default.nix new file mode 100644 index 0000000..ff211e6 --- /dev/null +++ b/sys/boot/stack/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./btrfs-toplevel-multidrive.nix + ./luks-ext4-fscrypt-impermanence.nix + ]; +} diff --git a/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix new file mode 100644 index 0000000..81feb60 --- /dev/null +++ b/sys/boot/stack/luks-ext4-fscrypt-impermanence.nix @@ -0,0 +1,98 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.local.boot.stack.luksExt4FscryptImpermanence; +in { + options.local.boot.stack.luksExt4FscryptImpermanence = { + enable = mkEnableOption "filesystem stack: whatever LUKS approach+ext4+impermanence with per-boot keys"; + + target = mkOption { + type = types.str; + }; + }; + + # - boot device + # - some unknown fs, probably vfat + # - detached luks header file + # + # - toplevel device + # - headerless luks + # - /toplevel (ext4) + # - /toplevel/nix + # - /toplevel/persist + # - /toplevel/boot-archive.pub + # - /toplevel/boot-keys + # - /toplevel/boot-keys/2000-01-01T00:00:00-06:00.key.crypt (encrypted for /toplevel/boot-archive.pub) + # - /toplevel/boot-keys/... + # - /toplevel/boot-keys/last.key.crypt -> 2000-01-01T00:00:00-06:00.key.crypt + # - /toplevel/boots + # - /toplevel/boots/2000-01-01T00:00:00-06:00 (raw protector in last.key.crypt) + # - /toplevel/boots/... + # - /toplevel/boots/last -> 2000-01-01T00:00:00-06:00 (mounted as /) + config = mkIf cfg.enable { + boot.initrd.luks.devices.${cfg.target}.postOpenCommands = let + fscryptctl = "${pkgs.fscryptctl}/bin/fscryptctl"; + in '' + # FIXME: posiblemente algunos --make-* son innecesarios a partir de aquĆ + mkdir -p /mnt-root /mnt-toplevel + mount -o noatime /dev/mapper/${cfg.target} /mnt-toplevel + mount --make-private /mnt-toplevel + + boot_stamp="$(date -Is)" + root_from_toplevel="/mnt-toplevel/boots/$boot_stamp" + + mkdir -p "$root_from_toplevel" /mnt-toplevel/boot-keys + chmod 700 /mnt-toplevel/boot-keys + + head -c64 /dev/urandom >/boot-key + key_id=$(${fscryptctl} add_key /mnt-toplevel </boot-key) + ${fscryptctl} set_policy "$key_id" "$root_from_toplevel" + (umask 077; test -f /mnt-toplevel/boot-archive.pub && \ + ${pkgs.openssl}/bin/openssl pkeyutl -encrypt \ + -in /boot-key -pubin -inkey /mnt-toplevel/boot-archive.pub \ + -out "/mnt-toplevel/boot-keys/$boot_stamp.key.crypt") + rm -f /boot-key + + ln -Tsf "$boot_stamp" /mnt-toplevel/boots/last + ln -Tsf "$boot_stamp.key.crypt" /mnt-toplevel/boot-keys/last.key.crypt + + mount --bind "$root_from_toplevel" /mnt-root + mount --make-shared /mnt-root + + # mount --move es mala idea, ya que "moving a mount residing under a + # shared mount is unsupported" + mkdir -p /mnt-root/toplevel + mount --bind /mnt-toplevel /mnt-root/toplevel + mount --make-private /mnt-root/toplevel + umount /mnt-toplevel + ''; + + fileSystems = { + "/" = { + device = "none"; + fsType = "ext4"; + options = ["remount"]; + }; + + "/nix" = { + device = "/persist/nix"; + options = ["bind"]; + }; + + "/persist" = { + device = "/toplevel/persist"; + options = ["bind"]; + neededForBoot = true; + }; + }; + + local.boot = { + fscrypt.enable = true; + impermanence.enable = true; + }; + }; +} diff --git a/sys/boot/tpm.nix b/sys/boot/tpm.nix new file mode 100644 index 0000000..da6f73a --- /dev/null +++ b/sys/boot/tpm.nix @@ -0,0 +1,128 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.local.boot.tpm; + + pcrList = concatStringsSep "," (map toString cfg.initrd.pcrs); + + # Crear signing-key con: + # $ openssl genrsa -out ~/vtmp/signing-key.priv 2048 + # $ openssl rsa -in ~/vtmp/signing-key.priv -out ~/vtmp/signing-key.pub -pubout + # Y copiar signing-key.pub a /boot/tpm-boot. Guardar signing-key.priv en lugar seguro. + # + # Crear llave con: + # $ tpm2_loadexternal -G rsa -C owner -u signing-key.pub -c signing-key.ctx -n signing-key.name + # $ tpm2_startauthsession -S session.ctx + # $ tpm2_policyauthorize -S session.ctx -L require-signed.policy -n signing-key.name + # $ tpm2_flushcontext session.ctx + # $ tpm2_createprimary -C owner -g sha256 -G ecc -c prim.ctx + # $ head -c128 /dev/urandom | tpm2_create -C prim.ctx -u key.pub -r key.priv -c key.ctx -L require-signed.policy -i- + # Y mover key.priv/key.pub a /boot/tpm-boot + # + # Usage: tpm2-grant-next-boot < signing-key.priv + # Genera auth.policy y auth.sig + tpm2-grant-next-boot = pkgs.writeShellApplication { + name = "tpm2-grant-next-boot"; + + runtimeInputs = [ + pkgs.jq + pkgs.openssl + pkgs.sbctl + pkgs.tpm2-tools + ]; + + text = '' + if [ -z "''${YES_I_DO_WANT_TO_SIGN_WITH_SECURE_BOOT_DISABLED:=}" ] && [ "$(sbctl status --json | jq .secure_boot)" != "truee" ]; then + echo "$0: bad Secure Boot state, check the output of \`sbctl status\`" >&2 + echo "$0: signing a TPM PCR policy with Secure Boot disabled is dangerous" >&2 + echo "$0: set 'YES_I_DO_WANT_TO_SIGN_WITH_SECURE_BOOT_DISABLED' to skip this check" >&2 + exit 1 + fi + + ctx_dir="$(mktemp -d)" + trap 'rm -rf -- "$ctx_dir"' EXIT + + tpm2_createprimary -Q -C owner -g sha256 -G ecc -c "$ctx_dir/prim.ctx" + + tpm2_startauthsession -Q -S "$ctx_dir/session.ctx" + tpm_resets=$(tpm2_readclock | grep reset_count | sed 's/.*: //') + tpm2_policycountertimer -Q -S "$ctx_dir/session.ctx" resets="$((tpm_resets+1))" + tpm2_policypcr -Q -S "$ctx_dir/session.ctx" -L auth.policy -l sha256:${pcrList} + tpm2_flushcontext -Q "$ctx_dir/session.ctx" + + openssl dgst -sha256 -sign /dev/stdin -out auth.sig auth.policy + ''; + }; +in { + options.local.boot.tpm = { + enable = mkEnableOption "Trusted Platform Module 2.0"; + + driver = mkOption { + type = types.enum ["tis" "crb"]; + }; + + initrd = { + enable = mkEnableOption "TPM2 in initrd"; + + pcrs = mkOption { + type = with types; listOf (ints.between 0 23); + + # From 'systemd-analyze pcrs' + default = [ + 0 # platform-code + 1 # platform-config + 2 # external-code + 3 # external-config + 4 # boot-loader-code + 5 # boot-loader-config + 7 # secure-boot-policy + 9 # kernel-initrd + 11 # kernel-boot + 12 # kernel-config + 13 # sysexts + 14 # shim-policy + ]; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.initrd.enable -> config.local.boot.efi.enable; + message = "TPM2 in initrd requires EFI"; + } + { + assertion = cfg.initrd.enable -> cfg.enable; + message = "TPM2 in initrd requires TPM2"; + } + ]; + + boot.initrd = mkIf cfg.initrd.enable { + extraUtilsCommands = '' + copy_bin_and_libs ${pkgs.tpm2-tools}/bin/.tpm2-wrapped + mv $out/bin/{.tpm2-wrapped,tpm2} + cp {${pkgs.tpm2-tss},$out}/lib/libtss2-tcti-device.so.0 + ''; + + kernelModules = [ + "tpm_${cfg.driver}" + ]; + }; + + environment.systemPackages = optionals cfg.initrd.enable [ + tpm2-grant-next-boot + ]; + + security.tpm2 = { + enable = true; + + pkcs11.enable = true; + tctiEnvironment.enable = true; + }; + }; +} |
