|
63 | 63 | ManifestFormat, |
64 | 64 | Network, |
65 | 65 | OutputFormat, |
| 66 | + PcrSigner, |
66 | 67 | SecureBootSignTool, |
67 | 68 | ShimBootloader, |
68 | 69 | Ssh, |
@@ -1583,8 +1584,10 @@ def want_signed_pcrs(config: Config) -> bool: |
1583 | 1584 | return config.sign_expected_pcr == ConfigFeature.enabled or ( |
1584 | 1585 | config.sign_expected_pcr == ConfigFeature.auto |
1585 | 1586 | and config.find_binary("systemd-measure", "/usr/lib/systemd/systemd-measure") is not None |
1586 | | - and bool(config.sign_expected_pcr_key) |
1587 | | - and bool(config.sign_expected_pcr_certificate) |
| 1587 | + and ( |
| 1588 | + (bool(config.sign_expected_pcr_key) and bool(config.sign_expected_pcr_certificate)) |
| 1589 | + or bool(config.sign_expected_pcr_with_phases) |
| 1590 | + ) |
1588 | 1591 | ) |
1589 | 1592 |
|
1590 | 1593 |
|
@@ -1755,73 +1758,98 @@ def build_uki( |
1755 | 1758 |
|
1756 | 1759 | arguments += ["--sign-kernel"] |
1757 | 1760 |
|
1758 | | - if want_signed_pcrs(context.config): |
1759 | | - assert context.config.sign_expected_pcr_key |
1760 | | - assert context.config.sign_expected_pcr_certificate |
| 1761 | + seen_bind_paths: set[Path] = set() |
1761 | 1762 |
|
| 1763 | + if want_signed_pcrs(context.config): |
1762 | 1764 | arguments += [ |
1763 | 1765 | # SHA1 might be disabled in OpenSSL depending on the distro so we opt to not sign |
1764 | 1766 | # for SHA1 to avoid having to manage a bunch of configuration to re-enable SHA1. |
1765 | 1767 | "--pcr-banks", "sha256", |
1766 | 1768 | ] # fmt: skip |
1767 | 1769 |
|
1768 | | - if ( |
1769 | | - systemd_tool_version( |
1770 | | - python_binary(context.config), |
1771 | | - ukify, |
1772 | | - sandbox=context.sandbox, |
1773 | | - ) |
1774 | | - >= "258" |
1775 | | - ): |
| 1770 | + ukify_version = systemd_tool_version( |
| 1771 | + python_binary(context.config), |
| 1772 | + ukify, |
| 1773 | + sandbox=context.sandbox, |
| 1774 | + ) |
| 1775 | + |
| 1776 | + if ukify_version >= "258": |
1776 | 1777 | cert_parameter = "--pcr-certificate" |
1777 | 1778 | else: |
1778 | 1779 | cert_parameter = "--pcr-public-key" |
1779 | 1780 |
|
1780 | | - # If we're providing the private key via an engine or provider, we have to pass in a X.509 |
1781 | | - # certificate via --pcr-certificate as well. |
1782 | | - if context.config.sign_expected_pcr_key_source.type != KeySourceType.file: |
1783 | | - if context.config.sign_expected_pcr_certificate_source.type == CertificateSourceType.provider: |
1784 | | - arguments += [ |
1785 | | - "--certificate-provider", |
1786 | | - f"provider:{context.config.sign_expected_pcr_certificate_source.source}", |
1787 | | - ] |
| 1781 | + if context.config.sign_expected_pcr_with_phases: |
| 1782 | + signers = context.config.sign_expected_pcr_with_phases |
| 1783 | + if ukify_version < "253": |
| 1784 | + die(f"ukify {ukify_version} does not support --phases (need >= 253)") |
| 1785 | + else: |
| 1786 | + assert context.config.sign_expected_pcr_key |
| 1787 | + assert context.config.sign_expected_pcr_certificate |
| 1788 | + signers = [ |
| 1789 | + PcrSigner( |
| 1790 | + key=context.config.sign_expected_pcr_key, |
| 1791 | + certificate=context.config.sign_expected_pcr_certificate, |
| 1792 | + phases=[], |
| 1793 | + key_source=context.config.sign_expected_pcr_key_source, |
| 1794 | + certificate_source=context.config.sign_expected_pcr_certificate_source, |
| 1795 | + ) |
| 1796 | + ] |
1788 | 1797 |
|
1789 | | - options += ["--bind", "/run", "/run"] |
| 1798 | + need_run_bind = False |
1790 | 1799 |
|
1791 | | - if context.config.sign_expected_pcr_certificate.exists(): |
1792 | | - arguments += [ |
1793 | | - cert_parameter, workdir(context.config.sign_expected_pcr_certificate), |
1794 | | - ] # fmt: skip |
1795 | | - options += [ |
1796 | | - "--ro-bind", context.config.sign_expected_pcr_certificate, workdir(context.config.sign_expected_pcr_certificate), # noqa: E501 |
1797 | | - ] # fmt: skip |
| 1800 | + for signer in signers: |
| 1801 | + # If we're providing the private key via an engine or provider, we have to pass in |
| 1802 | + # a X.509 certificate via --pcr-certificate as well. |
| 1803 | + if signer.key_source.type != KeySourceType.file: |
| 1804 | + if signer.certificate_source.type == CertificateSourceType.provider: |
| 1805 | + arguments += [ |
| 1806 | + "--certificate-provider", f"provider:{signer.certificate_source.source}", |
| 1807 | + ] # fmt: skip |
| 1808 | + |
| 1809 | + need_run_bind = True |
| 1810 | + |
| 1811 | + if signer.certificate.exists(): |
| 1812 | + arguments += [cert_parameter, workdir(signer.certificate)] |
| 1813 | + seen_bind_paths.add(signer.certificate) |
| 1814 | + else: |
| 1815 | + arguments += [cert_parameter, signer.certificate] |
| 1816 | + |
| 1817 | + if signer.key_source.type == KeySourceType.engine: |
| 1818 | + arguments += ["--signing-engine", signer.key_source.source] |
| 1819 | + elif signer.key_source.type == KeySourceType.provider: |
| 1820 | + arguments += ["--signing-provider", signer.key_source.source] |
| 1821 | + |
| 1822 | + if signer.key.exists(): |
| 1823 | + arguments += ["--pcr-private-key", workdir(signer.key)] |
| 1824 | + seen_bind_paths.add(signer.key) |
1798 | 1825 | else: |
1799 | | - arguments += [cert_parameter, context.config.sign_expected_pcr_certificate] |
| 1826 | + arguments += ["--pcr-private-key", signer.key] |
1800 | 1827 |
|
1801 | | - if context.config.sign_expected_pcr_key_source.type == KeySourceType.engine: |
1802 | | - arguments += ["--signing-engine", context.config.sign_expected_pcr_key_source.source] |
1803 | | - elif context.config.sign_expected_pcr_key_source.type == KeySourceType.provider: |
1804 | | - arguments += ["--signing-provider", context.config.sign_expected_pcr_key_source.source] |
| 1828 | + if signer.phases: |
| 1829 | + arguments += ["--phases", " ".join(signer.phases)] |
1805 | 1830 |
|
1806 | | - if context.config.sign_expected_pcr_key.exists(): |
1807 | | - arguments += ["--pcr-private-key", workdir(context.config.sign_expected_pcr_key)] |
1808 | | - options += [ |
1809 | | - "--ro-bind", context.config.sign_expected_pcr_key, workdir(context.config.sign_expected_pcr_key), # noqa: E501 |
1810 | | - ] # fmt: skip |
1811 | | - else: |
1812 | | - arguments += ["--pcr-private-key", context.config.sign_expected_pcr_key] |
| 1831 | + if need_run_bind: |
| 1832 | + options += ["--bind", "/run", "/run"] |
1813 | 1833 | elif ArtifactOutput.pcrs in context.config.split_artifacts: |
1814 | 1834 | assert context.config.sign_expected_pcr_certificate |
1815 | 1835 |
|
1816 | 1836 | json_out = True |
| 1837 | + cert = context.config.sign_expected_pcr_certificate |
1817 | 1838 | arguments += [ |
1818 | 1839 | "--policy-digest", |
1819 | 1840 | "--pcr-banks", "sha256", |
1820 | | - "--pcr-certificate", workdir(context.config.sign_expected_pcr_certificate), |
1821 | | - ] # fmt: skip |
1822 | | - options += [ |
1823 | | - "--ro-bind", context.config.sign_expected_pcr_certificate, workdir(context.config.sign_expected_pcr_certificate), # noqa: E501 |
| 1841 | + "--pcr-certificate", workdir(cert), |
1824 | 1842 | ] # fmt: skip |
| 1843 | + seen_bind_paths.add(cert) |
| 1844 | + |
| 1845 | + if (pcrpkey := context.config.sign_expected_pcr_uki_public_key) is not None: |
| 1846 | + if not pcrpkey.exists(): |
| 1847 | + die(f"SignExpectedPcrUKIPublicKey={pcrpkey} does not exist") |
| 1848 | + arguments += ["--pcrpkey", workdir(pcrpkey)] |
| 1849 | + seen_bind_paths.add(pcrpkey) |
| 1850 | + |
| 1851 | + for file in seen_bind_paths: |
| 1852 | + options += ["--ro-bind", file, workdir(file)] |
1825 | 1853 |
|
1826 | 1854 | if microcodes: |
1827 | 1855 | # new .ucode section support? |
@@ -2723,25 +2751,39 @@ def check_inputs(config: Config) -> None: |
2723 | 2751 | hint="Run mkosi genkey to generate a key/certificate pair", |
2724 | 2752 | ) |
2725 | 2753 |
|
2726 | | - if config.sign_expected_pcr == ConfigFeature.enabled and not config.sign_expected_pcr_key: |
| 2754 | + if ( |
| 2755 | + config.sign_expected_pcr == ConfigFeature.enabled |
| 2756 | + and not config.sign_expected_pcr_with_phases |
| 2757 | + and not config.sign_expected_pcr_key |
| 2758 | + ): |
2727 | 2759 | die( |
2728 | 2760 | "SignExpectedPcr= is enabled but no private key is configured", |
2729 | 2761 | hint="Run mkosi genkey to generate a key/certificate pair", |
2730 | 2762 | ) |
2731 | 2763 |
|
2732 | | - if config.sign_expected_pcr == ConfigFeature.enabled and not config.sign_expected_pcr_certificate: |
| 2764 | + if ( |
| 2765 | + config.sign_expected_pcr == ConfigFeature.enabled |
| 2766 | + and not config.sign_expected_pcr_with_phases |
| 2767 | + and not config.sign_expected_pcr_certificate |
| 2768 | + ): |
2733 | 2769 | die( |
2734 | 2770 | "SignExpectedPcr= is enabled but no certificate is configured", |
2735 | 2771 | hint="Run mkosi genkey to generate a key/certificate pair", |
2736 | 2772 | ) |
2737 | 2773 |
|
2738 | | - if config.secure_boot_key_source != config.sign_expected_pcr_key_source: |
2739 | | - die("Secure boot key source and expected PCR signatures key source have to be the same") |
| 2774 | + # When SignExpectedPcrWithPhases= is used the key/certificate sources live per-entry, so the |
| 2775 | + # scalar sign_expected_pcr_{key,certificate}_source fields default to "file" regardless of |
| 2776 | + # what the per-signer sources are. Comparing those scalars against secure boot's sources |
| 2777 | + # would be meaningless, so skip the check in that case and rely on ukify to reject |
| 2778 | + # inconsistent sources at invocation time. |
| 2779 | + if not config.sign_expected_pcr_with_phases: |
| 2780 | + if config.secure_boot_key_source != config.sign_expected_pcr_key_source: |
| 2781 | + die("Secure boot key source and expected PCR signatures key source have to be the same") |
2740 | 2782 |
|
2741 | | - if config.secure_boot_certificate_source != config.sign_expected_pcr_certificate_source: |
2742 | | - die( |
2743 | | - "Secure boot certificate source and expected PCR signatures certificate source have to be the same" # noqa: E501 |
2744 | | - ) # fmt: skip |
| 2783 | + if config.secure_boot_certificate_source != config.sign_expected_pcr_certificate_source: |
| 2784 | + die( |
| 2785 | + "Secure boot certificate source and expected PCR signatures certificate source have to be the same" # noqa: E501 |
| 2786 | + ) # fmt: skip |
2745 | 2787 |
|
2746 | 2788 | if config.verity == Verity.signed and not config.verity_key: |
2747 | 2789 | die( |
|
0 commit comments