]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
SecureBoot: add support for signing with an hardware token
authorLuca Boccassi <bluca@debian.org>
Sat, 10 Feb 2024 12:27:11 +0000 (12:27 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 4 Mar 2024 18:46:43 +0000 (19:46 +0100)
Use ukify/sbsigntools native support for engines/providers

Co-authored-by: Daan De Meyer <daan.j.demeyer@gmail.com>
mkosi/__init__.py
mkosi/config.py
mkosi/resources/mkosi.md
tests/test_json.py

index ababf3adf60ea50cd2168ae51c484e5634889114..ebaf499068d798ead632c641f56099ff67d235fd 100644 (file)
@@ -33,6 +33,7 @@ from mkosi.config import (
     ConfigFeature,
     DocFormat,
     JsonEncoder,
+    KeySource,
     ManifestFormat,
     Network,
     OutputFormat,
@@ -858,22 +859,28 @@ def sign_efi_binary(context: Context, input: Path, output: Path) -> None:
         find_binary("sbsign", root=context.config.tools()) is not None
     ):
         with open(output, "wb") as f:
+            cmd: list[PathString] = [
+                "sbsign",
+                "--key", context.config.secure_boot_key,
+                "--cert", context.config.secure_boot_certificate,
+                "--output", "/dev/stdout",
+            ]
+            options: list[PathString] = [
+                "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,
+                "--ro-bind", input, input,
+            ]
+            if context.config.secure_boot_key_source.type == KeySource.Type.engine:
+                cmd += ["--engine", context.config.secure_boot_key_source.source]
+            if context.config.secure_boot_key.exists():
+                options += ["--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key]
+            cmd += [input]
             run(
-                [
-                    "sbsign",
-                    "--key", context.config.secure_boot_key,
-                    "--cert", context.config.secure_boot_certificate,
-                    "--output", "/dev/stdout",
-                    input,
-                ],
+                cmd,
                 stdout=f,
                 sandbox=context.sandbox(
-                    options=[
-                        "--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key,
-                        "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,
-                        "--ro-bind", input, input,
-                    ]
-                ),
+                    options=options,
+                    devices=context.config.secure_boot_key_source.type != KeySource.Type.file,
+                )
             )
     elif (
         context.config.secure_boot_sign_tool == SecureBootSignTool.pesign or
@@ -991,26 +998,31 @@ def install_systemd_boot(context: Context) -> None:
             # We reuse the key for all secure boot databases to keep things simple.
             for db in ["PK", "KEK", "db"]:
                 with umask(~0o600), open(keys / f"{db}.auth", "wb") as f:
+                    cmd: list[PathString] = [
+                        "sbvarsign",
+                        "--attr",
+                            "NON_VOLATILE,BOOTSERVICE_ACCESS,RUNTIME_ACCESS,TIME_BASED_AUTHENTICATED_WRITE_ACCESS",
+                        "--key", context.config.secure_boot_key,
+                        "--cert", context.config.secure_boot_certificate,
+                        "--output", "/dev/stdout",
+                    ]
+                    options: list[PathString] = [
+                        "--ro-bind",
+                        context.config.secure_boot_certificate,
+                        context.config.secure_boot_certificate,
+                        "--ro-bind", context.workspace / "mkosi.esl", context.workspace / "mkosi.esl",
+                    ]
+                    if context.config.secure_boot_key_source.type == KeySource.Type.engine:
+                        cmd += ["--engine", context.config.secure_boot_key_source.source]
+                    if context.config.secure_boot_key.exists():
+                        options += ["--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key]
+                    cmd += [db, context.workspace / "mkosi.esl"]
                     run(
-                        [
-                            "sbvarsign",
-                            "--attr",
-                                "NON_VOLATILE,BOOTSERVICE_ACCESS,RUNTIME_ACCESS,TIME_BASED_AUTHENTICATED_WRITE_ACCESS",
-                            "--key", context.config.secure_boot_key,
-                            "--cert", context.config.secure_boot_certificate,
-                            "--output", "/dev/stdout",
-                            db,
-                            context.workspace / "mkosi.esl",
-                        ],
+                        cmd,
                         stdout=f,
                         sandbox=context.sandbox(
-                            options=[
-                                "--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key,
-                                "--ro-bind",
-                                context.config.secure_boot_certificate,
-                                context.config.secure_boot_certificate,
-                                "--ro-bind", context.workspace / "mkosi.esl", context.workspace / "mkosi.esl",
-                            ],
+                            options=options,
+                            devices=context.config.secure_boot_key_source.type != KeySource.Type.file,
                         ),
                     )
 
@@ -1887,9 +1899,12 @@ def build_uki(
                 context.config.secure_boot_certificate,
             ]
             options += [
-                "--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key,
                 "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,
             ]
+            if context.config.secure_boot_key_source.type == KeySource.Type.engine:
+                cmd += ["--signing-engine", context.config.secure_boot_key_source.source]
+            if context.config.secure_boot_key.exists():
+                options += ["--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key]
         else:
             pesign_prepare(context)
             cmd += [
@@ -1924,7 +1939,13 @@ def build_uki(
         options += ["--ro-bind", initrd, initrd]
 
     with complete_step(f"Generating unified kernel image for kernel version {kver}"):
-        run(cmd, sandbox=context.sandbox(options=options))
+        run(
+            cmd,
+            sandbox=context.sandbox(
+                options=options,
+                devices=context.config.secure_boot_key_source.type != KeySource.Type.file,
+            ),
+        )
 
 
 def want_efi(config: Config) -> bool:
index 40e647707111c6d4c03dd7f8ed9acd67b0905953..1195161728a03043fc46b14135de1f9f1b64032d 100644 (file)
@@ -482,6 +482,13 @@ def parse_path(value: str,
     return path
 
 
+def config_parse_key(value: Optional[str], old: Optional[str]) -> Optional[Path]:
+    if not value:
+        return None
+
+    return parse_path(value, secret=True) if Path(value).exists() else Path(value)
+
+
 def make_tree_parser(absolute: bool = True) -> Callable[[str], ConfigTree]:
     def parse_tree(value: str) -> ConfigTree:
         src, sep, tgt = value.partition(':')
@@ -994,6 +1001,32 @@ def config_parse_minimum_version(value: Optional[str], old: Optional[GenericVers
     return max(old, new)
 
 
+@dataclasses.dataclass(frozen=True)
+class KeySource:
+    class Type(StrEnum):
+        file   = enum.auto()
+        engine = enum.auto()
+
+    type: Type
+    source: str = ""
+
+    def __str__(self) -> str:
+        return f"{self.type}:{self.source}" if self.source else str(self.type)
+
+
+def config_parse_key_source(value: Optional[str], old: Optional[KeySource]) -> Optional[KeySource]:
+    if not value:
+        return old
+
+    typ, _, source = value.partition(":")
+    try:
+        type = KeySource.Type(typ)
+    except ValueError:
+        die(f"'{value}' is not a valid key source")
+
+    return KeySource(type=type, source=source)
+
+
 @dataclasses.dataclass(frozen=True)
 class ConfigSetting:
     dest: str
@@ -1290,6 +1323,7 @@ class Config:
     secure_boot: bool
     secure_boot_auto_enroll: bool
     secure_boot_key: Optional[Path]
+    secure_boot_key_source: KeySource
     secure_boot_certificate: Optional[Path]
     secure_boot_sign_tool: SecureBootSignTool
     verity_key: Optional[Path]
@@ -2309,11 +2343,19 @@ SETTINGS = (
     ),
     ConfigSetting(
         dest="secure_boot_key",
-        metavar="PATH",
+        metavar="KEY",
         section="Validation",
-        parse=config_make_path_parser(secret=True),
+        parse=config_parse_key,
         paths=("mkosi.key",),
-        help="UEFI SecureBoot private key in PEM format",
+        help="UEFI SecureBoot private key",
+    ),
+    ConfigSetting(
+        dest="secure_boot_key_source",
+        section="Validation",
+        metavar="SOURCE[:ENGINE]",
+        parse=config_parse_key_source,
+        default=KeySource(type=KeySource.Type.file),
+        help="The source to use to retrieve the secure boot signing key",
     ),
     ConfigSetting(
         dest="secure_boot_certificate",
@@ -3603,6 +3645,7 @@ def summary(config: Config) -> str:
                     UEFI SecureBoot: {yes_no(config.secure_boot)}
          UEFI SecureBoot AutoEnroll: {yes_no(config.secure_boot_auto_enroll)}
              SecureBoot Signing Key: {none_to_none(config.secure_boot_key)}
+      SecureBoot Signing Key Source: {config.secure_boot_key_source}
              SecureBoot Certificate: {none_to_none(config.secure_boot_certificate)}
                SecureBoot Sign Tool: {config.secure_boot_sign_tool}
                  Verity Signing Key: {none_to_none(config.verity_key)}
@@ -3746,6 +3789,10 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[
     ) -> Optional[GenericVersion]:
         return GenericVersion(version) if version is not None else None
 
+    def key_source_transformer(keysource: dict[str, Any], fieldtype: type[KeySource]) -> KeySource:
+        assert "type" in keysource
+        return KeySource(type=KeySource.Type(keysource["type"]), source=keysource.get("source", ""))
+
     transformers = {
         Path: path_transformer,
         Optional[Path]: optional_path_transformer,
@@ -3772,6 +3819,7 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[
         GenericVersion: generic_version_transformer,
         Cacheonly: enum_transformer,
         Network: enum_transformer,
+        KeySource: key_source_transformer,
     }
 
     def json_transformer(key: str, val: Any) -> Any:
index 68fed222afbc425faab449b3a90b06d3588431be..f8eb980db695df056f310c010c4c154080a28441 100644 (file)
@@ -1336,7 +1336,13 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 `SecureBootKey=`, `--secure-boot-key=`
 
 : Path to the PEM file containing the secret key for signing the
-  UEFI kernel image, if `SecureBoot=` is used.
+  UEFI kernel image, if `SecureBoot=` is used. When `SecureBootKeySource=` is specified, the input
+  type depends on the source.
+
+`SecureBootKeySource=`, `--secure-boot-key-source=`
+
+: Source of `SecureBootKey=`, to support OpenSSL engines. E.g.:
+  `--secure-boot-key-source=engine:pkcs11`
 
 `SecureBootCertificate=`, `--secure-boot-certificate=`
 
index 001a116b4dac2923c5b02968c87f5351053909b3..12c1a01cc0de0b317d27dcd4b32d3fc8ac4fd0e4 100644 (file)
@@ -19,6 +19,7 @@ from mkosi.config import (
     ConfigFeature,
     ConfigTree,
     DocFormat,
+    KeySource,
     ManifestFormat,
     Network,
     OutputFormat,
@@ -260,6 +261,10 @@ def test_config() -> None:
             "SecureBootAutoEnroll": true,
             "SecureBootCertificate": null,
             "SecureBootKey": "/path/to/keyfile",
+            "SecureBootKeySource": {
+                "source": "",
+                "type": "file"
+            },
             "SecureBootSignTool": "pesign",
             "Seed": "7496d7d8-7f08-4a2b-96c6-ec8c43791b60",
             "ShimBootloader": "none",
@@ -411,6 +416,7 @@ def test_config() -> None:
         secure_boot_auto_enroll = True,
         secure_boot_certificate = None,
         secure_boot_key = Path("/path/to/keyfile"),
+        secure_boot_key_source = KeySource(type=KeySource.Type.file),
         secure_boot_sign_tool = SecureBootSignTool.pesign,
         seed = uuid.UUID("7496d7d8-7f08-4a2b-96c6-ec8c43791b60"),
         selinux_relabel = ConfigFeature.disabled,