From: Daan De Meyer Date: Fri, 4 Oct 2024 14:19:29 +0000 (+0200) Subject: Allow signing expected PCRs independently of using secure boot X-Git-Tag: v25~237^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F3102%2Fhead;p=thirdparty%2Fmkosi.git Allow signing expected PCRs independently of using secure boot --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index e444cbde8..8775933d2 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -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) diff --git a/mkosi/config.py b/mkosi/config.py index eb2341582..37782c40e 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -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)} diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index 4f704d727..42a96c2c7 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -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` diff --git a/tests/test_json.py b/tests/test_json.py index 4ce60ed4c..21be8a404 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -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,