From 5cca978dae562763f27d96cdb6f5e23a11c48ee2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 7 Nov 2024 17:29:44 +0100 Subject: [PATCH 1/6] mkosi: Add pytest to tools --- mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf | 1 + mkosi.conf.d/05-tools/mkosi.conf.d/centos-fedora.conf | 1 + mkosi.conf.d/05-tools/mkosi.conf.d/debian-ubuntu.conf | 1 + mkosi.conf.d/05-tools/mkosi.conf.d/opensuse.conf | 1 + 4 files changed, 4 insertions(+) diff --git a/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf b/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf index 5188d045089..81a6ee3c974 100644 --- a/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf +++ b/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf @@ -9,5 +9,6 @@ ToolsTreePackages= libcap libmicrohttpd python-jinja + python-pytest tpm2-tss util-linux-libs diff --git a/mkosi.conf.d/05-tools/mkosi.conf.d/centos-fedora.conf b/mkosi.conf.d/05-tools/mkosi.conf.d/centos-fedora.conf index 349c87b6a02..be93f4c4776 100644 --- a/mkosi.conf.d/05-tools/mkosi.conf.d/centos-fedora.conf +++ b/mkosi.conf.d/05-tools/mkosi.conf.d/centos-fedora.conf @@ -15,3 +15,4 @@ ToolsTreePackages= pkgconfig(mount) tss2-devel python3-jinja2 + python3-pytest diff --git a/mkosi.conf.d/05-tools/mkosi.conf.d/debian-ubuntu.conf b/mkosi.conf.d/05-tools/mkosi.conf.d/debian-ubuntu.conf index 74cb1e90e6a..2550c0f96eb 100644 --- a/mkosi.conf.d/05-tools/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi.conf.d/05-tools/mkosi.conf.d/debian-ubuntu.conf @@ -15,3 +15,4 @@ ToolsTreePackages= libmount-dev libtss2-dev python3-jinja2 + python3-pytest diff --git a/mkosi.conf.d/05-tools/mkosi.conf.d/opensuse.conf b/mkosi.conf.d/05-tools/mkosi.conf.d/opensuse.conf index 2f4f7e1d606..09c279037bb 100644 --- a/mkosi.conf.d/05-tools/mkosi.conf.d/opensuse.conf +++ b/mkosi.conf.d/05-tools/mkosi.conf.d/opensuse.conf @@ -14,3 +14,4 @@ ToolsTreePackages= pkgconfig(mount) tss2-devel python3-jinja2 + python3-pytest From 4047b99c00eb9f5cdf2b6d1a6eb8b1a516421e07 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 6 Nov 2024 18:05:54 +0100 Subject: [PATCH 2/6] bootctl: Validate private key path --- src/bootctl/bootctl-install.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index a9e2eea4869..ebbdab0ce8a 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -960,6 +960,12 @@ int verb_install(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); + if (r < 0) + return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + } + r = openssl_load_private_key( arg_private_key_source_type, arg_private_key_source, From 5619a61829fa50063f53fe3d406683faf8f43900 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 6 Nov 2024 18:07:11 +0100 Subject: [PATCH 3/6] openssl-util: Set expected object type to private keys Configures the store to only try to fetch private keys and nothing else. --- src/shared/openssl-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index 5688d541141..914f30989bb 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1340,6 +1340,9 @@ static int load_key_from_provider( if (!store) return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri); + if (OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) == 0) + return log_openssl_errors("Failed to filter store by private keys"); + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); if (!info) return log_openssl_errors("Failed to load OpenSSL store via '%s'", private_key_uri); From a1d46e3078a67b128a2eb93da7ae51d253b326f7 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 6 Nov 2024 18:08:26 +0100 Subject: [PATCH 4/6] tree-wide: Introduce --certificate-source= option This allows loading the X.509 certificate from an OpenSSL provider instead of a file system path. This allows loading certficates directly from hardware tokens instead of having to export them to a file on disk first. --- man/bootctl.xml | 12 ++- man/systemd-measure.xml | 10 ++- man/systemd-repart.xml | 25 ++++-- man/systemd-sbsign.xml | 9 +- src/bootctl/bootctl-install.c | 12 ++- src/bootctl/bootctl.c | 30 +++++-- src/bootctl/bootctl.h | 2 + src/measure/measure.c | 35 +++++++- src/partition/repart.c | 41 ++++++++-- src/sbsign/sbsign.c | 56 ++++++++++++- src/shared/openssl-util.c | 150 ++++++++++++++++++++++++++++------ src/shared/openssl-util.h | 15 +++- 12 files changed, 336 insertions(+), 61 deletions(-) diff --git a/man/bootctl.xml b/man/bootctl.xml index eab18f7575a..3159f42347d 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -529,8 +529,9 @@ - + + Configure the ESP for secure boot auto-enrollment when invoking the install command. Takes a boolean argument. Disabled by default. Enabling this @@ -542,9 +543,12 @@ When specifying this option, a certificate and private key have to be provided as well using the and options. The - option takes a path to a PEM encoded X.509 certificate. The - option can take a path or a URI that will be passed to the OpenSSL - engine or provider, as specified by as a + option takes a path to a PEM encoded X.509 certificate or a URI + that's passed to the OpenSSL provider configured with which + takes one of file or provider, with the latter being followed + by a specific provider identifier, separated with a colon, e.g. provider:pkcs11. + The option can take a path or a URI that will be passed to the + OpenSSL engine or provider, as specified by as a type:name tuple, such as engine:pkcs11. The specified OpenSSL signing engine or provider will be used to sign the EFI signature lists. diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml index b82aabac049..c7e5a5e9e21 100644 --- a/man/systemd-measure.xml +++ b/man/systemd-measure.xml @@ -188,8 +188,9 @@ - - + + + As an alternative to for the sign command, these switches can be used to sign with an hardware token. The @@ -197,6 +198,11 @@ provider, as specified by as a type:name tuple, such as engine:pkcs11. The specified OpenSSL signing engine or provider will be used to sign. + The option also takes a path or a URI that will be passed to + the OpenSSL provider, as specified by as a + type:name tuple, such as provider:pkcs11. Note that unlike + this option only supports providers and not engines. + diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 1e6ffaa70fb..575be14912d 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -348,9 +348,9 @@ - Takes a file system path. Configures the signing key to use when creating verity - signature partitions with the Verity=signature setting in partition files. - + Takes a file system path or an engine or provider specific designation. Configures + the signing key to use when creating verity signature partitions with the + Verity=signature setting in partition files. @@ -361,7 +361,7 @@ Takes one of file, engine or provider. In the latter two cases, it is followed by the name of a provider or engine, separated by colon, that will be passed to OpenSSL's "engine" or "provider" logic. - Configures the signing mechanism to use when creating verity signature partitions with the + Configures how to load the private key to use when creating verity signature partitions with the Verity=signature setting in partition files. @@ -370,13 +370,24 @@ - Takes a file system path. Configures the PEM encoded X.509 certificate to use when - creating verity signature partitions with the Verity=signature setting in - partition files. + Takes a file system path or a provider specific designation. Configures the PEM + encoded X.509 certificate to use when creating verity signature partitions with the + Verity=signature setting in partition files. + + + + Takes one of file, or provider. In the latter + case, it is followed by the name of a provider, separated by colon, that will be passed to OpenSSL's + "provider" logic. Configures how to load the X.509 certificate to use when creating verity signature + partitions with the Verity=signature setting in partition files. + + + + diff --git a/man/systemd-sbsign.xml b/man/systemd-sbsign.xml index 1e42d601d67..1248377845f 100644 --- a/man/systemd-sbsign.xml +++ b/man/systemd-sbsign.xml @@ -85,11 +85,16 @@ + Set the Secure Boot private key and certificate for use with the sign. The option takes a path to a PEM encoded - X.509 certificate. The option can take a path or a URI that will be - passed to the OpenSSL engine or provider, as specified by as a + X.509 certificate or a URI that's passed to the OpenSSL provider configured with + . The takes one of + file or provider, with the latter being followed by a specific + provider identifier, separated with a colon, e.g. provider:pkcs11. The + option can take a path or a URI that will be passed to the OpenSSL + engine or provider, as specified by as a type:name tuple, such as engine:pkcs11. The specified OpenSSL signing engine or provider will be used to sign the PE binary. diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index ebbdab0ce8a..26ee2865b2f 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -956,7 +956,17 @@ int verb_install(int argc, char *argv[], void *userdata) { graceful = !install && arg_graceful; /* support graceful mode for updates */ if (arg_secure_boot_auto_enroll) { - r = openssl_load_x509_certificate(arg_certificate, &certificate); + if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) { + r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate); + if (r < 0) + return r; + } + + r = openssl_load_x509_certificate( + arg_certificate_source_type, + arg_certificate_source, + arg_certificate, + &certificate); if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 23a3d2f9228..98721347f4e 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -64,6 +64,8 @@ ImagePolicy *arg_image_policy = NULL; bool arg_varlink = false; bool arg_secure_boot_auto_enroll = false; char *arg_certificate = NULL; +CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; +char *arg_certificate_source = NULL; char *arg_private_key = NULL; KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; char *arg_private_key_source = NULL; @@ -77,6 +79,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); @@ -295,9 +298,14 @@ static int help(int argc, char *argv[], void *userdata) { " Specify how to use KEY for --private-key=. Allows\n" " an OpenSSL engine/provider to be used when setting\n" " up secure boot auto-enrollment\n" - " --certificate=PATH\n" - " PEM certificate to use when setting up secure boot\n" - " auto-enrollment\n" + " --certificate=PATH|URI\n" + " PEM certificate to use when setting up Secure Boot\n" + " auto-enrollment, or a provider specific designation\n" + " if --certificate-source= is used\n" + " --certificate-source=file|provider:PROVIDER\n" + " Specify how to interpret the certificate from\n" + " --certificate=. Allows the certificate to be loaded\n" + " from an OpenSSL provider\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -332,6 +340,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PRINT_STUB_PATH, ARG_SECURE_BOOT_AUTO_ENROLL, ARG_CERTIFICATE, + ARG_CERTIFICATE_SOURCE, ARG_PRIVATE_KEY, ARG_PRIVATE_KEY_SOURCE, }; @@ -366,6 +375,7 @@ static int parse_argv(int argc, char *argv[]) { { "dry-run", no_argument, NULL, ARG_DRY_RUN }, { "secure-boot-auto-enroll", required_argument, NULL, ARG_SECURE_BOOT_AUTO_ENROLL }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, {} @@ -526,12 +536,20 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_CERTIFICATE: { - r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_certificate); + case ARG_CERTIFICATE: + r = free_and_strdup_warn(&arg_certificate, optarg); + if (r < 0) + return r; + break; + + case ARG_CERTIFICATE_SOURCE: + r = parse_openssl_certificate_source_argument( + optarg, + &arg_certificate_source, + &arg_certificate_source_type); if (r < 0) return r; break; - } case ARG_PRIVATE_KEY: { r = free_and_strdup_warn(&arg_private_key, optarg); diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index 8a67f5d8f88..6d0dfec47f0 100644 --- a/src/bootctl/bootctl.h +++ b/src/bootctl/bootctl.h @@ -41,6 +41,8 @@ extern ImagePolicy *arg_image_policy; extern bool arg_varlink; extern bool arg_secure_boot_auto_enroll; extern char *arg_certificate; +extern CertificateSourceType arg_certificate_source_type; +extern char *arg_certificate_source; extern char *arg_private_key; extern KeySourceType arg_private_key_source_type; extern char *arg_private_key_source; diff --git a/src/measure/measure.c b/src/measure/measure.c index eacf90f08c0..979426c18fd 100644 --- a/src/measure/measure.c +++ b/src/measure/measure.c @@ -38,6 +38,8 @@ static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; static char *arg_private_key_source = NULL; static char *arg_public_key = NULL; static char *arg_certificate = NULL; +static char *arg_certificate_source = NULL; +static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_current = false; @@ -50,6 +52,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_append, freep); @@ -87,7 +90,13 @@ static int help(int argc, char *argv[], void *userdata) { " Specify how to use KEY for --private-key=. Allows\n" " an OpenSSL engine/provider to be used for signing\n" " --public-key=KEY Public key (PEM) to validate against\n" - " --certificate=PATH PEM certificate to use when signing with a URI\n" + " --certificate=PATH|URI\n" + " PEM certificate to use for signing, or a provider\n" + " specific designation if --certificate-source= is used\n" + " --certificate-source=file|provider:PROVIDER\n" + " Specify how to interpret the certificate from\n" + " --certificate=. Allows the certificate to be loaded\n" + " from an OpenSSL provider\n" " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short otherwise\n" " --append=PATH Load specified JSON signature, and append new signature to it\n" @@ -156,6 +165,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PRIVATE_KEY_SOURCE, ARG_PUBLIC_KEY, ARG_CERTIFICATE, + ARG_CERTIFICATE_SOURCE, ARG_TPM2_DEVICE, ARG_JSON, ARG_PHASE, @@ -186,6 +196,7 @@ static int parse_argv(int argc, char *argv[]) { { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, { "json", required_argument, NULL, ARG_JSON }, { "phase", required_argument, NULL, ARG_PHASE }, { "append", required_argument, NULL, ARG_APPEND }, @@ -265,10 +276,18 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_CERTIFICATE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate); + r = free_and_strdup_warn(&arg_certificate, optarg); if (r < 0) return r; + break; + case ARG_CERTIFICATE_SOURCE: + r = parse_openssl_certificate_source_argument( + optarg, + &arg_certificate_source, + &arg_certificate_source_type); + if (r < 0) + return r; break; case ARG_TPM2_DEVICE: { @@ -841,7 +860,17 @@ static int verb_sign(int argc, char *argv[], void *userdata) { /* This must be done before openssl_load_private_key() otherwise it will get stuck */ if (arg_certificate) { - r = openssl_load_x509_certificate(arg_certificate, &certificate); + if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) { + r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate); + if (r < 0) + return r; + } + + r = openssl_load_x509_certificate( + arg_certificate_source_type, + arg_certificate_source, + arg_certificate, + &certificate); if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); } diff --git a/src/partition/repart.c b/src/partition/repart.c index be5171d5e18..7e6fd2a29a4 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -154,6 +154,8 @@ static char *arg_private_key = NULL; static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; static char *arg_private_key_source = NULL; static char *arg_certificate = NULL; +static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; +static char *arg_certificate_source = NULL; static char *arg_tpm2_device = NULL; static uint32_t arg_tpm2_seal_key_handle = 0; static char *arg_tpm2_device_key = NULL; @@ -186,6 +188,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); @@ -7808,8 +7811,14 @@ static int help(void) { " Specify how to use KEY for --private-key=. Allows\n" " an OpenSSL engine/provider to be used when generating\n" " verity roothash signatures\n" - " --certificate=PATH PEM certificate to use when generating verity\n" - " roothash signatures\n" + " --certificate=PATH|URI\n" + " PEM certificate to use when generating verity roothash\n" + " signatures, or a provider specific designation if\n" + " --certificate-source= is used\n" + " --certificate-source=file|provider:PROVIDER\n" + " Specify how to interpret the certificate from\n" + " --certificate=. Allows the certificate to be loaded\n" + " from an OpenSSL provider\n" "\n%3$sEncryption:%4$s\n" " --key-file=PATH Key to use when encrypting partitions\n" " --tpm2-device=PATH Path to TPM2 device node to use\n" @@ -7878,6 +7887,7 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * ARG_PRIVATE_KEY, ARG_PRIVATE_KEY_SOURCE, ARG_CERTIFICATE, + ARG_CERTIFICATE_SOURCE, ARG_TPM2_DEVICE, ARG_TPM2_DEVICE_KEY, ARG_TPM2_SEAL_KEY_HANDLE, @@ -7922,6 +7932,7 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, @@ -8130,12 +8141,20 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * return r; break; - case ARG_CERTIFICATE: { - r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_certificate); + case ARG_CERTIFICATE: + r = free_and_strdup_warn(&arg_certificate, optarg); + if (r < 0) + return r; + break; + + case ARG_CERTIFICATE_SOURCE: + r = parse_openssl_certificate_source_argument( + optarg, + &arg_certificate_source, + &arg_certificate_source_type); if (r < 0) return r; break; - } case ARG_TPM2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -8468,7 +8487,17 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * } if (arg_certificate) { - r = openssl_load_x509_certificate(arg_certificate, &certificate); + if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) { + r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate); + if (r < 0) + return r; + } + + r = openssl_load_x509_certificate( + arg_certificate_source_type, + arg_certificate_source, + arg_certificate, + &certificate); if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); } diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index 961a514dac6..d65f28b4c4e 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -21,12 +21,15 @@ static PagerFlags arg_pager_flags = 0; static char *arg_output = NULL; static char *arg_certificate = NULL; +static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; +static char *arg_certificate_source = NULL; static char *arg_private_key = NULL; static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; static char *arg_private_key_source = NULL; STATIC_DESTRUCTOR_REGISTER(arg_output, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); @@ -42,13 +45,19 @@ static int help(int argc, char *argv[], void *userdata) { "\n%5$sSign binaries for EFI Secure Boot%6$s\n" "\n%3$sCommands:%4$s\n" " sign EXEFILE Sign the given binary for EFI Secure Boot\n" - " validate-key Load and validate the given private key\n" + " validate-key Load and validate the given certificate and private key\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Print version\n" " --no-pager Do not pipe output into a pager\n" " --output Where to write the signed PE binary\n" - " --certificate=PATH PEM certificate to use when signing with a URI\n" + " --certificate=PATH|URI\n" + " PEM certificate to use for signing, or a provider\n" + " specific designation if --certificate-source= is used\n" + " --certificate-source=file|provider:PROVIDER\n" + " Specify how to interpret the certificate from\n" + " --certificate=. Allows the certificate to be loaded\n" + " from an OpenSSL provider\n" " --private-key=KEY Private key (PEM) to sign with\n" " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" " Specify how to use KEY for --private-key=. Allows\n" @@ -70,6 +79,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_PAGER, ARG_OUTPUT, ARG_CERTIFICATE, + ARG_CERTIFICATE_SOURCE, ARG_PRIVATE_KEY, ARG_PRIVATE_KEY_SOURCE, }; @@ -80,6 +90,7 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "output", required_argument, NULL, ARG_OUTPUT }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, {} @@ -112,10 +123,18 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_CERTIFICATE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate); + r = free_and_strdup_warn(&arg_certificate, optarg); if (r < 0) return r; + break; + case ARG_CERTIFICATE_SOURCE: + r = parse_openssl_certificate_source_argument( + optarg, + &arg_certificate_source, + &arg_certificate_source_type); + if (r < 0) + return r; break; case ARG_PRIVATE_KEY: @@ -168,7 +187,17 @@ static int verb_sign(int argc, char *argv[], void *userdata) { if (!arg_output) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output="); - r = openssl_load_x509_certificate(arg_certificate, &certificate); + if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) { + r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate); + if (r < 0) + return r; + } + + r = openssl_load_x509_certificate( + arg_certificate_source_type, + arg_certificate_source, + arg_certificate, + &certificate); if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); @@ -470,14 +499,33 @@ static int verb_sign(int argc, char *argv[], void *userdata) { } static int verb_validate_key(int argc, char *argv[], void *userdata) { + _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; int r; + if (!arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No certificate specified, use --certificate="); + if (!arg_private_key) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No private key specified, use --private-key=."); + if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) { + r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate); + if (r < 0) + return r; + } + + r = openssl_load_x509_certificate( + arg_certificate_source_type, + arg_certificate_source, + arg_certificate, + &certificate); + if (r < 0) + return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index 914f30989bb..417f8425468 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1482,6 +1482,80 @@ static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSS *ret = TAKE_PTR(ui); return 0; } + +static int load_x509_certificate_from_file(const char *path, X509 **ret) { + _cleanup_free_ char *rawcert = NULL; + _cleanup_(X509_freep) X509 *cert = NULL; + _cleanup_(BIO_freep) BIO *cb = NULL; + size_t rawcertsz; + int r; + + assert(path); + assert(ret); + + r = read_full_file_full( + AT_FDCWD, path, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &rawcert, &rawcertsz); + if (r < 0) + return log_debug_errno(r, "Failed to read certificate file '%s': %m", path); + + cb = BIO_new_mem_buf(rawcert, rawcertsz); + if (!cb) + return log_oom_debug(); + + cert = PEM_read_bio_X509(cb, NULL, NULL, NULL); + if (!cert) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (ret) + *ret = TAKE_PTR(cert); + + return 0; +} + +static int load_x509_certificate_from_provider(const char *provider, const char *certificate_uri, X509 **ret) { + assert(provider); + assert(certificate_uri); + assert(ret); + +#if OPENSSL_VERSION_MAJOR >= 3 + /* Load the provider so that this can work without any custom written configuration in /etc/. + * Also load the 'default' as that seems to be the recommendation. */ + if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); + if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + return log_openssl_errors("Failed to load OpenSSL provider 'default'"); + + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( + certificate_uri, + /*ui_method=*/ NULL, + /*ui_method=*/ NULL, + /* post_process= */ NULL, + /* post_process_data= */ NULL); + if (!store) + return log_openssl_errors("Failed to open OpenSSL store via '%s'", certificate_uri); + + if (OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) == 0) + return log_openssl_errors("Failed to filter store by X.509 certificates"); + + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); + if (!info) + return log_openssl_errors("Failed to load OpenSSL store via '%s'", certificate_uri); + + _cleanup_(X509_freep) X509 *cert = OSSL_STORE_INFO_get1_CERT(info); + if (!cert) + return log_openssl_errors("Failed to load certificate via '%s'", certificate_uri); + + *ret = TAKE_PTR(cert); + + return 0; +#else + return -EOPNOTSUPP; +#endif +} #endif OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { @@ -1517,36 +1591,33 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { #endif } -int openssl_load_x509_certificate(const char *path, X509 **ret) { +int openssl_load_x509_certificate( + CertificateSourceType certificate_source_type, + const char *certificate_source, + const char *certificate, + X509 **ret) { #if HAVE_OPENSSL - _cleanup_free_ char *rawcert = NULL; - _cleanup_(X509_freep) X509 *cert = NULL; - _cleanup_(BIO_freep) BIO *cb = NULL; - size_t rawcertsz; int r; - assert(path); - assert(ret); + assert(certificate); - r = read_full_file_full( - AT_FDCWD, path, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &rawcert, &rawcertsz); + switch (certificate_source_type) { + + case OPENSSL_CERTIFICATE_SOURCE_FILE: + r = load_x509_certificate_from_file(certificate, ret); + break; + case OPENSSL_CERTIFICATE_SOURCE_PROVIDER: + r = load_x509_certificate_from_provider(certificate_source, certificate, ret); + break; + default: + assert_not_reached(); + } if (r < 0) - return log_debug_errno(r, "Failed to read certificate file '%s': %m", path); - - cb = BIO_new_mem_buf(rawcert, rawcertsz); - if (!cb) - return log_oom_debug(); - - cert = PEM_read_bio_X509(cb, NULL, NULL, NULL); - if (!cert) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s", - ERR_error_string(ERR_get_error(), NULL)); - - if (ret) - *ret = TAKE_PTR(cert); + return log_debug_errno( + r, + "Failed to load certificate '%s' from OpenSSL certificate source %s: %m", + certificate, + certificate_source); return 0; #else @@ -1606,6 +1677,35 @@ int openssl_load_private_key( #endif } +int parse_openssl_certificate_source_argument( + const char *argument, + char **certificate_source, + CertificateSourceType *certificate_source_type) { + + CertificateSourceType type; + const char *e = NULL; + int r; + + assert(argument); + assert(certificate_source); + assert(certificate_source_type); + + if (streq(argument, "file")) + type = OPENSSL_CERTIFICATE_SOURCE_FILE; + else if ((e = startswith(argument, "provider:"))) + type = OPENSSL_CERTIFICATE_SOURCE_PROVIDER; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid certificate source '%s'", argument); + + r = free_and_strdup_warn(certificate_source, e); + if (r < 0) + return r; + + *certificate_source_type = type; + + return 0; +} + int parse_openssl_key_source_argument( const char *argument, char **private_key_source, diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 853aded2c74..7eb1ea15c0f 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -6,6 +6,13 @@ #include "macro.h" #include "sha256.h" +typedef enum CertificateSourceType { + OPENSSL_CERTIFICATE_SOURCE_FILE, + OPENSSL_CERTIFICATE_SOURCE_PROVIDER, + _OPENSSL_CERTIFICATE_SOURCE_MAX, + _OPENSSL_CERTIFICATE_SOURCE_INVALID = -EINVAL, +} CertificateSourceType; + typedef enum KeySourceType { OPENSSL_KEY_SOURCE_FILE, OPENSSL_KEY_SOURCE_ENGINE, @@ -16,6 +23,8 @@ typedef enum KeySourceType { typedef struct OpenSSLAskPasswordUI OpenSSLAskPasswordUI; +int parse_openssl_certificate_source_argument(const char *argument, char **certificate_source, CertificateSourceType *certificate_source_type); + int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); #define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE @@ -182,7 +191,11 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_ int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]); -int openssl_load_x509_certificate(const char *path, X509 **ret); +int openssl_load_x509_certificate( + CertificateSourceType certificate_source_type, + const char *certificate_source, + const char *certificate, + X509 **ret); int openssl_load_private_key( KeySourceType private_key_source_type, From c4bc0fd6de48d8d66bf648d5a5186db7ef333dc4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 7 Nov 2024 14:44:44 +0100 Subject: [PATCH 5/6] measure: Add pcrpkey verb This verb writes a public key to stdout extracted from either a public key path, from a certificate (path or provider) or from a private key (path, engine, provider). We'll use this in ukify to get rid of the use of the python cryptography module to convert a private key or certificate to a public key. --- man/systemd-measure.xml | 10 +++++ src/measure/measure.c | 89 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml index c7e5a5e9e21..5ca373f1814 100644 --- a/man/systemd-measure.xml +++ b/man/systemd-measure.xml @@ -104,6 +104,16 @@ + + + pcrpkey + + This commands prints the public key either given with , + or extracted from the certificate given with or the private key given + with . + + + diff --git a/src/measure/measure.c b/src/measure/measure.c index 979426c18fd..ac294d28b36 100644 --- a/src/measure/measure.c +++ b/src/measure/measure.c @@ -77,6 +77,7 @@ static int help(int argc, char *argv[], void *userdata) { " status Show current PCR values\n" " calculate Calculate expected PCR values\n" " sign Calculate and sign expected PCR values\n" + " pcrpkey Extract the PCR public key\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Print version\n" @@ -1173,12 +1174,100 @@ static int verb_status(int argc, char *argv[], void *userdata) { return 0; } +static int verb_pcrpkey(int argc, char *argv[], void *userdata) { + _cleanup_(EVP_PKEY_freep) EVP_PKEY *public_key = NULL; + int r; + + if (arg_public_key) { + _cleanup_fclose_ FILE *public_keyf = NULL; + + public_keyf = fopen(arg_public_key, "re"); + if (!public_keyf) + return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key); + + public_key = PEM_read_PUBKEY(public_keyf, NULL, NULL, NULL); + if (!public_key) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); + + } else if (arg_certificate) { + _cleanup_(X509_freep) X509 *certificate = NULL; + + if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) { + r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate); + if (r < 0) + return r; + } + + r = openssl_load_x509_certificate( + arg_certificate_source_type, + arg_certificate_source, + arg_certificate, + &certificate); + if (r < 0) + return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); + + public_key = X509_get_pubkey(certificate); + if (!public_key) + return log_error_errno( + SYNTHETIC_ERRNO(EIO), + "Failed to extract public key from certificate %s.", + arg_certificate); + + } else if (arg_private_key) { + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; + + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); + if (r < 0) + return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + } + + r = openssl_load_private_key( + arg_private_key_source_type, + arg_private_key_source, + arg_private_key, + &(AskPasswordRequest) { + .id = "measure-private-key-pin", + .keyring = arg_private_key, + .credential = "measure.private-key-pin", + }, + &private_key, + &ui); + if (r < 0) + return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key); + + _cleanup_(memstream_done) MemStream m = {}; + FILE *tf = memstream_init(&m); + if (!tf) + return log_oom(); + + if (i2d_PUBKEY_fp(tf, private_key) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to extract public key from private key file '%s'.", arg_private_key); + + fflush(tf); + rewind(tf); + + if (!d2i_PUBKEY_fp(tf, &public_key)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to parse extracted public key of private key file '%s'.", arg_private_key); + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "One of --public-key=, --certificate=, or --private-key= must be specified"); + + if (PEM_write_PUBKEY(stdout, public_key) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key to stdout"); + + return 0; +} + static int measure_main(int argc, char *argv[]) { static const Verb verbs[] = { { "help", VERB_ANY, VERB_ANY, 0, help }, { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, { "calculate", VERB_ANY, 1, 0, verb_calculate }, { "sign", VERB_ANY, 1, 0, verb_sign }, + { "pcrpkey", VERB_ANY, 1, 0, verb_pcrpkey }, {} }; From 64cc7ba517c7d57a25ede9833e60aa8aa25fc6ed Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 6 Nov 2024 18:09:37 +0100 Subject: [PATCH 6/6] ukify: Introduce --certificate-provider= option This translates to --certificate-source=provider: for signing tools invoked by ukify. --- man/ukify.xml | 11 +++++ src/ukify/test/test_ukify.py | 19 ++++---- src/ukify/ukify.py | 88 +++++++++++++++++++++--------------- 3 files changed, 71 insertions(+), 47 deletions(-) diff --git a/man/ukify.xml b/man/ukify.xml index 6a697ee6e14..c42d6ae5c75 100644 --- a/man/ukify.xml +++ b/man/ukify.xml @@ -527,6 +527,17 @@ + + CertificateProvider=PROVIDER + + + An OpenSSL provider to be used for loading the certificate used to sign the + resulting binary and PCR measurements. This option can only be used when using + systemd-sbsign as the signing tool. + + + + SignKernel=BOOL diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 70e1a4e1d0a..9eebf7eca1b 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -138,7 +138,7 @@ def test_apply_config(tmp_path): assert ns._groups == ['NAME'] assert ns.pcr_private_keys == ['some/path7'] - assert ns.pcr_public_keys == [pathlib.Path('some/path8')] + assert ns.pcr_public_keys == ['some/path8'] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] ukify.finalize_options(ns) @@ -156,12 +156,12 @@ def test_apply_config(tmp_path): assert ns.pcr_banks == ['sha512', 'sha1'] assert ns.signing_engine == 'engine1' assert ns.sb_key == 'some/path5' - assert ns.sb_cert == 'some/path6' + assert ns.sb_cert == pathlib.Path('some/path6') assert ns.sign_kernel is False assert ns._groups == ['NAME'] assert ns.pcr_private_keys == ['some/path7'] - assert ns.pcr_public_keys == [pathlib.Path('some/path8')] + assert ns.pcr_public_keys == ['some/path8'] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] def test_parse_args_minimal(): @@ -207,11 +207,11 @@ def test_parse_args_many_deprecated(): assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') assert opts.pcr_private_keys == ['PKEY1'] - assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] + assert opts.pcr_public_keys == ['PKEY2'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' assert opts.sb_key == 'SBKEY' - assert opts.sb_cert == 'SBCERT' + assert opts.sb_cert == pathlib.Path('SBCERT') assert opts.sign_kernel is False assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') @@ -253,11 +253,11 @@ def test_parse_args_many(): assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') assert opts.pcr_private_keys == ['PKEY1'] - assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] + assert opts.pcr_public_keys == ['PKEY2'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' assert opts.sb_key == 'SBKEY' - assert opts.sb_cert == 'SBCERT' + assert opts.sb_cert == pathlib.Path('SBCERT') assert opts.sign_kernel is False assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') @@ -360,13 +360,12 @@ def test_config_priority(tmp_path): assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') assert opts.pcr_private_keys == ['PKEY1', 'some/path7'] - assert opts.pcr_public_keys == [pathlib.Path('PKEY2'), - pathlib.Path('some/path8')] + assert opts.pcr_public_keys == ['PKEY2', 'some/path8'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' assert opts.signtool == ukify.SbSign # from args assert opts.sb_key == 'SBKEY' # from args - assert opts.sb_cert == 'SBCERT' # from args + assert opts.sb_cert == pathlib.Path('SBCERT') # from args assert opts.sb_certdir == 'some/path5' # from config assert opts.sb_cert_name == 'some/name1' # from config assert opts.sign_kernel is False diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index ef4e9264c28..355e3f99f41 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -249,21 +249,22 @@ class UkifyConfig: output: Optional[str] pcr_banks: list[str] pcr_private_keys: list[str] - pcr_public_keys: list[Path] + pcr_public_keys: list[str] pcrpkey: Optional[Path] phase_path_groups: Optional[list[str]] profile: Union[str, Path, None] - sb_cert: Path + sb_cert: Union[str, Path, None] sb_cert_name: Optional[str] sb_cert_validity: int sb_certdir: Path - sb_key: Optional[Path] + sb_key: Union[str, Path, None] sbat: Optional[list[str]] sections: list['Section'] sections_by_name: dict[str, 'Section'] sign_kernel: bool signing_engine: Optional[str] signing_provider: Optional[str] + certificate_provider: Optional[str] signtool: Optional[type['SignTool']] splash: Optional[Path] stub: Path @@ -554,6 +555,11 @@ class SystemdSbSign(SignTool): if opts.signing_provider is not None else [] ), + *( + ['--certificate-source', f'provider:{opts.certificate_provider}'] + if opts.certificate_provider is not None + else [] + ), input_f, '--output', output_f, ] # fmt: skip @@ -666,7 +672,7 @@ def combine_signatures(pcrsigs: list[dict[str, str]]) -> str: return json.dumps(combined) -def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[Path], Optional[str]]]: +def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[str], Optional[str]]]: if not opts.pcr_private_keys: return @@ -757,6 +763,10 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> extra += [f'--certificate={pub_key}'] elif pub_key: extra += [f'--public-key={pub_key}'] + + if opts.certificate_provider is not None: + extra += [f'--certificate-source=provider:{opts.certificate_provider}'] + extra += [f'--phase={phase_path}' for phase_path in group or ()] print('+', shell_join(cmd + extra)) # type: ignore @@ -1007,34 +1017,30 @@ def make_uki(opts: UkifyConfig) -> None: pcrpkey: Union[bytes, Path, None] = opts.pcrpkey if pcrpkey is None: + measure_tool = find_tool('systemd-measure', '/usr/lib/systemd/systemd-measure') + cmd = [measure_tool, 'pcrpkey'] + if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1: - pcrpkey = opts.pcr_public_keys[0] - # If we are getting a certificate when using an engine or provider, we need to convert it to - # public key format. - if (opts.signing_engine or opts.signing_provider) and Path(pcrpkey).exists(): - from cryptography.hazmat.primitives import serialization - from cryptography.x509 import load_pem_x509_certificate + # If we're using an engine or provider, the public key will be an X.509 certificate. + if opts.signing_engine or opts.signing_provider: + cmd += ['--certificate', opts.pcr_public_keys[0]] + if opts.certificate_provider: + cmd += ['--certificate-source', f'provider:{opts.certificate_provider}'] + else: + cmd += ['--public-key', opts.pcr_public_keys[0]] - try: - cert = load_pem_x509_certificate(Path(pcrpkey).read_bytes()) - except ValueError: - raise ValueError(f'{pcrpkey} must be an X.509 certificate when signing with an engine') - else: - pcrpkey = cert.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ) + print('+', shell_join(cmd)) + pcrpkey = subprocess.check_output(cmd) elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1: - from cryptography.hazmat.primitives import serialization + cmd += ['--private-key', Path(opts.pcr_private_keys[0])] - privkey = serialization.load_pem_private_key( - Path(opts.pcr_private_keys[0]).read_bytes(), - password=None, - ) - pcrpkey = privkey.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ) + if opts.signing_engine: + cmd += ['--private-key-source', f'engine:{opts.signing_engine}'] + if opts.signing_provider: + cmd += ['--private-key-source', f'provider:{opts.signing_provider}'] + + print('+', shell_join(cmd)) + pcrpkey = subprocess.check_output(cmd) sections = [ # name, content, measure? @@ -1270,9 +1276,9 @@ def generate_keys(opts: UkifyConfig) -> None: ) print(f'Writing SecureBoot private key to {opts.sb_key}') with temporary_umask(0o077): - opts.sb_key.write_bytes(key_pem) + Path(opts.sb_key).write_bytes(key_pem) print(f'Writing SecureBoot certificate to {opts.sb_cert}') - opts.sb_cert.write_bytes(cert_pem) + Path(opts.sb_cert).write_bytes(cert_pem) work = True @@ -1284,7 +1290,7 @@ def generate_keys(opts: UkifyConfig) -> None: Path(priv_key).write_bytes(priv_key_pem) if pub_key: print(f'Writing public key for PCR signing to {pub_key}') - pub_key.write_bytes(pub_key_pem) + Path(pub_key).write_bytes(pub_key_pem) work = True @@ -1674,6 +1680,12 @@ CONFIG_ITEMS = [ help='OpenSSL provider to use for signing', config_key='UKI/SigningProvider', ), + ConfigItem( + '--certificate-provider', + metavar='PROVIDER', + help='OpenSSL provider to load certificate from', + config_key='UKI/CertificateProvider', + ), ConfigItem( '--signtool', choices=('sbsign', 'pesign', 'systemd-sbsign'), @@ -1746,7 +1758,6 @@ CONFIG_ITEMS = [ '--pcr-public-key', dest='pcr_public_keys', metavar='PATH', - type=Path, action='append', help='public part of the keypair or engine/provider designation for signing PCR signatures', config_key='PCRSignature:/PCRPublicKey', @@ -1982,11 +1993,11 @@ def finalize_options(opts: argparse.Namespace) -> None: if opts.signing_engine and opts.signing_provider: raise ValueError('Only one of --signing-engine= and --signing-provider= may be specified') - if opts.signing_engine is None and opts.signing_provider is None: - if opts.sb_key: - opts.sb_key = Path(opts.sb_key) - if opts.sb_cert: - opts.sb_cert = Path(opts.sb_cert) + if opts.signing_engine is None and opts.signing_provider is None and opts.sb_key: + opts.sb_key = Path(opts.sb_key) + + if opts.certificate_provider is None and opts.sb_cert: + opts.sb_cert = Path(opts.sb_cert) if bool(opts.sb_key) ^ bool(opts.sb_cert): # one param only given, sbsign needs both @@ -2012,6 +2023,9 @@ def finalize_options(opts: argparse.Namespace) -> None: if opts.signing_provider and opts.signtool != SystemdSbSign: raise ValueError('--signing-provider= can only be used with--signtool=systemd-sbsign') + if opts.certificate_provider and opts.signtool != SystemdSbSign: + raise ValueError('--certificate-provider= can only be used with--signtool=systemd-sbsign') + if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name: raise ValueError( '--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified' # noqa: E501