File sdbootutil-1+git20250903.f5a076b.obscpio of Package sdbootutil
07070100000000000081A400000000000000000000000168B8435A00000026000000000000000000000000000000000000003000000000sdbootutil-1+git20250903.f5a076b/.dir-locals.el((sh-mode . ((sh-basic-offset . 8))))
07070100000001000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002900000000sdbootutil-1+git20250903.f5a076b/.github07070100000002000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000003800000000sdbootutil-1+git20250903.f5a076b/.github/ISSUE_TEMPLATE07070100000003000081A400000000000000000000000168B8435A0000111C000000000000000000000000000000000000004700000000sdbootutil-1+git20250903.f5a076b/.github/ISSUE_TEMPLATE/bug_report.ymlname: "Bug report"
description: "Report a problem with sdbootutil/FDE on openSUSE"
labels: [bug]
title: "[bug]: <short summary>"
body:
- type: checkboxes
id: preflight
attributes:
label: Pre‑submission checklist
options:
- label: I searched existing issues and discussions to avoid duplicates
required: true
- label: I tested with the latest released packages or the current git (if applicable)
required: true
- label: I can reproduce this on a clean boot or after reboot
required: false
- type: input
id: summary
attributes:
label: Short summary
description: One line that captures the essence of the bug
placeholder: e.g., "TPM2 unlock fails after kernel update when Secure Boot is enabled"
validations:
required: true
- type: textarea
id: observed
attributes:
label: Observed behavior
description: What actually happens? Include exact error messages shown on screen or in logs.
render: text
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What should have happened instead?
render: text
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: Numbered, minimal steps we can follow to reproduce the issue.
placeholder: |
1. …
2. …
3. …
render: markdown
validations:
required: true
- type: dropdown
id: product
attributes:
label: Product / distro
options:
- MicroOS
- Tumbleweed
- Leap
- Other
validations:
required: true
- type: dropdown
id: architecture
attributes:
label: Architecture
options:
- x86_64
- aarch64
- s390x
- ppc64le
- Other
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform
options:
- Bare metal
- VM (QEMU/KVM)
- VM (other)
validations:
required: true
- type: dropdown
id: bootloader
attributes:
label: Bootloader
options:
- systemd-boot
- grub2-bls
- Other/unknown
validations:
required: true
- type: input
id: versions
attributes:
label: sdbootutil version
description: Output of `zypper info sdbootutil | grep Version`
placeholder: sdbootutil 1+git20250804.8dccab3-1.1
validations:
required: false
- type: textarea
id: sysinfo
attributes:
label: System specs (paste output)
description: |
Paste the output of this one‑liner (run as root). It collects common diagnostics without requiring extra tools:
```sh
printf '## os-release\n'; cat /etc/os-release; \
printf '\n## uname\n'; uname -a; \
printf '\n## bootctl status\n'; bootctl status --no-pager || true; \
printf '\n## lsblk\n'; lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS; \
printf '\n## systemd-detect-virt\n'; systemd-detect-virt || true; \
printf '\n## sdbootutil version\n'; rpm -q sdbootutil || true
```
render: shell
validations:
required: false
- type: textarea
id: debuglog
attributes:
label: Debug trace excerpts (sanitized)
description: |
If you enabled the **opt‑in debug trace** (create `/var/log/sdbootutil.log` and set permissions to 600 before running), please paste only the **relevant, sanitized excerpts** here.
⚠️ **Privacy warning:** trace lines may include sensitive data (e.g. passwords typed as command arguments). **You must review and redact** any secrets before sharing. If in doubt, **omit** the trace and instead describe what you observed.
Tips:
* Use `sed`/`grep` to keep only the relevant portion (e.g. around the failure time).
* Replace secrets with placeholders like `<REDACTED>`.
render: text
validations:
required: false
- type: textarea
id: extrainfo
attributes:
label: Additional context
description: Any other logs (journal excerpts), config, or details that help diagnose the issue.
render: text
validations:
required: false07070100000004000081A400000000000000000000000168B8435A00000762000000000000000000000000000000000000004C00000000sdbootutil-1+git20250903.f5a076b/.github/ISSUE_TEMPLATE/feature_request.ymlname: "Feature request"
description: "Suggest an enhancement or new capability"
labels: [enhancement]
title: "[feat]: <short description>"
body:
- type: checkboxes
id: preflight
attributes:
label: Pre‑submission checklist
options:
- label: I searched existing issues and discussions to avoid duplicates
required: true
- label: This is not a bug report
required: true
- type: input
id: summary
attributes:
label: Short description
placeholder: e.g., "Allow selecting FIDO2 during initial enrollment via CLI flag"
validations:
required: true
- type: textarea
id: problem
attributes:
label: Problem / use case
description: What problem does this solve? Who benefits?
render: text
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution / expected behavior
description: Describe how you expect it to work (CLI flags, UI, workflow...).
render: text
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Other approaches you’ve tried or could work
render: text
validations:
required: false
- type: input
id: scope
attributes:
label: Affected component(s)
placeholder: e.g., sdbootutil, enrollment, predictions, installer, docs
validations:
required: false
- type: input
id: versions
attributes:
label: Version(s) you’re using
placeholder: e.g., "sdbootutil 0.9.0 on Tumbleweed x86_64"
validations:
required: false
- type: textarea
id: extra
attributes:
label: Additional context
description: Mockups, links, related issues, prior art, etc.
render: text
validations:
required: false07070100000005000081ED00000000000000000000000168B8435A000007D9000000000000000000000000000000000000003700000000sdbootutil-1+git20250903.f5a076b/10-sdbootutil.snapper#!/bin/bash
shopt -s extglob nullglob
set -e
# Check whether it's a transactional system
is_transactional()
{
findmnt --fstab / -O ro >/dev/null
}
# When creating a snapshot we fetch all bls configs from previous
# snapshot dir, mangle them to contain current snapshot number, then
# install to efi partition.
create_snapshot()
{
path="$1"
fs="$2"
num="$3"
[ "$fs" = btrfs ] || return 1
/usr/bin/sdbootutil update --sync "$num" || :
# If we are in a transactional system we abort here. The
# kernel entries are added later via set_default_snapshot,
# when tukit set via snapper the new snapshot as complete
is_transactional && return 0
# The entries are added here only for Tumbleweed
# (non-transactional systems)
/usr/bin/sdbootutil add-all-kernels "$num" || :
# In Tumblweed clean the default snapshot, not the new created
/usr/bin/sdbootutil cleanup || :
}
delete_snapshot()
{
local path="$1"
local fs="$2"
local num="$3"
[ "$fs" = btrfs ] || return 1
/usr/bin/sdbootutil remove-all-kernels "$num" || :
/usr/bin/sdbootutil cleanup "$num" || :
}
set_default_snapshot()
{
local path="$1"
local fs="$2"
local num="$3"
[ "$fs" = btrfs ] || return 1
if is_transactional; then
# In transactional systems the boot entries are added
# here, once the transaction is complete and tukit
# decides to keep it
/usr/bin/sdbootutil add-all-kernels "$num" || :
if [ -e /usr/lib/module-init-tools/regenerate-initrd-posttrans ]; then
/usr/lib/module-init-tools/regenerate-initrd-posttrans
fi
fi
/usr/bin/sdbootutil set-default-snapshot "$num" || :
/usr/bin/sdbootutil update --sync "$num" || :
}
h()
{
echo "Available commands:"
echo "${!commands[@]}"
}
declare -A commands
commands['create-snapshot-post']=create_snapshot
commands['delete-snapshot-pre']=delete_snapshot
commands['set-default-snapshot-post']=set_default_snapshot
commands['help']=h
cmd="$1"
shift
[ -n "$cmd" ] || cmd=help
if [ "${#commands[$cmd]}" -gt 0 ]; then
${commands[$cmd]} "$@"
fi
07070100000006000081ED00000000000000000000000168B8435A000001B6000000000000000000000000000000000000003500000000sdbootutil-1+git20250903.f5a076b/10-sdbootutil.tukit#!/bin/bash
shopt -s extglob nullglob
set -e
callExt_post()
{
path="$1"
if [ -e "$path"/run/regenerate-initrd ]; then
cp -a "$path"/run/regenerate-initrd /run
fi
}
h()
{
echo "Available commands:"
echo "${!commands[@]}"
}
declare -A commands
commands['callExt-post']=callExt_post
commands['help']=h
cmd="$1"
shift
[ -n "$cmd" ] || cmd=help
if [ "${#commands[$cmd]}" -gt 0 ]; then
${commands[$cmd]} "$@"
fi
07070100000007000081ED00000000000000000000000168B8435A00000353000000000000000000000000000000000000003700000000sdbootutil-1+git20250903.f5a076b/50-sdbootutil.install#!/bin/bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2024 SUSE LLC
set -e
COMMAND="${1:?}"
KERNEL_VERSION="${2:?}"
# shellcheck disable=SC2034
ENTRY_DIR_ABS="${3:?}"
# shellcheck disable=SC2034
KERNEL_IMAGE="$4"
[ "$KERNEL_INSTALL_LAYOUT" = "bls" ] || exit 0
MACHINE_ID="${KERNEL_INSTALL_MACHINE_ID:?}"
args=()
[ "$KERNEL_INSTALL_VERBOSE" -lt 1 ] || args+=("-v")
args+=("--esp-path=$KERNEL_INSTALL_BOOT_ROOT" "--entry-token=$KERNEL_INSTALL_ENTRY_TOKEN")
case "$COMMAND" in
remove)
exec /usr/bin/sdbootutil "${args[@]}" remove-kernel "$KERNEL_VERSION"
;;
add)
if [ "${KERNEL_IMAGE#/usr/lib/modules/$KERNEL_VERSION}" = "$KERNEL_IMAGE" ]; then
echo "Unsupported kernel location $KERNEL_IMAGE" >&2
exit 1
fi
exec /usr/bin/sdbootutil "${args[@]}" 'add-kernel' "$KERNEL_VERSION"
;;
*)
exit 0
;;
esac
07070100000008000081A400000000000000000000000168B8435A00002425000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/ARCHITECTURE.mdsystemd implementation of the bootloader spec
---------------------------------------------
The kernel-install script shipped with systemd can install kernels in the EFI
partition according to the [bootloader
specification](https://uapi-group.org/specifications/specs/boot_loader_specification/).
Assuming two installed kernels, 1.2.3-1-default and 4.5.6-1-default
one would call
kernel-install add /lib/modules/1.2.3-1-default/vmlinuz
kernel-install add /lib/modules/3.4.5-1-default/vmlinuz
Scripts in `/usr/lib/kernel/install.d/` would copy the kernel into
the ESP, generate an initrd and create a config to boot the
specified kernel.
With an entry token (eg machine-id) of 2ceda9f (shortened for
readability) the ESP would look like this:
├── 2ceda9f
│ ├── 1.2.3-1-default
│ │ ├── initrd
│ │ └── linux
│ └── 4.5.6-1-default
│ ├── initrd
│ └── linux
├── EFI
│ ├── BOOT
│ │ └── BOOTX64.EFI
│ └── systemd
│ └── systemd-bootx64.efi
└── loader
└── entries
├── 2ceda9f-1.2.3-1-default.conf
└── 2ceda9f-4.5.6-1-default.conf
Therefore an entry file might look like this:
title openSUSE Tumbleweed
version 1.2.3-1-default
machine-id 2ceda9f
sort-key opensuse-tumbleweed
options root=UUID=abc...
linux /2ceda9f/1.2.3-1-default/linux
initrd /2ceda9f/1.2.3-1-default/initrd
This scheme is basically designed so a specific instance of an
operating system (in the example 2ceda9f) can be booted with
different kernel versions. It is assumed that a kernel with a given
version is unique and each kernel has one specific initrd. Removing
a kernel (ie kernel-install remove 1.2.3-1-default.conf) would
remove the kernel, initrd and entry file. The root filesystem can
be specified as option or discovered automatically according to the
[discoverable partition specification](https://uapi-group.org/specifications/specs/discoverable_partitions_specification/).
Introducing snapshots
---------------------
With btrfs snapshots, the same root filesystem holds multiple
generations of the operating system. It's necessary to add a
`rootflags=` parameter to the options to tell the kernel which
subvolume to mount. Absence of the parameter would boot the default
subvolume.
So when creating a snapshot, snapper hook scripts could copy the
existing entry files, adding parameters to boot the new snapshot. So
for example if snapshot 15 was created the files would look like
this:
└── loader
└── entries
├── 2ceda9f-1.2.3-1-default.conf
├── 2ceda9f-4.5.6-1-default.conf
├── 2ceda9f-1.2.3-1-default-15.conf
└── 2ceda9f-4.5.6-1-default-15.conf
Since the ESP is FAT, a separator like @ can't be used to add the
snapshot number to the file name.
An entry file might now look like this:
title openSUSE Tumbleweed
version 15@1.2.3-1-default
machine-id 2ceda9f
sort-key opensuse-tumbleweed
options root=UUID=abc... rootflags=subvol=@/.snapshots/15/snapshot
linux /2ceda9f/1.2.3-1-default/linux
initrd /2ceda9f/1.2.3-1-default/initrd
In this example the snapshot number is prepended to the version.
This seems to be the best way right now to get the sorting right.
At this point the entry refers to the same kernel and initrd as used
in the running system. The system may install a different kernel
with same uname or recreate the initrd any time though. That would
potentially break booting the snapshot. So a snapshot entry must
also point to a matching kernel and initrd. FAT has no copy-on-write
features, so somehow those files need to be made unique in a
different way. It would be possible to encode the snapshot number
into the file name but then every snapshot would have it's own copy
of kernel and initrd, even when they could be shared. So a better
option is to use the file's checksum instead.
So that makes an entry look like this
title openSUSE Tumbleweed
version 15@1.2.3-1-default
machine-id 2ceda9f
sort-key opensuse-tumbleweed
options root=UUID=abc... rootflags=subvol=@/.snapshots/15/snapshot
linux /2ceda9f/1.2.3-1-default/linux-b021b508eb42b2afd06de8f0242b9727aa7dc494
initrd /2ceda9f/1.2.3-1-default/initrd-7b200fad3d005285ca914069a4740a5b6874c0ae
To avoid wasting any space, the default entries should also use
checkums and never store plain "linux" and "initrd" files.
With this scheme `kernel-install remove` would no longer work
though. It must especially not delete the kernel version directory
(eg 1.2.3-1-default) as it may contain files needed by other snapshots.
Therefore a `bootctl unlink` and `bootctl cleanup` features were
imlemented (https://github.com/systemd/systemd/pull/26103) to use
reference counting when deleting entries.
Default boot entry
------------------
So far the btrfs default subvolume flag is used to select which
subvolume to boot in absence of boot options. Now this means each
time the default subvolume is changed (ie rollback), the
systemd-boot entries that do not refer to a subvolume need to be
changed to ones that select the correct kernel and initrd combo.
Systemd-boot itself has a mechanism to set a default entry. So
setting the btrfs default subvolume alone actually has no effect.
Therefore snapper also needs to set the systemd-boot default when
changing the default subvolume.
Now creating a snapshot would create an entry with snapshot number
embedded anyway. Therefore the extra step to also create an entry
without snapshot number is not necessary. Instead all entries could
just have the subvolume setting. Selecting the default would only be
done based on the entry, not on the default subvolume. This would
also be the more natural way for MicroOS where no read-write
subvolume exists.
So the ESP would look like this:
├── 2ceda9f
│ ├── 1.2.3-1-default
│ │ ├── initrd-6272c61615fc18d5158aadda22fb0955c98382c8
│ │ └── linux-dcebae96db3cab7ffeb8883e78423a99d31d2bfd
│ └── 4.5.6-1-default
│ ├── initrd-7b200fad3d005285ca914069a4740a5b6874c0ae
│ ├── initrd-1b6b609e33104d68bd543a11a0b8a4d354478f46
│ └── linux-b021b508eb42b2afd06de8f0242b9727aa7dc494
├── EFI
│ ├── BOOT
│ │ └── BOOTX64.EFI
│ └── systemd
│ └── systemd-bootx64.efi
└── loader
└── entries
├── 2ceda9f-1.2.3-1-default-1.conf
├── 2ceda9f-4.5.6-1-default-1.conf
├── 2ceda9f-1.2.3-1-default-15.conf
└── 2ceda9f-4.5.6-1-default-15.conf
Distribution integration
------------------------
The scheme described in this document is implemented by
[sdbootutil](https://github.com/lnussel/sdbootutil). Similar to
`kernel-install` it allows to add and remove kernels to/from the
ESP but is fully snapshot aware. To speed up snapshot creation,
`sdbootutil` also tries to be smart about the initrd and reuses
existing ones from the parent snapshot if possible.
That also means initrds are not updated based on user space
changes at this point. Only kernel changes trigger initrd creation.
`sdbootutil` is called from a [snapper
plugin](https://github.com/lnussel/sdbootutil/blob/main/10-sdbootutil.snapper)
to manage entires on snapshot creation and removal, as well as when
the default subvolume is set.
Kernel package installation and removal also trigger calls to
`sdbootutil` via [file
triggers](https://github.com/lnussel/sdbootutil/blob/main/kernelhooks.lua).
Unfortunately the file trigger method seems to be unreliable with zypper.
Moreover, file triggers can't make an rpm transaction fail
(https://github.com/rpm-software-management/rpm/issues/2581).
Secure boot support
------------------------
Upstream `bootctl` has no support for shim yet
(https://github.com/systemd/systemd/issues/27234). That's required
for secure boot support though. Therefore `sdbootutil` also
implements an `install` feature.
If `/usr/share/efi/$(uname -m)` exists, shim and related files get
installed into the ESP
└── EFI
├── boot
│ ├── BOOTX64.EFI <-- shim
│ ├── MokManager.efi
│ └── fallback.efi
└─── systemd
├── MokManager.efi
├── boot.csv
├── grub.efi <-- actually systemd-boot
└── shim.efi
Future plans
------------
In general sdbootutil should not exist. It's features should be
implemented in `bootctl`, `kernel-install` or `snapper`.
To simplify handling of the initrd, it would be desirable to switch
to e.g. UKIs that are built on server side. That way no guessing
would be needed as to whether an initrd needs to be updated.
07070100000009000081A400000000000000000000000168B8435A0000042E000000000000000000000000000000000000002900000000sdbootutil-1+git20250903.f5a076b/LICENSEMIT License
Copyright (c) 2023 Ludwig Nussel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
0707010000000A000081A400000000000000000000000168B8435A000000E4000000000000000000000000000000000000002B00000000sdbootutil-1+git20250903.f5a076b/README.mdTool to manage systemd-boot and grub2-bls in a btrfs based, snapper
managed system.
Can install systemd-boot with shim as well as install kernels into
the ESP.
A non-interactive mode can be called from scriptlets or triggers.
0707010000000B000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002D00000000sdbootutil-1+git20250903.f5a076b/completions0707010000000C000081ED00000000000000000000000168B8435A000009E7000000000000000000000000000000000000003D00000000sdbootutil-1+git20250903.f5a076b/completions/bash_sdbootutil# shellcheck disable=SC2148
shopt -s nullglob
# shellcheck disable=SC2148
err()
{
:
}
_none()
{
declare -ga result
result=()
}
_find_kernels()
{
local fn kv
declare -ga result
result=()
for fn in /usr/lib/modules/*/"$image"; do
kv="${fn%/*}"
kv="${kv##*/}"
result+=("$kv")
done
}
_find_entry_ids()
{
declare -ga result
result=()
mapfile -t result < <(bootctl list --json=short | jq -r '.[].id')
}
_arch_name()
{
declare -ga result
result=("${!arch_image_map[@]}")
}
_image_name()
{
declare -ga result
result=()
result=("${!arch_image_map[@]}")
}
_method()
{
declare -ga result
result=()
for i in "tpm2" "tpm2+pin" "fido2" "password" "recovery-key"; do
result+=("$i")
done
}
_devices()
{
declare -ga result
result=()
mapfile -t result < <(sdbootutil list-devices)
}
_sdbootutil_completion()
{
eval "$(sdbootutil _print_bash_completion_data)"
local cur prev words cword image
COMPREPLY=()
_get_comp_words_by_ref -n : cur prev words cword
local command_found=0
eval_bootctl
set_image_name
define_commands
define_options
# shellcheck disable=SC2154
IFS=, read -r -a opts <<< "$opts_long"
local opts_with_dashes=()
for opt in "${opts[@]}"; do
opts_with_dashes+=("--${opt//:/}")
done
local i=0
for word in "${words[@]:1:$cword-1}"; do
if [ -n "${commands["$word"]+yes}" ]; then
command_found=1
fi
if [ " --arch " == " $word " ]; then
# shellcheck disable=SC2034
firmware_arch="${words[i+2]}"
set_image_name
fi
if [ " --image " == " $word " ]; then
image="${words[i+2]}"
fi
((i++))
done
# shellcheck disable=SC2154
local opt_arg_fun="${options_with_arg[${prev#--}]}"
if [ "${commands["$prev"]}" == "kernel" ]; then
_find_kernels
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "${result[*]}" -- "$cur") )
elif [ "${commands["$prev"]}" == "id" ]; then
_find_entry_ids
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "${result[*]}" -- "$cur") )
elif [ "$opt_arg_fun" == "_path" ]; then
# shellcheck disable=SC2207
COMPREPLY=($(compgen -d -- "$cur"))
elif [ "$opt_arg_fun" ]; then
eval "$opt_arg_fun"
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "${result[*]}" -- "$cur") )
elif [[ $cur == -* ]] || [ $command_found -eq 1 ]; then
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "${opts_with_dashes[*]}" -- "$cur") )
elif [ $command_found -eq 0 ]; then
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "${!commands[*]}" -- "$cur") )
fi
}
complete -F _sdbootutil_completion sdbootutil
0707010000000D000081A400000000000000000000000168B8435A00001850000000000000000000000000000000000000003700000000sdbootutil-1+git20250903.f5a076b/jeos-firstboot-enroll#!/bin/bash
# shellcheck disable=SC2034
enroll_title=$"Full Disk Encryption Enrollment"
# shellcheck disable=SC2034
enroll_description=$"Enroll a device using TPM2 or FIDO2 key"
crypt_keyid=""
crypt_pw=""
crypt_tpm_pin=""
with_fido2=
with_tpm2=
with_recovery_key=
luks2_devices=()
have_luks2() {
lsblk --noheadings -o FSTYPE | grep -q crypto_LUKS
}
# exit early without defining any helper functions if there are no luks devices
have_luks2 || return 0
enroll_systemd_firstboot() {
[ -e /usr/bin/systemd-cryptenroll ] || return 0
crypt_keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || return 0
[ -n "$crypt_keyid" ] || return 0
welcome_screen_with_console_switch
local has_fido2=${JEOS_HAS_FIDO2:-}
local has_tpm2=
[ -z "$(systemd-cryptenroll --fido2-device=list 2>/dev/null)" ] || has_fido2=1
[ ! -e '/sys/class/tpm/tpm0' ] || has_tpm2=lock
while true; do
local list=()
if [ -z "$with_recovery_key" ]; then
list+=('recovery-key' $'Enroll recovery key')
fi
if [ -z "$with_fido2" ] && [ -z "$with_tpm2" ] && [ -n "$has_fido2" ]; then
list+=('FIDO2' $'Enroll FIDO2 token')
fi
if [ -z "$with_tpm2" ] && [ -z "$with_fido2" ] && [ -n "$has_tpm2" ]; then
list+=('TPM2' $'Enroll TPM2 based token' 'TPM2_interactive' 'Enroll TPM2 based token with PIN')
fi
if [ -z "$crypt_pw" ]; then
if [ -n "$password" ]; then
list+=('root' $'Enroll root password')
fi
list+=('password' $'Enroll extra password')
fi
[ -n "$list" ] || break
list+=('done' $'Done')
d --no-tags --default-item "${list[0]}" --menu $"Disk Encryption" 0 0 "$(menuheight ${#list[@]})" "${list[@]}"
if [ "$result" = 'done' ]; then
if [ -z "$with_recovery_key" ] && [ -z "$crypt_pw" ] && [ -z "$with_fido2" ] && [ -z "$with_tpm2" ] && [ -z "$is_jeos_config" ]; then
d_styled --msgbox $"Can not continue without selecting an enrollment" 5 52
continue
fi
break;
elif [ "$result" = 'FIDO2' ]; then
with_fido2=1
elif [ "$result" = 'TPM2' ]; then
with_tpm2="$has_tpm2"
elif [ "$result" = 'TPM2_interactive' ]; then
while true; do
d --insecure --passwordbox $"Enter new PIN (actually just passphrase)" 0 0
if [ -z "$result" ]; then
d_styled --yesno $"Retry?" 0 0 || break
continue
fi
crypt_tpm_pin="$result"
d --insecure --passwordbox $"Confirm PIN" 0 0
[ "$crypt_tpm_pin" != "$result" ] || { with_tpm2="$has_tpm2"; break; }
d --msgbox $"PINs don't match. Try again" 0 0
done
elif [ "$result" = 'recovery-key' ]; then
with_recovery_key=1
elif [ "$result" = 'root' ]; then
crypt_pw="$password"
elif [ "$result" = 'password' ]; then
while true; do
d --insecure --passwordbox $"Enter encryption password" 0 0
if [ -z "$result" ]; then
d --aspect 29 --msgbox $"No encryption password set. You can add more keys manually using systemd-cryptenroll." 0 0
break
fi
crypt_pw="$result"
d --insecure --passwordbox $"Confirm encryption password" 0 0
[ "$crypt_pw" != "$result" ] || break
d --msgbox $"Passwords don't match. Try again" 0 0
done
else
d --msgbox "Error: $result" 0 0
fi
done
return 0
}
write_issue_file() {
local recovery_key="$1"
local issuefile="/run/issue.d/90-recovery-key.issue"
[ -x '/usr/sbin/issue-generator' ] && issuefile="/run/issue.d/90-recovery-key.conf"
mkdir -p "/run/issue.d"
echo "$recovery_key" > "$issuefile"
[ -x '/usr/sbin/issue-generator' ] && run issue-generator
}
enroll_post() {
[ -e /usr/bin/systemd-cryptenroll ] || return 0
[ -n "$crypt_keyid" ] || return 0
do_enroll
}
do_enroll() {
local out r error=0
[ -z "$with_recovery_key" ] || {
# Note that if --no-reuse-initrd is used, then a new
# initrd will be created and will break the
# measurement of the initial components if later the
# TPM2 enrollment is called
extra=
if [ -z "$with_tpm2" ] && [ -z "$with_fido2" ]; then
extra="--no-reuse-initrd"
fi
d --infobox "Enrolling recovery-key ..." 3 40
out="$(run sdbootutil enroll --method=recovery-key "$extra" 2>&1)"
r="$?"
if [ $r -ne 0 ]; then
d --msgbox "Error (recovery-key): $out" 0 0
error=1
else
write_issue_file "$out"
fi
}
[ -z "$crypt_pw" ] || {
# Note that if --no-reuse-initrd is used, then a new
# initrd will be created and will break the
# measurement of the initial components if later the
# TPM2 enrollment is called
extra=
if [ -z "$with_tpm2" ] && [ -z "$with_fido2" ]; then
extra="--no-reuse-initrd"
fi
d --infobox "Enrolling password ..." 3 40
out="$(PW="$crypt_pw" run sdbootutil enroll --method=password "$extra" 2>&1)"
r="$?"
[ $r -eq 0 ] || {
d --msgbox "Error (password): $out" 0 0
error=1
}
}
if [ -n "$with_tpm2" ]; then
if [ -n "$crypt_tpm_pin" ]; then
d --infobox "Enrolling TPM2+PIN ..." 3 40
out="$(SDB_ADD_INITIAL_COMPONENT=1 PIN="$crypt_tpm_pin" run sdbootutil enroll --method=tpm2+pin 2>&1)"
r="$?"
[ $r -eq 0 ] || {
d --msgbox "Error (TPM2+PIN): $out" 0 0
error=1
}
else
d --infobox "Enrolling TPM2 ..." 3 40
out="$(SDB_ADD_INITIAL_COMPONENT=1 run sdbootutil enroll --method=tpm2 2>&1)"
r="$?"
[ $r -eq 0 ] || {
d --msgbox "Error (TPM2): $out" 0 0
error=1
}
fi
fi
[ -z "$with_fido2" ] || {
d --infobox "Enrolling FIDO2 ..." 3 40
out="$(run sdbootutil enroll --method=fido2 2>&1)"
r="$?"
[ $r -eq 0 ] || {
d --msgbox "Error (FIDO2): $out" 0 0
error=1
}
}
if [ "$error" -eq 1 ]; then
d --msgbox "One or more enrollment methods failed. Resolve the issue and re-try with 'jeos-config enroll'" 0 0
else
# Clean the enrollment key. disk-encryption-tool
# creates it in the keyslot 0 with the name
# "enrollment-key", that is showed by
# systemd-cryptenroll as "other"
local slots
while read -r dev; do
slots=$(systemd-cryptenroll "$dev")
if grep -q "other" <<<"$slots"; then
systemd-cryptenroll --wipe-slot=0 "$dev"
fi
done < <(sdbootutil list-devices)
fi
}
enroll_jeos_config() {
is_jeos_config=1
d --insecure --passwordbox $"Enter decryption password" 0 0
[ -n "$result" ] || return 0
echo -n "$result" | keyctl padd user cryptenroll @u &> /dev/null
enroll_systemd_firstboot
do_enroll
}
0707010000000E000081A400000000000000000000000168B8435A0000001D000000000000000000000000000000000000004500000000sdbootutil-1+git20250903.f5a076b/jeos-firstboot-enroll-override.conf[Service]
KeyringMode=shared
0707010000000F000081A400000000000000000000000168B8435A00000150000000000000000000000000000000000000004000000000sdbootutil-1+git20250903.f5a076b/kernel-install-sdbootutil.conf# disable all kernel-install scripts that are known incompatible
# with sdbootutil.
L /run/kernel/install.d/50-depmod.install - - - - /dev/null
L /run/kernel/install.d/50-dracut.install - - - - /dev/null
L /run/kernel/install.d/51-dracut-rescue.install - - - - /dev/null
L /run/kernel/install.d/90-loaderentry.install - - - - /dev/null
07070100000010000081A400000000000000000000000168B8435A00000743000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/kernelhooks.luakernelhooks = { debug = nil } -- package
kernelhooks.found = {}
kernelhooks.legacy = {} -- entries in /boot
kernelhooks.imagename = "vmlinuz" -- FIXME: arch dependent!
kernelhooks.kernelpattern = string.format("^/usr/lib/modules/(.*)/%s", kernelhooks.imagename)
-- beware of crazy lua patterns, - is similar to * and % is escape character
kernelhooks.legacypattern = string.format("^/boot/%s%%-(.+)", kernelhooks.imagename)
-- for testing outside rpm
if posix == nil then
posix = require("posix")
end
function _log(msg)
if kernelhooks.debug then
print(kernelhooks.debug .. ": " .. msg)
end
end
function rootpath(path)
_, _, p = string.find(path, "^/usr(/.*)")
return p
end
function dirname(path)
_, _, d, b = string.find(path, "^(.*)/([^/]*)")
return d
end
function kernelhooks.filter(path)
_, _, kver = string.find(path, kernelhooks.kernelpattern)
if kver then
_log("found kernel " .. kver)
kernelhooks.found[kver] = path
return
end
_, _, kver = string.find(path, kernelhooks.legacypattern)
if kver then
_log("kernel " .. kver .. " in /boot")
kernelhooks.legacy[kver] = 1
return
end
end
function kernelhooks.add()
for kver in pairs(kernelhooks.found) do
if kernelhooks.legacy[kver] then
_log("not adding " .. kver .. " due to legacy /boot location")
else
_log("adding " .. kver)
rpm.execute("/usr/sbin/depmod", "-a", kver)
rpm.execute("/usr/bin/sdbootutil", "add-kernel", kver)
rpm.execute("/usr/bin/sdbootutil", "set-default-snapshot")
end
end
end
function kernelhooks.remove()
for kver in pairs(kernelhooks.found) do
if kernelhooks.legacy[kver] then
_log("not removing " .. kver .. " due to legacy /boot location")
else
_log("removing " .. kver)
rpm.execute("/usr/bin/sdbootutil", "remove-kernel", kver)
rpm.execute("/usr/bin/sdbootutil", "set-default-snapshot")
end
end
end
07070100000011000081A400000000000000000000000168B8435A0000071F000000000000000000000000000000000000003A00000000sdbootutil-1+git20250903.f5a076b/measure-pcr-generator.sh#!/bin/bash
set -euo pipefail
# For a generator, the first parameter `normal-dir` is not optional
[ -n "$1" ] || { echo "Missing normal-dir parameter"; exit 1; }
[ -f "/etc/crypttab" ] || exit 0;
# Read /etc/crypttab lines that contains tpm2-device and
# tpm2-measure-pcr. It will order the services as listed in this file
after=""
while read -r name _ _ opts; do
# Only the entries in /etc/crypttab in the initrd should
# participate from the extension for now. The reason is that
# extensions after the switch root cannot participate in abort
# the boot process from initrd itself
[ -f "/etc/initrd-release" ] || continue
[[ "$name" = \#* ]] && continue
[[ "$opts" != *"tpm2-device="* ]] && continue
[[ "$opts" != *"tpm2-measure-pcr="* ]] && continue
mkdir -p "$1/systemd-cryptsetup@$name.service.d"
cat > "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" <<-EOF
# Automatically generated by measure-pcr-generator
[Service]
Environment="SYSTEMD_FORCE_MEASURE=yes"
EOF
if [ -n "$after" ]; then
cat >> "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" <<-EOF
[Unit]
After=systemd-cryptsetup@$after.service
EOF
fi
after="$name"
done < /etc/crypttab
# Do a similar loop for devices that can be unlocked by FIDO2 keys
after=""
while read -r name _ _ opts; do
[[ "$name" = \#* ]] && continue
[[ "$opts" != *"fido2-device="* ]] && continue
mkdir -p "$1/systemd-cryptsetup@$name.service.d"
[ -f "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" ] || {
echo "# Automatically generated by measure-pcr-generator" > "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf"
}
if [ -n "$after" ]; then
cat >> "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" <<-EOF
[Unit]
After=systemd-cryptsetup@$after.service
EOF
fi
after="$name"
done < /etc/crypttab
07070100000012000081A400000000000000000000000168B8435A000001FA000000000000000000000000000000000000003F00000000sdbootutil-1+git20250903.f5a076b/measure-pcr-validator.service[Unit]
Description=Validate LUKS2 devices
DefaultDependencies=false
FailureAction=poweroff-immediate
Wants=cryptsetup.target
After=cryptsetup.target
Before=initrd-root-device.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/measure-pcr-validator
Environment=TERM=linux
ExecStopPost=/bin/sh -c "/usr/bin/plymouth quit 2>/dev/null || :"
StandardOutput=tty
StandardInput=tty
[Install]
# If we use RequiredBy we trigger its OnFailure=emergency.target
WantedBy=initrd-root-device.target
07070100000013000081A400000000000000000000000168B8435A000008EF000000000000000000000000000000000000003A00000000sdbootutil-1+git20250903.f5a076b/measure-pcr-validator.sh#!/bin/bash
set -euo pipefail
WHITE="\e[1;37m"
LIGHT_BLUE="\e[1;34m"
END="\e[m"
measure_pcr_crypttab() {
grep -q "tpm2-measure-pcr=yes" /etc/crypttab
}
get_measure_pcr_ignore() {
(set +eu; . /lib/dracut-lib.sh; getargbool no measure-pcr-validator.ignore)
}
validate_measure_pcr_signature() {
openssl dgst -sha256 \
-verify /var/lib/sdbootutil/measure-pcr-public.pem \
-signature /var/lib/sdbootutil/measure-pcr-prediction.sha256 \
/var/lib/sdbootutil/measure-pcr-prediction &> /dev/null
}
validate_measure_pcr() {
if [ ! -e "/sys/class/tpm/tpm0" ]; then
echo "Error: TPM2 not found in /sys/class/tpm/tpm0"
return 1
fi
local res=1
for sha in sha1 sha256 sha384 sha512; do
[ -e "/sys/class/tpm/tpm0/pcr-$sha/15" ] || continue
read -r expected_pcr_15 < "/sys/class/tpm/tpm0/pcr-$sha/15"
grep -Fixq "$expected_pcr_15" /var/lib/sdbootutil/measure-pcr-prediction; res="$?"
break
done
return "$res"
}
exit_with_msg() {
local msg="$1"
if ! measure_pcr_crypttab; then
echo "INFO: No PCR 15 validation"
exit 0
elif get_measure_pcr_ignore; then
echo "WARNING: The validation of PCR 15 failed"
echo "WARNING: $msg"
exit 0
else
echo "ERROR: the validation of PCR 15 failed"
kill -SIGRTMIN+21 1
sleep 1
echo -ne '\n\n\a'
echo -e "${WHITE}*********************************************************************${END}"
echo -e "${WHITE}ERROR: $msg${END}"
echo -e "${WHITE}Use${END} '${LIGHT_BLUE}measure-pcr-validator.ignore=yes${END}' ${WHITE}in cmdline to bypass the check${END}"
echo -e "${WHITE}*********************************************************************${END}"
echo
read -n1 -s -r -t 10 -p $'\e[1;37m*** The system will be halted. Press any key ...\e[0m' || true
echo
kill -SIGRTMIN+20 1
exit 1
fi
}
[ -f /etc/crypttab ] || exit 0
grep -q 'tpm2-measure-pcr=yes' /etc/crypttab || exit 0
[ -f "/var/lib/sdbootutil/measure-pcr-prediction" ] || exit_with_msg "Missing measure-pcr-prediction file"
[ -f "/var/lib/sdbootutil/measure-pcr-prediction.sha256" ] || exit_with_msg "Missing measure-pcr-prediction.sha256 signature file"
validate_measure_pcr_signature || exit_with_msg "Signature for the prediction file is not valid"
validate_measure_pcr || exit_with_msg "PCR 15 mismatch. Encrypted devices compromised"
07070100000014000081A400000000000000000000000168B8435A000002B7000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/module-setup.sh#!/bin/bash
# Prerequisite check(s) for module.
check() {
# Return 255 to only include the module, if another module
# requires it.
return 0
}
install() {
inst_multiple grep openssl
inst_script "$moddir/measure-pcr-generator.sh" "/usr/lib/systemd/system-generators/measure-pcr-generator"
inst_script "$moddir/measure-pcr-validator.sh" "/usr/bin/measure-pcr-validator"
inst_simple "$moddir/measure-pcr-validator.service" "$systemdsystemunitdir/measure-pcr-validator.service"
[ -f "/var/lib/sdbootutil/measure-pcr-public.pem" ] && inst_simple "/var/lib/sdbootutil/measure-pcr-public.pem"
$SYSTEMCTL -q --root "$initdir" enable measure-pcr-validator.service
}
07070100000015000081ED00000000000000000000000168B8435A0001B38E000000000000000000000000000000000000002C00000000sdbootutil-1+git20250903.f5a076b/sdbootutil#!/bin/bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2023 SUSE LLC
set -e
shopt -s nullglob
DEBUG_LOG="/var/log/sdbootutil.log"
if [ -f "$DEBUG_LOG" ]; then
exec 3>>"$DEBUG_LOG"
export BASH_XTRACEFD=3
export PS4='+ \D{%F %T} ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-main}: '
set -x
fi
verbose=
nl=$'\n'
shimdir="/usr/share/efi/$(uname -m)"
arg_esp_path="$SYSTEMD_ESP_PATH"
arg_entry_token=
arg_arch=
arg_all_entries=
arg_entry_keys=()
arg_no_variables=
arg_no_reuse_initrd=
arg_no_random_seed=
arg_portable=
arg_secure_boot=
arg_sync=
arg_only_default=
arg_default_snapshot=
arg_ask_key_pin_or_pw=
arg_method=
arg_signed_policy=
arg_no_measure_pcr=
arg_measure_pcr=
arg_pcr=
have_snapshots=
# for x in vmlinuz image vmlinux linux bzImage uImage Image zImage; do
image=
unlock_method=
chroot_dir=
color_red=
color_end=
# The shell is interactive and the colors haven't been disabled
# forcefully or the shell is not connected to a terminal but the
# colors have been forcefully enabled
if { [ -t 1 ] \
&& [ "$SYSTEMD_COLORS" != "false" ] && [ "$SYSTEMD_COLORS" != "0" ]; } \
|| { [ ! -t 1 ] \
&& [ "$SYSTEMD_COLORS" = "true" ] || [ "$SYSTEMD_COLORS" = "1" ]; }; then
color_red="\e[31m"
color_green="\e[32m"
color_yellow="\e[33m"
color_bu="\e[1;4m" # bold underscore
color_end="\e[m"
fi
# State file for transactional systems
state_file="/var/lib/misc/transactional-update.state"
update_predictions=
tracked_devices=()
rollback=()
declare -g -A eventlog
tmpdir=$(mktemp -d -t sdbootutil.XXXXXX)
cleanup()
{
local i
for i in "${rollback[@]}"; do
if [ -e "$i.bak" ]; then
info "Restoring $i"
mv "$i.bak" "$i"
else
info "Removing $i"
rm -f "$i"
fi
done
dbg "Cleaning temporary directory $tmpdir"
rm -rf "$tmpdir"
[ -z "$chroot_dir" ] || umount_chroot "$chroot_dir"
}
trap cleanup EXIT
entryfile="$tmpdir/entries.json"
initialentryfile="$tmpdir/initial_entries.json"
snapperfile="$tmpdir/snapper.json"
tmpfile="$tmpdir/tmp"
helpandquit()
{
# Tabs are removed from the start of the line. Use spaces to
# indent
cat <<-EOF
Usage: $0 [OPTIONS] [COMMAND]
OPTIONS:
--esp-path Manually specify path to ESP
--arch Manually set architecture
--entry-token Override entry token
--image Specify Linux kernel file name
--all List all entries (inc. from other systems)
--entry-keys Comma separated list of keys
--no-variables Do not update UEFI variables
--no-reuse-initrd Always regenerate initrd
--sync Synchronize (update, downgrade) the bootloader
--portable Handle bootloader on portable devices
(also --removable possible)
--secure-boot When installing the bootloader, use the shim
--only-default Only list the default entry
--default-snapshot [SNAPSHOT] refers to the default snapshot
--ask-key Ask recovery Key when initial enrollment
(or randomly generated)
--ask-pin Ask recovery PIN for re-enrollment
Ask TPM2 PIN when initial enrollment
--ask-pw Ask password when initial enrollment
--method "tpm2", "tpm2+pin", "fido2", "password", "recovery-key"
--signed-policy Use signed policy for TPM2 enrollment
--no-measure-pcr During enrollment, do not include PCR 15 protection
--measure-pcr Force update of PCR 15 prediction for LUKS2 volume key
Requires LUKS2 password to accessing the volume key
--pcr Comma seperated list of PCRs to enroll
--devices Comma separated list of devices to enroll or unenroll
By default all (not ignored) devices are [un]enrolled
-v, --verbose More verbose output
-h, --help This screen
COMMAND:
bootloader [SNAPSHOT]
Print the detected bootloader
add-kernel VERSION [SNAPSHOT]
Create boot entry for specified kernel
add-all-kernels [SNAPSHOT]
Create boot entries for all kernels in SNAPSHOT
mkinitrd [SNAPSHOT]
Create boot entries for all kernels in SNAPSHOT,
assumes --no-reuse-initrd to regenerate initrds
remove-kernel VERSION [SNAPSHOT]
Remove boot entry for specified kernel
remove-all-kernels [SNAPSHOT]
Remove boot entries for all kernels in SNAPSHOT
cleanup [SNAPSHOT]
Remove boot entries with missing kernels from SNAPSHOT
list-kernels [SNAPSHOT]
List all kernels related to SNAPSHOT
list-entries [SNAPSHOT]
List all entries related to SNAPSHOT
list-snapshots
List all snapshots
list-devices
List encrypted devices that are tracked
show-entry VERSION [SNAPSHOT]
Show fields for an entry with an specified kernel
version
update-entry VERSION [SNAPSHOT]
Update "options" field from /etc/kernel/cmdline
for an entry
update-all-entries [SNAPSHOT]
Update "options" field from /etc/kernel/cmdline
for all entries
set-default-snapshot [SNAPSHOT]
Make SNAPSHOT the default for next boot.
Also install all kernels if needed
is-bootable [SNAPSHOT]
Check whether SNAPSHOT has any kernels registered, ie
is potentially bootable
install
Install the bootloader and shim into ESP
needs-update
Check whether the bootloader in ESP needs updating
update
Update the bootloader in the ESP if a newer version
is available. Passing the --sync option will also
allow downgrades, ensuring that the version in the ESP
matches the one installed in the system.
force-update
Update the bootloader in any case
set-default ID
Set default boot loader entry
get-default
Get default boot loader entry
set-timeout SECONDS|menu-disabled|menu-hidden|menu-force
Set the menu timeout
menu-disabled or menu-hidden=0; menu-force=-1
get-timeout
Get the menu timeout in seconds
enroll
Enroll a TPM2 (+PIN), a FIDO2 key or a password for
all devices
unenroll
Unenroll a TPM2 (+PIN), a FIDO2 key or a password for
all devices
update-predictions
Update TPM2 predictions
Variables:
SYSTEMD_COLORS Set 0 to disable colored output
KEY Recovery key (initial enrollment; %u:sdbootutil)
PIN TPM2 PIN (initial enrollment; %u:sdbootutil)
PIN Recovery PIN (re-enrollment; %u:sdbootutil)
PW Password / Recovery Key (initial enrollment; %u:sdbootutil)
(%u:cryptenroll for changes via systemd-cryptenroll)
Misc:
Ignoring Devices A LUKS2 device can be un-tracked (ignored)
by sdbootutil if is present in /etc/crypttab
and has the "x-sdbootutil.ignore" option
EOF
exit 0
}
dbg()
{
[ "${verbose:-0}" -gt 1 ] || return 0
echo "DEBUG: $*" >&2
}
dbg_var()
{
[ "${verbose:-0}" -gt 1 ] || return 0
local v="${1:?}"
echo "DEBUG: $v: ${!v}" >&2
}
dbg_cat()
{
[ "${verbose:-0}" -gt 1 ] || return 0
[ ! -e "$1" ] || { echo "DEBUG: $1" >&2; cat "$1" >&2; }
}
info()
{
[ "${verbose:-0}" -gt 0 ] || return 0
echo "$@" >&2
}
warn()
{
echo "WARNING: $*" >&2
}
err()
{
echo "ERROR: $*" >&2
exit 1
}
is_config_file()
{
[ -e /usr/etc/default/fde-tools ] ||
[ -e /etc/default/fde-tools ] ||
[ -e /etc/sysconfig/fde-tools ]
}
load_config_file()
{
local f
# Some old installations have /etc/sysconfig/fde-tools
for f in /usr/etc/default/fde-tools /etc/default/fde-tools /etc/sysconfig/fde-tools; do
[ ! -e "$f" ] || {
# shellcheck disable=SC1090
. "$f"
info "Loading config file $f"
dbg_cat "$f"
}
done
}
is_secure_boot()
{
grep -q $'\x01' /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
}
is_sdboot()
{
local sdboot grub2_bls
sdboot="$(find_sdboot "${1-$root_snapshot}")"
grub2_bls="$(find_grub2_bls "${1-$root_snapshot}")"
# If boot loader is not found, then we check LOADER_TYPE, but
# if is not present and systemd-boot and grub2-bls are
# co-installed, we favor grub2-bls in the detection
if [ ! -e "$sdboot" ] && [ ! -e "$grub2_bls" ]; then
[ -z "$LOADER_TYPE" ] || [ "$LOADER_TYPE" = "systemd-boot" ]
else
[ -e "$sdboot" ] && [ ! -e "$grub2_bls" ]
fi
}
is_grub2_bls()
{
local sdboot grub2_bls
sdboot="$(find_sdboot "${1-$root_snapshot}")"
grub2_bls="$(find_grub2_bls "${1-$root_snapshot}")"
# If boot loader is not found, then we check LOADER_TYPE, but
# if is not present and systemd-boot and grub2-bls are
# co-installed, we favor grub2-bls in the detection
if [ ! -e "$sdboot" ] && [ ! -e "$grub2_bls" ]; then
[ -z "$LOADER_TYPE" ] || [ "$LOADER_TYPE" = "grub2-bls" ]
else
[ -e "$grub2_bls" ]
fi
}
reset_rollback()
{
for i in "${rollback[@]}"; do
[ -e "$i.bak" ] || continue
info "Removing $i.bak"
rm -f "$i.bak"
done
rollback=()
}
is_transactional()
{
findmnt --fstab / -O ro >/dev/null
}
keyctl_add_with_timeout()
{
local key="$1"
local value="$2"
info "Creating key $key with timeout"
# When we create a new key with sudo, we do not create a new
# login session, so the key is not accessible to the owner
# See https://mjg59.dreamwidth.org/37333.html
local keyid
keyid="$(echo -n "$value" | keyctl padd user "$key" @s)"
# Remove permission from the possesor. For some reason that I
# do not understand, this can fail in YaST when called via
# cheetah
keyctl setperm "$keyid" 0x003f0000 2> /dev/null || {
warn "Failed to change the timeout for $key"
return 0
}
keyctl timeout "$keyid" 120
keyctl link "$keyid" @u
# systemd tools are doing `keyctl request user "$key"`, and
# for some reason this means that the key must still be in the
# session keyring, so we cannot do `keyctl unlink "$keyid" @s`
}
ask_password()
{
local msg="$1"
local -n nameref_pw="$2"
read -r -s -p "$msg: " nameref_pw
echo >&2
}
ask_new_password()
{
local msg="$1"
local -n nameref_pw="$2"
local pw1 pw2
ask_password "New $msg" pw1
ask_password "Re-type $msg" pw2
while [ "$pw1" != "$pw2" ]; do
warn "Inputs did't match!"
ask_password "New $msg" pw1
ask_password "Re-type $msg" pw2
done
# shellcheck disable=SC2034
nameref_pw="$pw1"
}
subvol_is_ro()
{
[ -n "$have_snapshots" ] || return 0
local subvol="${1:?}"
while read -r line; do
[ "$line" = "ro=true" ] && return 0
done < <(btrfs prop get -t s "${subvol#"${subvol_prefix}"}" ro)
return 1
}
detect_parent()
{
local subvol="$1"
[ -n "$have_snapshots" ] || return 0
local parent_uuid
parent_uuid="$(btrfs subvol show "${subvol#"${subvol_prefix}"}" | sed -ne 's/\s*Parent UUID:\s*//p')"
[ "$parent_uuid" != '-' ] || return 0
local -a parent_subvol_uuid
# shellcheck disable=SC2207
parent_subvol_uuid=($(btrfs subvol show -u "$parent_uuid" "${subvol#"${subvol_prefix}"}" 2> /dev/null))
local btrfs_subvol_status=$?
[ "$btrfs_subvol_status" = 0 ] || return 0
parent_subvol="${parent_subvol_uuid[0]}"
parent_snapshot="${parent_subvol#"${subvol_prefix}"/.snapshots/}"
if [ "$parent_subvol" = "$parent_snapshot" ]; then
unset parent_subvol parent_snapshot
else
parent_snapshot="${parent_snapshot%/snapshot}"
fi
dbg_var "parent_subvol"
dbg_var "parent_snapshot"
}
sedrootflags()
{
local subvol="$1"
# - Delete everything before BOOT_IMAGE= and initrd=
# (see https://github.com/openSUSE/sdbootutil/issues/182)
# - Delete BOOT_IMAGE= and initrd=
# - Replace or add root= to refers to UUID or mapped device
# (if encrypted)
# - Replace or add rootflags to point at correct subvolume
# - Replace or add systemd.machine-id to match current
# machine-id
#
# From the sed manual:
# ‘t’
# branch conditionally (that is: jump to a label) _only
# if_ a ‘s///’ command has succeeded since the last input
# line was read or another conditional branch was taken.
#
# We use the t command to jump over an expression that appends
# a parameter if replacing the parameter succeeded (ie it was
# already there). Since we always operate on the same line,
# "empty" t jumps are used to reset the condition after very
# s///.
local root_param="UUID=$root_uuid"
[ -z "$root_device_is_crypt" ] || root_param="$root_device"
local sed_arguments=("-e s/[ \t]\+/ /g"
"-e s/^.*\(initrd=[^ ]*\|BOOT_IMAGE=[^ ]*\)\s*/\1 /"
"-e s/\<\(BOOT_IMAGE\|initrd\)=[^ ]* \?//"
"-e s/\$//;ta;:a"
"-e s,\<root=[^ ]*,root=$root_param,;tb;s,\$, root=$root_param,;tc;:c;:b")
[ -z "$have_snapshots" ] || sed_arguments+=("-e s,\<rootflags=subvol=[^ ]*,rootflags=subvol=$subvol,;td;s,\$, rootflags=subvol=$subvol,;te;:e;:d")
[ -z "$machine_id" ] || sed_arguments+=("-e s,\<systemd.machine_id=[^ ]*,systemd.machine_id=$machine_id,;tf;s,\$, systemd.machine_id=$machine_id,;tg;:g;:f")
sed "${sed_arguments[@]}"
}
entry_filter=("cat")
update_entries()
{
[ -z "$1" ] || entry_filter=("$@")
bootctl list --json=short | "${entry_filter[@]}" > "$entryfile"
dbg "Entry filter: ${entry_filter[*]}"
dbg_cat "$entryfile"
}
update_entries_for_subvol()
{
local subvol="$1"
local ext="${2:-}"
[ -z "$ext" ] || ext="|$ext"
update_entries jq "[.[]|select(has(\"options\"))|select(.options|test(\"root=(?:UUID=$root_uuid|$root_device) .*rootflags=subvol=$subvol\")$ext)]"
}
update_entries_for_snapshot()
{
local n="$1"
update_entries_for_subvol "${subvol_prefix}/.snapshots/$n/snapshot"
}
update_entries_for_snapshot_invert()
{
local n="$1"
update_entries_for_subvol "${subvol_prefix}/.snapshots/$n/snapshot" "not"
}
update_entries_for_this_system()
{
update_entries jq "[.[]|select(has(\"options\"))|select(.options|test(\"root=(?:UUID=$root_uuid|$root_device)\"))]"
}
entry_conf_file()
{
local kernel_version="${1:?}"
local snapshot="$2"
local tries="$3"
# GRUB2 with the BLS patches does not follow the expected
# ordering rules, using only rpmvercmp() with the entry
# filename. To provide an order we add a prefix ("system")
# for entries that are not RO, making it newer that any other
# entry name that start with the "snapper" prefix: ("system" >
# "snapper").
local prefix=""
local subvol=""
[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
if ! is_transactional && is_grub2_bls; then
if ! subvol_is_ro "$subvol"; then
prefix="system"
else
prefix="snapper"
fi
fi
echo "${prefix:+$prefix-}$entry_token-$kernel_version${snapshot:+-$snapshot}${tries:++$tries}.conf"
}
find_conf_file()
{
local kernel_version="${1:?}"
local snapshot="$2"
local id
id="$(entry_conf_file "$kernel_version" "$snapshot")"
update_entries_for_snapshot "$snapshot"
while IFS= read -r path; do
if [ -f "$path" ]; then
echo "$path"
return 0
fi
done < <(jq -r --arg id "$id" '.[] | select(.id == $id) | .path' < "$entryfile")
return 1
}
settle_entry_token()
{
local snapshot="$1"
set_os_release "$snapshot"
set_machine_id "$snapshot"
case "$arg_entry_token" in
""|auto)
if [ -s '/etc/kernel/entry-token' ]; then
read -r entry_token < '/etc/kernel/entry-token'
else
local var
for var in machine_id os_release_IMAGE_ID os_release_ID; do
entry_token="${!var}"
[ -z "$entry_token" ] || break
done
fi
;;
machine-id)
[ -n "$machine_id" ] || err "Couldn't determine machine-id"
entry_token="$machine_id"
;;
os-id)
# shellcheck disable=SC2154
entry_token="$os_release_ID"
[ -n "$entry_token" ] || err "Missing ID"
;;
os-image)
# shellcheck disable=SC2154
entry_token="$os_release_IMAGE_ID"
[ -n "$entry_token" ] || err "Missing IMAGE_ID"
;;
literal:*)
entry_token="${arg_entry_token#literal:}"
;;
*) err "Unexpected parameter for --entry-token=: $arg_entry_token" ;;
esac
[ -n "$entry_token" ] || err "Can't determine entry-token"
dbg_var "entry_token"
return 0
}
remove_kernel()
{
local snapshot="$1"
local kernel_version="$2"
[ -n "$kernel_version" ] || err "Missing kernel version"
info "Removing kernel $kernel_version"
dbg_var "snapshot"
settle_entry_token "${snapshot}"
local id
id="$(entry_conf_file "$kernel_version" "$snapshot")"
info "Removing boot entry $id"
bootctl unlink "$id"
# This action will require to update the PCR predictions
update_predictions=1
}
install_with_rollback()
{
local src="${1:?}"
local dst="${2:?}"
if [ -e "$dst" ]; then
if cmp -s "$src" "$dst"; then
info "$dst unchanged"
return 0
fi
mv "$dst" "$dst.bak" || return "$?"
fi
rollback+=("$dst")
install -p -m 0644 "$src" "$dst" || return "$?"
chown root:root "$dst" 2>/dev/null || :
info "Installed $dst"
}
update_snapper()
{
snapper --jsonout --no-dbus list --disable-used-space > "$snapperfile"
dbg_cat "$snapperfile"
}
set_snapper_title_and_sortkey()
{
[ -n "$have_snapshots" ] || return 0
snapshot="${1:?}"
local type date desc important pre_num
local snapshot_info
[ -s "$snapperfile" ] || update_snapper
# shellcheck disable=SC2046
IFS="|" read -r type date desc important pre_num <<<\
$(jq -r --arg snapshot "$snapshot" \
'.["root"][]|select(.number==($snapshot|tonumber))|[.type,.date,(.description|gsub("\\|";"_")),.userdata.important,."pre-number"//""]|join("|")'\
< "$snapperfile")
if [ -z "$desc" ] && [ "$type" = "post" ] && [ -n "$pre_num" ]; then
read -r desc <<<"$(jq -r --arg snapshot "$pre_num" '.["root"][]|select(.number==($snapshot|tonumber))|.description' < "$snapperfile")"
fi
if [ "$important" = "yes" ]; then important="*"; else important=""; fi
[ "$type" = "single" ] && type=""
snapshot_info="$snapshot,$kernel_version,$date${type:+, $type}${desc:+, $desc}"
# shellcheck disable=SC2154
title="Snapper: ${important}$title ($snapshot_info)"
sort_key="snapper-$sort_key"
}
set_os_release()
{
local snapshot="$1"
local subvol=""
[ -z "$snapshot" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
os_release_files=(
"${subvol#"${subvol_prefix}"}/usr/lib/os-release"
"${subvol#"${subvol_prefix}"}/etc/os-release"
)
for file in "${os_release_files[@]}"; do
[ -f "$file" ] || continue
eval "$(sed -ne '/^[A-Z_]\+=/s/^/os_release_/p' < "$file")"
break
done
}
set_machine_id()
{
local snapshot="$1"
local subvol=""
[ -z "$snapshot" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
machine_id_files=()
if is_transactional && [ -z "$TRANSACTIONAL_UPDATE" ]; then
[ -n "$snapshot" ] && machine_id_files+=("/var/lib/overlay/$snapshot/etc/machine-id")
fi
machine_id_files+=("${subvol#"${subvol_prefix}"}/etc/machine-id")
for file in "${machine_id_files[@]}"; do
if [ -s "$file" ]; then
read -r machine_id < "$file"
break
fi
done
}
reuse_initrd()
{
local snapshot="$1"
local subvol="$2"
local kernel_version="${3:?}"
local conf
[ -z "$arg_no_reuse_initrd" ] || return 1
settle_entry_token "${snapshot}"
conf="$(find_conf_file "$kernel_version" "${snapshot}")"
local find_conf_status=$?
if [ $find_conf_status -ne 0 ]; then
# check if we can reuse the initrd from the parent
# to avoid expensive regeneration
detect_parent "$subvol"
if [ -n "$parent_subvol" ]; then
settle_entry_token "$parent_snapshot"
conf="$(find_conf_file "$kernel_version" "$parent_snapshot")"
find_conf_status=$?
fi
fi
if [ "$find_conf_status" -eq 0 ]; then
local k v
while read -r k v; do
[ "$k" = 'initrd' ] || continue
[ -f "${boot_root}$v" ] || continue
info "Found existing initrd $v"
dstinitrd+=("$v")
done < "$conf"
[ "${#dstinitrd[@]}" -eq 0 ] || return 0
fi
return 1
}
mount_chroot()
{
local snapshot_dir="$1"
# We include the rootfs (the first line usually), as is needed
# to appear in the mounts under the chroot, allowing dracut to
# properly detect the fs type and load the relevant module.
findmnt -o TARGET,FSTYPE -Rv --pairs / > "$tmpdir/mounts"
mount --bind "$snapshot_dir" "$snapshot_dir"
while read -r line; do
eval "$line"
# shellcheck disable=SC2153
[ "$FSTYPE" = "btrfs" ] || [ "$FSTYPE" = "vfat" ] || [ "$FSTYPE" = "xfs" ] || [[ "$FSTYPE" == ext* ]] || continue
[ "$TARGET" != "/" ] || continue
[ "$TARGET" = "/etc" ] && [ "$FSTYPE" = "btrfs" ] && continue
[[ "$TARGET" != /.snapshots* ]] || continue
mountpoint --quiet "$snapshot_dir$TARGET" || mount --bind "$TARGET" "$snapshot_dir$TARGET"
done < "$tmpdir/mounts"
rm "$tmpdir/mounts"
mount -t tmpfs -o size=10m tmpfs "$snapshot_dir/run"
if [ -e /run/systemd/journal ]; then
mkdir -p "$snapshot_dir/run/systemd/journal"
mount --bind /run/systemd/journal "$snapshot_dir/run/systemd/journal"
fi
for i in proc dev sys tmp; do
mount --bind "/$i" "$snapshot_dir/$i"
done
chroot_dir="$snapshot_dir"
}
umount_chroot()
{
local snapshot_dir="$1"
umount -R "$snapshot_dir"
chroot_dir=
}
mount_etc()
{
local snapshot_dir="$1"
# Don't mount if we are within a transactional-update shell
[ -z "$TRANSACTIONAL_UPDATE" ] || return 0
# Only overlayfs needs special treatment
[ "$(findmnt --tab-file "${snapshot_dir}/etc/fstab" --noheadings --nofsroot --output FSTYPE /etc)" = "overlay" ] || return 0
IFS=',' read -ra fields <<<\
"$(findmnt --tab-file "${snapshot_dir}/etc/fstab" --noheadings --nofsroot --output OPTIONS /etc | sed 's#/sysroot##g' | sed 's#:/etc,#:'"${snapshot_dir}"'/etc,#g')"
local lower=""
local upper=""
for element in "${fields[@]}"; do
IFS='=' read -r key value <<<"$element"
[ "$key" = "lowerdir" ] && lower="$value"
[ "$key" = "upperdir" ] && upper="$value"
done
mount overlay -t overlay -o ro,"lowerdir=${upper}:${lower}" "${snapshot_dir}/etc"
}
add_version_to_title()
{
# TW pretty name does not include the version
# shellcheck disable=SC2154
[ -n "$os_release_VERSION" ] || title="$title $os_release_VERSION_ID"
}
add_kernel_version_to_title()
{
# grub2-bls does not show the `version` field
title="$title ($snapshot@$kernel_version)"
}
pending_kernel_size()
{
echo $(($(stat -c %s "$1") / 1024 + 1))
}
pending_initrds_size()
{
local size=0
local i=0
while [ -e "$1/initrd-$i" ]; do
size=$((size + $(stat -c %s "$1/initrd-$i")))
((++i))
done
echo $((size / 1024 + 1))
}
boot_free_space()
{
echo $(($(findmnt -n -b -o AVAIL --target "$boot_root" | head -n 1) / 1024))
}
regex_snapshot_ids_for_free_space()
{
local snapshot="$1"
[ -s "$snapperfile" ] || update_snapper
# Select the default and the active snapshots. Also include
# one extra snapshot if it is passed as a parameter, that
# refers to the snapshot from where we are installing the
# kernel
declare -A snapshots
[ -z "$snapshot" ] || snapshots["$snapshot"]=1
[ -z "$root_snapshot" ] || snapshots["$root_snapshot"]=1
local id
while read -r id; do
snapshots[$id]=1
done < <(jq -r '.root[]|select(.active==true or .default==true)|.number' "$snapperfile")
local re
if [ "${#snapshots[@]}" = 1 ]; then
re="${!snapshots[*]}"
else
IFS='|' eval re='"(:?${!snapshots[*]})"'
fi
echo "$re"
}
select_entries_for_free_space()
{
local snapshot="$1"
# Extend the entry file with a "priority" field, based on the
# ordered snapshot IDs, and the "kernel" field, based on the
# kernel version. Note that the textual suffix (-default,
# -slowroll) is removed and does not participate in the
# ordering
#
# The final list is a join of two list. One that contains all
# the entries that belong to the safe-to-delete snapshots, and
# another one that contain the entries from the default
# snapshot, that is not the default entry. This last sub-list
# has the higher priority value, so when sorted get the last
# order
#
# Expected order for removal:
#
# MicroOS
# - The default / active snapshot is always the last one,
# except when we do a rollback
# - The default boot entry should never be removed
# - The removal order is sort(snapshot_id, kernel), except
# for entries that belong to the default snapshot. Those
# should be removed as a last resort
# - If there are snapshots after the default and active one,
# they are remainigs from a rollback, and they can be
# removed in the same order (ideally early)
#
# Tumbleweed
# - The default / active snapshot is always the first one,
# except when we do a rollback
# - The default boot entry should never be removed
# - The removal order is sort(snapshot_id, kernel), the same
# as in MicroOS
# - If there are snapshot before the default and active one,
# they are remainings from a rollback and they can be
# removed in the same order
# Select the entries that are safe to remove, i.e. the ones
# that are not the default, the active nor the one from where
# we are extracting the kernel (that usually is the default or
# the active one)
update_entries_for_snapshot_invert "$(regex_snapshot_ids_for_free_space "$snapshot")"
jq 'map(. + {"priority": .version | scan("(\\d+)@") | .[] | tonumber, "kernel": .version | scan(".*@(?:(\\d+).(\\d+).(\\d+)-(\\d+))") | map(. | tonumber)})' < "$entryfile" > "${entryfile}.ext-1"
# The name is confusing. The maximum priority is the minimal
# one once is sorted by priority
local max_priority
read -r max_priority <<<"$(jq -r 'max_by(.priority)|.priority' < "${entryfile}.ext-1")"
((++max_priority))
# From the no-safe ones, we can remove some entries from the
# default entry (the active is the one under development in
# MicroOS)
update_entries_for_snapshot "$root_snapshot"
jq --arg max_priority "$max_priority" 'map(. + {"priority": $max_priority | tonumber, "kernel": .version | scan(".*@(?:(\\d+).(\\d+).(\\d+)-(\\d+))") | map(. | tonumber)})' < "$entryfile" > "${entryfile}.ext-2"
# The default boot entry should be in the second list, but
# puting the filter here guarantees that it will not appears
# in the final list
jq --slurp 'add|[.[]|select(.isDefault==false)]' "${entryfile}.ext-1" "${entryfile}.ext-2" > "$entryfile"
dbg "Added priority and kernel version to entry file (for free space)"
dbg_cat "$entryfile"
}
make_free_space()
{
local snapshot="$1"
local required_size="$2"
info "Required free space in ESP: $required_size KB"
# If there is already free space, shortcut the code
free_space="$(boot_free_space)"
[ "$required_size" -gt "$free_space" ] || return 0
# "Cleaning /boot/efi" message is presented via stderr
dbg "Calling bootctl cleanup"
bootctl -q cleanup 2> /dev/null
select_entries_for_free_space "$snapshot"
local id
while read -r id; do
free_space="$(boot_free_space)"
dbg "Free space in the ESP: $free_space KB"
if [ "$required_size" -gt "$free_space" ]; then
info "Removing boot entry $id"
bootctl unlink "$id"
else
return 0
fi
done < <(jq -r 'sort_by(.priority, .kernel) | .[] | .id' < "$entryfile")
free_space="$(boot_free_space)"
dbg "Free space in the ESP after deallocation: $free_space KB"
[ "$required_size" -lt "$free_space" ]
}
make_free_space_for_kernel()
{
local snapshot="$1"
# Calculate the free space and the required size. All sizes
# are in Kb to avoid big numbers
local free_space total_size
total_size=$(($(pending_kernel_size "$src") + $(pending_initrds_size "$tmpdir")))
make_free_space "$snapshot" "$total_size"
}
create_boot_options() {
local subvol="$1"
local boot_options=
for i in "${subvol:1}/etc/kernel/cmdline" "${subvol:1}/usr/lib/kernel/cmdline" /proc/cmdline; do
[ -f "$i" ] || continue
dbg_cat "$i"
boot_options="$(sedrootflags "$subvol" < "$i")"
break
done
echo "$boot_options"
}
install_kernel()
{
local snapshot="$1"
local subvol=""
[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
local kernel_version="$2"
local dstinitrd=()
local src="${subvol#"${subvol_prefix}"}/lib/modules/$kernel_version/$image"
local initrddir="${subvol#"${subvol_prefix}"}/usr/lib/initrd"
[ -n "$kernel_version" ] || err "Missing kernel version"
[ -e "$src" ] || err "Can't find $src"
info "Installing kernel $kernel_version"
dbg_var "snapshot"
calc_chksum "$src"
settle_entry_token "${snapshot}"
local dst="/$entry_token/$kernel_version/linux-$chksum"
local initrd="${src%/*}/initrd"
mkdir -p "$boot_root${dst%/*}"
if [ -e "$initrd" ]; then
ln -s "$initrd" "$tmpdir/initrd-0"
elif [ -d "$initrddir" ] && [ -x "/usr/bin/mkmoduleinitrd" ]; then
local f i
i=0
for f in "$initrddir"/*; do
ln -s "$f" "$tmpdir/initrd-$i"
((++i))
done
/usr/bin/mkmoduleinitrd "${subvol#"${subvol_prefix}"}" "$kernel_version" "$tmpdir/initrd-$i"
elif ! reuse_initrd "$snapshot" "$subvol" "$kernel_version"; then
local snapshot_dir="/.snapshots/$snapshot/snapshot"
local dracut_args=(
'--reproducible'
'--force'
'--tmpdir' '/var/tmp'
)
if [ "${verbose:-0}" -le 1 ]; then
dracut_args+=('--quiet')
fi
info "Generating new initrd"
if [ "$subvol" != "$root_subvol" ] && [ -n "$have_snapshots" ]; then
mount_chroot "${snapshot_dir}"
# In MicroOS we need to be sure to have the same /etc
# inside the snapshot. For example, /etc/crypttab can
# have modifications in the overlay that will be
# visible once the snapshot is active, but the version
# in /.snapshots is still the unmodified base
is_transactional && mount_etc "${snapshot_dir}"
chroot "${snapshot_dir}" dracut "${dracut_args[@]}" "$tmpdir/initrd-0" "$kernel_version"
umount_chroot "${snapshot_dir}"
else
dracut "${dracut_args[@]}" "$tmpdir/initrd-0" "$kernel_version"
fi
fi
make_free_space_for_kernel "$snapshot" || err "No free space in $boot_root for new kernel"
local boot_options
boot_options="$(create_boot_options "$subvol")"
if [ "${#dstinitrd[@]}" -eq 0 ] && [ -e "$tmpdir/initrd-0" ]; then
i=0
while [ -e "$tmpdir/initrd-$i" ]; do
calc_chksum "$tmpdir/initrd-$i"
dstinitrd+=("${dst%/*}/initrd-$chksum")
((++i))
done
fi
title="${os_release_PRETTY_NAME:-Linux $kernel_version}"
# shellcheck disable=SC2154
sort_key="$os_release_ID"
add_version_to_title
if ! is_transactional && subvol_is_ro "$subvol"; then
set_snapper_title_and_sortkey "$snapshot"
elif is_grub2_bls; then
add_kernel_version_to_title
fi
local entry_machine_id=
[ "$entry_token" = "$machine_id" ] && entry_machine_id="$machine_id"
cat > "$tmpdir/entry.conf" <<-EOF
# Boot Loader Specification type#1 entry
title $title
version $snapshot@$kernel_version${entry_machine_id:+${nl}machine-id $entry_machine_id}${sort_key:+${nl}sort-key $sort_key}
options $boot_options
linux $dst
EOF
for i in "${dstinitrd[@]}"; do
echo "initrd $i" >> "$tmpdir/entry.conf"
done
dbg "Generated new boot entry"
dbg_cat "$tmpdir/entry.conf"
local failed=
if [ ! -e "$boot_root$dst" ]; then
install_with_rollback "$src" "$boot_root$dst" || failed=kernel
else
info "Reusing $boot_root$dst"
fi
if [ -z "$failed" ] && [ -e "$tmpdir/initrd-0" ]; then
i=0
while [ -e "$tmpdir/initrd-$i" ]; do
if [ ! -e "$boot_root${dstinitrd[$i]}" ]; then
install_with_rollback "$tmpdir/initrd-$i" "$boot_root${dstinitrd[$i]}" || { failed=initrd; break; }
rm -f "$tmpdir/initrd-$i"
fi
((++i))
done
fi
if [ -z "$failed" ]; then
local tries
if [ -f /etc/kernel/tries ]; then
read -r tries < /etc/kernel/tries
fi
if ! [[ "$tries" =~ ^[0-9]+$ ]]; then
tries=
fi
loader_entry="$boot_root/loader/entries/$(entry_conf_file "$kernel_version" "$snapshot" "$tries")"
install_with_rollback "$tmpdir/entry.conf" "$loader_entry" || failed="bootloader entry"
rm -f "$tmpdir/entry.conf"
fi
[ -z "$failed" ] || err "Failed to install $failed"
reset_rollback
# Do a final cleanup, as sometimes we are replacing an old
# initrd
bootctl -q cleanup 2> /dev/null
# This action will require to update the PCR predictions
update_predictions=1
}
install_all_kernels()
{
local snapshot="$1"
info "Installing all kernels"
dbg_var "snapshot"
dbg_var "arg_no_reuse_initrd"
find_kernels "$snapshot"
for kv in "${!found_kernels[@]}"; do
install_kernel "${snapshot}" "$kv"
done
}
remove_all_kernels()
{
local snapshot="$1"
info "Removing all kernels"
dbg_var "snapshot"
find_kernels "$snapshot"
for kv in "${!found_kernels[@]}"; do
remove_kernel "${snapshot}" "$kv"
done
}
cleanup_entries()
{
info "Cleaning up boot entries"
if [ ! -s "$entryfile" ]; then
if [ -n "$1" ]; then
update_entries_for_snapshot "$1"
else
update_entries_for_this_system
fi
fi
local id path snapshot kernel_version subvol src
while read -r id; do
read -r path
read -r snapshot
read -r kernel_version
subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
src="${subvol#"${subvol_prefix}"}/lib/modules/$kernel_version/$image"
[ -e "$src" ] || {
info "Cleaning boot entry $id"
rm "$path"
}
done < <(jq -r '.[] | .id, .path, (.version | scan("(.*)@(.*)") | .[])' "$entryfile")
dbg "Calling bootctl cleanup"
bootctl -q cleanup 2> /dev/null
}
list_entries()
{
info "Listing boot entries"
if [ ! -s "$entryfile" ]; then
if [ -n "$1" ]; then
update_entries_for_snapshot "$1"
elif [ -n "$arg_all_entries" ]; then
update_entries
else
update_entries_for_this_system
fi
fi
local isdefault isreported type id root conf title
while read -r isdefault isreported type id root conf title; do
color=
if [ "$isdefault" = "true" ]; then
color="$color_bu"
elif [ -n "$arg_only_default" ]; then
continue
fi
if [ "$isreported" = "false" ]; then
color="$color${color_green}"
fi
if [ "$type" = "loader" ]; then
color="$color${color_yellow}"
fi
local errors=()
if [ -n "$verbose" ] && [ -n "$conf" ] && [ -e "$conf" ]; then
local k
local v
while read -r k v; do
if [ "$k" = 'linux' ] || [ "$k" = 'initrd' ]; then
if [ ! -e "$root$v" ]; then
errors+=("$root/$v does not exist")
fi
fi
[ -n "$have_snapshots" ] || break
if [ "$k" = 'options' ]; then
local snapshot
# shellcheck disable=SC2001
read -r snapshot <<<"$(echo "$v" | sed -e "s,.*rootflags=subvol=${subvol_prefix}/.snapshots/\([0-9]\+\)/snapshot.*,\1,")"
if [ ! -d "/.snapshots/$snapshot/snapshot" ]; then
errors+=("/.snapshot/$snapshot/snapshot does not exist")
fi
fi
done < "$conf"
fi
if [ "${#errors[@]}" -gt 0 ]; then
echo -e " ${color_red}${errors[*]}${color_end}" >&2
fi
echo -e "$color$id${verbose:+: $title}${color_end}"
done < <(jq '.[]|[.isDefault, if has("isReported") then .isReported else 0 end, if has("type") then .type else "unknown" end, .id, .root, .path, .showTitle]|join(" ")' -r < "$entryfile")
}
show_entry_fields()
{
local snapshot="$1"
local kernel_version="$2"
[ -n "$kernel_version" ] || err "Missing kernel version"
settle_entry_token "${snapshot}"
local id
id="$(entry_conf_file "$kernel_version" "$snapshot")"
local conf
conf="$(find_conf_file "$kernel_version" "$snapshot")"
[ -z "$verbose" ] || echo -e "ID\t$id"
local k
local v
while read -r k v; do
case "$k" in
title|version|sort-key|options|linux|initrd) ;;
*) continue ;;
esac
if [ "${#arg_entry_keys[@]}" -eq 0 ] || [[ ${arg_entry_keys[*]} == *"all"* ]] || [[ ${arg_entry_keys[*]} == *"$k"* ]]; then
echo -e "$k\t$v"
fi
done < "$conf"
}
update_entry_conf()
{
local conf="$1"
local snapshot="$2"
local subvol=""
[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
local boot_options
boot_options="$(create_boot_options "$subvol")"
cp "$conf" "$tmpdir/entry.conf"
sed -i "s|^options\s*.*$|options $boot_options|g" "$tmpdir/entry.conf"
cp "$tmpdir/entry.conf" "$conf"
}
update_entry()
{
local snapshot="$1"
local kernel_version="$2"
settle_entry_token "${snapshot}"
local id
id="$(entry_conf_file "$kernel_version" "$snapshot")"
local conf
conf="$(find_conf_file "$kernel_version" "$snapshot")"
[ -f "$conf" ] || return 0
info "Updating boot entry $id"
update_entry_conf "$conf" "$snapshot"
# This action will require to update the PCR predictions
update_predictions=1
}
update_all_entries()
{
local snapshot="$1"
make_free_space "$snapshot" 1024
update_entries_for_snapshot "$1"
while read -r conf; do
update_entry_conf "$conf" "$snapshot"
done < <(jq -r '.[]|.path' < "$entryfile")
# This action will require to update the PCR predictions
update_predictions=1
}
list_snapshots()
{
[ -n "$have_snapshots" ] || { info "System does not support snapshots."; return 0; }
[ -s "$snapperfile" ] || update_snapper 2>"$tmpfile" || err "$(cat "$tmpfile")"
info "Listing snapshots"
local n=0
while read -r n isdefault title; do
[ "$n" != "0" ] || continue
local id="$n"
if [ "$isdefault" = "true" ]; then
id="$color_bu$id$color_end"
fi
update_kernels "$n"
[ "$is_bootable" = 1 ] || id="!$id"
echo -e "$id $title"
done < <(jq '.root|.[]|[.number, .default, .description]|join(" ")' -r < "$snapperfile")
}
calc_chksum()
{
# shellcheck disable=SC2046
set -- $(sha1sum "$1")
chksum="$1"
}
# map with kernel version as key and checksum as value
declare -A found_kernels
find_kernels()
{
local subvol=""
[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${1:?}/snapshot"
local fn kv
found_kernels=()
for fn in "${subvol#"${subvol_prefix}"}"/usr/lib/modules/*/"$image"; do
kv="${fn%/*}"
kv="${kv##*/}"
calc_chksum "$fn"
found_kernels["$kv"]="$chksum"
info "Found kernel $kv = $chksum"
done
}
# Map that uses expected path on the ESP for each installed kernel as
# key. The value is the entry id if an entry exists.
declare -A installed_kernels
# Map of ESP path to id of kernels that are not in the subvol
declare -A stale_kernels
is_bootable=
update_kernels()
{
local snapshot="$1"
local path id
installed_kernels=()
stale_kernels=()
is_bootable=
find_kernels "$snapshot"
settle_entry_token "${snapshot}"
for kv in "${!found_kernels[@]}"; do
installed_kernels["/$entry_token/$kv/linux-${found_kernels[$kv]}"]=''
done
update_entries_for_snapshot "$snapshot"
# XXX: maybe we should parse the actual path in the entry
while read -r path id; do
if [ "${installed_kernels[$path]+none}" = 'none' ]; then
installed_kernels["$path"]="$id"
is_bootable=1
else
# kernel in ESP that is not installed
stale_kernels["$path"]="$id"
fi
done < <(jq -r '.[]|select(has("linux"))|[.linux,.id]|join(" ")'< "$entryfile")
}
list_kernels()
{
local snapshot=""
[ -z "$have_snapshots" ] || snapshot="${1:?}"
info "Listing kernels"
update_kernels "$snapshot"
local kernelfiles=("${!installed_kernels[@]}")
for k in "${kernelfiles[@]}"; do
local id="${installed_kernels[$k]}"
local kv="${k%/*}"
kv="${kv##*/}"
if [ -z "$id" ]; then
echo -e "${color_yellow}missing /lib/modules/$kv/$image${color_end}"
else
echo -e "${color_green}ok /lib/modules/$kv/$image -> $id${color_end}"
fi
done
kernelfiles=("${!stale_kernels[@]}")
for k in "${kernelfiles[@]}"; do
local id="${stale_kernels[$k]}"
printf "${color_red}stale %s${color_end}\n" "$id"
done
}
list_devices()
{
info "Listing devices"
detect_tracked_devices
for dev in "${tracked_devices[@]}"; do
echo "$dev"
done
}
is_shim_installed()
{
# Check if shim is installed in the ESP
# In installed systems there should be a shim.efi
[ ! -f "${boot_root}${boot_dst}/shim.efi" ] || return 0
# In removable media there is only BOOT${arch}.EFI
! grep -q "UEFI SHIM" "${boot_root}/EFI/BOOT/BOOT${firmware_arch^^}.EFI" || return 0
return 1
}
is_bootable()
{
local snapshot="$1"
update_kernels "$snapshot"
[ "$is_bootable" = 1 ] || return 1
return 0
}
bootloader_version()
{
local fn="$1"
if [ -z "$1" ]; then
if [ -e "$shimdir/shim.efi" ]; then
fn="$boot_root$boot_dst/grub.efi"
else
local bootloader
bootloader="$(find_bootloader)"
fn="$boot_root$boot_dst/${bootloader##*/}"
fi
fi
[ -e "$fn" ] || return 1
if is_sdboot; then
read -r _ _ _ v _ < <(grep -ao '#### LoaderInfo: systemd-boot [^#]\+ ####' "$fn")
else
# Useless as it reports mayor.minor, so append the
# last update time until the minutes, as the FAT store
# dates differently than other filesystems
read -r _ _ _ v _ < <(grep -aoP 'GNU GRUB version %s\x00[^\x00]+\x00' "$fn")
v="${v:2}-$(date -r "$fn" +'%Y%m%d%H%M')"
fi
[ -n "$v" ] || return 1
dbg "Bootloader version $v"
echo "$v"
}
is_installed()
{
info "Checking if the bootloader is installed"
bootloader_version > /dev/null && [ -e "$boot_root/$boot_dst/installed_by_sdbootutil" ]
}
find_sdboot()
{
local prefix=""
[ -z "$have_snapshots" ] || prefix="/.snapshots/${1-$root_snapshot}/snapshot"
# XXX: this is a hack in case we need to inject a signed
# systemd-boot from a separate package
local sdboot="$prefix/usr/lib/systemd-boot/systemd-boot$firmware_arch.efi"
[ -e "$sdboot" ] || sdboot="$prefix/usr/lib/systemd/boot/efi/systemd-boot$firmware_arch.efi"
echo "$sdboot"
}
find_grub2_bls()
{
local prefix=""
[ -z "$have_snapshots" ] || prefix="/.snapshots/${1-$root_snapshot}/snapshot"
local grub2_arch
grub2_arch="$(uname -m)"
case "$grub2_arch" in
i[[3456]]86) grub2_arch=i386 ;;
x86_64) grub2_arch=x86_64 ;;
amd64) grub2_arch=x86_64 ;;
sparc) grub2_arch=sparc64 ;;
mipsel|mips64el) grub2_arch=mipsel ;;
mips|mips64) grub2_arch=mips ;;
arm*) grub2_arch=arm ;;
aarch64*) grub2_arch=arm64 ;;
loongarch64) grub2_arch=loongarch64 ;;
riscv32*) grub2_arch=riscv32 ;;
riscv64*) grub2_arch=riscv64 ;;
esac
# The old grub.efi will contain the BLS patches, but we cannot
# use it because we also dropped the process of creating the
# configuration file and installing bli.mod
echo "$prefix/usr/share/grub2/${grub2_arch}-efi/grubbls.efi"
}
find_bootloader()
{
if is_sdboot "${1-$root_snapshot}"; then
find_sdboot "${1-$root_snapshot}"
elif is_grub2_bls "${1-$root_snapshot}"; then
find_grub2_bls "${1-$root_snapshot}"
else
err "Bootloader not detected"
fi
}
bootloader_needs_update()
{
local prefix=""
local snapshot=""
if [ -n "$have_snapshots" ]; then
snapshot="${1-$root_snapshot}"
prefix="/.snapshots/${snapshot}/snapshot"
fi
info "Checking if bootloader needs update"
local bldr_name
local v nv
v="$(bootloader_version)"
[ -n "$v" ] || return 1
info "Deployed version $v"
nv="$(bootloader_version "$(find_bootloader "$snapshot")")"
[ -n "$nv" ] || return 1
info "System version $nv"
systemd-analyze compare-versions "$v" "$nv" 2>/dev/null
local status="$?"
bldr_name=$(bootloader_name "$snapshot")
if [ "$status" = "11" ]; then
info "$bldr_name is newer than system bootloader"
return 2
elif [ "$status" = "12" ]; then
info "$bldr_name needs to be updated"
return 0
fi
info "$bldr_name is already up-to-date"
return 1
}
install_bootloader()
{
local snapshot=""
local prefix=""
if [ -n "$have_snapshots" ]; then
snapshot="${1:-$root_snapshot}"
prefix="/.snapshots/${root_snapshot}/snapshot"
fi
info "Installing bootloader"
dbg_var "$snapshot"
local bootloader bldr_name blkpart drive partno
settle_entry_token "${snapshot}"
bootloader=$(find_bootloader "$snapshot")
bldr_name=$(bootloader_name "$snapshot")
dbg_var "bootloader"
dbg_var "bldr_name"
mkdir -p "$boot_root/loader/entries"
# The ESP presence was checked early
blkpart="$(findmnt -nvo SOURCE "$boot_root")"
[ -L "/sys/class/block/${blkpart##*/}" ] || err "$blkpart is not a partition"
drive="$(readlink -f "/sys/class/block/${blkpart##*/}")"
drive="${drive%/*}"
drive="/dev/${drive##*/}"
read -r partno < "/sys/class/block/${blkpart##*/}"/partition
if [ -n "$arg_secure_boot" ] && [ ! -e "$prefix$shimdir/shim.efi" ]; then
warn "A secure boot installation cannot be done. The shim package is not installed"
fi
if [ -n "$arg_secure_boot" ] && [ -e "$prefix$shimdir/shim.efi" ]; then
info "Installing $bldr_name with shim into $boot_root"
entry="$boot_dst/shim.efi"
for i in MokManager shim; do
[ -n "$arg_portable" ] || install -p -D "$prefix$shimdir/$i.efi" "$boot_root$boot_dst/$i.efi"
done
install -p -D "$bootloader" "$boot_root$boot_dst/grub.efi"
# boot entry point
install -p -D "$prefix$shimdir/MokManager.efi" "$boot_root/EFI/BOOT/MokManager.efi"
[ -n "$arg_portable" ] || install -p -D "$prefix$shimdir/fallback.efi" "$boot_root/EFI/BOOT/fallback.efi"
install -p -D "$prefix$shimdir/shim.efi" "$boot_root/EFI/BOOT/BOOT${firmware_arch^^}.EFI"
else
info "Installing $bldr_name without shim into $boot_root"
entry="$boot_dst/${bootloader##*/}"
[ -n "$arg_portable" ] || install -p -D "$bootloader" "$boot_root$entry"
install -p -D "$bootloader" "$boot_root/EFI/BOOT/BOOT${firmware_arch^^}.EFI"
fi
# shellcheck disable=SC2154
[ -n "${os_release_NAME}" ] || set_os_release "${snapshot}"
local split
IFS=" " read -r -a split <<<"${os_release_NAME}"
local boot_manager
boot_manager="${split[0]} Boot Manager"
# This is for shim to create the entry if missing
[ -n "$arg_portable" ] || echo "${entry##*/},$boot_manager" | { echo -ne "\xff\xfe"; iconv -f ascii -t ucs-2le; } > "$boot_root$boot_dst/boot.csv"
mkdir -p "$boot_root/$entry_token"
echo "$entry_token" > "$boot_root$boot_dst/installed_by_sdbootutil"
mkdir -p "/etc/kernel"
[ -s /etc/kernel/entry-token ] || echo "$entry_token" > /etc/kernel/entry-token
update_random_seed
if is_sdboot "$snapshot"; then
[ -s "$boot_root/loader/entries.srel" ] || echo type1 > "$boot_root/loader/entries.srel"
[ -e "$boot_root/loader/loader.conf" ] || echo -e "#timeout 3\n#console-mode keep\n" > "$boot_root/loader/loader.conf"
fi
# Create boot menu entry if it does not exist
local escaped_entry="${entry//\//\\\\}"
[ -n "$arg_no_variables" ] || [ -n "$arg_portable" ] || efibootmgr | grep -q "Boot.*${boot_manager}.*${escaped_entry}" || efibootmgr -q --create --disk "$drive" --part "$partno" --label "${boot_manager} ($bldr_name)" --loader "$entry" || true
# Make it the first option
if [ -z "$arg_no_variables" ] && [ -z "$arg_portable" ]; then
local boot_order
boot_order="$(efibootmgr | grep BootOrder)"
boot_order="${boot_order#BootOrder: }"
local boot_entry
boot_entry="$(efibootmgr | grep "Boot.*${boot_manager}.*${escaped_entry}")"
boot_entry="${boot_entry%\* *}"
boot_entry="${boot_entry#Boot}"
efibootmgr -q -D -o "$boot_entry,$boot_order" || true
fi
# This action will require to update the PCR predictions
update_predictions=1
}
bootloader_update()
{
local status=0
info "Updating bootloader"
bootloader_needs_update "${1:-$root_snapshot}" || status=$?
if [ $status -eq 0 ]; then
info "The bootloader needs to be updated"
install_bootloader "${1:-$root_snapshot}"
elif [ -n "$arg_sync" ] && [ $status -eq 2 ]; then
info "The bootloader will be downgraded"
install_bootloader "${1:-$root_snapshot}"
fi
}
hex_to_binary()
{
local s="$1"
local i
for ((i=0;i<${#s};i+=2)); do echo -ne "\x${s:$i:2}"; done
}
update_random_seed()
{
[ -z "$arg_no_random_seed" ] || return 0
local s _p
read -r s _p < <({ dd if=/dev/urandom bs=32 count=1 status=none; [ -e "$boot_root/loader/random-seed" ] && dd if="$boot_root/loader/random-seed" bs=32 count=1 status=none; } | sha256sum)
[ "${#s}" = 64 ] || { warn "Invalid random seed"; return 0; }
hex_to_binary "$s" > "$boot_root/loader/random-seed.new"
mv "$boot_root/loader/random-seed.new" "$boot_root/loader/random-seed"
}
bli_efi_var_get()
{
# BLI uses this vendor UUID
local efi_var="/sys/firmware/efi/efivars/${1:?}-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
if [ -e "$efi_var" ]; then
dd "if=$efi_var" bs=2 skip=2 conv=lcase status=none | tr -d '\0'
fi
}
loader_conf_set()
{
local key="${1:?}"
local value="${2:?}"
[ -e "${boot_root}/loader/loader.conf" ] || touch "${boot_root}/loader/loader.conf"
if grep -q "^$key " "${boot_root}/loader/loader.conf"; then
sed -i -e "s/^$key .*/$key $value/" "${boot_root}/loader/loader.conf"
else
echo "$key $value" >> "${boot_root}/loader/loader.conf"
fi
}
loader_conf_get()
{
local key="${1:?}"
if [ -e "${boot_root}/loader/loader.conf" ]; then
IFS=' ' read -r key value < <(grep "^$key " "${boot_root}/loader/loader.conf")
echo -n "$value"
fi
}
grubenv_set()
{
local key="${1:?}"
local value="${2:?}"
[ -e "${boot_root}${boot_dst}/grubenv" ] || touch "${boot_root}${boot_dst}/grubenv"
grubenv="$(mktemp -t grubenv.XXXXXX)"
echo "# GRUB Environment Block" > "$grubenv"
while read -r line; do
[[ "$line" == '#'* ]] && continue
[[ "$line" == "$key"=* ]] && continue
echo "$line" >> "$grubenv"
done < "${boot_root}${boot_dst}/grubenv"
echo "$key=$value" >> "$grubenv"
local filler
filler=$((1024 - $(stat -c %s "$grubenv")))
printf '#%.0s' $(seq 1 $filler) >> "$grubenv"
mv "$grubenv" "${boot_root}${boot_dst}/grubenv"
}
grubenv_get()
{
local key="${1:?}"
if [ -e "${boot_root}${boot_dst}/grubenv" ]; then
IFS='=' read -r key value < <(grep "^$key=" "${boot_root}${boot_dst}/grubenv")
echo -n "$value"
fi
}
set_default_sdboot()
{
local id="${1:?}"
if ! bootctl set-default "$id" > "$tmpfile" 2>&1; then
if grep -q "Failed to update EFI variable" "$tmpfile" ||
grep -q "Not booted with a supported boot loader" "$tmpfile" ||
grep -q "Not booted with UEFI" "$tmpfile"; then
loader_conf_set "default" "$id"
else
err "$(cat "$tmpfile")"
fi
fi
}
set_default_grub2_bls()
{
local id="${1:?}"
set_default_sdboot "$id"
grubenv_set "default" "$id"
}
set_default_entry()
{
local id="${1:?}"
info "Setting default entry $id"
if [ ! -f "${boot_root}/loader/entries/$id" ] && [ ! -f "${boot_root}/loader/entries/$id.conf" ]; then
update_entries
while read -r path; do
if [ ! -f "$path" ]; then
err "Boot loader entry $id not found"
fi
done < <(jq -r --arg id "$id" '.[] | select(.id == $id) | .path' < "$entryfile")
fi
if is_sdboot; then
set_default_sdboot "$id"
elif is_grub2_bls; then
set_default_grub2_bls "$id"
else
err "Bootloader not detected"
fi
# Setting a different boot entry invalidates "$entryfile"
rm -f "$entryfile"
# This action will require to update the PCR predictions
update_predictions=1
}
get_default_sdboot()
{
local val
val="$(bli_efi_var_get "LoaderEntryDefault")"
[ -n "$val" ] || val="$(loader_conf_get "default")"
[ -n "$val" ] || {
update_entries_for_this_system
jq -r '.[] | select(.isDefault == true) | .id' < "$entryfile"
}
[ -z "$val" ] || echo "$val"
}
get_default_grub2_bls()
{
local val
val="$(grubenv_get "default")"
[ -n "$val" ] || val="$(get_default_sdboot "default")"
[ -z "$val" ] || echo "$val"
}
get_default_entry()
{
if is_sdboot; then
get_default_sdboot
elif is_grub2_bls; then
get_default_grub2_bls
else
err "Bootloader not detected"
fi
}
set_timeout_sdboot()
{
local timeout="${1:?}"
[ "$timeout" = "-1" ] && timeout="menu-force"
if ! bootctl set-timeout "$timeout" > "$tmpfile" 2>&1; then
if grep -q "Failed to update EFI variable" "$tmpfile" ||
grep -q "Not booted with a supported boot loader" "$tmpfile" ||
grep -q "Not booted with UEFI" "$tmpfile"; then
loader_conf_set "timeout" "$timeout"
else
err "$(cat "$tmpfile")"
fi
fi
}
set_timeout_grub2_bls()
{
local timeout="${1:?}"
set_timeout_sdboot "$timeout"
[ "$timeout" = "menu-disabled" ] || [ "$timeout" = "menu-hidden" ] && timeout=0
[ "$timeout" = "menu-force" ] && timeout=-1
grubenv_set "timeout" "$timeout"
}
set_timeout()
{
local timeout="${1:?}"
info "Setting timeout $timeout"
if is_sdboot; then
set_timeout_sdboot "$timeout"
elif is_grub2_bls; then
set_timeout_grub2_bls "$timeout"
else
err "Bootloader not detected"
fi
# This action will require to update the PCR predictions
update_predictions=1
}
get_timeout_sdboot()
{
local val
val="$(bli_efi_var_get "LoaderConfigTimeout")"
[ -n "$val" ] || val="$(loader_conf_get "timeout")"
if [ "$val" = 4294967295 ]; then
val=-1
fi
[ -z "$val" ] || echo "$val"
}
get_timeout_grub2_bls()
{
local val
val="$(grubenv_get "timeout")"
[ -z "$val" ] || echo "$val"
}
get_timeout()
{
if is_sdboot; then
get_timeout_sdboot
elif is_grub2_bls; then
get_timeout_grub2_bls
else
err "Bootloader not detected"
fi
}
set_default_snapshot()
{
[ -n "$have_snapshots" ] || { info "System does not support snapshots."; return 0; }
local num="${1:?}"
local configs
info "Setting default snapshot $num"
update_entries_for_snapshot "$num"
mapfile configs < <(jq '.[]|[.id]|join(" ")' -r < "$entryfile")
configs=("${configs[@]%$nl}")
if [ -z "${configs[0]}" ]; then
info "Snapshot $num has no configs, trying to create them..."
install_all_kernels "$num"
update_entries_for_snapshot "$num"
mapfile configs < <(jq '.[]|[.id]|join(" ")' -r < "$entryfile")
configs=("${configs[@]%$nl}")
if [ -z "${configs[0]}" ]; then
err "snapshot $num has no kernels"
fi
fi
# The default snapshot is not the criteria used for the
# bootloader to select the default boot entry, but for
# coherence we synchronize it here, invalidating any previous
# "$snapperfile"
#
# We do not use snapper, to avoid recursion and do not trigger
# any plugin
local subvolume_id
read -r _ subvolume_id _ < <(btrfs subvolume list -o /.snapshots | grep "${subvol_prefix}/.snapshots/$num/snapshot")
btrfs subvolume set-default "${subvolume_id}" /.snapshots
rm -f "$snapperfile"
set_default_entry "${configs[0]}"
}
have_pcrlock()
{
[ -e /usr/bin/systemd-pcrlock ] || [ -e /usr/lib/systemd/systemd-pcrlock ]
}
have_pcr_oracle()
{
[ -e /usr/bin/pcr-oracle ]
}
pcrlock()
{
dbg "systemd-pcrlock $*"
local pcrlock_cmd="/usr/bin/systemd-pcrlock"
[ -e "$pcrlock_cmd" ] || pcrlock_cmd="/usr/lib/systemd/systemd-pcrlock"
SYSTEMD_LOG_LEVEL="${SYSTEMD_LOG_LEVEL:-warning}" "$pcrlock_cmd" "$@"
}
is_pcr_oracle()
{
[ -e /etc/systemd/tpm2-pcr-public-key.pem ] && \
[ -e /etc/systemd/tpm2-pcr-private-key.pem ] && \
have_pcr_oracle
}
snapshot_ids_for_prediction()
{
# Select the ID of the snapshots that participate in the
# prediction. The order is important, so the most relevant
# should be presented first
# Get the numbers for the last three snapshots
[ -s "$snapperfile" ] || update_snapper
# Select the default and the active snapshots.
local -A snapshots
local -a snapshot_ids
local id
if [ -n "$root_snapshot" ]; then
[ -n "${snapshots[$root_snapshot]}" ] || snapshot_ids+=("$root_snapshot")
snapshots["$root_snapshot"]=1
fi
while read -r id; do
[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
snapshots[$id]=1
done < <(jq -r '.root[]|select(.default==true)|.number' "$snapperfile")
while read -r id; do
[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
snapshots[$id]=1
done < <(jq -r '.root[]|select(.active==true)|.number' "$snapperfile")
if is_transactional && [ -e "${state_file}" ]; then
# shellcheck disable=SC1090
. "${state_file}"
for id in $LAST_WORKING_SNAPSHOTS; do
if [ "${#snapshots[@]}" -lt 3 ]; then
[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
snapshots[$id]=1
fi
done
else
while read -r id; do
if [ "${#snapshots[@]}" -lt 3 ]; then
[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
snapshots[$id]=1
fi
done < <(jq -r '.root|sort_by(.date)[-2:]|reverse|.[]|.number' "$snapperfile")
fi
echo "${snapshot_ids[@]}"
}
regex_snapshot_ids_for_prediction()
{
local re
re="$(snapshot_ids_for_prediction)"
re="${re// /|}"
echo "(:?$re)"
}
select_entries_for_prediction()
{
# The regex of ids is ordered by relevance, but the returns
# set is not. We need to add a "priority" and "kernel"
# (version) field that can be used to order the set
local ids
ids="$(snapshot_ids_for_prediction)"
update_entries_for_snapshot "$(regex_snapshot_ids_for_prediction)"
# Extend the entry file with a "priority" field, based on the
# ordered snapshot IDs, and "kernel" field, based on the
# kernel version. Note that the textual suffix (-default,
# -slowroll) is removed and does not participate in the
# ordering
#
# Expected order for prediction:
#
# MicroOS
# - The default snapshot usually has the top priority (0),
# then the active (1). The order is set by
# `snapshot_ids_for_prediction`
# - Last working snapshots are added after those. Also set
# by `snapshot_ids_for_prediction`
# - If the snapshot contains multiple kernel, the higher
# version has more priority
#
# Tumbleweed
# - Same than MicroOS (`snapshot_ids_for_prediction`)
# - Newest working snapshot are added after those. Also set
# by `snapshot_ids_for_prediction`
# - If the snapshot contains multiple kernel, the higher
# version has more priority
#
jq --arg ids "$ids" 'def priority(id): id as $id | $ids | split(" ") | index($id); map(. + {"priority": priority(.version | scan("(\\d+)@") | .[]), "kernel": .version | scan(".*@(?:(\\d+).(\\d+).(\\d+)-(\\d+))") | map(. | tonumber)})' < "$entryfile" > "${entryfile}.ext"
mv "${entryfile}.ext" "$entryfile"
dbg "Added priority and kernel version to entry file (for prediction)"
dbg_cat "$entryfile"
}
backup_initial_components()
{
select_entries_for_prediction
mv "$entryfile" "$initialentryfile"
cp -a "$boot_root/." "$tmpdir"
}
parse_eventlog()
{
[ "${#eventlog[@]}" -eq 0 ] || return 0
while read -r line; do
eventlog["$line"]=1
done < <(pcrlock log --json=short | jq -r '.log | .[] | .sha256' | sort -u)
}
find_matching_variation()
{
local component="$1"
local hashes=()
for variation in /var/lib/pcrlock.d/"$component".pcrlock.d/*.pcrlock; do
mapfile -t hashes < <(jq -r '.records | .[] | .digests | .[] | select(.hashAlg == "sha256") | .digest' < "$variation")
for h in "${hashes[@]}"; do
[ "${eventlog["$h"]+_}" ] || continue 2
done
echo "$variation"
break
done
}
shift_component()
{
local component="$1"
parse_eventlog
dbg "Shifting component $component"
[ -d "/var/lib/pcrlock.d/$component.pcrlock.d" ] || {
dbg "The component is not a directory or is missing"
return 0
}
# Instead of moving all the variations of a component (as this
# will increase the amount of combinations, reaching early the
# PolicyOR limitation), we keep the one that matches the
# current eventlog. If the variation is also duplicated, it
# will be dropped by `systemd-pcrlock`
local variation vname
variation="$(find_matching_variation "$component")"
if [ -n "$variation" ]; then
vname="$(basename "$variation")"
dbg "$variation found in the eventlog"
[[ "$vname" == shift-* ]] || {
# Drop any previous shifted variation, as
# there is no guarantee that the new one will
# share the name
find /var/lib/pcrlock.d/"$component".pcrlock.d -name 'shift-*.pcrlock' -delete
mv "$variation" "/var/lib/pcrlock.d/$component.pcrlock.d/shift-$vname"
}
else
dbg "No matching variation found for $component"
return 0
fi
# Remove the rest of the variations
find /var/lib/pcrlock.d/"$component".pcrlock.d -name '*.pcrlock' ! -name 'shift-*.pcrlock' -delete
}
pcrlock_manual_raw()
{
local pcr="$1"
local pcrlock="$2"
local element="$3"
echo -n '{"records":[{"pcr":'"$pcr"',"digests":[' > "$pcrlock"
local separator=0
local digest
for dgst in sha1 sha256 sha384 sha512; do
[ "$separator" = "0" ] || echo -n "," >> "$pcrlock"
separator=1
hash "${dgst}sum" || continue
read -r digest _ < <("${dgst}sum" "$element")
echo -n '{"hashAlg":"'"$dgst"'","digest":"'"$digest"'"}' >> "$pcrlock"
done
echo ']}]}' >> "$pcrlock"
}
pcrlock_sdboot_cmdline_initrd()
{
local cmdline="$1"
local initrd="$2"
local suffix="$3"
# 710-kernel-cmdline-initrd-entry.pcrlock.d is not part of the
# pcrlock standards
echo "$cmdline" > "$tmpdir/cmdline"
pcrlock \
lock-kernel-cmdline \
--pcrlock="$tmpdir/cmdline.pcrlock" \
"$tmpdir/cmdline"
pcrlock \
lock-kernel-initrd \
--pcrlock="$tmpdir/initrd.pcrlock" \
"$initrd" 2> /dev/null || pcrlock_manual_raw 9 "$tmpdir/initrd.pcrlock" "$initrd"
mkdir -p /var/lib/pcrlock.d/710-kernel-cmdline-initrd-entry.pcrlock.d
jq --slurp '{"records": [.[].records[0]]}' \
"$tmpdir/cmdline.pcrlock" \
"$tmpdir/initrd.pcrlock" \
> "/var/lib/pcrlock.d/710-kernel-cmdline-initrd-entry.pcrlock.d/cmdline-initrd-$suffix.pcrlock"
rm "$tmpdir/cmdline"
rm "$tmpdir/cmdline.pcrlock"
rm "$tmpdir/initrd.pcrlock"
# 710-kernel-cmdline-boot-loader.pcrlock.d is not part of the
# pcrlock standards
echo -ne "$cmdline\0" > "$tmpdir/cmdline"
iconv -t UTF-16LE -o "$tmpdir/cmdline.utf16" "$tmpdir/cmdline"
pcrlock \
lock-raw \
--pcr=12 \
--pcrlock="/var/lib/pcrlock.d/710-kernel-cmdline-boot-loader.pcrlock.d/cmdline-$suffix.pcrlock" \
"$tmpdir/cmdline.utf16"
rm "$tmpdir/cmdline.utf16"
}
pcrlock_grub2_bls_kernel_initrd()
{
local linux="$1"
local initrd="$2"
local suffix="$3"
local elements=("$linux" "$initrd")
local locks=()
local n=0
for element in "${elements[@]}"; do
n=$((n+1))
pcrlock \
lock-raw \
--pcr=9 \
--pcrlock "$tmpdir/element-$n.pcrlock" \
"$element" 2> /dev/null || pcrlock_manual_raw 9 "$tmpdir/element-$n.pcrlock" "$element"
locks+=("$tmpdir/element-$n.pcrlock")
done
mkdir -p /var/lib/pcrlock.d/710-grub2-bls-kernel-initrd-entry.pcrlock.d
jq --slurp '{"records": [.[].records[0]]}' \
"${locks[@]}" \
> "/var/lib/pcrlock.d/710-grub2-bls-kernel-initrd-entry.pcrlock.d/kernel-initrd-$suffix.pcrlock"
rm "${locks[@]}"
}
pcrlock_grub2_bls_cmdline()
{
local linux="$1"
local cmdline="$2"
local initrd="$3"
local suffix="$4"
local lines=("$linux" "$cmdline" "$initrd")
local locks=()
local n=0
for line in "${lines[@]}"; do
n=$((n+1))
echo -n "$line" > "$tmpdir/line"
pcrlock \
lock-raw \
--pcr=8 \
--pcrlock "$tmpdir/line-$n.pcrlock" \
"$tmpdir/line"
locks+=("$tmpdir/line-$n.pcrlock")
rm "$tmpdir/line"
done
mkdir -p /var/lib/pcrlock.d/650-grub2-bls-entry-cmdline.pcrlock.d
jq --slurp '{"records": [.[].records[0]]}' \
"${locks[@]}" \
> "/var/lib/pcrlock.d/650-grub2-bls-entry-cmdline.pcrlock.d/cmdline-$suffix.pcrlock"
rm "${locks[@]}"
}
pcrlock_grub2_bls_entry_files()
{
local suffix="${1:+-$1}"
local base="${2:-$boot_root}"
local locks=()
local n=0
for i in "$base"/loader/entries/*.conf; do
n=$((n+1))
pcrlock \
lock-raw \
--pcr=9 \
--pcrlock="$tmpdir/entry-$n.pcrlock" \
"$i"
locks+=("$tmpdir/entry-$n.pcrlock")
done
mkdir -p /var/lib/pcrlock.d/643-grub2-bls-entry-files.pcrlock.d
jq --slurp '{"records": [.[].records[0]]}' \
"${locks[@]}" \
> "/var/lib/pcrlock.d/643-grub2-bls-entry-files.pcrlock.d/generated$suffix.pcrlock"
rm "${locks[@]}"
}
pcrlock_sdboot()
{
info "Generating TPM2 predictions with systemd-pcrlock (systemd-boot)"
# 641-sdboot-loader-conf.pcrlock is not part of the pcrlock
# standards
if [ -e "${boot_root}/loader/loader.conf" ]; then
shift_component 641-sdboot-loader-conf
pcrlock \
lock-raw "${boot_root}/loader/loader.conf" \
--pcr=5 \
--pcrlock=/var/lib/pcrlock.d/641-sdboot-loader-conf.pcrlock.d/generated.pcrlock
fi
# 650-kernel-efi-application.pcrlock is not part of the
# pcrlock standards
# TODO: move to kernel-TYPE-pcrlock.rpm
shift_component 650-kernel-efi-application
local n=0
local -A kernels
while read -r linux; do
[ -f "${boot_root}$linux" ] || {
info "Missing ${boot_root}$linux, ignoring entry for prediction"
continue
}
[ -z "${kernels["$linux"]}" ] || continue
kernels["$linux"]=1
n=$((n+1))
# Limit to 4 because of the separator
[ "$n" -le 4 ] || {
info "More than 4 variations for 650-kernel-efi-application"
continue
}
pcrlock \
lock-pe \
--pcrlock=/var/lib/pcrlock.d/650-kernel-efi-application.pcrlock.d/linux-"$n".pcrlock \
"${boot_root}/$linux"
done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | map(.linux) | .[]' "$entryfile")
# Join the cmdline and the initrd in a single component
shift_component 710-kernel-cmdline-initrd-entry
shift_component 710-kernel-cmdline-boot-loader
n=0
while read -r cmdline; do
read -r initrd
[ -f "${boot_root}$initrd" ] || {
info "Missing ${boot_root}$initrd, ignoring entry for prediction"
continue
}
n=$((n+1))
[ "$n" -le 8 ] || {
info "More than 8 variations for 710-kernel-cmdline-initrd-entry"
continue
}
pcrlock_sdboot_cmdline_initrd "initrd=$cmdline" "${boot_root}$initrd" "$n"
done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | .[] | ([(.initrd[0] | sub("/"; "\\"; "g")), .options] | join(" ")), .initrd[0]' "$entryfile")
# Generate variation for 710-kernel-cmdline-initrd-entry
# component that contains the current cmdline and the current
# initrd, even if this will never be used again. This is
# required because disk-encryption-tool generates a new initrd
# during the first boot, making the event log impossible to
# align for systemd-pcrlock
n=0
if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
while read -r cmdline; do
read -r initrd
n=$((n+1))
pcrlock_sdboot_cmdline_initrd "initrd=$cmdline" "${tmpdir}$initrd" "0-$n"
done < <(jq --raw-output '.[] | ([(.initrd[0] | sub("/"; "\\"; "g")), .options] | join(" ")), .initrd[0]' "$initialentryfile")
fi
}
pcrlock_grub2_bls()
{
info "Generating TPM2 predictions with systemd-pcrlock (grub2-bls)"
# 641-grub2-bls-grubenv.pcrlock is not part of the pcrlock
# standards
if [ -e "${boot_root}${boot_dst}/grubenv" ]; then
shift_component 641-grub2-bls-grubenv
pcrlock \
lock-raw "${boot_root}${boot_dst}/grubenv" \
--pcr=9 \
--pcrlock=/var/lib/pcrlock.d/641-grub2-bls-grubenv.pcrlock.d/generated.pcrlock
fi
# 643-grub2-bls-entry-files.pcrlock is not part of the pcrlock
# standards
shift_component 643-grub2-bls-entry-files
pcrlock_grub2_bls_entry_files
if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
pcrlock_grub2_bls_entry_files "0" "$tmpdir"
fi
blkpart="$(findmnt -nvo SOURCE "$boot_root")"
read -r partno < "/sys/class/block/${blkpart##*/}"/partition
# Once we are out of the BIOS / EFI, the numeration cannot be
# done without device.map. It is safe to assume that the ESP
# is always the first disk (hd0)
grub2_bls_drive="(hd0,gpt$partno)"
# Join linux, initrd and cmdline in a single pcrlock file
shift_component 650-grub2-bls-entry-cmdline
n=0
while read -r options; do
read -r linux
read -r initrd
[ -f "${boot_root}$linux" ] || {
info "Missing ${boot_root}$linux, ignoring entry for prediction"
continue
}
[ -f "${boot_root}$initrd" ] || {
info "Missing ${boot_root}$initrd, ignoring entry for prediction"
continue
}
n=$((n+1))
[ "$n" -le 8 ] || {
info "More than 8 variations for 650-grub2-bls-entry-cmdline"
continue
}
pcrlock_grub2_bls_cmdline "linux ${grub2_bls_drive}$linux $options" \
"${grub2_bls_drive}$linux $options" \
"initrd ${grub2_bls_drive}$initrd" "$n"
done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | .[] | .options, .linux, .initrd[0]' "$entryfile")
# Generate variation for 650-grub2-bls-entry-cmdline component
# that contains the current cmdline and the current initrd,
# even if this will never be used again. This is required
# because disk-encryption-tool generates a new initrd during
# the first boot, making the event log impossible to align for
# systemd-pcrlock
n=0
if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
while read -r options; do
read -r linux
read -r initrd
n=$((n+1))
pcrlock_grub2_bls_cmdline "linux ${grub2_bls_drive}$linux $options" \
"${grub2_bls_drive}$linux $options" \
"initrd ${grub2_bls_drive}$initrd" "0-$n"
done < <(jq --raw-output '.[] | .options, .linux, .initrd[0]' "$initialentryfile")
fi
# With secure boot, grub2-bls invokes shim to extend PCR4
if is_secure_boot; then
# 650-kernel-efi-application.pcrlock is not part of
# the pcrlock standards
# TODO: move to kernel-TYPE-pcrlock.rpm
shift_component 650-kernel-efi-application
local n=0
local -A kernels
while read -r linux; do
[ -f "${boot_root}$linux" ] || {
info "Missing ${boot_root}$linux, ignoring entry for prediction"
continue
}
[ -z "${kernels["$linux"]}" ] || continue
kernels["$linux"]=1
n=$((n+1))
# Limit to 4 because of the separator
[ "$n" -le 4 ] || {
info "More than 4 variations for 650-kernel-efi-application"
continue
}
pcrlock \
lock-pe \
--pcrlock=/var/lib/pcrlock.d/650-kernel-efi-application.pcrlock.d/linux-"$n".pcrlock \
"${boot_root}/$linux"
done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | map(.linux) | .[]' "$entryfile")
fi
# Join the kernel and the initrd in a single component
shift_component 710-grub2-bls-kernel-initrd-entry
n=0
while read -r linux; do
read -r initrd
[ -f "${boot_root}$linux" ] || continue
[ -f "${boot_root}$initrd" ] || continue
n=$((n+1))
[ "$n" -le 8 ] || continue
pcrlock_grub2_bls_kernel_initrd "${boot_root}$linux" "${boot_root}$initrd" "$n"
done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | .[] | .linux, .initrd[0]' "$entryfile")
# Generate variation for 710-grub2-bls-kernel-initrd-entry for the
# same reason than before.
n=0
if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
while read -r linux; do
read -r initrd
n=$((n+1))
pcrlock_grub2_bls_kernel_initrd "${tmpdir}$linux" "${tmpdir}$initrd" "0-$n"
done < <(jq --raw-output '.[] | .linux, .initrd[0]' "$initialentryfile")
fi
}
clean_pcrlock_d()
{
[ -d /var/lib/pcrlock.d ] || return 0
# Remove the shifted measurements since the last reboot. They
# are used to link the current components with the event log,
# so pcrlock can work with the aligments. For example, if a
# file gets replaced (loader.conf) the new measurement cannot
# be found in the event log, as contains the old hash, making
# the aligment fail.
local btime
read -r _ btime < <(grep btime /proc/stat)
local minutes=$((1 + ($(date +%s) - btime) / 60))
dbg "Cleaning shifted measurements older than $minutes minutes"
find /var/lib/pcrlock.d -name 'shift-*.pcrlock' -type f -cmin +"$minutes" -delete
# Remove older (1 week) generated measurements. This will
# keep the predictions at minimum and decrease the
# combinations. Removing all can be a problem in certain
# conditions. For example, after the first boot some pcrlock
# files contain hashes for the original ESP assets, that are
# required for the event log aligment.
find /var/lib/pcrlock.d -name '*.pcrlock' -type f -mtime +7 -delete
# Sometimes, like in tests, the user will generate new entries
# and reboot in a short period of time
if [ "$(find /var/lib/pcrlock.d -type f -name '*-7.pcrlock' | wc -l)" -gt 0 ]; then
rm -fr /var/lib/pcrlock.d
fi
}
generate_tpm2_predictions_pcrlock()
{
local pcrs="$FDE_SEAL_PCR_LIST"
info "Generating TPM2 predictions with systemd-pcrlock"
# Select the affected entries
select_entries_for_prediction
clean_pcrlock_d
shift_component 250-firmware-code-early
shift_component 550-firmware-code-late
pcrlock lock-firmware-code
shift_component 250-firmware-config-early
shift_component 550-firmware-config-late
pcrlock lock-firmware-config
# If secure boot is disabled, this can fail. There is patch
# for the policy generation, and for the authority is planned
shift_component 240-secureboot-policy
pcrlock lock-secureboot-policy &> /dev/null || true
shift_component 620-secureboot-authority
pcrlock lock-secureboot-authority &> /dev/null || true
# Uses / by default, but firmware measures GPT of the disk a
# boot application was loaded from, which is effectively the
# disk where our ESP is located.
shift_component 600-gpt
pcrlock lock-gpt "$boot_root"
# Measure the boot loader. Combinations:
# - Removable media with shim: BOOTX64.EFI, grub.efi
# - Removable media w/out shim: BOOTX64.EFI
# - Installed system with shim: shim.efi, grub.efi
# - Installed system w/out shim and grub2-bls: grub.efi
# - Installed system w/out shim and systemd-boot: systemd-bootx64.efi
local shim_path bootloader_path
if is_shim_installed; then
if [ -n "$arg_portable" ]; then
shim_path="${boot_root}/EFI/BOOT/BOOT${firmware_arch^^}.EFI"
bootloader_path="${boot_root}/EFI/BOOT/grub.efi"
else
shim_path="${boot_root}${boot_dst}/shim.efi"
bootloader_path="${boot_root}${boot_dst}/grub.efi"
fi
else
local bootloader_filename
if is_sdboot; then
bootloader_filename="systemd-boot${firmware_arch,,}.efi"
elif is_grub2_bls; then
bootloader_filename="grub.efi"
fi
if [ -n "$arg_portable" ]; then
bootloader_path="${boot_root}/EFI/BOOT/${bootloader_filename}"
else
bootloader_path="${boot_root}${boot_dst}/${bootloader_filename}"
fi
fi
if [ -n "$shim_path" ]; then
# 630-shim-efi-application is not part of the pcrlock
# standards
# TODO: move to shim-pcrlock.rpm
shift_component 630-shim-efi-application
pcrlock \
lock-pe \
--pcrlock=/var/lib/pcrlock.d/630-shim-efi-application.pcrlock.d/generated.pcrlock \
"${shim_path}"
fi
# 640-boot-loader-efi-application is not part of the
# pcrlock standards
# This is measuring the grub / systemd-boot EFI binary
# TODO: move to systemd-boot-pcrlock.rpm / grub2-bls.rpm
shift_component 640-boot-loader-efi-application
pcrlock \
lock-pe \
--pcrlock=/var/lib/pcrlock.d/640-boot-loader-efi-application.pcrlock.d/generated.pcrlock \
"${bootloader_path}"
if is_sdboot; then
pcrlock_sdboot
elif is_grub2_bls; then
pcrlock_grub2_bls
fi
# If the prediction fails, the system will ask for a password,
# but we can do a re-enrollment using the recovery PIN. To
# register a recovery PIN the installer (sdbootutil-enroll,
# YaST) will call this script deploying in the %u keyring
# "sdbootutil[-pin]" entry. For re-enrollments we can use the
# same entry, the PIN environment variable, or the
# --ask-{key,pin,pw} parameter.
local pin
local extra=()
local keyid keyid_int
keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
keyid_int="$(keyctl id %user:sdbootutil-pin 2> /dev/null)" || true
if [ -n "$arg_ask_key_pin_or_pw" ]; then
ask_password "Recovery PIN" pin
extra=("--recovery-pin=yes")
elif [ -n "$PIN" ]; then
pin="$PIN"
extra=("--recovery-pin=yes")
elif [ -n "$keyid_int" ]; then
pin="$(keyctl pipe "$keyid_int")"
extra=("--recovery-pin=yes")
elif [ -n "$keyid" ]; then
pin="$(keyctl pipe "$keyid")"
extra=("--recovery-pin=yes")
else
# No PIN was provided, systemd-pcrlock will generate
# one
extra=("--recovery-pin=show")
fi
local output
output="$(PIN="$pin" pcrlock --pcr="$pcrs" "${extra[@]}" make-policy)"
local pcrlock_status=$?
if [ $pcrlock_status -ne 0 ]; then
echo "Error creating the systemd-pcrlock policy!"
return 1
elif echo "$output" | grep -q "recovery PIN"; then
local split
IFS=":" read -r -a split <<<"$output"
pin="${split[1]}"
pin="${pin## }"
pin="${pin%% }"
echo "Recovery PIN: $pin"
if [ -x /usr/bin/qrencode ]; then
echo "You can also scan it with your mobile phone:"
qrencode -t utf8i "$pin"
fi
# Add the generated recovery PIN to the kernel
# keyring, so that it is available to `sdbootutil
# enroll --method=recovery-key`
keyctl_add_with_timeout "sdbootutil-pin" "$pin"
fi
# Publish the assets in the ESP, so can be imported by
# dracut-pcr-signature
[ -e /var/lib/systemd/pcrlock.json ] && \
cp /var/lib/systemd/pcrlock.json "${boot_root}${boot_dst}" && {
echo "NVIndex policy created"
}
}
get_pcrs()
{
local pcrs
local jq_pcr='.tokens[]|select(.type == "systemd-tpm2")|."tpm2_pubkey_pcrs"|join(",")'
# We can have multiple devices, each one of them with
# different PCRs
while read -r dev; do
pcrs=$(cryptsetup luksDump --dump-json-metadata "$dev" | jq -r "$jq_pcr")
[ -z "$pcrs" ] || echo "$pcrs"
done <<<"$(blkid -t TYPE=crypto_LUKS -o device)"
}
generate_tpm2_predictions_pcr_oracle()
{
local entry
local all_pcrs
info "Generating TPM2 predictions with pcr-oracle"
# Select the affected entries
select_entries_for_prediction
all_pcrs=$(get_pcrs)
[ -n "$all_pcrs" ] || all_pcrs="$FDE_SEAL_PCR_LIST"
rm -f /etc/systemd/tpm2-pcr-signature.json
# We make as many predictions as |all_pcrs| * |entries| to
# cover all the combinations. pcr-oracle is smart to include
# the entry only one time, so we will not have duplications.
# This is a step for multi device configurations.
local -a entries
mapfile -t entries < <(jq -r '.[]|.id' "$entryfile")
if [ -z "${entries[0]}" ]; then
err "No bootloader entries found"
fi
for pcrs in $all_pcrs; do
for entry in "${entries[@]}"; do
info "Generate prediction for $entry with PCRs $pcrs"
if ! pcr-oracle \
--private-key /etc/systemd/tpm2-pcr-private-key.pem \
--from eventlog \
--output /etc/systemd/tpm2-pcr-signature.json \
--target-platform=systemd \
--boot-entry "${entry}" \
sign "$pcrs"; then
err "Failed to install TPM predictions for ${entry}"
fi
done
done
# Publish the assets in the ESP, so can be imported by
# dracut-pcr-signature
cp /etc/systemd/tpm2-pcr-public-key.pem "${boot_root}${boot_dst}"
[ -e /etc/systemd/tpm2-pcr-signature.json ] && \
cp /etc/systemd/tpm2-pcr-signature.json "${boot_root}${boot_dst}" && {
echo "Signed policy created"
}
}
get_volume_password()
{
local dev="$1"
local pw keyid
# If we are enrolling for the first time, sdbootutil-pin can
# contain the recovery PIN, that cannot be used to unlock the
# device (unless later it is done an enrollment of a new
# recovery key)
keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
keyid_ce="$(keyctl id %user:cryptenroll 2> /dev/null)" || true
if [ -n "$keyid_ce" ]; then
pw="$(keyctl pipe "$keyid_ce")"
elif [ -n "$keyid" ]; then
pw="$(keyctl pipe "$keyid")"
else
ask_password "Password for $dev" pw
# If the key was missing for all the keyrings put back
# into the cryptenroll keyring, as there is a chance
# that this is part or a re-enrollment
keyctl_add_with_timeout "cryptenroll" "$pw"
fi
echo "$pw"
}
get_volume_key()
{
local dev="$1"
local pw out
pw="$(get_volume_password "$dev")"
out="$(cryptsetup luksDump --batch-mode --dump-master-key "$dev" <<<"$pw")" || {
# If luksDump fails, remove the password from the
# keyring. Can be that the password was wrong, and
# systemd-cryptenroll ask later for the password.
# Both passwords can appear in the keyring separated
# by NULL
keyctl revoke %user:cryptenroll &> /dev/null || true
keyctl reap &> /dev/null || true
# Try one more time
pw="$(get_volume_password "$dev")"
out="$(cryptsetup luksDump --batch-mode --dump-master-key "$dev" <<<"$pw")" || return 1
}
echo "$out" | sed -n '/MK dump:/,$p' | sed -E 's/MK dump:|[[:blank:]]+//g' | sed -z 's/\n//g'
}
extend_pcr()
{
local dgst="$1"
local pcr="$2"
local val="$3"
local digest
hash "${dgst}sum" || return
hex_to_binary "$pcr$val" > "$tmpdir/pcr"
read -r digest _ < <("${dgst}sum" "$tmpdir/pcr")
echo "$digest"
}
generate_tpm2_predictions_pcr_15()
{
local devs=()
local msgs=()
local vks=()
local name dev opts extra uuid pw
grep -q "tpm2-measure-pcr=yes" /etc/crypttab || return
info "Generating predictions for PCR15"
# Read /etc/crypttab lines that contains tpm2-device and
# tpm2-measure-pcr. This code is the similar from
# measure-pcr-generator.sh, so we guarantee the same ordering
# for PCR 15 extension
while read -r name dev _ opts; do
# Only the entries in /etc/crypttab in the initrd
# (marked with x-initrd.attach) should participate
# from the extension for now. The reason is that
# extensions after the switch root cannot participate
# in abort the boot process from initrd itself
#
# Note that dracut will add the cr_swap partition even
# if it is not marked as x-initrd.attach
[[ "$name" = \#* ]] && continue
[[ "$opts" != *"tpm2-device="* ]] && continue
[[ "$opts" != *"tpm2-measure-pcr="* ]] && continue
# If the device name is UUID= convert as a real device
# name, and if not, retrieve the UUID
if [[ "$dev" = "UUID="* ]]; then
uuid="${dev#"UUID="}"
dev="$(blkid --uuid "$uuid")"
else
uuid="$(blkid "$dev" -o value -s UUID)"
fi
# Get the FSTYPE of the real device (crypto_LUKS) and
# the slave / holder one (btrfs, swap, etc). Also get
# the mount point so we can identify /etc or /var.
#
# According to https://systemd.io/MOUNT_REQUIREMENTS/
# /etc is mounted in initrd, and /var is mounted after
# initrd, but in MicroOS (via microos-tools) is adding
# /var to be mounted in the initrd stage too because
# of selinux (98selinux-microos).
extra="$(lsblk --noheadings -o FSTYPE,MOUNTPOINT "$dev")"
if [[ "$extra" != *"swap"* ]] && [[ "$extra" != *"/etc"* ]] && { [[ "$extra" != *"/var"* ]] || ! is_transactional; }; then
[[ "$opts" != *"x-initrd.attach"* ]] && continue
fi
dbg "Adding $dev (cryptsetup:$name:$uuid) for PCR15"
devs+=("$dev")
msgs+=("cryptsetup:$name:$uuid")
done < /etc/crypttab
# We need to separate this into a different loop because we
# cannot nest two reads (one for crypttab and another for the
# password)
for dev in "${devs[@]}"; do
local vk
vk="$(get_volume_key "$dev")"
[ -n "$vk" ] || { warn "Volume key cannot be extracted. Dropping PCR 15"; return 0; }
vks+=("$vk")
done
rm -f /var/lib/sdbootutil/measure-pcr-prediction
rm -f /var/lib/sdbootutil/measure-pcr-prediction.sha256
local dgsts=("sha1" "sha256" "sha384" "sha512")
local sizes=(40 64 96 128)
local pcr15 hmac
for i in "${!dgsts[@]}"; do
pcr15="$(printf '0%.0s' $(seq 1 "${sizes[i]}"))"
# Add the 0x00...0 predictions as a valid one.
# Workaround the systemd-cryptsetup issue for LVM
# devices
echo "$pcr15" >> "/var/lib/sdbootutil/measure-pcr-prediction"
for j in "${!msgs[@]}"; do
hmac="$(echo -ne "${msgs[j]}" | /usr/libexec/sdbootutil/uhmac "${dgsts[i]}" <(echo "${vks[j]}"))"
pcr15="$(extend_pcr "${dgsts[i]}" "$pcr15" "$hmac")"
dbg "${msgs[j]} (${dgsts[i]}): $hmac"
done
echo "$pcr15" >> "/var/lib/sdbootutil/measure-pcr-prediction"
done
if [ -f "/var/lib/sdbootutil/measure-pcr-prediction" ] && [ -f "/var/lib/sdbootutil/measure-pcr-private.pem" ]; then
openssl dgst -sha256 \
-sign /var/lib/sdbootutil/measure-pcr-private.pem \
-out /var/lib/sdbootutil/measure-pcr-prediction.sha256 \
/var/lib/sdbootutil/measure-pcr-prediction
fi
# Register the hash of the parsed crypttab
local crypttab_sha1
read -r crypttab_sha1 _ < <(sha1sum /etc/crypttab)
echo "$crypttab_sha1" > /var/lib/sdbootutil/crypttab.sha1
# Publish the assets in the ESP, so can be imported by
# dracut-pcr-signature
[ ! -e /var/lib/sdbootutil/measure-pcr-prediction ] || \
cp /var/lib/sdbootutil/measure-pcr-prediction "${boot_root}${boot_dst}"
[ ! -e /var/lib/sdbootutil/measure-pcr-prediction.sha256 ] || \
cp /var/lib/sdbootutil/measure-pcr-prediction.sha256 "${boot_root}${boot_dst}"
}
updated_crypttab()
{
local crypttab_sha1
read -r crypttab_sha1 _ < <(sha1sum /etc/crypttab)
grep -Fixq "$crypttab_sha1" /var/lib/sdbootutil/crypttab.sha1 2> /dev/null
}
generate_tpm2_predictions()
{
[ -e /etc/crypttab ] || return 0
grep -q "tpm2-device" /etc/crypttab || return 0
! in_lockout || err "The TPM2 is in lockout. Use 'tpm2_dictionarylockout -c [ -p passwd ]' to clear the DA lockout and re-try the sdbootutil command"
# The PCR list is used by both models (pcr-oracle,
# systemd-pcrlock). The first one will try first to get the
# PCRs from the LUKS2 header, that will succeed in the normal
# situation but fail during the initial enrollment. This is
# because we generate the prediction first and later we do the
# enrollment
is_config_file || err "/etc/sysconfig/fde-tools not found"
load_config_file
[ -z "$arg_pcr" ] || FDE_SEAL_PCR_LIST="$arg_pcr"
if is_pcr_oracle; then
generate_tpm2_predictions_pcr_oracle
elif have_pcrlock; then
generate_tpm2_predictions_pcrlock || return 1
fi
# Generate a PCR 15 prediction only in certain cases, as for
# now this will ask the password (can be resolved by an
# external tool that extract the password from the TPM2 if the
# policy is still valid)
updated_crypttab || { generate_tpm2_predictions_pcr_15; arg_measure_pcr=; }
# shellcheck disable=SC2015
[ -f "/var/lib/sdbootutil/measure-pcr-prediction" ] && [ -z "$arg_measure_pcr" ] || generate_tpm2_predictions_pcr_15
# The user can remove measure-pcr-prediction file from the
# ESP, but generate_tpm2_predictions_pcr_15 will not be called
[ -f "${boot_root}${boot_dst}/measure-prediction" ] || {
[ ! -e /var/lib/sdbootutil/measure-pcr-prediction ] || \
cp /var/lib/sdbootutil/measure-pcr-prediction "${boot_root}${boot_dst}"
[ ! -e /var/lib/sdbootutil/measure-pcr-prediction.sha256 ] || \
cp /var/lib/sdbootutil/measure-pcr-prediction.sha256 "${boot_root}${boot_dst}"
}
}
have_tracked_devices()
{
[ "${#tracked_devices[@]}" -gt 0 ]
}
detect_tracked_devices()
{
# A LUKS2 device can be un-tracked (ignored) by sdbootutil if
# is present in /etc/crypttab and has the
# "x-sdbootutil.ignore" option
local dev fstype uuid
! have_tracked_devices || return 0
dbg_cat "/etc/crypttab"
while read -r dev fstype uuid; do
[ "$fstype" = 'crypto_LUKS' ] || continue
cryptsetup isLuks --type luks2 "$dev" || continue
[ -e /etc/crypttab ] && grep -E -q "${dev}[[:space:]].*x-sdbootutil.ignore" /etc/crypttab && continue
[ -e /etc/crypttab ] && grep -E -q "${uuid}[[:space:]].*x-sdbootutil.ignore" /etc/crypttab && continue
dbg "Tracking encrypted device $dev"
tracked_devices+=("$dev")
done < <(lsblk --noheadings -o PATH,FSTYPE,UUID)
have_tracked_devices
}
have_tpm2()
{
[ -n "$(systemd-cryptenroll --tpm2-device=list 2>/dev/null)" ]
}
have_fido2()
{
[ -n "$(systemd-cryptenroll --fido2-device=list 2>/dev/null)" ]
}
have_slot()
{
local dev="${1:?}"
local kind="${2:?}"
grep -q "$kind" < <(systemd-cryptenroll "$dev")
}
in_lockout()
{
hash tpm2_getcap &> /dev/null || { warn "tpm2_getcap not found"; return 1; }
tpm2_getcap properties-variable | grep -q 'inLockout: *1'
}
add_crypttab_option()
{
# This version will share the same options for all crypto_LUKS
# devices. This imply that all of them will be unlocked by
# the same TPM2, or the same FIDO2 key.
#
# The ones ignored will be untouched.
local option="$1"
dbg "Adding \"$option\" to /etc/crypttab"
dbg_cat "/etc/crypttab"
local crypttab
crypttab="$(mktemp -t crypttab.XXXXXX)"
echo "# File created by sdbootutil. Comments will be removed" > "$crypttab"
echo "# Add the 'x-sdbootutil.ignore' option to un-track a device" >> "$crypttab"
local name
local device
local key
local opts
while read -r name device key opts; do
[[ "$name" = \#* ]] && continue
if [[ "$opts" != *"x-sdbootutil.ignore"* ]] && [[ "$opts" != *"$option"* ]]; then
[ -z "$opts" ] && opts="$option" || opts="$opts,$option"
# crypttab has changed so initrd needs to be
# updated
arg_no_reuse_initrd=1
fi
echo "$name $device ${key:-none} $opts" >> "$crypttab"
done < /etc/crypttab
mv -Z "$crypttab" /etc/crypttab
chmod 644 /etc/crypttab
dbg "Added \"$option\" to /etc/crypttab"
dbg_cat "/etc/crypttab"
}
remove_crypttab_option()
{
# Similar to add_crypttab_option, the option will be removed
# from all the entries.
#
# The ones ignored will be untouched.
local option="$1"
dbg "Removing \"$option\" to /etc/crypttab"
dbg_cat "/etc/crypttab"
local crypttab
crypttab="$(mktemp -t crypttab.XXXXXX)"
echo "# File created by sdbootutil. Comments will be removed" > "$crypttab"
echo "# Add the 'x-sdbootutil.ignore' option to un-track a device" >> "$crypttab"
local name
local device
local key
local opts
while read -r name device key opts; do
[[ "$name" = \#* ]] && continue
if [[ "$opts" != *"x-sdbootutil.ignore"* ]] && [[ "$opts" = *"$option"* ]]; then
opts="${opts#"$option",}"
opts="${opts//,"$option"}"
opts="${opts//"$option"}"
# crypttab has changed so initrd needs to be
# updated
arg_no_reuse_initrd=1
fi
[ -n "$opts" ] && echo "$name $device ${key:-none} $opts" >> "$crypttab"
[ -z "$opts" ] && echo "$name $device ${key:-none}" >> "$crypttab"
done < /etc/crypttab
mv -Z "$crypttab" /etc/crypttab
chmod 644 /etc/crypttab
dbg "Removed \"$option\" to /etc/crypttab"
dbg_cat "/etc/crypttab"
}
generate_rsa_key()
{
pcr-oracle \
--rsa-generate-key \
--private-key /etc/systemd/tpm2-pcr-private-key.pem \
--public-key /etc/systemd/tpm2-pcr-public-key.pem \
store-public-key
}
set_unlock_method()
{
local dev="$1"
unlock_method=
# If %user:cryptenroll is set, use it as an automatic
# unlocker, if not, try the TPM2 or the FIDO2 key
local keyid
keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || true
[ -z "$keyid" ] || return 0
# Do not use TPM2 slot for enrolling TPM2
if [ "$arg_method" != "tpm2" ] && [ "$arg_method" != "tpm2+pin" ] && have_slot "$dev" "tpm2"; then
info "Unlocking using TPM2"
unlock_method="--unlock-tpm2-device=auto"
# Same for FIDO2
elif [ "$arg_method" != "fido2" ] && have_slot "$dev" "fido2"; then
info "Unlocking using FIDO2"
unlock_method="--unlock-fido2-device=auto"
fi
}
enroll_pcrlock()
{
local dev="$1"
local tpm2_pin="$2"
local extra_args=()
if [ -z "$tpm2_pin" ]; then
info "Enrolling with TPM2 (pcrlock): $dev"
else
info "Enrolling with TPM2+PIN (pcrlock): $dev"
extra_args+=(--tpm2-with-pin=1)
fi
if [ ! -f /var/lib/systemd/pcrlock.json ]; then
warn "Could not find /var/lib/systemd/pcrlock.json"
fi
set_unlock_method "$dev"
if [ -n "$unlock_method" ]; then
extra_args+=("$unlock_method")
fi
# Note that the PCRs are now not stored in the LUKS2 header
if NEWPIN="$tpm2_pin" systemd-cryptenroll \
--wipe-slot=tpm2 \
--tpm2-device=auto \
"${extra_args[@]}" \
--tpm2-pcrlock=/var/lib/systemd/pcrlock.json \
"$dev"; then
# systemd-cryptenroll exits successfully even if the
# token was not enrolled. Manually check if the
# device has a tpm2 slot enrolled
systemd-cryptenroll "$dev" | grep -q "tpm2"
else
return 1
fi
# Not all PCR predictions match the current system state and
# will be excluded from the policy. Trigger an update on the
# next boot which should include them
systemctl --quiet enable sdbootutil-update-predictions.service || true
}
enroll_pcroracle()
{
local dev="$1"
local tpm_pin="$2"
local extra_args=()
if [ -z "$tpm_pin" ]; then
info "Enrolling with TPM2 (pcr-oracle): $dev"
else
info "Enrolling with TPM2+PIN (pcr-oracle): $dev"
extra_args+=(--tpm2-with-pin=1)
fi
if [ ! -f /etc/systemd/tpm2-pcr-signature.json ]; then
warn "Could not find /etc/systemd/tpm2-pcr-signature.json"
fi
set_unlock_method "$dev"
if [ -n "$unlock_method" ]; then
extra_args+=("$unlock_method")
fi
# Note that the PCRs are now not stored in the LUKS2 header
if NEWPIN="$tpm_pin" systemd-cryptenroll \
--wipe-slot=tpm2 \
--tpm2-device=auto \
"${extra_args[@]}" \
--tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
--tpm2-public-key-pcrs="${FDE_SEAL_PCR_LIST}" \
"$dev"; then
# systemd-cryptenroll exits successfully even if the
# token was not enrolled. Manually check if the
# device has a tpm2 slot enrolled
systemd-cryptenroll "$dev" | grep -q "tpm2"
else
return 1
fi
}
enroll_fido2()
{
local dev="$1"
info "Enrolling with FIDO2: $dev"
local extra_args=()
set_unlock_method "$dev"
if [ -n "$unlock_method" ]; then
extra_args+=("$unlock_method")
fi
systemd-cryptenroll --wipe-slot=fido2 --fido2-device=auto "${extra_args[@]}" "$dev"
}
enroll_password()
{
local dev="$1"
local pw="$2"
info "Enrolling with password: $dev"
local extra_args=()
set_unlock_method "$dev"
if [ -n "$unlock_method" ]; then
extra_args+=("$unlock_method")
fi
NEWPASSWORD="$pw" systemd-cryptenroll --wipe-slot=password --password "${extra_args[@]}" "$dev"
}
enroll_recovery_key()
{
local dev="$1"
info "Enrolling with recovery key: $dev"
local extra_args=()
set_unlock_method "$dev"
if [ -n "$unlock_method" ]; then
extra_args+=("$unlock_method")
fi
# If no recovery key is provided, systemd-cryptenroll will
# generate one
local key keyid keyid_int
keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
keyid_int="$(keyctl id %user:sdbootutil-pin 2> /dev/null)" || true
if [ -n "$arg_ask_key_pin_or_pw" ]; then
ask_new_password "recovery key" key
elif [ -n "$KEY" ]; then
key="$KEY"
elif [ -n "$keyid_int" ]; then
key="$(keyctl pipe "$keyid_int")"
elif [ -n "$keyid" ]; then
key="$(keyctl pipe "$keyid")"
fi
local generated_key=
# This function will be called for every device. Let systemd
# generate a secure recovery key only the first time if there
# is no recovery pin selected (%user:sdbootutil[-pin])
if [ -z "$key" ]; then
# systemd-cryptenroll will put in stdout the recovery
# key, and the rest of the information in stderr
key="$(systemd-cryptenroll --wipe-slot=recovery --recovery-key "${extra_args[@]}" "$dev" 2> /dev/null)"
generated_key=1
else
# A recovery key has already been generated, use it
# for all the devices. systemd-cryptenroll always
# generates a random recovery key, but we want $key.
# Replace it by using cryptsetup, so we can still use
# %u:cryptenroll in systemd-cryptenroll, and the
# temporary recovery key in cryptsetup, to avoid
# requesting a password
local tmp_key
tmp_key="$(systemd-cryptenroll --wipe-slot=recovery --recovery-key "${extra_args[@]}" "$dev" 2> /dev/null)"
local split
read -r -a split < <(systemd-cryptenroll "$dev" | grep recovery)
local keyslot="${split[0]}"
# cryptsetup can only read the new passphrase from a
# keyfile
local tmp_key_file
tmp_key_file="$(mktemp -t key_file.XXXXXX)"
echo -n "$key" > "$tmp_key_file"
cryptsetup luksChangeKey --key-slot "$keyslot" --force-password "$dev" "$tmp_key_file" <<<"$tmp_key"
shred "$tmp_key_file"
fi
# If we enroll a recovery key first, we can use the generated
# key as a recovery PIN later when we enroll a TPM2[+PIN]
# (note that the recovery PIN is not the same PIN for the
# tpm2+pin method).
#
# If we enroll the recovery key after the TPM2 enrollment and
# we send the recovery PIN via the keyring, then we can make
# the recovery key the same as the recovery PIN. But if the
# PIN is missing from the keyring, then we missed the
# synchronization and the key and the PIN are different.
if [ -z "$keyid_int" ] && have_slot "$dev" "tpm2"; then
warn "There is already a recovery PIN for the TPM2"
warn "The recovery key and the recovery PIN are now different"
fi
if [ -n "$generated_key" ]; then
echo "Recovery key: $key"
if [ -x /usr/bin/qrencode ]; then
echo "You can also scan it with your mobile phone:"
qrencode -t utf8i "$key"
fi
fi
# Make sure that the registered recovery key is in the kernel
# keyring, so that it is available to systemd-cryptenroll
keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || true
if [ -z "$keyid" ] && [ -z "$unlock_method" ]; then
keyctl_add_with_timeout "cryptenroll" "$key"
fi
# ... and to --method=tpm2[+pin] via a private keyring
keyctl_add_with_timeout "sdbootutil-pin" "$key"
}
enroll_device()
{
local dev="$1"
local pin_or_pw="$2"
case "$arg_method" in
"tpm2"|"tpm2+pin")
if ! is_pcr_oracle && have_pcrlock; then
enroll_pcrlock "$dev" "$pin_or_pw" || {
warn "Enrollment with systemd-pcrlock failed"
warn "Re-trying with pcr-oracle"
unenroll_pcrlock
# This function generates
# /etc/systemd/tpm2-pcr-public-key.pem
# which is needed by
# enroll_pcroracle
generate_rsa_key
enroll_pcroracle "$dev" "$pin_or_pw"
}
elif have_pcr_oracle; then
enroll_pcroracle "$dev" "$pin_or_pw"
else
info "No TMP2 enrollment mechanism found"
fi
;;
"fido2")
enroll_fido2 "$dev"
;;
"password")
enroll_password "$dev" "$pin_or_pw"
;;
"recovery-key")
enroll_recovery_key "$dev"
;;
*)
err "Unexpected parameter for --method=: $arg_method"
;;
esac
}
enroll()
{
[ -e /etc/crypttab ] || { info "/etc/crypttab not found. No encrypted devices?"; return 0; }
[ -e /usr/bin/systemd-cryptenroll ] || { info "systemd-cryptenroll not found"; return 0; }
detect_tracked_devices || { info "No LUKS2 devices found"; return 0; }
info "Enrolling devices ($arg_method): ${tracked_devices[*]}"
have_pcrlock || have_pcr_oracle || err "Not systemd-pcrlock nor pcr-oracle found"
# Prepare /etc/crypttab and update initrd if required
case "$arg_method" in
"tpm2"|"tpm2+pin")
have_tpm2 || err "No TPM2 found found"
! in_lockout || err "The TPM2 is in lockout. Use 'tpm2_dictionarylockout -c [ -p passwd ]' to continue"
add_crypttab_option 'tpm2-device=auto'
[ -n "$arg_no_measure_pcr" ] || add_crypttab_option 'tpm2-measure-pcr=yes'
;;
"fido2")
have_fido2 || err "No FIDO2 key found"
add_crypttab_option 'fido2-device=auto'
;;
esac
# For predicting PCR 15 we need to sign a file. Create the
# public and private key if missing. This (as the
# /etc/crypttab change) can require a new initrd
if [ "$arg_method" = "tpm2" ] || [ "$arg_method" = "tpm2+pin" ]; then
local private="/var/lib/sdbootutil/measure-pcr-private.pem"
local public="/var/lib/sdbootutil/measure-pcr-public.pem"
[ -f "$private" ] || openssl genrsa -out "$private" 4096
# Writes "writing RSA key" in stderr and -noout is not
# doing what I was expecting
[ -f "$public" ] || openssl rsa -in "$private" -pubout -out "$public" 2> /dev/null
fi
# If the crypttab file changed (that is expected), we need to
# generate a new initrd
if [ "$arg_no_reuse_initrd" = "1" ]; then
install_all_kernels "$root_snapshot"
# Avoid the call of generate_tpm2_predictions at the
# end of the script
update_predictions=
fi
# If systemd-pcrlock is missing then we need to use
# pcr-oracle. For this we need to generate the RSA keys
# early. Also we need to set the PCRs used to assest the
# system status
if [ "$arg_method" = "tpm2" ] || [ "$arg_method" = "tpm2+pin" ]; then
load_config_file
[ -z "$arg_pcr" ] || FDE_SEAL_PCR_LIST="$arg_pcr"
if [ -z "${FDE_SEAL_PCR_LIST}" ]; then
if systemd-detect-virt -q; then
info "Virtualized systemd detected ($(systemd-detect-virt)). Dropping PCR0 and PCR2"
FDE_SEAL_PCR_LIST=""
else
FDE_SEAL_PCR_LIST="0,2,"
fi
if is_sdboot; then
FDE_SEAL_PCR_LIST+="4,7,9"
elif is_grub2_bls; then
FDE_SEAL_PCR_LIST+="4,7,8,9"
else
err "Bootloader not detected"
fi
fi
is_config_file || echo "FDE_SEAL_PCR_LIST=${FDE_SEAL_PCR_LIST}" >> /etc/default/fde-tools
if { ! have_pcrlock || [ -n "$arg_signed_policy" ]; } && have_pcr_oracle; then
# Once the RSA key is present, then
# is_pcr_oracle is true
generate_rsa_key
fi
# During the initial enrollment it is expected that
# for systemd-pcrlock the recovery PIN will be
# extracted from the %u keyring "sdbootutil[-pin]"
# entry
generate_tpm2_predictions || {
# If the generation of prediction fails and we
# are trying with systemd-pcrlock, we try
# again with pcr-oracle
if ! is_pcr_oracle; then
echo "Predictions with systemd-pcrlock failed"
echo "Re-trying with pcr-oracle"
unenroll_pcrlock
# This function generates
# /etc/systemd/tpm2-pcr-public-key.pem
# which is needed by
# generate_tpm2_predictions
generate_rsa_key
generate_tpm2_predictions
fi
}
fi
# For the PIN (tpm2+pin) or password (password), we can get it
# from the %u keyring "sdbootutil" entry, the PIN or PW
# environment variable, or introduced by the user
local pin_or_pw keyid
if [ "$arg_method" = "tpm2+pin" ]; then
keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
if [ -n "$arg_ask_key_pin_or_pw" ]; then
ask_new_password "TPM2 PIN" pin_or_pw
elif [ -n "$PIN" ]; then
pin_or_pw="$PIN"
elif [ -n "$keyid" ]; then
pin_or_pw="$(keyctl pipe "$keyid")"
else
err "Use %u:sdbootutil, PIN or --ask-pin to provide the TPM2 PIN"
fi
elif [ "$arg_method" = "password" ]; then
keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
if [ -n "$arg_ask_key_pin_or_pw" ]; then
ask_new_password "password" pin_or_pw
elif [ -n "$PW" ]; then
pin_or_pw="$PW"
elif [ -n "$keyid" ]; then
pin_or_pw="$(keyctl pipe "$keyid")"
else
err "Use %u:sdbootutil, PW or --ask-pw to provide the password"
fi
fi
for dev in "${tracked_devices[@]}"; do
enroll_device "$dev" "$pin_or_pw"
done
}
unenroll_pcrlock()
{
systemctl --quiet disable sdbootutil-update-predictions.service || true
pcrlock remove-policy &> /dev/null || true
rm -fr /var/lib/pcrlock.d
rm -f /var/lib/systemd/pcrlock.json
rm -f "${boot_root}${boot_dst}/pcrlock.json"
rm -f /var/lib/sdbootutil/measure-pcr-prediction
rm -f /var/lib/sdbootutil/measure-pcr-prediction.sha256
rm -f "${boot_root}${boot_dst}/measure-pcr-prediction"
rm -f "${boot_root}${boot_dst}/measure-pcr-prediction.sha256"
}
unenroll_pcr_oracle()
{
rm -f /etc/systemd/tpm2-pcr-private-key.pem
rm -f /etc/systemd/tpm2-pcr-public-key.pem
rm -f /etc/systemd/tpm2-pcr-signature.json
rm -f "${boot_root}${boot_dst}/tpm2-pcr-public-key.pem"
rm -f "${boot_root}${boot_dst}/tpm2-pcr-signature.json"
rm -f /var/lib/sdbootutil/measure-pcr-prediction
rm -f /var/lib/sdbootutil/measure-pcr-prediction.sha256
rm -f "${boot_root}${boot_dst}/measure-pcr-prediction"
rm -f "${boot_root}${boot_dst}/measure-pcr-prediction.sha256"
}
unenroll_device()
{
local dev="$1"
case "$arg_method" in
"tpm2"|"tpm2+pin")
systemd-cryptenroll \
--wipe-slot=tpm2 \
"$dev"
;;
"fido2")
systemd-cryptenroll \
--wipe-slot=fido2 \
"$dev"
;;
"password")
systemd-cryptenroll \
--wipe-slot=password \
"$dev"
;;
"recovery-key")
systemd-cryptenroll \
--wipe-slot=recovery \
"$dev"
;;
*)
err "Unexpected parameter for --method=: $arg_method"
;;
esac
}
unenroll()
{
[ -e /etc/crypttab ] || { info "/etc/crypttab not found. No encrypted devices?"; return 0; }
[ -e /usr/bin/systemd-cryptenroll ] || { info "systemd-cryptenroll not found"; return 0; }
detect_tracked_devices || { info "No LUKS2 devices found"; return 0; }
info "Unenrolling devices ($arg_method): ${tracked_devices[*]}"
# Prepare /etc/crypttab and update initrd if required
case "$arg_method" in
"tpm2"|"tpm2+pin")
have_tpm2 || err "No TPM2 found found"
remove_crypttab_option 'tpm2-device=auto'
remove_crypttab_option 'tpm2-measure-pcr=yes'
;;
"fido2")
have_fido2 || err "No FIDO2 key found"
remove_crypttab_option 'fido2-device=auto'
;;
esac
if [ "$arg_no_reuse_initrd" = "1" ]; then
install_all_kernels "$root_snapshot"
# Avoid the call of generate_tpm2_predictions at the
# end of the script
update_predictions=
fi
if [ "$arg_method" = "tpm2" ] || [ "$arg_method" = "tpm2+pin" ]; then
unenroll_pcrlock
unenroll_pcr_oracle
fi
for dev in "${tracked_devices[@]}"; do
unenroll_device "$dev"
done
}
eval_bootctl()
{
# XXX: bootctl should have json output for that too
# shellcheck disable=SC2016
eval "$(bootctl 2>/dev/null | sed -ne 's/Firmware Arch: *\(\w\+\)/firmware_arch="\1"/p;s/ *token: *\(\w\+\)/entry_token="\1"/p;s, *\$BOOT: *\([^ ]\+\).*,boot_root="\1",p')"
}
bootloader_name()
{
info "Checking the bootloader name"
if is_sdboot "${1:-$root_snapshot}"; then
echo "systemd-boot"
elif is_grub2_bls "${1:-$root_snapshot}"; then
echo "grub2-bls"
else
err "Bootloader not detected"
fi
}
set_image_name() {
[ -z "$image" ] || return
declare -gA arch_image_map=(
[x64]="vmlinuz"
[aa64]="Image"
[riscv64]="Image"
)
if [ -n "${arch_image_map[$firmware_arch]}" ]; then
image="${arch_image_map[$firmware_arch]}"
else
err "Unsupported architecture $firmware_arch"
fi
}
define_commands() {
declare -gA commands=(
[install]=""
[needs-update]=""
[update]=""
[force-update]=""
[add-kernel]="kernel"
[remove-kernel]="kernel"
[cleanup]=""
[set-default-snapshot]=""
[add-all-kernels]=""
[mkinitrd]=""
[remove-all-kernels]=""
[is-installed]=""
[list-snapshots]=""
[list-entries]=""
[list-kernels]=""
[list-devices]=""
[show-entry]="kernel"
[update-entry]="kernel"
[update-all-entries]=""
[is-bootable]=""
[set-default]="id"
[get-default]=""
[set-timeout]="seconds"
[get-timeout]=""
[enroll]=""
[unenroll]=""
[update-predictions]=""
[bootloader]=""
)
}
define_options() {
declare -gA options_with_arg=(
[help]=""
[verbose]=""
[esp-path]="_path"
[entry-token]="_path"
[arch]="_arch_name"
[image]="_image_name"
[entry-keys]="_find_kernels"
[no-variables]=""
[no-reuse-initrd]=""
[no-random-seed]=""
[all]=""
[sync]=""
[portable]=""
[secure-boot]=""
[removable]=""
[only-default]=""
[default-snapshot]=""
[ask-key]=""
[ask-pin]=""
[ask-pw]=""
[method]="_method"
[signed-policy]=""
[no-measure-pcr]=""
[measure-pcr]=""
[pcr]="_none"
[devices]="_devices"
)
opts_long=""
for opt in "${!options_with_arg[@]}"; do
if [ "${options_with_arg[$opt]}" ]; then
opts_long+="${opt}:,"
else
opts_long+="${opt},"
fi
done
opts_long="${opts_long%,}"
}
####### main #######
if [ "$1" = "_print_bash_completion_data" ]; then
declare -f set_image_name
declare -f eval_bootctl
declare -f define_commands
declare -f define_options
exit 0
fi
define_options
getopt_tmp=$(getopt -o hv --long "$opts_long" -n "${0##*/}" -- "$@")
eval set -- "$getopt_tmp"
while true ; do
case "$1" in
-h|--help) helpandquit ;;
-v|--verbose) verbose=$((++verbose)); shift ;;
--esp-path) arg_esp_path="$2"; shift 2 ;;
--arch) arg_arch="$2"; shift 2 ;;
--entry-token) arg_entry_token="$2"; shift 2 ;;
--image) image="$2"; shift 2 ;;
--entry-keys) IFS=',' read -r -a arg_entry_keys <<<"$2"; shift 2 ;;
--no-variables) arg_no_variables=1; shift ;;
--no-reuse-initrd) arg_no_reuse_initrd=1; shift ;;
--no-random-seed) arg_no_random_seed=1; shift ;;
--all) arg_all_entries=1; shift ;;
--sync) arg_sync=1; shift ;;
--portable) arg_portable=1; shift ;;
--removable) arg_portable=1; shift ;;
--secure-boot) arg_secure_boot=1; shift ;;
--only-default) arg_only_default=1; shift ;;
--default-snapshot) arg_default_snapshot=1; shift ;;
--ask-key|--ask-pin|--ask-pw) arg_ask_key_pin_or_pw=1; shift ;;
--method) arg_method="$2"; shift 2 ;;
--signed-policy) arg_signed_policy=1; shift ;;
--no-measure-pcr) arg_no_measure_pcr=1; shift ;;
--measure-pcr) arg_measure_pcr=1; shift ;;
--pcr) arg_pcr="$2"; shift 2 ;;
--devices) IFS=',' read -r -a tracked_devices <<<"$2"; shift 2 ;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
if [ -z "$SYSTEMD_LOG_LEVEL" ] && [ "${verbose:-0}" -gt 1 ]; then
if [ "$verbose" -gt 2 ]; then
SYSTEMD_LOG_LEVEL=debug
else
SYSTEMD_LOG_LEVEL=info
fi
export SYSTEMD_LOG_LEVEL
fi
define_commands
if [ -z "$1" ]; then
helpandquit
elif [ -z ${commands["$1"]+yes} ]; then
err "unknown command $1"
fi
[ -n "$arg_esp_path" ] && export SYSTEMD_ESP_PATH="$arg_esp_path"
eval_bootctl
read -r root_uuid root_device < <(findmnt / -v -r -n -o UUID,SOURCE)
root_device_is_crypt=
[ "$(lsblk --noheadings -o TYPE "$root_device")" = "crypt" ] && root_device_is_crypt=1
root_subvol=""
subvol_prefix=""
if [ "$(stat -f -c %T /)" = "btrfs" ] && [ -d /.snapshots ]; then
have_snapshots=1
root_subvol=$(btrfs subvol show / 2>/dev/null|head -1)
subvol_prefix="${root_subvol%/.snapshots/*}"
fi
root_snapshot=""
if [ -n "$have_snapshots" ]; then
if [ -n "$arg_default_snapshot" ]; then
[ -s "$snapperfile" ] || update_snapper
read -r root_snapshot <<<"$(jq -r '.root[]|select(.default==true)|.number' < "$snapperfile")"
else
root_snapshot="${root_subvol#"${subvol_prefix}"/.snapshots/}"
root_snapshot="${root_snapshot%/snapshot}"
fi
fi
if [ -n "$arg_esp_path" ] && [ "$boot_root" != "$arg_esp_path" ]; then
err "mismatch of esp path"
fi
[ -n "$arg_arch" ] && firmware_arch="$arg_arch"
[ -n "$boot_root" ] || err "No ESP detected. Legacy system?"
[ -n "$root_uuid" ] || err "Can't determine root UUID"
[ -n "$root_subvol" ] || [ -z "$have_snapshots" ] || err "Can't determine root subvolume"
[ -n "$root_device" ] || err "Can't determine root device"
[ -n "$firmware_arch" ] || err "Can't determine firmware arch"
set_image_name
mountpoint -q "$boot_root" || err "$boot_root is not a valid mountpoint"
dbg_var "root_snapshot"
dbg_var "boot_root"
# shellcheck disable=SC1091
[ -e /etc/sysconfig/bootloader ] && . /etc/sysconfig/bootloader
if is_sdboot; then
boot_dst="/EFI/systemd"
elif is_grub2_bls; then
set_os_release "${root_snapshot}"
# shellcheck disable=SC2154
read -r -a name <<<"${os_release_NAME,,}"
boot_dst="/EFI/${name[0]}"
else
msg="Bootloader not detected"
[ -z "$LOADER_TYPE" ] || msg+=". /etc/sysconfig/bootloader has LOADER_TYPE=\"$LOADER_TYPE\", but only \"systemd-boot\" or \"grub2-bls\" are recognized."
err "$msg"
fi
# Removable media layout is described in
# https://github.com/rhboot/shim/blob/main/README.fallback
if [ -n "$arg_portable" ]; then
if [ -d "${boot_root}${boot_dst}" ]; then
err "Bootloader is already installed permanently"
fi
boot_dst="/EFI/BOOT"
fi
# When we are doing an operation different from installation, the
# boot_dst should be already present. We can use it to identify a
# removable installation
if [ "$1" != "install" ] && [ ! -d "${boot_root}${boot_dst}" ]; then
info "Removable installation detected"
boot_dst="/EFI/BOOT"
arg_portable=1
fi
dbg_var "boot_dst"
if [ "$SECURE_BOOT" = "yes" ] || is_shim_installed; then
arg_secure_boot=1
fi
if [ "$UPDATE_NVRAM" = "no" ]; then
arg_no_variables=1
fi
# Keep initial components before they are replaced by some actions
# (new initrd, new entry, etc)
if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
backup_initial_components
fi
case "$1" in
install)
install_bootloader "${2:-$root_snapshot}" ;;
needs-update)
bootloader_needs_update "${2:-$root_snapshot}" ;;
update)
bootloader_update "${2:-$root_snapshot}" ;;
force-update)
if is_installed; then
install_bootloader "${2:-$root_snapshot}"
else
:
fi ;;
bootloader)
bootloader_name "${2:-$root_snapshot}" ;;
add-kernel)
install_kernel "${3:-$root_snapshot}" "$2" ;;
add-all-kernels)
install_all_kernels "${2:-$root_snapshot}" ;;
mkinitrd)
arg_no_reuse_initrd=1
install_all_kernels "${2:-$root_snapshot}" ;;
remove-kernel)
remove_kernel "${3:-$root_snapshot}" "$2" ;;
remove-all-kernels)
remove_all_kernels "${2:-$root_snapshot}" ;;
cleanup)
cleanup_entries "${2:-}" ;;
set-default-snapshot)
set_default_snapshot "${2:-$root_snapshot}" ;;
is-installed)
if is_installed; then
info "systemd-boot was installed using sdbootutil"
exit 0
else
info "not installed using this tool"
exit 1
fi ;;
list-kernels)
list_kernels "${2:-$root_snapshot}" ;;
list-entries)
list_entries "${2:-}" ;;
list-snapshots)
list_snapshots ;;
list-devices)
list_devices ;;
show-entry)
show_entry_fields "${3:-$root_snapshot}" "$2" ;;
update-entry)
update_entry "${3:-$root_snapshot}" "$2" ;;
update-all-entries)
update_all_entries "${2:-$root_snapshot}" ;;
is-bootable)
is_bootable "${2:-$root_snapshot}" ;;
set-default)
set_default_entry "$2" ;;
get-default)
get_default_entry "$2" ;;
set-timeout)
set_timeout "$2" ;;
get-timeout)
get_timeout "$2" ;;
enroll)
enroll ;;
unenroll)
unenroll ;;
update-predictions)
update_predictions=1 ;;
*)
helpandquit ;;
esac
[ -z "$update_predictions" ] || generate_tpm2_predictions
07070100000016000081ED00000000000000000000000168B8435A00000BCB000000000000000000000000000000000000003300000000sdbootutil-1+git20250903.f5a076b/sdbootutil-enroll#!/bin/bash
get_credential() {
local var="${1:?}"
local name="${2:?}"
local keyid
keyid="$(keyctl id %user:"$name" 2> /dev/null)" || true
if [ -n "$CREDENTIALS_DIRECTORY" ] && [ -e "$CREDENTIALS_DIRECTORY/$name" ]; then
read -r "$var" < "$CREDENTIALS_DIRECTORY/$name"
elif [ -n "$keyid" ]; then
read -r "$var" <<<"$(keyctl pipe "$keyid")"
fi
}
have_luks2() {
lsblk --noheadings -o FSTYPE | grep -q crypto_LUKS
}
write_issue_file()
{
local recovery_key="$1"
local issuefile="/run/issue.d/90-recovery-key.issue"
[ -x '/usr/sbin/issue-generator' ] && issuefile="/run/issue.d/90-recovery-key.conf"
mkdir -p "/run/issue.d"
echo "$recovery_key" > "$issuefile"
[ -x '/usr/sbin/issue-generator' ] && issue-generator
}
[ ! -e "/var/lib/YaST2/reconfig_system" ] || exit 0
have_luks2 || exit 0
# disk-encryption-tool-dracut uses "cryptenroll" for the keyring
# parameter, if we use a different one we need to move it back to
# "cryptenroll", to do the enrollment via sdbootutil without
# requesting a password
enroll_keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || exit 0
[ -n "$enroll_keyid" ] || {
echo "Enrollment key not registered in the keyring. Aborting" > /dev/stderr
exit 1
}
# Proceed with the enrollment
rk=
get_credential rk "sdbootutil-enroll.rk"
pw=
get_credential pw "sdbootutil-enroll.pw"
tpm2_pin=
get_credential tpm2_pin "sdbootutil-enroll.tpm2+pin"
tpm2=
get_credential tpm2 "sdbootutil-enroll.tpm2"
fido2=
get_credential fido2 "sdbootutil-enroll.fido2"
[ -z "$rk" ] || {
echo "Enrolling recovery key"
# Note that if --no-reuse-initrd is used, then a new initrd
# will be created and will break the measurement of the
# initial components if later the TPM2 enrollment is called
extra=
if [ -z "$tpm2_pin" ] && [ -z "$tpm2" ] && [ -z "$fido2" ]; then
extra="--no-reuse-initrd"
fi
recovery_key="$(sdbootutil enroll --method=recovery-key "$extra")"
write_issue_file "$recovery_key"
}
[ -z "$pw" ] || {
echo "Enrolling password"
# Note that if --no-reuse-initrd is used, then a new initrd
# will be created and will break the measurement of the
# initial components if later the TPM2 enrollment is called
extra=
if [ -z "$tpm2_pin" ] && [ -z "$tpm2" ] && [ -z "$fido2" ]; then
extra="--no-reuse-initrd"
fi
PW="$pw" sdbootutil enroll --method=password "$extra"
}
if [ -n "$tpm2_pin" ]; then
echo "Enrolling TPM2 with PIN"
SDB_ADD_INITIAL_COMPONENT=1 PIN="$crypt_tpm_pin" sdbootutil enroll --method=tpm2+pin
elif [ -n "$tpm2" ]; then
echo "Enrolling TPM2"
SDB_ADD_INITIAL_COMPONENT=1 sdbootutil enroll --method=tpm2
fi
[ -z "$fido2" ] || {
echo "Enrolling a FIDO2 key"
sdbootutil enroll --method=fido2
}
# Clean the enrollment key. disk-encryption-tool creates it in the
# keyslot 0 with the name "enrollment-key", that is showed by
# systemd-cryptenroll as "other"
while read -r dev; do
slots=$(systemd-cryptenroll "$dev")
if grep -q "other" <<<"$slots"; then
systemd-cryptenroll --wipe-slot=0 "$dev"
fi
done < <(sdbootutil list-devices)
07070100000017000081A400000000000000000000000168B8435A00000144000000000000000000000000000000000000003B00000000sdbootutil-1+git20250903.f5a076b/sdbootutil-enroll.service[Unit]
Description=Enroll encrypted root disk
DefaultDependencies=false
After=jeos-firstboot.service
#ConditionPathExists=/var/lib/YaST2/enroll_system
[Service]
Type=oneshot
RemainAfterExit=yes
KeyringMode=shared
ExecStart=/usr/bin/sdbootutil-enroll
ImportCredential=sdbootutil-enroll.*
[Install]
WantedBy=default.target07070100000018000081A400000000000000000000000168B8435A00000105000000000000000000000000000000000000004700000000sdbootutil-1+git20250903.f5a076b/sdbootutil-update-predictions.service[Unit]
Description=Update TPM predictions
ConditionSecurity=tpm2
[Service]
Type=oneshot
RemainAfterExit=yes
KeyringMode=shared
ExecStart=/usr/bin/sdbootutil update-predictions
ImportCredential=sdbootutil-update-predictions.*
[Install]
WantedBy=default.target
07070100000019000081A400000000000000000000000168B8435A0000228C000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/sdbootutil.spec#
# spec file for package sdbootutil
#
# Copyright (c) 2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
%global rustflags '-Clink-arg=-Wl,-z,relro,-z,now'
Name: sdbootutil
Version: 0
Release: 0
Summary: bootctl wrapper for BLS boot loaders
License: MIT
URL: https://github.com/openSUSE/sdbootutil
Source: %{name}-%{version}.tar
Source1: vendor.tar.zst
Source2: config.toml
BuildRequires: cargo
BuildRequires: cargo-packaging
BuildRequires: libopenssl-devel
BuildRequires: systemd-rpm-macros
Requires: %{name}-dracut-measure-pcr
Requires: dialog
Requires: dracut-pcr-signature
Requires: efibootmgr
Requires: jq
Requires: keyutils
Requires: openssl
Requires: pcr-oracle
Requires: qrencode
Requires: sed
Requires: (%{name}-snapper if (snapper and btrfsprogs))
Requires: (%{name}-tukit if read-only-root-fs)
# While systemd-pcrlock is in experimental
Requires: systemd-experimental
# something needs to require it. Can be us.
Requires: tpm2.0-tools
# While bootctl is in udev
Requires: udev
Supplements: (grub2-x86_64-efi-bls and shim)
Supplements: (systemd-boot and shim)
# Because uhmac it is not a noarch package
# BuildArch: noarch
ExclusiveArch: aarch64 riscv64 x86_64
%{?systemd_requires}
%description
bootctl wrapper for BLS boot loaders, like systemd-boot and grub2-bls.
Implements also the life cycle of a full disk encryption installation,
based on systemd.
%package snapper
Summary: plugin script for snapper
Requires: %{name} = %{version}
Requires: btrfsprogs
Requires: snapper
BuildArch: noarch
%description snapper
Plugin scripts for snapper to handle BLS config files
%package tukit
Summary: plugin script for tukit
Requires: %{name} = %{version}
Requires: tukit
BuildArch: noarch
%description tukit
Plugin scripts for tukit to handle BLS config files
%package kernel-install
Summary: Hook script for kernel-install
Requires: %{name} = %{version}
# While kernel-install is in udev
Requires: udev
BuildArch: noarch
%description kernel-install
Plugin script for kernel-install. Note: installation of this
package may disable other plugin scripts that are incompatible.
%package enroll
Summary: Full disk encryption enrollment
Requires: %{name} = %{version}
BuildArch: noarch
%description enroll
Systemd service and script for full disk encryption enrollment.
%package jeos-firstboot-enroll
Summary: JEOS module for full disk encryption enrollment
Requires: %{name} = %{version}
Requires: %{name}-enroll = %{version}
Requires: jeos-firstboot
BuildArch: noarch
%description jeos-firstboot-enroll
JEOS module for full disk encryption enrollment. The module
present the different options and delegate into sdbootutil-enroll
service the effective enrollment.
%package bash-completion
Summary: Bash completions for sdbootutil
Requires: %{name} = %{version}
Requires: bash
Requires: bash-completion
BuildArch: noarch
%description bash-completion
Bash completions script for sdbootutil.
Allows the user to press TAB to see available commands,
options and parameters.
%package dracut-measure-pcr
Summary: Dracut module to measure PCR 15
BuildRequires: pkgconfig
BuildRequires: rpm-config-SUSE
BuildRequires: pkgconfig(dracut)
BuildArch: noarch
%description dracut-measure-pcr
Dracut module from sdbootutil to measure PCR 15 in non-UKIs systems
%prep
%autosetup -a1 -p1
mv vendor uhmac
cd uhmac
mkdir .cargo
install -D -m 644 %{SOURCE2} .cargo/config.toml
%build
cd uhmac
%{cargo_build}
%install
install -D -m 755 %{name} %{buildroot}%{_bindir}/%{name}
# Install uhmac binary
pushd uhmac
%{cargo_install}
install -D -m 755 %{buildroot}%{_bindir}/uhmac %{buildroot}%{_libexecdir}/%{name}/uhmac
rm %{buildroot}%{_bindir}/uhmac
popd
# Update prediction service
install -D -m 644 %{name}-update-predictions.service \
%{buildroot}%{_unitdir}/%{name}-update-predictions.service
# Enrollment service
install -m 755 %{name}-enroll %{buildroot}%{_bindir}/%{name}-enroll
install -D -m 644 %{name}-enroll.service %{buildroot}/%{_unitdir}/%{name}-enroll.service
# Jeos module
install -D -m 644 jeos-firstboot-enroll-override.conf \
%{buildroot}%{_prefix}/lib/systemd/system/jeos-firstboot.service.d/jeos-firstboot-enroll-override.conf
install -D -m 644 jeos-firstboot-enroll %{buildroot}%{_datadir}/jeos-firstboot/modules/enroll
# Snapper
install -D -m 755 10-%{name}.snapper %{buildroot}%{_prefix}/lib/snapper/plugins/10-%{name}.snapper
# Tukit
install -D -m 755 10-%{name}.tukit %{buildroot}%{_prefix}/lib/tukit/plugins/10-%{name}.tukit
# kernel-install
install -D -m 755 50-%{name}.install %{buildroot}%{_prefix}/lib/kernel/install.d/50-%{name}.install
# Bash completions
install -D -m 755 completions/bash_sdbootutil %{buildroot}%{_datadir}/bash-completion/completions/sdbootutil
# Dracut module
install -D -m 755 module-setup.sh %{buildroot}%{_prefix}/lib/dracut/modules.d/50measure-pcr/module-setup.sh
install -D -m 755 measure-pcr-generator.sh %{buildroot}%{_prefix}/lib/dracut/modules.d/50measure-pcr/measure-pcr-generator.sh
install -D -m 755 measure-pcr-validator.sh %{buildroot}%{_prefix}/lib/dracut/modules.d/50measure-pcr/measure-pcr-validator.sh
install -D -m 644 measure-pcr-validator.service %{buildroot}/%{_prefix}/lib/dracut/modules.d/50measure-pcr/measure-pcr-validator.service
install -d -m 700 %{buildroot}%{_sharedstatedir}/%{name}
# tmpfiles
install -D -m 755 kernel-install-%{name}.conf \
%{buildroot}%{_prefix}/lib/tmpfiles.d/kernel-install-%{name}.conf
%transfiletriggerin -- %{_prefix}/lib/systemd/boot/efi %{_datadir}/grub2/%{_build_arch}-efi %{_datadir}/efi/%{_build_arch}
cat > /dev/null || :
[ "$YAST_IS_RUNNING" != 'instsys' ] || exit 0
[ -e /sys/firmware/efi/efivars ] || exit 0
[ -z "$TRANSACTIONAL_UPDATE" ] || exit 0
[ -z "$VERBOSE_FILETRIGGERS" ] || echo "%{name}-%{version}-%{release}: updating bootloader"
if [ -e /etc/sysconfig/bootloader ]; then
. /etc/sysconfig/bootloader &> /dev/null
if [ "$LOADER_TYPE" = "grub2-bls" ] || [ "$LOADER_TYPE" = "systemd-boot" ]; then
sdbootutil update
fi
else
sdbootutil update
fi
%preun
%service_del_preun %{name}-update-predictions.service
%postun
%service_del_postun %{name}-update-predictions.service
%pre
%service_add_pre %{name}-update-predictions.service
%post
%service_add_post %{name}-update-predictions.service
%preun enroll
%service_del_preun %{name}-enroll.service
%postun enroll
%service_del_postun %{name}-enroll.service
%pre enroll
%service_add_pre %{name}-enroll.service
%post enroll
%service_add_post %{name}-enroll.service
%posttrans kernel-install
%tmpfiles_create kernel-install-%{name}.conf
%post dracut-measure-pcr
%{?regenerate_initrd_post}
%posttrans dracut-measure-pcr
%{?regenerate_initrd_posttrans}
%postun dracut-measure-pcr
%{?regenerate_initrd_post}
%files
%license LICENSE
%dir %{_sharedstatedir}/%{name}
%{_bindir}/%{name}
%{_unitdir}/%{name}-update-predictions.service
%dir %{_libexecdir}/%{name}
%{_libexecdir}/%{name}/uhmac
%files snapper
%dir %{_prefix}/lib/snapper
%dir %{_prefix}/lib/snapper/plugins
%{_prefix}/lib/snapper/plugins/*
%files tukit
%dir %{_prefix}/lib/tukit
%dir %{_prefix}/lib/tukit/plugins
%{_prefix}/lib/tukit/plugins/*
%files kernel-install
%dir %{_prefix}/lib/kernel
%dir %{_prefix}/lib/kernel/install.d
%{_prefix}/lib/kernel/install.d/*
%{_prefix}/lib/tmpfiles.d/kernel-install-%{name}.conf
%files enroll
%{_bindir}/%{name}-enroll
%{_unitdir}/%{name}-enroll.service
%files jeos-firstboot-enroll
%dir %{_datadir}/jeos-firstboot
%dir %{_datadir}/jeos-firstboot/modules
%{_datadir}/jeos-firstboot/modules/enroll
%dir %{_unitdir}/jeos-firstboot.service.d
%{_unitdir}/jeos-firstboot.service.d/jeos-firstboot-enroll-override.conf
%files bash-completion
%dir %{_datadir}/bash-completion
%dir %{_datadir}/bash-completion/completions
%{_datadir}/bash-completion/completions/sdbootutil
%files dracut-measure-pcr
%dir %{_prefix}/lib/dracut
%dir %{_prefix}/lib/dracut/modules.d
%{_prefix}/lib/dracut/modules.d/50measure-pcr
%changelog
0707010000001A000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002700000000sdbootutil-1+git20250903.f5a076b/uhmac0707010000001B000081A400000000000000000000000168B8435A00002638000000000000000000000000000000000000003200000000sdbootutil-1+git20250903.f5a076b/uhmac/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "cc"
version = "1.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "uhmac"
version = "0.1.0"
dependencies = [
"clap",
"hex",
"openssl",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
0707010000001C000081A400000000000000000000000168B8435A000000A3000000000000000000000000000000000000003200000000sdbootutil-1+git20250903.f5a076b/uhmac/Cargo.toml[package]
name = "uhmac"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.38", features = ["derive"] }
hex = "0.4.3"
openssl = "0.10.72"
0707010000001D000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002B00000000sdbootutil-1+git20250903.f5a076b/uhmac/src0707010000001E000081A400000000000000000000000168B8435A0000071E000000000000000000000000000000000000003300000000sdbootutil-1+git20250903.f5a076b/uhmac/src/main.rsuse std::fs;
use std::io;
use std::io::Read;
use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::sign::Signer;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
/// Digest used to calculate the HMAC
digest: Digest,
/// File with the secret key as an hex string
key: PathBuf,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Digest {
SHA1,
SHA256,
SHA384,
SHA512,
}
fn main() -> io::Result<()> {
let cli = Cli::parse();
let key = PKey::hmac(
&hex::decode(fs::read_to_string(&cli.key)?.trim()).expect("valid hexadecimal string"),
)
.expect("valid HMAC key");
let mut message = String::new();
io::stdin().read_to_string(&mut message)?;
let mut signer;
let result = match cli.digest {
Digest::SHA1 => {
signer = Signer::new(MessageDigest::sha1(), &key).unwrap();
signer.update(message.as_bytes()).unwrap();
hex::encode(signer.sign_to_vec().unwrap())
}
Digest::SHA256 => {
signer = Signer::new(MessageDigest::sha256(), &key).unwrap();
signer.update(message.as_bytes()).unwrap();
hex::encode(signer.sign_to_vec().unwrap())
}
Digest::SHA384 => {
signer = Signer::new(MessageDigest::sha384(), &key).unwrap();
signer.update(message.as_bytes()).unwrap();
hex::encode(signer.sign_to_vec().unwrap())
}
Digest::SHA512 => {
signer = Signer::new(MessageDigest::sha512(), &key).unwrap();
signer.update(message.as_bytes()).unwrap();
hex::encode(signer.sign_to_vec().unwrap())
}
};
println!("{result}");
Ok(())
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!347 blocks