From 5bf44dbca6718184a4273d00b65a1f3b573907c5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 20 Jan 2025 01:50:52 +0000 Subject: [PATCH] wip --- mkosi/__init__.py | 30 ++++++++++-- mkosi/config.py | 6 +++ mkosi/resources/mkosi-obs/mkosi.build | 54 +++++++++++++++++++++- mkosi/resources/mkosi-obs/mkosi.conf | 6 +++ mkosi/resources/mkosi-obs/mkosi.postoutput | 20 +++++--- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 5826cc4d8..9e02da0c5 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -1520,7 +1520,7 @@ def run_ukify( arguments: Sequence[PathString] = (), options: Sequence[PathString] = (), sign: bool = True, -) -> None: +) -> str: ukify = context.config.find_binary("ukify", "/usr/lib/systemd/ukify") if not ukify: die("Could not find ukify") @@ -1590,13 +1590,14 @@ def run_ukify( else: cmd += ["--secureboot-certificate", context.config.secure_boot_certificate] - run( + result = run( cmd, stdin=( sys.stdin if context.config.secure_boot_key_source.type != KeySourceType.file else subprocess.DEVNULL ), + stdout=subprocess.PIPE, env=context.config.environment, sandbox=context.sandbox( options=[*opt, *options], @@ -1604,6 +1605,8 @@ def run_ukify( ), ) + return result.stdout.strip() + def build_uki( context: Context, @@ -1615,7 +1618,7 @@ def build_uki( cmdline: Sequence[str], profiles: Sequence[Path], output: Path, -) -> None: +) -> Union[str, None]: if not (ukify := context.config.find_binary("ukify", "/usr/lib/systemd/ukify")): die("Could not find ukify") @@ -1686,6 +1689,19 @@ def build_uki( ] # fmt: skip else: arguments += ["--pcr-private-key", context.config.sign_expected_pcr_key] + elif ArtifactOutput.pcrs in context.config.split_artifacts: + assert context.config.sign_expected_pcr_certificate + + arguments += [ + "--json=short", + "--policy-digests", + "--pcr-public-key", workdir(context.config.sign_expected_pcr_certificate), + # As above + "--pcr-banks", "sha256", + ] # fmt: skip + options += [ + "--ro-bind", context.config.sign_expected_pcr_certificate, workdir(context.config.sign_expected_pcr_certificate), # noqa: E501 + ] # fmt: skip if microcodes: # new .ucode section support? @@ -1710,7 +1726,7 @@ def build_uki( options += ["--ro-bind", initrd, workdir(initrd)] with complete_step(f"Generating unified kernel image for kernel version {kver}"): - run_ukify(context, stub, output, cmdline=cmdline, arguments=arguments, options=options) + return run_ukify(context, stub, output, cmdline=cmdline, arguments=arguments, options=options) def systemd_stub_binary(context: Context) -> Path: @@ -2147,7 +2163,7 @@ def make_uki( ) initrds = [context.workspace / "initrd"] - build_uki( + pcrs = build_uki( context, stub, kver, @@ -2165,6 +2181,10 @@ def make_uki( if ArtifactOutput.initrd in context.config.split_artifacts: extract_pe_section(context, output, ".initrd", context.staging / context.config.output_split_initrd) + if ArtifactOutput.pcrs in context.config.split_artifacts: + with open(context.staging / context.config.output_split_pcrs, "w") as f: + f.write(pcrs) + def make_addon(context: Context, stub: Path, output: Path) -> None: make_cpio(context.root, context.workspace / "initrd", sandbox=context.sandbox) diff --git a/mkosi/config.py b/mkosi/config.py index 4c1de4d50..4869694a0 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -537,6 +537,7 @@ class ArtifactOutput(StrEnum): kernel = enum.auto() initrd = enum.auto() partitions = enum.auto() + pcrs = enum.auto() @staticmethod def compat_no() -> list["ArtifactOutput"]: @@ -1998,6 +1999,10 @@ def output_split_kernel(self) -> str: def output_split_initrd(self) -> str: return f"{self.output}.initrd" + @property + def output_split_pcrs(self) -> str: + return f"{self.output}.pcrs" + @property def output_nspawn_settings(self) -> str: return f"{self.output}.nspawn" @@ -2027,6 +2032,7 @@ def outputs(self) -> list[str]: self.output_split_uki, self.output_split_kernel, self.output_split_initrd, + self.output_split_pcrs, self.output_nspawn_settings, self.output_checksum, self.output_signature, diff --git a/mkosi/resources/mkosi-obs/mkosi.build b/mkosi/resources/mkosi-obs/mkosi.build index ab2962abc..8c746f664 100755 --- a/mkosi/resources/mkosi-obs/mkosi.build +++ b/mkosi/resources/mkosi-obs/mkosi.build @@ -14,7 +14,11 @@ if [ ! -f /usr/src/packages/SOURCES/mkosi.cpio.rsasign.sig ]; then exit 0 fi +cpio -t < /usr/src/packages/SOURCES/mkosi.cpio.rsasign.sig +mkdir -p hashes +pushd hashes cpio -idm < /usr/src/packages/SOURCES/mkosi.cpio.rsasign.sig +popd OUTPUTDIR=/work/src/usr/src/packages/OTHER @@ -52,5 +56,51 @@ do fi rm -f "$(basename "${infile}").sattrs" "$SIG" "$infile" -done < <(find . -type f \( -name '*efi.sig' -o -name 'vmlinu*.sig' \) -printf '%P\n') -rm -rf nss-db "$OUTPUTDIR"/*.sig +done < <(find hashes/ukis -type f \( -name '*efi.sig' -o -name 'vmlinu*.sig' \) -printf '%P\n') +rm -rf nss-db "$OUTPUTDIR"/*.sig hashes/ukis + +while read -r SIG +do + uki="$OUTPUTDIR/$(basename $(dirname ${SIG%.sig}))" + pcrs="${uki%.efi}.pcrs" + pol="$(basename ${SIG%.sig})" + + test -f "${pcrs}" + + jq --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}" + + 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 + +while read -r PCRS +do + uki="${PCRS%.pcrs}.efi" + # Attach the content of the PCRS file to the uki with objcopy in a .pcrsig section + objcopy --add-section .pcrsig="$PCRS" "$uki" "$uki" + mkdir -p hashes/ukis + pesign --force -n sql:"$nss_db" -i "$uki" -E "hashes/ukis/$f" +done < <(find "$OUTPUTDIR" -type f -name '*.pcrs') +rm -f "$OUTPUTDIR"/*.pcrs** + +# pack everything into a CPIO archive and place it where OBS expects it for the 3rd stage +if [ -d hashes/ukis ]; then + pushd hashes + find . -type f | cpio -H newc -o > "$OUTPUTDIR/mkosi.cpio.rsasign" + popd +fi +rm -rf hashes "$nss_db" diff --git a/mkosi/resources/mkosi-obs/mkosi.conf b/mkosi/resources/mkosi-obs/mkosi.conf index 867bf308e..392146c0c 100644 --- a/mkosi/resources/mkosi-obs/mkosi.conf +++ b/mkosi/resources/mkosi-obs/mkosi.conf @@ -1,3 +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 index ba72551fa..c3808cd22 100755 --- a/mkosi/resources/mkosi-obs/mkosi.postoutput +++ b/mkosi/resources/mkosi-obs/mkosi.postoutput @@ -10,10 +10,6 @@ set -e set -x -if [ -f /usr/src/packages/SOURCES/mkosi.cpio.rsasign.sig ]; then - exit 0 -fi - nss_db="$PWD/nss-db" # certutil will fail if it's called twice rm -rf "$nss_db" @@ -25,15 +21,25 @@ UNSIGNED=( "$(find "$OUTPUTDIR" -type f \( -name "*.efi" -o -name "vmlinu*" \) - for f in "${UNSIGNED[@]}" do - pesign --force -n sql:"$nss_db" -i "$OUTPUTDIR/$f" -E "hashes/$f" + if ukify --json=short inspect "${OUTPUTDIR}/${f}" | jq -e 'has(".linux")' > /dev/null && \ + ! ukify --json=short inspect "${OUTPUTDIR}/${f}" | jq -e 'has(".pcrsig")' > /dev/null; then + mkdir -p "hashes/pcrs/$f" + cat "${OUTPUTDIR}/${f%.efi}.pcrs" | jq -r 'to_entries[] | .value[].pol' | while read -r pol; do + echo -n "$pol" | base64 --decode > "hashes/pcrs/${f}/${pol}" + done + else + mkdir -p hashes/ukis + pesign --force -n sql:"$nss_db" -i "${OUTPUTDIR}/${f}" -E "hashes/ukis/$f" + fi done -rm -rf "$nss_db" # pack everything into a CPIO archive and place it where OBS expects it pushd hashes find . -type f | cpio -H newc -o > "$OUTPUTDIR/mkosi.cpio.rsasign" popd -rm -rf hashes +rm -rf hashes "$nss_db" + +cpio -t < "$OUTPUTDIR/mkosi.cpio.rsasign" cat <"$OUTPUTDIR/mkosi.conf" [Distribution]