ukify: add naming a certificate created by genkey
Image builds create a key separate from the SecureBoot one for verity signing. Its certificate ships inside the image as the sysext trust anchor. `genkey` certs had a hardcoded "SecureBoot signing key on host <fqdn of the build machine>" CN, which describes neither that verity role nor a meaningful hostname. Make that name configurable with `SecureBootCertificateCommonName=`, like mkosi's `--genkey-common-name`. That allows image build tools to re-use `ukify genkey` for non-SB keys. Unlike the implicit truncation for the default name, a specified one which is too long is an error. Add a citation where that limit comes frm.
This commit is contained in:
@@ -144,6 +144,10 @@
|
||||
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option>, and
|
||||
<varname>SecureBootPrivateKey=</varname>/<option>--secureboot-private-key=</option> below.</para>
|
||||
|
||||
<para>The generated certificate's name and lifetime are set with
|
||||
<varname>SecureBootCertificateCommonName=</varname>/<option>--secureboot-certificate-common-name=</option> and
|
||||
<varname>SecureBootCertificateValidity=</varname>/<option>--secureboot-certificate-validity=</option>.</para>
|
||||
|
||||
<para>The output files must not exist.</para>
|
||||
</refsect2>
|
||||
|
||||
@@ -591,6 +595,18 @@
|
||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SecureBootCertificateCommonName=<replaceable>CN</replaceable></varname></term>
|
||||
<term><option>--secureboot-certificate-common-name=<replaceable>CN</replaceable></option></term>
|
||||
|
||||
<listitem><para>The common name and issuer name (certificate is self-signed) of a
|
||||
certificate created by <command>genkey</command>. Defaults to <literal>SecureBoot signing key on host
|
||||
<replaceable>HOST</replaceable></literal> with the fully qualified domain name of the machine it runs on.
|
||||
Must not be empty or longer than 64 bytes (X.509 CN limit).</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SecureBootCertificateDir=<replaceable>SB_PATH</replaceable></varname></term>
|
||||
<term><option>--secureboot-certificate-dir=<replaceable>SB_PATH</replaceable></option></term>
|
||||
|
||||
@@ -1245,6 +1245,65 @@ def test_key_cert_generation(tmp_path):
|
||||
assert re.search(r'Issuer: CN\s?=\s?SecureBoot signing key on host', out)
|
||||
|
||||
|
||||
def test_key_cert_generation_common_name(tmp_path):
|
||||
opts = ukify.parse_args(
|
||||
[
|
||||
'genkey',
|
||||
f'--secureboot-private-key={tmp_path / "sb.priv.pem"}',
|
||||
f'--secureboot-certificate={tmp_path / "sb.cert.pem"}',
|
||||
'--secureboot-certificate-common-name=ukify test key',
|
||||
]
|
||||
)
|
||||
assert opts.verb == 'genkey'
|
||||
|
||||
pytest.importorskip('cryptography')
|
||||
|
||||
ukify.generate_keys(opts)
|
||||
|
||||
if not shutil.which('openssl'):
|
||||
return
|
||||
|
||||
out = subprocess.check_output(
|
||||
[
|
||||
'openssl', 'x509',
|
||||
'-in', tmp_path / 'sb.cert.pem',
|
||||
'-text',
|
||||
'-noout',
|
||||
],
|
||||
text=True,
|
||||
) # fmt: skip
|
||||
assert re.search(r'Subject: CN\s?=\s?ukify test key', out)
|
||||
assert re.search(r'Issuer: CN\s?=\s?ukify test key', out)
|
||||
|
||||
|
||||
def test_key_cert_generation_empty_common_name(tmp_path):
|
||||
opts = ukify.parse_args(
|
||||
[
|
||||
'genkey',
|
||||
f'--secureboot-private-key={tmp_path / "sb.priv.pem"}',
|
||||
f'--secureboot-certificate={tmp_path / "sb.cert.pem"}',
|
||||
'--secureboot-certificate-common-name=',
|
||||
]
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match='--secureboot-certificate-common-name= must not be empty'):
|
||||
ukify.generate_keys(opts)
|
||||
|
||||
|
||||
def test_key_cert_generation_common_name_too_long(tmp_path):
|
||||
opts = ukify.parse_args(
|
||||
[
|
||||
'genkey',
|
||||
f'--secureboot-private-key={tmp_path / "sb.priv.pem"}',
|
||||
f'--secureboot-certificate={tmp_path / "sb.cert.pem"}',
|
||||
f'--secureboot-certificate-common-name={"x" * 65}',
|
||||
]
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match='is longer than 64 bytes'):
|
||||
ukify.generate_keys(opts)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not slow_tests, reason='slow')
|
||||
def test_join_pcrsig(capsys, kernel_initrd, tmp_path):
|
||||
if kernel_initrd is None:
|
||||
|
||||
+28
-5
@@ -81,6 +81,9 @@ EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), [])
|
||||
DEFAULT_CONFIG_DIRS = ['/etc/systemd', '/run/systemd', '/usr/local/lib/systemd', '/usr/lib/systemd']
|
||||
DEFAULT_CONFIG_FILE = 'ukify.conf'
|
||||
|
||||
# https://datatracker.ietf.org/doc/html/rfc5280 ub-common-name; bytes, not chars
|
||||
COMMON_NAME_MAX_LEN = 64
|
||||
|
||||
|
||||
class Style:
|
||||
bold = '\033[0;1;39m' if sys.stderr.isatty() else ''
|
||||
@@ -297,6 +300,7 @@ class UkifyConfig:
|
||||
policy_digest: bool
|
||||
profile: Optional[str]
|
||||
sb_cert: Union[str, Path, None]
|
||||
sb_cert_common_name: Optional[str]
|
||||
sb_cert_name: Optional[str]
|
||||
sb_cert_validity: int
|
||||
sb_certdir: Path
|
||||
@@ -1682,12 +1686,24 @@ def generate_keys(opts: UkifyConfig) -> None:
|
||||
# This will generate keys and certificates and write them to the paths that
|
||||
# are specified as input paths.
|
||||
if opts.sb_key and opts.sb_cert:
|
||||
fqdn = socket.getfqdn()
|
||||
# The length of CN must not exceed 64 bytes
|
||||
if opts.sb_cert_common_name is not None:
|
||||
cn = opts.sb_cert_common_name
|
||||
if len(cn) == 0:
|
||||
raise ValueError('--secureboot-certificate-common-name= must not be empty')
|
||||
|
||||
cn = f'SecureBoot signing key on host {fqdn}'
|
||||
if len(cn) > 64:
|
||||
# The length of CN must not exceed 64 bytes
|
||||
cn = cn[:61] + '...'
|
||||
# Older cryptography versions (checked 41) don't check that by themselves.
|
||||
# Newer (at least 49) do, but with a much less useful error message, so keep this extra check.
|
||||
if len(cn.encode()) > COMMON_NAME_MAX_LEN:
|
||||
raise ValueError(
|
||||
f'--secureboot-certificate-common-name= is longer than {COMMON_NAME_MAX_LEN} bytes: {cn!r}'
|
||||
)
|
||||
else:
|
||||
fqdn = socket.getfqdn()
|
||||
|
||||
cn = f'SecureBoot signing key on host {fqdn}'
|
||||
if len(cn) > COMMON_NAME_MAX_LEN:
|
||||
cn = cn[: COMMON_NAME_MAX_LEN - 3] + '...'
|
||||
|
||||
key_pem, cert_pem = generate_key_cert_pair(
|
||||
common_name=cn,
|
||||
@@ -2203,6 +2219,13 @@ CONFIG_ITEMS = [
|
||||
),
|
||||
config_key='UKI/SecureBootCertificate',
|
||||
),
|
||||
ConfigItem(
|
||||
'--secureboot-certificate-common-name',
|
||||
metavar='CN',
|
||||
dest='sb_cert_common_name',
|
||||
help="common name of a certificate created by 'genkey'",
|
||||
config_key='UKI/SecureBootCertificateCommonName',
|
||||
),
|
||||
ConfigItem(
|
||||
'--secureboot-certificate-dir',
|
||||
dest='sb_certdir',
|
||||
|
||||
Reference in New Issue
Block a user