diff --git a/man/systemd-keyring-setup.service.xml b/man/systemd-keyring-setup.service.xml index ad674204258..ee061d39674 100644 --- a/man/systemd-keyring-setup.service.xml +++ b/man/systemd-keyring-setup.service.xml @@ -113,8 +113,8 @@ OS/kernel-keyring/CONTEXT/x509/NAME-certificate.pem, and trust anchors from OS/trust-anchor-kernel-keyring/CONTEXT/x509/NAME-certificate.pem - below each load path. Both are enrolled into the same keyring. NAME may consist - of lowercase letters, digits, + below each load path. Both are enrolled into the same keyring, and only differ in how their usage is + checked, see below. NAME may consist of lowercase letters, digits, ., _ and -, other files are ignored. Each file must contain a single PEM-encoded certificate. A file that contains more than one certificate is ignored as a whole. CA certificates should be placed in separate files among the trust anchors instead. @@ -152,6 +152,13 @@ world-writable, and directories that are world-writable, are ignored. Once a directory owned by an unprivileged user has been entered, the lookup does not continue into files or directories owned by anyone else. + + As required by the VOA specification, the usage of each certificate is checked against its purpose. + An artifact verifier is ignored if it has an extended key usage extension that permits neither code signing + nor any extended key usage, or a key usage extension that does not permit digital signatures. A trust anchor + that is not a CA certificate is enrolled nonetheless, since self-signed certificates that act as their own + trust anchor are common, but a message is logged about it. Certificates with invalid extensions are + ignored. diff --git a/src/keyring-setup/keyring-setup.rs b/src/keyring-setup/keyring-setup.rs index e352cf55012..27a56b07dca 100644 --- a/src/keyring-setup/keyring-setup.rs +++ b/src/keyring-setup/keyring-setup.rs @@ -21,7 +21,7 @@ use systemd_shared::recurse_dir::{self, RECURSE_DIR_IGNORE_DOT, RECURSE_DIR_SORT use systemd_shared::sys::COMMAND_EXPERIMENTAL; use systemd_shared::table::{Table, TABLE_ERSATZ_DASH}; use systemd_shared::tmpfile::{LinkableTmpfile, LINK_TMPFILE_REPLACE}; -use systemd_shared::voa::{self, Lookup}; +use systemd_shared::voa::{self, Lookup, VoaMode}; use systemd_shared::x509::{self, Der, X509}; use systemd_shared::{creds, fd, json, sys}; use systemd_shared::{libcrypto_note, libkmod_note, log_openssl_errors, table_log_add_error}; @@ -227,6 +227,50 @@ impl Keyring { } } +/// As the VOA specification requires, usage of each certificate is checked against its purpose. An artifact +/// verifier whose extended key usage permits neither code signing nor any usage is not enrolled. A trust +/// anchor that is not a CA certificate is still enrolled and only logged about at notice level, since a +/// self-signed leaf certificate doubling as its own anchor is common. +fn check_usage(path: &CStr, x: &X509, mode: VoaMode) -> Result<()> { + let flags = x.extension_flags(); + if flags & sys::EXFLAG_INVALID != 0 { + return Err(log_warning_errno!( + SYNTHETIC_ERRNO(EKEYREJECTED), + "'{}' carries an invalid X.509 extension, ignoring.", + display(path) + )); + } + + if mode == voa::VOA_MODE_ARTIFACT_VERIFIER { + if x.extended_key_usage() + .is_some_and(|u| u & (sys::XKU_CODE_SIGN | sys::XKU_ANYEKU) == 0) + { + return Err(log_warning_errno!( + SYNTHETIC_ERRNO(EKEYREJECTED), + "'{}' is not a code signing certificate, ignoring.", + display(path) + )); + } + if x.key_usage().is_some_and(|u| u & sys::KU_DIGITAL_SIGNATURE == 0) { + return Err(log_warning_errno!( + SYNTHETIC_ERRNO(EKEYREJECTED), + "'{}' does not permit digital signatures, ignoring.", + display(path) + )); + } + } else if (flags & sys::EXFLAG_BCONS != 0 && flags & sys::EXFLAG_CA == 0) + || x.key_usage().is_some_and(|u| u & sys::KU_KEY_CERT_SIGN == 0) + { + // A trust anchor that is explicitly not a CA is common enough to only log a notice about + log_notice!( + "'{}' is not a CA certificate, but placed among the trust anchors.", + display(path) + ); + } + + Ok(()) +} + /// The kernel describes a certificate as ": ", with the subject key identifier or, lacking one, /// the serial number as hex. Returns the ": " part, separator included. fn certificate_description_suffix(x: &X509) -> Result { @@ -265,7 +309,7 @@ impl core::fmt::Display for Hex<'_> { } /// Returns true if the certificate was added, false if the same certificate was found before. -fn load_certificate(k: &mut Keyring, f: &ConfFile) -> Result { +fn load_certificate(k: &mut Keyring, f: &ConfFile, mode: VoaMode) -> Result { let path = f.original_path(); let text = f @@ -302,7 +346,8 @@ fn load_certificate(k: &mut Keyring, f: &ConfFile) -> Result { )); } - // The kernel takes DER + check_usage(path, &x, mode)?; + let Some(der) = x.to_der() else { return Err(log_openssl_errors!( LOG_WARNING, @@ -370,7 +415,7 @@ fn collect_certificates(k: &mut Keyring, root: BorrowedFd<'_>, os: &Strv) -> Res })?; for f in files.iter() { - match load_certificate(k, f) { + match load_certificate(k, f, mode) { Err(e @ Errno::ENOMEM) => return Err(e), Err(_) => k.data_error = true, Ok(_) => {}