]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add mkosi-obs conf and scripts for multi-stage signing 3377/head
authorLuca Boccassi <luca.boccassi@gmail.com>
Sat, 18 Jan 2025 19:39:16 +0000 (19:39 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Sat, 8 Feb 2025 18:56:52 +0000 (18:56 +0000)
Signs both PCR digests (including multi-profile) and UKIs
themselves. Requires new ukify.

REUSE.toml
mkosi/config.py
mkosi/resources/mkosi-obs/mkosi.build [new file with mode: 0755]
mkosi/resources/mkosi-obs/mkosi.conf [new file with mode: 0644]
mkosi/resources/mkosi-obs/mkosi.postoutput [new file with mode: 0755]
pyproject.toml

index d84d24343af878f51defde19b1274babfc378c9a..c277d5aea8b0980a49125f9ec85d75acbfb6d8c6 100644 (file)
@@ -13,6 +13,7 @@ path = [
     "docs/CNAME",
     "**.gitignore",
     "**.bash",
+    "**.build",
     "**.chroot",
     "**.conf",
     "**.css",
@@ -24,6 +25,7 @@ path = [
     "**.md",
     "**.png",
     "**.postinst",
+    "**.postoutput",
     "**.prepare",
     "**.preset",
     "**.py",
index b2a350627fec3abba65709bb63ce0dc3c53945b6..e8ca9e75ca2d947d01537eaec76f0b7b6cc6fadb 100644 (file)
@@ -55,7 +55,7 @@ ConfigParseCallback = Callable[[Optional[str], Optional[T]], Optional[T]]
 ConfigMatchCallback = Callable[[str, T], bool]
 ConfigDefaultCallback = Callable[[argparse.Namespace], T]
 
-BUILTIN_CONFIGS = ("mkosi-tools", "mkosi-initrd", "mkosi-vm", "mkosi-addon")
+BUILTIN_CONFIGS = ("mkosi-tools", "mkosi-initrd", "mkosi-vm", "mkosi-addon", "mkosi-obs")
 
 
 class Verb(StrEnum):
diff --git a/mkosi/resources/mkosi-obs/mkosi.build b/mkosi/resources/mkosi-obs/mkosi.build
new file mode 100755 (executable)
index 0000000..f297962
--- /dev/null
@@ -0,0 +1,114 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Second stage of build:
+# - signed hashes are in cpio archive in SOURCES/ together with artifacts from previous build
+# - if there are PCR policies to attach, do so and then prepare the hashes of the UKIs themselves
+# - if not, attach the signatures to the UKI(s) with pesign
+# - remove shasums of previous artifacts, given we are re-creating them after this step
+# - place artifacts from previous builds and signed UKI in output directory
+
+set -e
+
+if [ ! -f /usr/src/packages/SOURCES/hashes.cpio.rsasign.sig ]; then
+    exit 0
+fi
+
+echo "Signed files to be attached:"
+cpio -t </usr/src/packages/SOURCES/hashes.cpio.rsasign.sig
+cpio -t </usr/src/packages/SOURCES/hashes.cpio.rsasign
+mkdir -p hashes
+pushd hashes
+cpio -idm </usr/src/packages/SOURCES/hashes.cpio.rsasign.sig
+cpio -idm </usr/src/packages/SOURCES/hashes.cpio.rsasign
+popd
+
+OUTPUTDIR=/work/src/usr/src/packages/OTHER
+
+# OBS signs a hash without certificate information so it cannot simply be
+# attached to the PE binaries, certificate metadata has to be provided separately
+# so we need to create a certutil db and import the certificate manually
+rm -rf nss-db
+mkdir nss-db
+nss_db="$PWD/nss-db"
+certutil -N -d sql:"$nss_db" --empty-password
+certutil -A -d sql:"$nss_db" -n cert -t CT,CT,CT -i /usr/src/packages/SOURCES/_projectcert.crt
+
+cp -r /usr/src/packages/SOURCES/"$IMAGE_ID"* "$OUTPUTDIR"
+rm -f "$OUTPUTDIR/hashes.cpio.rsasign*" "$OUTPUTDIR"/*.sha*
+
+# First step: if there are UKI signatures, attach them
+while read -r SIG; do
+    infile="${SIG%.sig}"
+    sattrs="hashes/ukis/${SIG%.sig}"
+    test -s "$sattrs"
+
+    DEST="$OUTPUTDIR/${SIG#*/}"
+    DEST="${DEST%%.sig}"
+
+    # ensure the EFI hash matches before and after attaching the signature
+    old_hash=$(pesign -n sql:"$nss_db" -h -P -i "/usr/src/packages/SOURCES/$infile" | cut -d' ' -f1)
+
+    pesign -n sql:"$nss_db" --force -c cert -i "/usr/src/packages/SOURCES/$infile" -o "$DEST" -d sha256 -I "$sattrs" -R "hashes/ukis/$SIG"
+
+    new_hash=$(pesign -n sql:"$nss_db" -h -i "$DEST" | cut -d' ' -f1)
+    if [ "$old_hash" != "$new_hash" ]; then
+        echo "Pesign hash mismatch error: $old_hash $new_hash"
+        exit 1
+    fi
+
+    rm -f "$(basename "${infile}").sattrs" "$SIG" "$infile"
+done < <(find hashes/ukis hashes/kernels -type f \( -name '*efi.sig' -o -name 'vmlinu*.sig' \) -printf '%P\n')
+rm -rf nss-db "$OUTPUTDIR"/*.sig hashes/ukis
+
+# Second step: if there are PCR policy signatures, rebuild the JSON
+# blobs with the attached signatures
+while read -r SIG; do
+    uki="$OUTPUTDIR/$(basename "$(dirname "${SIG%.sig}")")"
+    pcrs="${uki%.efi}.pcrs"
+    pol="$(basename "${SIG%.sig}")"
+
+    test -f "${pcrs}"
+
+    jq -c --arg pol "$pol" --arg sig "$(base64 -w0 <"$SIG")" '
+        to_entries | map(
+            .value |= map(
+                if .pol == $pol then
+                    .sig = $sig
+                else
+                    .
+                end
+            )
+        ) | from_entries
+    ' "$pcrs" >"${pcrs}.new"
+    mv "${pcrs}.new" "$pcrs"
+    cp "$pcrs" "${pcrs}.sig"
+
+    rm -f "$SIG"
+done < <(find hashes/pcrs -type f -name '*.sig')
+rm -rf hashes/pcrs
+
+mkdir -p "$nss_db"
+certutil -N -d sql:"$nss_db" --empty-password
+
+# Third step: now that the JSON blob is rebuilt, merge it in the UKI
+while read -r PCRS; do
+    uki="${PCRS%.pcrs.sig}.efi"
+    ukify --json=short --pcrsig "@$PCRS" --join-pcrsig "$uki" --output "$uki.attached" build
+    mv "$uki.attached" "$uki"
+    mkdir -p hashes/ukis
+    pesign --force -n sql:"$nss_db" -i "$uki" -E "hashes/ukis/$(basename "$uki")"
+done < <(find "$OUTPUTDIR" -type f -name '*.pcrs.sig')
+rm -f "$OUTPUTDIR"/*.pcrs*
+
+# Fourth step: take hash of the UKIs after the signed JSON blobs have been merged
+# and prepare for the next iteration
+if [ -d hashes/ukis ]; then
+    pushd hashes
+    find . -type f | cpio -H newc -o >"$OUTPUTDIR/hashes.cpio.rsasign"
+    popd
+    cp /usr/src/packages/SOURCES/mkosi.conf "$OUTPUTDIR"
+    echo "Staging the following files for signing:"
+    cpio -t <"$OUTPUTDIR/hashes.cpio.rsasign"
+fi
+rm -rf hashes "$nss_db"
diff --git a/mkosi/resources/mkosi-obs/mkosi.conf b/mkosi/resources/mkosi-obs/mkosi.conf
new file mode 100644 (file)
index 0000000..392146c
--- /dev/null
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Build]
+SandboxTrees=/usr/src/packages/SOURCES:/usr/src/packages/SOURCES
+
+[Output]
+SplitArtifacts=pcrs
+
+[Validation]
+SignExpectedPcrCertificate=/usr/src/packages/SOURCES/_projectcert.crt
diff --git a/mkosi/resources/mkosi-obs/mkosi.postoutput b/mkosi/resources/mkosi-obs/mkosi.postoutput
new file mode 100755 (executable)
index 0000000..64b3b4f
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# End of first stage of build:
+# - built UKI is in $OUTPUTDIR
+# - get PCR policy digests if any, or PE hash(es) with pesign
+# - pack them up in a cpio as OBS expects and store them in OTHER/
+# - create minimal recipe for second stage that will continue from here
+
+set -e
+
+declare -a UKIS
+UKIS=( "$(find "$OUTPUTDIR" -type f -name "*.efi" -printf '%P\n')" )
+declare -a KERNELS
+KERNELS=( "$(find "$OUTPUTDIR" -type f -name "vmlinu*" -printf '%P\n')" )
+
+if ((${#UKIS[@]} == 0)) && ((${#KERNELS[@]} == 0)); then
+    echo "No unsigned files found, exiting"
+    exit 0
+fi
+
+nss_db="$PWD/nss-db"
+# certutil will fail if it's called twice
+rm -rf "$nss_db"
+mkdir -p "$nss_db" hashes
+certutil -N -d sql:"$nss_db" --empty-password
+
+for f in "${UKIS[@]}"; do
+    test -f "${OUTPUTDIR}/${f}" || continue
+    if [ -f "${OUTPUTDIR}/${f%.efi}.pcrs" ]; then
+        mkdir -p "hashes/pcrs/$f"
+        while read -r pol; do
+            echo -n "$pol" | tr '[:lower:]' '[:upper:]' | basenc --base16 --decode >"hashes/pcrs/${f}/${pol}"
+        done < <(jq -r 'to_entries[] | .value[].pol' <"${OUTPUTDIR}/${f%.efi}.pcrs")
+    else
+        mkdir -p hashes/ukis
+        pesign --force -n sql:"$nss_db" -i "${OUTPUTDIR}/${f}" -E "hashes/ukis/$f"
+    fi
+done
+
+for f in "${KERNELS[@]}"; do
+    test -f "${OUTPUTDIR}/${f}" || continue
+    mkdir -p hashes/kernels
+    pesign --force -n sql:"$nss_db" -i "${OUTPUTDIR}/${f}" -E "hashes/kernels/$f"
+done
+
+# Pack everything into a CPIO archive and place it where OBS expects it
+pushd hashes
+find . -type f | cpio -H newc -o >"$OUTPUTDIR/hashes.cpio.rsasign"
+popd
+rm -rf hashes "$nss_db"
+
+echo "Staging the following files for signing:"
+cpio -t <"$OUTPUTDIR/hashes.cpio.rsasign"
+
+# The second stage will not do a full rebuild, but only attach signatures to the existing UKI
+cat >"$OUTPUTDIR/mkosi.conf" <<EOF
+[Distribution]
+Distribution=custom
+[Output]
+Format=none
+ImageId=$IMAGE_ID
+[Include]
+Include=mkosi-obs
+[Build]
+BuildSources=/usr/src/packages/OTHER:/usr/src/packages/OTHER
+EOF
index d71386cc3e9112d4be327b23aac25bb0a4b6af70..2aa36685b1e97ad3af06ff07f675f763aac624cb 100644 (file)
@@ -38,6 +38,7 @@ packages = [
     "man/*",
     "mkosi-addon/**/*",
     "mkosi-initrd/**/*",
+    "mkosi-obs/**/*",
     "mkosi-tools/**/*",
     "mkosi-vm/**/*",
     "repart/**/*",