]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Allow signing expected PCRs independently of using secure boot 3102/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 4 Oct 2024 14:19:29 +0000 (16:19 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 4 Oct 2024 17:35:04 +0000 (19:35 +0200)
mkosi/__init__.py
mkosi/config.py
mkosi/resources/man/mkosi.1.md
tests/test_json.py

index e444cbde894204632b7178be9a84550867dd5054..8775933d23e7977441853e62557474dfe93ea943 100644 (file)
@@ -1477,6 +1477,8 @@ def want_signed_pcrs(config: Config) -> bool:
     return config.sign_expected_pcr == ConfigFeature.enabled or (
         config.sign_expected_pcr == ConfigFeature.auto
         and config.find_binary("systemd-measure", "/usr/lib/systemd/systemd-measure") is not None
+        and bool(config.sign_expected_pcr_key)
+        and bool(config.sign_expected_pcr_certificate)
     )
 
 
@@ -1593,24 +1595,28 @@ def build_uki(
 
         arguments += ["--sign-kernel"]
 
-        if want_signed_pcrs(context.config):
+    if want_signed_pcrs(context.config):
+        assert context.config.sign_expected_pcr_key
+        assert context.config.sign_expected_pcr_certificate
+        arguments += [
+            "--pcr-private-key", context.config.sign_expected_pcr_key,
+            # SHA1 might be disabled in OpenSSL depending on the distro so we opt to not sign
+            # for SHA1 to avoid having to manage a bunch of configuration to re-enable SHA1.
+            "--pcr-banks", "sha256",
+        ]  # fmt: skip
+        if context.config.sign_expected_pcr_key.exists():
+            options += ["--bind", context.config.sign_expected_pcr_key, context.config.sign_expected_pcr_key]
+        if context.config.sign_expected_pcr_key_source.type == KeySourceType.engine:
             arguments += [
-                "--pcr-private-key", context.config.secure_boot_key,
-                # SHA1 might be disabled in OpenSSL depending on the distro so we opt to not sign
-                # for SHA1 to avoid having to manage a bunch of configuration to re-enable SHA1.
-                "--pcr-banks", "sha256",
+                "--signing-engine", context.config.sign_expected_pcr_key_source.source,
+                "--pcr-public-key", context.config.sign_expected_pcr_certificate,
+            ]  # fmt: skip
+            options += [
+                "--ro-bind",
+                context.config.sign_expected_pcr_certificate,
+                context.config.sign_expected_pcr_certificate,
+                "--bind-try", "/run/pcscd", "/run/pcscd",
             ]  # fmt: skip
-            if context.config.secure_boot_key.exists():
-                options += ["--bind", context.config.secure_boot_key, context.config.secure_boot_key]
-            if context.config.secure_boot_key_source.type == KeySourceType.engine:
-                arguments += [
-                    "--signing-engine", context.config.secure_boot_key_source.source,
-                    "--pcr-public-key", context.config.secure_boot_certificate,
-                ]  # fmt: skip
-                options += [
-                    "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,  # noqa
-                    "--bind-try", "/run/pcscd", "/run/pcscd",
-                ]  # fmt: skip
 
     if microcodes:
         # new .ucode section support?
@@ -2376,15 +2382,30 @@ def check_inputs(config: Config) -> None:
     if config.secure_boot and not config.secure_boot_key:
         die(
             "SecureBoot= is enabled but no secure boot key is configured",
-            hint="Run mkosi genkey to generate a secure boot key/certificate pair",
+            hint="Run mkosi genkey to generate a key/certificate pair",
         )
 
     if config.secure_boot and not config.secure_boot_certificate:
         die(
-            "SecureBoot= is enabled but no secure boot key is configured",
-            hint="Run mkosi genkey to generate a secure boot key/certificate pair",
+            "SecureBoot= is enabled but no secure boot certificate is configured",
+            hint="Run mkosi genkey to generate a key/certificate pair",
         )
 
+    if config.sign_expected_pcr == ConfigFeature.enabled and not config.sign_expected_pcr_key:
+        die(
+            "SignExpectedPcr= is enabled but no private key is configured",
+            hint="Run mkosi genkey to generate a key/certificate pair",
+        )
+
+    if config.sign_expected_pcr == ConfigFeature.enabled and not config.sign_expected_pcr_certificate:
+        die(
+            "SignExpectedPcr= is enabled but no certificate is configured",
+            hint="Run mkosi genkey to generate a key/certificate pair",
+        )
+
+    if config.secure_boot_key_source != config.sign_expected_pcr_key_source:
+        die("Secure boot key source and expected PCR signatures key source have to be the same")
+
 
 def check_tool(config: Config, *tools: PathString, reason: str, hint: Optional[str] = None) -> Path:
     tool = config.find_binary(*tools)
index eb23415826b40d578d0349599d5740e8ed807293..37782c40e71f76afb42c1bca5a98701360ec1eac 100644 (file)
@@ -1624,6 +1624,9 @@ class Config:
     verity_key_source: KeySource
     verity_certificate: Optional[Path]
     sign_expected_pcr: ConfigFeature
+    sign_expected_pcr_key: Optional[Path]
+    sign_expected_pcr_key_source: KeySource
+    sign_expected_pcr_certificate: Optional[Path]
     passphrase: Optional[Path]
     checksum: bool
     sign: bool
@@ -2780,6 +2783,33 @@ SETTINGS = (
         help="Measure the components of the unified kernel image (UKI) and "
         "embed the PCR signature into the UKI",
     ),
+    ConfigSetting(
+        dest="sign_expected_pcr_key",
+        metavar="KEY",
+        section="Validation",
+        parse=config_parse_key,
+        paths=("mkosi.key",),
+        help="Private key for signing expected PCR signature",
+        scope=SettingScope.universal,
+    ),
+    ConfigSetting(
+        dest="sign_expected_pcr_key_source",
+        section="Validation",
+        metavar="SOURCE[:ENGINE]",
+        parse=config_parse_key_source,
+        default=KeySource(type=KeySourceType.file),
+        help="The source to use to retrieve the expected PCR signing key",
+        scope=SettingScope.universal,
+    ),
+    ConfigSetting(
+        dest="sign_expected_pcr_certificate",
+        metavar="PATH",
+        section="Validation",
+        parse=config_make_path_parser(),
+        paths=("mkosi.crt",),
+        help="Certificate for signing expected PCR signature in X509 format",
+        scope=SettingScope.universal,
+    ),
     ConfigSetting(
         dest="passphrase",
         metavar="PATH",
@@ -4447,6 +4477,9 @@ def summary(config: Config) -> str:
           Verity Signing Key Source: {config.verity_key_source}
                  Verity Certificate: {none_to_none(config.verity_certificate)}
                  Sign Expected PCRs: {config.sign_expected_pcr}
+          Expected PCRs Signing Key: {none_to_none(config.sign_expected_pcr_key)}
+           Expected PCRs Key Source: {config.sign_expected_pcr_key_source}
+          Expected PCRs Certificate: {none_to_none(config.sign_expected_pcr_certificate)}
                          Passphrase: {none_to_none(config.passphrase)}
                            Checksum: {yes_no(config.checksum)}
                                Sign: {yes_no(config.sign)}
index 4f704d727c57209d197839298ac4b262e44b3729..42a96c2c78cdfdf10ae045962c35b4a898bc2e30 100644 (file)
@@ -1145,6 +1145,18 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
     `systemd-measure` binary is in `PATH`.  Depends on `SecureBoot=`
     being enabled and key from `SecureBootKey=`.
 
+`SignExpectedPcrKey=`, `--sign-expected-pcr-key=`
+:   Path to the PEM file containing the secret key for signing the expected PCR signatures.
+    When `SignExpectedPcrKeySource=` is specified, the input type depends on
+    the source.
+
+`SignExpectedPcrKeySource=`, `--sign-expected-key-source=`
+:   Source of `VerityKey=`, to support OpenSSL engines. E.g.:
+    `--verity-key-source=engine:pkcs11`
+
+`SignExpectedPcrCertificate=`, `--sign-expected-pcr-certificate=`
+:   Path to the X.509 file containing the certificate for signing the expected PCR signatures.
+
 `Passphrase=`, `--passphrase`
 :   Specify the path to a file containing the passphrase to use for LUKS
     encryption. It should contain the passphrase literally, and not end in
@@ -2608,6 +2620,9 @@ and cannot be configured in subimages:
 - `VerityCertificate=`
 - `VerityKey=`
 - `VerityKeySource=`
+- `SignExpectedPcrCertificate=`
+- `SignExpectedPcrKey=`
+- `SignExpectedPcrSource=`
 - `VolatilePackageDirectories=`
 - `WithNetwork=`
 - `WithTests`
index 4ce60ed4cdd2fd8cdee41c6d732bdad12ec0b855..21be8a404c11f40f6730c857f9903228d14851c7 100644 (file)
@@ -308,6 +308,12 @@ def test_config() -> None:
             "ShimBootloader": "none",
             "Sign": false,
             "SignExpectedPcr": "disabled",
+            "SignExpectedPcrCertificate": "/my/cert",
+            "SignExpectedPcrKey": "/my/key",
+            "SignExpectedPcrKeySource": {
+                "Source": "",
+                "Type": "file"
+            },
             "SkeletonTrees": [
                 {
                     "Source": "/foo/bar",
@@ -505,6 +511,9 @@ def test_config() -> None:
         shim_bootloader=ShimBootloader.none,
         sign=False,
         sign_expected_pcr=ConfigFeature.disabled,
+        sign_expected_pcr_key=Path("/my/key"),
+        sign_expected_pcr_key_source=KeySource(type=KeySourceType.file),
+        sign_expected_pcr_certificate=Path("/my/cert"),
         skeleton_trees=[ConfigTree(Path("/foo/bar"), Path("/")), ConfigTree(Path("/bar/baz"), Path("/qux"))],
         source_date_epoch=12345,
         split_artifacts=True,