Skip to content

Commit

Permalink
Allow specifying OpenPGP implementation to use for signing
Browse files Browse the repository at this point in the history
  • Loading branch information
wiktor-k committed Oct 3, 2024
1 parent 601b152 commit 49e8075
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ jobs:
- name: Install
run: |
sudo apt-get update
sudo apt-get install python3-pytest lvm2 cryptsetup-bin btrfs-progs
sudo apt-get install python3-pytest lvm2 cryptsetup-bin btrfs-progs sqop
# Make sure the latest changes from the pull request are used.
sudo ln -svf $PWD/bin/mkosi /usr/bin/mkosi
working-directory: ./
Expand Down
1 change: 1 addition & 0 deletions mkosi.conf.d/20-arch.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ Packages=
python
qemu-user-static
shim
sequoia-sop
1 change: 1 addition & 0 deletions mkosi.conf.d/20-fedora/mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Packages=
perf
qemu-user-static
rpmautospec
sequoia-sop
1 change: 1 addition & 0 deletions mkosi.conf.d/20-ubuntu/mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Repositories=universe
[Content]
Packages=
linux-tools-generic
sqop
1 change: 1 addition & 0 deletions mkosi.conf.d/30-debian-kali-ubuntu/mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ Packages=
python3
qemu-user-static
shim-signed
sqop
39 changes: 39 additions & 0 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,14 @@ def calculate_signature(context: Context) -> None:
if not context.config.sign or not context.config.checksum:
return

pgptool = context.config.openpgp_tool
if pgptool == "gpg":
calculate_signature_gpg(context)
else:
calculate_signature_sop(context)


def calculate_signature_gpg(context: Context) -> None:
cmdline: list[PathString] = ["gpg", "--detach-sign", "--pinentry-mode", "loopback"]

# Need to specify key before file to sign
Expand Down Expand Up @@ -2203,6 +2211,37 @@ def calculate_signature(context: Context) -> None:
)


def calculate_signature_sop(context: Context) -> None:
pgptool = context.config.openpgp_tool
signing_key = context.config.key
if signing_key is None:
die("Signing key is mandatory when using SOP signing")

cmdline: list[PathString] = [pgptool, "sign", "/signing-key.pgp"]

options: list[PathString] = [
"--bind", signing_key, "/signing-key.pgp",
"--bind", context.staging, workdir(context.staging),
"--bind", "/run", "/run",
] # fmt: skip

with (
complete_step("Signing SHA256SUMS…"),
open(context.staging / context.config.output_checksum, "rb") as i,
open(context.staging / context.config.output_signature, "wb") as o,
):
run(
cmdline,
env=context.config.environment,
stdin=i,
stdout=o,
sandbox=context.sandbox(
binary=pgptool,
options=options,
),
)


def dir_size(path: Union[Path, os.DirEntry[str]]) -> int:
dir_sum = 0
for entry in os.scandir(path):
Expand Down
10 changes: 9 additions & 1 deletion mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1632,7 +1632,8 @@ class Config:
passphrase: Optional[Path]
checksum: bool
sign: bool
key: Optional[str]
openpgp_tool: str
key: Optional[Union[Path, str]]

tools_tree: Optional[Path]
tools_tree_distribution: Optional[Distribution]
Expand Down Expand Up @@ -2856,6 +2857,12 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple
section="Validation",
help="GPG key to use for signing",
),
ConfigSetting(
dest="openpgp_tool",
section="Validation",
default="gpg",
help="OpenPGP implementation to use for signing",
),
# Build section
ConfigSetting(
dest="tools_tree",
Expand Down Expand Up @@ -4445,6 +4452,7 @@ def summary(config: Config) -> str:
Passphrase: {none_to_none(config.passphrase)}
Checksum: {yes_no(config.checksum)}
Sign: {yes_no(config.sign)}
OpenPGP Tool: ({config.openpgp_tool or "gpg"})
GPG Key: ({"default" if config.key is None else config.key})
"""

Expand Down
15 changes: 11 additions & 4 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import subprocess
import sys
import uuid
from collections.abc import Iterator, Sequence
from collections.abc import Iterator, Mapping, Sequence
from pathlib import Path
from types import TracebackType
from typing import Any, Optional
Expand Down Expand Up @@ -60,6 +60,7 @@ def mkosi(
user: Optional[int] = None,
group: Optional[int] = None,
check: bool = True,
env: Optional[Mapping[str, str]] = None,
) -> CompletedProcess:
return run(
[
Expand All @@ -74,10 +75,15 @@ def mkosi(
stdout=sys.stdout,
user=user,
group=group,
env=os.environ,
env=env or os.environ,
) # fmt: skip

def build(self, options: Sequence[PathString] = (), args: Sequence[str] = ()) -> CompletedProcess:
def build(
self,
options: Sequence[PathString] = (),
args: Sequence[str] = (),
env: Optional[Mapping[str, str]] = None,
) -> CompletedProcess:
kcl = [
"loglevel=6",
"systemd.log_level=debug",
Expand All @@ -100,7 +106,7 @@ def build(self, options: Sequence[PathString] = (), args: Sequence[str] = ()) ->
*options,
] # fmt: skip

self.mkosi("summary", opt, user=self.uid, group=self.uid)
self.mkosi("summary", opt, user=self.uid, group=self.uid, env=env)

return self.mkosi(
"build",
Expand All @@ -109,6 +115,7 @@ def build(self, options: Sequence[PathString] = (), args: Sequence[str] = ()) ->
stdin=sys.stdin if sys.stdin.isatty() else None,
user=self.uid,
group=self.gid,
env=env,
)

def boot(self, options: Sequence[str] = (), args: Sequence[str] = ()) -> CompletedProcess:
Expand Down
2 changes: 2 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def test_config() -> None:
"MinimumVersion": "123",
"Mirror": null,
"NSpawnSettings": null,
"OpenpgpTool": "gpg",
"Output": "outfile",
"OutputDirectory": "/your/output/here",
"OutputMode": 83,
Expand Down Expand Up @@ -434,6 +435,7 @@ def test_config() -> None:
minimum_version=GenericVersion("123"),
mirror=None,
nspawn_settings=None,
openpgp_tool="gpg",
output="outfile",
output_dir=Path("/your/output/here"),
output_format=OutputFormat.uki,
Expand Down
60 changes: 60 additions & 0 deletions tests/test_signing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: LGPL-2.1-or-later


from collections.abc import Mapping
from pathlib import Path

import pytest

from mkosi.run import run

from . import Image, ImageConfig

pytestmark = pytest.mark.integration


def test_signing_checksums_with_sop(config: ImageConfig, tmp_path: Path) -> None:
signing_key = tmp_path / "signing-key.pgp"
signing_cert = tmp_path / "signing-cert.pgp"

# create a brand new signing key
with open(signing_key, "wb") as o:
run(cmdline=["sqop", "generate-key", "--signing-only", "Test"], stdout=o)

# extract public key (certificate)
with open(signing_key, "rb") as i, open(signing_cert, "wb") as o:
run(cmdline=["sqop", "extract-cert"], stdin=i, stdout=o)

with Image(config) as image:
image.build(
options=["--checksum=true", "--openpgp-tool=sqop", "--sign=true", f"--key={signing_key}"]
)

signed_file = image.output_dir / "image.SHA256SUMS"
signature = image.output_dir / "image.SHA256SUMS.gpg"

with open(signed_file, "rb") as i:
run(cmdline=["sqop", "verify", signature, signing_cert], stdin=i)


def test_signing_checksums_with_gpg(config: ImageConfig, tmp_path: Path) -> None:
signing_key = "mkosi-test@example.org"
signing_cert = tmp_path / "signing-cert.pgp"

env: Mapping[str, str] = dict(GNUPGHOME=str(tmp_path / ".gnupg"))

# create a brand new signing key
run(cmdline=["gpg", "--quick-gen-key", "--batch", "--passphrase", "", signing_key], env=env)

# export public key (certificate)
with open(signing_cert, "wb") as o:
run(cmdline=["gpg", "--export", signing_key], env=env, stdout=o)

with Image(config) as image:
image.build(options=["--checksum=true", "--sign=true", f"--key={signing_key}"], env=env)

signed_file = image.output_dir / "image.SHA256SUMS"
signature = image.output_dir / "image.SHA256SUMS.gpg"

with open(signed_file, "rb") as i:
run(cmdline=["sqop", "verify", signature, signing_cert], stdin=i)

0 comments on commit 49e8075

Please sign in to comment.