diff --git a/man/systemd-sbsign.xml b/man/systemd-sbsign.xml
new file mode 100644
index 0000000000..cd7d06d79f
--- /dev/null
+++ b/man/systemd-sbsign.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+ systemd-sbsign
+ systemd
+
+
+
+ systemd-sbsign
+ 1
+
+
+
+ systemd-sbsign
+ Sign PE binaries for EFI Secure Boot
+
+
+
+
+ systemd-sbsign
+ OPTIONS
+ COMMAND
+
+
+
+
+ Description
+
+ systemd-sbsign can be used to sign PE binaries for EFI Secure Boot.
+
+
+
+ Commands
+
+
+
+
+
+ Signs the given PE binary for EFI Secure Boot. Takes a path to a PE binary as its
+ argument. If the PE binary already has a certificate table, the new signature will be added to it.
+ Otherwise a new certificate table will be created. The signed PE binary will be written to the path
+ specified with .
+
+
+
+
+
+
+
+
+ Checks that we can load the private key specified with
+ .
+
+ As a side effect, if the private key is loaded from a PIN-protected hardware token, this
+ command can be used to cache the PIN in the kernel keyring. The
+ $SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC and
+ $SYSTEMD_ASK_PASSWORD_KEYRING_TYPE environment variables can be used to control
+ how long and in which kernel keyring the PIN is cached.
+
+
+
+
+
+
+
+
+ Options
+ The following options are understood:
+
+
+
+
+
+ Specifies the path where to write the signed PE binary.
+
+
+
+
+
+
+
+
+
+ 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
+ type:name tuple, such as engine:pkcs11. The specified OpenSSL
+ signing engine or provider will be used to sign the PE binary.
+
+
+
+
+
+
+
+
+
+
+
+ See Also
+
+ bootctl1
+
+
+
diff --git a/man/ukify.xml b/man/ukify.xml
index c78a12ada0..6a697ee6e1 100644
--- a/man/ukify.xml
+++ b/man/ukify.xml
@@ -100,10 +100,12 @@
the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust
policies for different phases of the boot. In the config file, PCRPrivateKey=,
PCRPublicKey=, and Phases= are grouped into separate sections,
- describing separate boot phases. If SigningEngine=/
- is specified, then the private keys arguments will be passed verbatim to OpenSSL as URIs, and the public
- key arguments will be loaded as X.509 certificates, so that signing can be performed with an OpenSSL
- engine.
+ describing separate boot phases. If one of
+ SigningEngine=/ or
+ SigningProvider=/ is specified, then the private
+ key arguments will be passed verbatim to OpenSSL as URIs, and the public key arguments will be loaded
+ as X.509 certificates, so that signing can be performed with an OpenSSL engine or provider
+ respectively.
If a SecureBoot signing key is provided via the
SecureBootPrivateKey=/ option, the resulting
@@ -440,9 +442,9 @@
SecureBootSigningTool=SIGNER
- Whether to use sbsign or pesign.
- Depending on this choice, different parameters are required in order to sign an image.
- Defaults to sbsign.
+ Whether to use sbsign, pesign, or
+ systemd-sbsign. Depending on this choice, different parameters are required in
+ order to sign an image. Defaults to sbsign.
@@ -452,8 +454,9 @@
A path to a private key to use for signing of the resulting binary. If the
- SigningEngine=/ option is used, this may also be
- an engine-specific designation. This option is required by
+ SigningEngine=/ or
+ SigningProvider=/ option is used, this may
+ also be an engine or provider specific designation. This option is required by
SecureBootSigningTool=sbsign/.
@@ -464,8 +467,9 @@
A path to a certificate to use for signing of the resulting binary. If the
- SigningEngine=/ option is used, this may also
- be an engine-specific designation. This option is required by
+ SigningEngine=/ or
+ SigningProvider=/ option is used, this may
+ also be an engine or provider specific designation. This option is required by
SecureBootSigningTool=sbsign/.
@@ -506,14 +510,23 @@
SigningEngine=ENGINE
- An "engine" for signing of the resulting binary. This option is currently passed
- verbatim to the option of
- sbsign1.
+ An OpenSSL engine to be used for signing the resulting binary and PCR measurements.
+
+ SigningProvider=PROVIDER
+
+
+ An OpenSSL provider to be used for signing 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/mkosi.conf.d/05-tools/mkosi.conf b/mkosi.conf.d/05-tools/mkosi.conf
index 6da7766dc1..8ac2ebe24d 100644
--- a/mkosi.conf.d/05-tools/mkosi.conf
+++ b/mkosi.conf.d/05-tools/mkosi.conf
@@ -2,7 +2,9 @@
[Build]
ToolsTreePackages=
- meson
gcc
gperf
+ meson
+ mypy
pkgconf
+ ruff
diff --git a/src/boot/authenticode.h b/src/boot/authenticode.h
new file mode 100644
index 0000000000..98a8374805
--- /dev/null
+++ b/src/boot/authenticode.h
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+#include "macro.h"
+
+#define SPC_INDIRECT_DATA_OBJID "1.3.6.1.4.1.311.2.1.4"
+#define SPC_PE_IMAGE_DATA_OBJID "1.3.6.1.4.1.311.2.1.15"
+
+typedef struct {
+ ASN1_OBJECT *type;
+ ASN1_TYPE *value;
+} SpcAttributeTypeAndOptionalValue;
+
+DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
+
+ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
+ ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
+ ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
+} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue);
+
+IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
+
+typedef struct {
+ ASN1_OBJECT *algorithm;
+ ASN1_TYPE *parameters;
+} AlgorithmIdentifier;
+
+DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier);
+
+ASN1_SEQUENCE(AlgorithmIdentifier) = {
+ ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
+ ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
+} ASN1_SEQUENCE_END(AlgorithmIdentifier)
+
+IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier);
+
+typedef struct {
+ AlgorithmIdentifier *digestAlgorithm;
+ ASN1_OCTET_STRING *digest;
+} DigestInfo;
+
+DECLARE_ASN1_FUNCTIONS(DigestInfo);
+
+ASN1_SEQUENCE(DigestInfo) = {
+ ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
+ ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(DigestInfo);
+
+IMPLEMENT_ASN1_FUNCTIONS(DigestInfo);
+
+typedef struct {
+ SpcAttributeTypeAndOptionalValue *data;
+ DigestInfo *messageDigest;
+} SpcIndirectDataContent;
+
+DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent);
+
+ASN1_SEQUENCE(SpcIndirectDataContent) = {
+ ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
+ ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
+} ASN1_SEQUENCE_END(SpcIndirectDataContent);
+
+IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL);
+
+typedef struct {
+ int type;
+ union {
+ ASN1_BMPSTRING *unicode;
+ ASN1_IA5STRING *ascii;
+ } value;
+} SpcString;
+
+DECLARE_ASN1_FUNCTIONS(SpcString);
+
+ASN1_CHOICE(SpcString) = {
+ ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0),
+ ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1)
+} ASN1_CHOICE_END(SpcString);
+
+IMPLEMENT_ASN1_FUNCTIONS(SpcString);
+
+typedef struct {
+ ASN1_OCTET_STRING *classId;
+ ASN1_OCTET_STRING *serializedData;
+} SpcSerializedObject;
+
+DECLARE_ASN1_FUNCTIONS(SpcSerializedObject);
+
+ASN1_SEQUENCE(SpcSerializedObject) = {
+ ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING),
+ ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(SpcSerializedObject);
+
+IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject);
+
+typedef struct {
+ int type;
+ union {
+ ASN1_IA5STRING *url;
+ SpcSerializedObject *moniker;
+ SpcString *file;
+ } value;
+} SpcLink;
+
+DECLARE_ASN1_FUNCTIONS(SpcLink);
+
+ASN1_CHOICE(SpcLink) = {
+ ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0),
+ ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1),
+ ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2)
+} ASN1_CHOICE_END(SpcLink);
+
+IMPLEMENT_ASN1_FUNCTIONS(SpcLink);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL);
+
+typedef struct {
+ ASN1_BIT_STRING *flags;
+ SpcLink *file;
+} SpcPeImageData;
+
+DECLARE_ASN1_FUNCTIONS(SpcPeImageData);
+
+ASN1_SEQUENCE(SpcPeImageData) = {
+ ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING),
+ ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0)
+} ASN1_SEQUENCE_END(SpcPeImageData)
+
+IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL);
diff --git a/src/boot/measure.c b/src/boot/measure.c
index ed253b79f0..eacf90f08c 100644
--- a/src/boot/measure.c
+++ b/src/boot/measure.c
@@ -839,7 +839,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
/* When signing we only support JSON output */
arg_json_format_flags &= ~SD_JSON_FORMAT_OFF;
- /* This must be done before openssl_load_key_from_token() otherwise it will get stuck */
+ /* 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 (r < 0)
diff --git a/src/boot/meson.build b/src/boot/meson.build
index 9c28c623d4..a72388baff 100644
--- a/src/boot/meson.build
+++ b/src/boot/meson.build
@@ -62,6 +62,14 @@ executables += [
'sources' : files('measure.c'),
'dependencies' : libopenssl,
},
+ libexec_template + {
+ 'name' : 'systemd-sbsign',
+ 'conditions' : [
+ 'HAVE_OPENSSL',
+ ],
+ 'sources' : files('sbsign.c'),
+ 'dependencies' : libopenssl,
+ },
libexec_template + {
'name' : 'systemd-boot-check-no-failures',
'sources' : files('boot-check-no-failures.c'),
diff --git a/src/boot/sbsign.c b/src/boot/sbsign.c
new file mode 100644
index 0000000000..7dc2cc86af
--- /dev/null
+++ b/src/boot/sbsign.c
@@ -0,0 +1,523 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "ansi-color.h"
+#include "authenticode.h"
+#include "build.h"
+#include "copy.h"
+#include "efi-fundamental.h"
+#include "fd-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "openssl-util.h"
+#include "parse-argument.h"
+#include "pe-binary.h"
+#include "pretty-print.h"
+#include "stat-util.h"
+#include "tmpfile-util.h"
+#include "verbs.h"
+
+static PagerFlags arg_pager_flags = 0;
+static char *arg_output = NULL;
+static char *arg_certificate = 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_private_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
+
+static int help(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-sbsign", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] COMMAND ...\n"
+ "\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"
+ "\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"
+ " --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"
+ " an OpenSSL engine/provider to be used for signing\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_OUTPUT,
+ ARG_CERTIFICATE,
+ ARG_PRIVATE_KEY,
+ ARG_PRIVATE_KEY_SOURCE,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "output", required_argument, NULL, ARG_OUTPUT },
+ { "certificate", required_argument, NULL, ARG_CERTIFICATE },
+ { "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
+ { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ help(0, NULL, NULL);
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case ARG_OUTPUT:
+ r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_output);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_CERTIFICATE:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_PRIVATE_KEY:
+ r = free_and_strdup_warn(&arg_private_key, optarg);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_PRIVATE_KEY_SOURCE:
+ r = parse_openssl_key_source_argument(
+ optarg,
+ &arg_private_key_source,
+ &arg_private_key_source_type);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (arg_private_key_source && !arg_certificate)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified.");
+
+ return 1;
+}
+
+static int verb_sign(int argc, char *argv[], void *userdata) {
+ _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
+ _cleanup_(X509_freep) X509 *certificate = NULL;
+ int r;
+
+ if (argc < 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No input file specified");
+
+ 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_output)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output=");
+
+ r = openssl_load_x509_certificate(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)
+ 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 = "sbsign-private-key-pin",
+ .keyring = arg_private_key,
+ .credential = "sbsign.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_(PKCS7_freep) PKCS7 *p7 = NULL;
+ p7 = PKCS7_sign(certificate, private_key, /*certs=*/ NULL, /*data=*/ NULL, PKCS7_BINARY|PKCS7_PARTIAL);
+ if (!p7)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate pkcs7 signing context: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ STACK_OF(PKCS7_SIGNER_INFO) *si_stack = PKCS7_get_signer_info(p7);
+ if (!si_stack)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get pkcs7 signer info stack: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(si_stack, 0);
+ if (!si)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get pkcs7 signer info: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ int idcnid = OBJ_create(SPC_INDIRECT_DATA_OBJID, "spcIndirectDataContext", "Indirect Data Context");
+
+ if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(idcnid)) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to pkcs7 signer info: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_close_ int srcfd = open(argv[1], O_RDONLY|O_CLOEXEC);
+ if (srcfd < 0)
+ return log_error_errno(errno, "Failed to open %s: %m", argv[1]);
+
+ struct stat st;
+ if (fstat(srcfd, &st) < 0)
+ return log_debug_errno(errno, "Failed to stat %s: %m", argv[1]);
+
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return log_debug_errno(r, "%s is not a regular file: %m", argv[1]);
+
+ _cleanup_(unlink_and_freep) char *tmp = NULL;
+ _cleanup_close_ int dstfd = open_tmpfile_linkable(arg_output, O_RDWR|O_CLOEXEC, &tmp);
+ if (dstfd < 0)
+ return log_error_errno(r, "Failed to open temporary file: %m");
+
+ r = copy_bytes(srcfd, dstfd, UINT64_MAX, COPY_REFLINK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy %s to %s: %m", argv[1], tmp);
+
+ _cleanup_free_ void *hash = NULL;
+ size_t hashsz;
+ r = pe_hash(dstfd, EVP_sha256(), &hash, &hashsz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
+
+ /* <<>> in unicode bytes. */
+ static const uint8_t obsolete[] = {
+ 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x4f,
+ 0x00, 0x62, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c,
+ 0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3e,
+ 0x00, 0x3e, 0x00, 0x3e
+ };
+
+ _cleanup_(SpcLink_freep) SpcLink *link = SpcLink_new();
+ if (!link)
+ return log_oom();
+
+ link->type = 2;
+ link->value.file = SpcString_new();
+ if (!link->value.file)
+ return log_oom();
+
+ link->value.file->type = 0;
+ link->value.file->value.unicode = ASN1_BMPSTRING_new();
+ if (!link->value.file->value.unicode)
+ return log_oom();
+
+ if (ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new();
+ if (!peid)
+ return log_oom();
+
+ if (ASN1_BIT_STRING_set_bit(peid->flags, 0, 1) == 0)
+ return log_oom();
+
+ peid->file = TAKE_PTR(link);
+
+ _cleanup_free_ uint8_t *peidraw = NULL;
+ int peidrawsz = i2d_SpcPeImageData(peid, &peidraw);
+ if (peidrawsz < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcPeImageData to BER: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new();
+ idc->data->value = ASN1_TYPE_new();
+ if (!idc->data->value)
+ return log_oom();
+
+ idc->data->value->type = V_ASN1_SEQUENCE;
+ idc->data->value->value.sequence = ASN1_STRING_new();
+ if (!idc->data->value->value.sequence)
+ return log_oom();
+
+ idc->data->type = OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /*no_name=*/ 1);
+ if (!idc->data->type)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcPeImageData object: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ idc->data->value->value.sequence->data = TAKE_PTR(peidraw);
+ idc->data->value->value.sequence->length = peidrawsz;
+ idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(NID_sha256);
+ if (!idc->messageDigest->digestAlgorithm->algorithm)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SHA256 object: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new();
+ if (!idc->messageDigest->digestAlgorithm->parameters)
+ return log_oom();
+
+ idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
+
+ if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashsz) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_free_ uint8_t *idcraw = NULL;
+ int idcrawsz = i2d_SpcIndirectDataContent(idc, &idcraw);
+ if (idcrawsz < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcIndirectDataContent to BER: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_(BIO_free_allp) BIO *bio = PKCS7_dataInit(p7, NULL);
+ if (!bio)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to create PKCS7 data bio: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ int tag, class;
+ long psz;
+ const uint8_t *p = idcraw;
+
+ /* This function weirdly enough reports errors by setting the 0x80 bit in its return value. */
+ if (ASN1_get_object(&p, &psz, &tag, &class, idcrawsz) & 0x80)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ if (BIO_write(bio, p, psz) < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write to PKCS7 data bio: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ if (PKCS7_final(p7, bio, PKCS7_BINARY) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_(PKCS7_freep) PKCS7 *p7c = PKCS7_new();
+ if (!p7c)
+ return log_oom();
+
+ p7c->type = OBJ_nid2obj(idcnid);
+ if (!p7c->type)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ p7c->d.other = ASN1_TYPE_new();
+ if (!p7c->d.other)
+ return log_oom();
+
+ p7c->d.other->type = V_ASN1_SEQUENCE;
+ p7c->d.other->value.sequence = ASN1_STRING_new();
+ if (!p7c->d.other->value.sequence)
+ return log_oom();
+
+ if (ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ if (PKCS7_set_content(p7, p7c) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 data: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ TAKE_PTR(p7c);
+
+ _cleanup_free_ uint8_t *sig = NULL;
+ int sigsz = i2d_PKCS7(p7, &sig);
+ if (sigsz < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ r = pe_load_headers(srcfd, &dos_header, &pe_header);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load headers from PE file: %m");
+
+ const IMAGE_DATA_DIRECTORY *certificate_table;
+ certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
+ if (!certificate_table)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
+
+ off_t end = st.st_size;
+ ssize_t n;
+
+ if (st.st_size % 8 != 0) {
+ if (certificate_table->VirtualAddress != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Certificate table is not aligned to 8 bytes");
+
+ n = pwrite(dstfd, (const uint8_t[8]) {}, 8 - (st.st_size % 8), st.st_size);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to write zero padding: %m");
+ if (n != 8 - (st.st_size % 8))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing zero padding.");
+
+ end += n;
+ }
+
+ uint32_t certsz = offsetof(WIN_CERTIFICATE, bCertificate) + sigsz;
+ n = pwrite(dstfd,
+ &(WIN_CERTIFICATE) {
+ .wRevision = htole16(0x200),
+ .wCertificateType = htole16(0x0002), /* PKCS7 signedData */
+ .dwLength = htole32(ROUND_UP(certsz, 8)),
+ },
+ sizeof(WIN_CERTIFICATE),
+ end);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to write certificate header: %m");
+ if (n != sizeof(WIN_CERTIFICATE))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing certificate header.");
+
+ end += n;
+
+ n = pwrite(dstfd, sig, sigsz, end);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to write signature: %m");
+ if (n != sigsz)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing signature.");
+
+ end += n;
+
+ if (certsz % 8 != 0) {
+ n = pwrite(dstfd, (const uint8_t[8]) {}, 8 - (certsz % 8), end);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to write zero padding: %m");
+ if ((size_t) n != 8 - (certsz % 8))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing zero padding.");
+ }
+
+ n = pwrite(dstfd,
+ &(IMAGE_DATA_DIRECTORY) {
+ .VirtualAddress = certificate_table->VirtualAddress ?: htole32(ROUND_UP(st.st_size, 8)),
+ .Size = htole32(le32toh(certificate_table->Size) + ROUND_UP(certsz, 8)),
+ },
+ sizeof(IMAGE_DATA_DIRECTORY),
+ le32toh(dos_header->e_lfanew) + PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]));
+ if (n < 0)
+ return log_error_errno(errno, "Failed to update PE certificate table: %m");
+ if ((size_t) n != sizeof(IMAGE_DATA_DIRECTORY))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while updating PE certificate table.");
+
+ uint32_t checksum;
+ r = pe_checksum(dstfd, &checksum);
+ if (r < 0)
+ return log_error_errno(r, "Failed to calculate PE file checksum: %m");
+
+ n = pwrite(dstfd,
+ &(le32_t) { htole32(checksum) },
+ sizeof(le32_t),
+ le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum));
+ if (n < 0)
+ return log_error_errno(errno, "Failed to update PE checksum: %m");
+ if ((size_t) n != sizeof(le32_t))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while updating PE checksum.");
+
+ r = link_tmpfile(dstfd, tmp, arg_output, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC);
+ if (r < 0)
+ return log_error_errno(r, "Failed to link temporary file to %s: %m", arg_output);
+
+ log_info("Wrote signed PE binary to %s", arg_output);
+ return 0;
+}
+
+static int verb_validate_key(int argc, char *argv[], void *userdata) {
+ _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
+ int r;
+
+ if (!arg_private_key)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "No private key specified, use --private-key=.");
+
+ 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 = "sbsign-private-key-pin",
+ .keyring = arg_private_key,
+ .credential = "sbsign.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);
+
+ puts("OK");
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "help", VERB_ANY, VERB_ANY, 0, help },
+ { "sign", 2, 2, 0, verb_sign },
+ { "validate-key", VERB_ANY, 1, 0, verb_validate_key },
+ {}
+ };
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build
index a31b30bb15..8c8728a3a0 100644
--- a/src/pcrlock/meson.build
+++ b/src/pcrlock/meson.build
@@ -10,7 +10,6 @@ executables += [
'sources' : files(
'pcrlock.c',
'pcrlock-firmware.c',
- 'pehash.c',
),
'dependencies' : [
libm,
diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c
index c1915761ee..d824914ae7 100644
--- a/src/pcrlock/pcrlock.c
+++ b/src/pcrlock/pcrlock.c
@@ -40,7 +40,7 @@
#include "path-util.h"
#include "pcrextend-util.h"
#include "pcrlock-firmware.h"
-#include "pehash.h"
+#include "pe-binary.h"
#include "pretty-print.h"
#include "proc-cmdline.h"
#include "random-util.h"
diff --git a/src/pcrlock/pehash.c b/src/pcrlock/pehash.c
deleted file mode 100644
index 39ed61cc2e..0000000000
--- a/src/pcrlock/pehash.c
+++ /dev/null
@@ -1,264 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include
-#include
-
-#include "alloc-util.h"
-#include "hexdecoct.h"
-#include "pe-binary.h"
-#include "pehash.h"
-#include "sort-util.h"
-#include "stat-util.h"
-#include "string-table.h"
-
-/* Implements:
- *
- * https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
- * → Section "Calculating the PE Image Hash"
- */
-
-#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
-
-static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
- uint8_t buffer[64*1024];
-
- log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
-
- while (size > 0) {
- size_t m = MIN(size, sizeof(buffer));
- ssize_t n;
-
- n = pread(fd, buffer, m, offset);
- if (n < 0)
- return log_debug_errno(errno, "Failed to read file for hashing: %m");
- if ((size_t) n != m)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
-
- if (EVP_DigestUpdate(md_ctx, buffer, m) != 1)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
-
- offset += m;
- size -= m;
- }
-
- return 0;
-}
-
-static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
- return CMP(ASSERT_PTR(a)->PointerToRawData, ASSERT_PTR(b)->PointerToRawData);
-}
-
-int pe_hash(int fd,
- const EVP_MD *md,
- void **ret_hash,
- size_t *ret_hash_size) {
-
- _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
- _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
- _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
- _cleanup_free_ PeHeader *pe_header = NULL;
- const IMAGE_DATA_DIRECTORY *certificate_table;
- struct stat st;
- uint64_t p, q;
- int r;
-
- assert(fd >= 0);
- assert(md);
- assert(ret_hash_size);
- assert(ret_hash);
-
- if (fstat(fd, &st) < 0)
- return log_debug_errno(errno, "Failed to stat file: %m");
- r = stat_verify_regular(&st);
- if (r < 0)
- return log_debug_errno(r, "Not a regular file: %m");
-
- r = pe_load_headers(fd, &dos_header, &pe_header);
- if (r < 0)
- return r;
-
- r = pe_load_sections(fd, dos_header, pe_header, §ions);
- if (r < 0)
- return r;
-
- certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
- if (!certificate_table)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
-
- mdctx = EVP_MD_CTX_new();
- if (!mdctx)
- return log_oom_debug();
-
- if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
-
- /* Everything from beginning of file to CheckSum field in PE header */
- p = (uint64_t) dos_header->e_lfanew +
- offsetof(PeHeader, optional.CheckSum);
- r = hash_file(fd, mdctx, 0, p);
- if (r < 0)
- return r;
- p += sizeof(le32_t);
-
- /* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
- q = (uint64_t) dos_header->e_lfanew +
- PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
- r = hash_file(fd, mdctx, p, q - p);
- if (r < 0)
- return r;
- q += sizeof(IMAGE_DATA_DIRECTORY);
-
- /* The rest of the header + the section table */
- p = pe_header->optional.SizeOfHeaders;
- if (p < q)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
- r = hash_file(fd, mdctx, q, p - q);
- if (r < 0)
- return r;
-
- /* Sort by location in file */
- typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
-
- FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
- r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
- if (r < 0)
- return r;
-
- p += section->SizeOfRawData;
- }
-
- if ((uint64_t) st.st_size > p) {
-
- if (st.st_size - p < certificate_table->Size)
- return log_debug_errno(errno, "No space for certificate table, refusing.");
-
- r = hash_file(fd, mdctx, p, st.st_size - p - certificate_table->Size);
- if (r < 0)
- return r;
-
- /* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */
- if (st.st_size % 8 != 0 && EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
- }
-
- int hsz = EVP_MD_CTX_size(mdctx);
- if (hsz < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
-
- unsigned hash_size = (unsigned) hsz;
- _cleanup_free_ void *hash = malloc(hsz);
- if (!hash)
- return log_oom_debug();
-
- if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
-
- assert(hash_size == (unsigned) hsz);
-
- *ret_hash = TAKE_PTR(hash);
- *ret_hash_size = hash_size;
-
- return 0;
-}
-
-typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
-
-static void section_hash_array_done(SectionHashArray *array) {
- assert(array);
-
- for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
- free((*array)[i]);
-}
-
-int uki_hash(int fd,
- const EVP_MD *md,
- void* ret_hashes[static _UNIFIED_SECTION_MAX],
- size_t *ret_hash_size) {
-
- _cleanup_(section_hash_array_done) SectionHashArray hashes = {};
- _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
- _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
- _cleanup_free_ PeHeader *pe_header = NULL;
- int r;
-
- assert(fd >= 0);
- assert(ret_hashes);
- assert(ret_hash_size);
-
- r = pe_load_headers(fd, &dos_header, &pe_header);
- if (r < 0)
- return r;
-
- r = pe_load_sections(fd, dos_header, pe_header, §ions);
- if (r < 0)
- return r;
-
- int hsz = EVP_MD_size(md);
- if (hsz < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
-
- FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
- _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
- _cleanup_free_ char *n = NULL;
- ssize_t i;
-
- n = memdup_suffix0(section->Name, sizeof(section->Name));
- if (!n)
- return log_oom_debug();
-
- i = string_table_lookup(unified_sections, _UNIFIED_SECTION_MAX, n);
- if (i < 0)
- continue;
-
- if (hashes[i])
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
-
- mdctx = EVP_MD_CTX_new();
- if (!mdctx)
- return log_oom_debug();
-
- if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
-
- r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData));
- if (r < 0)
- return r;
-
- if (section->SizeOfRawData < section->VirtualSize) {
- uint8_t zeroes[1024] = {};
- size_t remaining = section->VirtualSize - section->SizeOfRawData;
-
- while (remaining > 0) {
- size_t sz = MIN(sizeof(zeroes), remaining);
-
- if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
-
- remaining -= sz;
- }
- }
-
- hashes[i] = malloc(hsz);
- if (!hashes[i])
- return log_oom_debug();
-
- unsigned hash_size = (unsigned) hsz;
- if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
-
- assert(hash_size == (unsigned) hsz);
-
- if (DEBUG_LOGGING) {
- _cleanup_free_ char *hs = NULL;
-
- hs = hexmem(hashes[i], hsz);
- log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs));
- }
- }
-
- memcpy(ret_hashes, hashes, sizeof(hashes));
- zero(hashes);
- *ret_hash_size = (unsigned) hsz;
-
- return 0;
-}
diff --git a/src/pcrlock/pehash.h b/src/pcrlock/pehash.h
deleted file mode 100644
index 26f2fb1e7b..0000000000
--- a/src/pcrlock/pehash.h
+++ /dev/null
@@ -1,11 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include
-
-#include "openssl-util.h"
-#include "uki.h"
-
-int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
-
-int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);
diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c
index 818f4a5f19..5688d54114 100644
--- a/src/shared/openssl-util.c
+++ b/src/shared/openssl-util.c
@@ -24,6 +24,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
REENABLE_WARNING;
# endif
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
+
/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error
* string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL
* errors." This logs at level debug. Returns -EIO (or -ENOMEM). */
@@ -1315,12 +1317,10 @@ int pkey_generate_volume_keys(
static int load_key_from_provider(
const char *provider,
const char *private_key_uri,
- OpenSSLAskPasswordUI *ui,
EVP_PKEY **ret) {
assert(provider);
assert(private_key_uri);
- assert(ui);
assert(ret);
#if OPENSSL_VERSION_MAJOR >= 3
@@ -1333,8 +1333,8 @@ static int load_key_from_provider(
_cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open(
private_key_uri,
- ui->method,
- &ui->request,
+ /*ui_method=*/ NULL,
+ /*ui_method=*/ NULL,
/* post_process= */ NULL,
/* post_process_data= */ NULL);
if (!store)
@@ -1356,10 +1356,9 @@ static int load_key_from_provider(
#endif
}
-static int load_key_from_engine(const char *engine, const char *private_key_uri, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret) {
+static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) {
assert(engine);
assert(private_key_uri);
- assert(ui);
assert(ret);
#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
@@ -1371,13 +1370,7 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri,
if (ENGINE_init(e) == 0)
return log_openssl_errors("Failed to initialize signing engine '%s'", engine);
- if (ENGINE_ctrl(e, ENGINE_CTRL_SET_USER_INTERFACE, /*i=*/ 0, ui->method, /*f=*/ NULL) <= 0)
- return log_openssl_errors("Failed to set engine user interface");
-
- if (ENGINE_ctrl(e, ENGINE_CTRL_SET_CALLBACK_DATA, /*i=*/ 0, &ui->request, /*f=*/ NULL) <= 0)
- return log_openssl_errors("Failed to set engine user interface data");
-
- _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, ui->method, &ui->request);
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, /*ui_method=*/ NULL, /*callback_data=*/ NULL);
if (!private_key)
return log_openssl_errors("Failed to load private key from '%s'", private_key_uri);
REENABLE_WARNING;
@@ -1390,36 +1383,13 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri,
#endif
}
-int openssl_load_key_from_token(
- KeySourceType private_key_source_type,
- const char *private_key_source,
- const char *private_key,
- OpenSSLAskPasswordUI *ui,
- EVP_PKEY **ret_private_key) {
-
- assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER));
- assert(private_key_source);
- assert(ui);
- assert(private_key);
-
- switch (private_key_source_type) {
-
- case OPENSSL_KEY_SOURCE_ENGINE:
- return load_key_from_engine(private_key_source, private_key, ui, ret_private_key);
- case OPENSSL_KEY_SOURCE_PROVIDER:
- return load_key_from_provider(private_key_source, private_key, ui, ret_private_key);
- default:
- assert_not_reached();
- }
-}
-
static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
int r;
switch(UI_get_string_type(uis)) {
case UIT_PROMPT: {
/* If no ask password request was configured use the default openssl UI. */
- AskPasswordRequest *req = UI_get0_user_data(ui);
+ AskPasswordRequest *req = (AskPasswordRequest*) UI_method_get_ex_data(UI_get_method(ui), 0);
if (!req)
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
@@ -1448,10 +1418,41 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
}
}
-#endif
-int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) {
-#if HAVE_OPENSSL
+static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
+ _cleanup_(erase_and_freep) char *rawkey = NULL;
+ _cleanup_(BIO_freep) BIO *kb = NULL;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
+ size_t rawkeysz;
+ int r;
+
+ assert(path);
+ assert(ret);
+
+ r = read_full_file_full(
+ AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
+ NULL,
+ &rawkey, &rawkeysz);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read key file '%s': %m", path);
+
+ kb = BIO_new_mem_buf(rawkey, rawkeysz);
+ if (!kb)
+ return log_oom_debug();
+
+ pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
+ if (!pk)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ if (ret)
+ *ret = TAKE_PTR(pk);
+
+ return 0;
+}
+
+static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) {
assert(ret);
_cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password");
@@ -1467,12 +1468,31 @@ int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) {
*ui = (OpenSSLAskPasswordUI) {
.method = TAKE_PTR(method),
+ .request = *request,
};
+ UI_set_default_method(ui->method);
+
+ if (UI_method_set_ex_data(ui->method, 0, &ui->request) == 0)
+ return log_openssl_errors("Failed to set extra data for UI method");
+
*ret = TAKE_PTR(ui);
return 0;
+}
+#endif
+
+OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
+#if HAVE_OPENSSL
+ if (!ui)
+ return NULL;
+
+ assert(UI_get_default_method() == ui->method);
+ UI_set_default_method(UI_OpenSSL());
+ UI_destroy_method(ui->method);
+ return mfree(ui);
#else
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot create ask-password user interface.");
+ assert(ui == NULL);
+ return NULL;
#endif
}
@@ -1531,43 +1551,6 @@ int openssl_load_x509_certificate(const char *path, X509 **ret) {
#endif
}
-static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
-#if HAVE_OPENSSL
- _cleanup_(erase_and_freep) char *rawkey = NULL;
- _cleanup_(BIO_freep) BIO *kb = NULL;
- _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
- size_t rawkeysz;
- int r;
-
- assert(path);
- assert(ret);
-
- r = read_full_file_full(
- AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
- READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
- NULL,
- &rawkey, &rawkeysz);
- if (r < 0)
- return log_debug_errno(r, "Failed to read key file '%s': %m", path);
-
- kb = BIO_new_mem_buf(rawkey, rawkeysz);
- if (!kb)
- return log_oom_debug();
-
- pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
- if (!pk)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
- ERR_error_string(ERR_get_error(), NULL));
-
- if (ret)
- *ret = TAKE_PTR(pk);
-
- return 0;
-#else
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
-#endif
-}
-
int openssl_load_private_key(
KeySourceType private_key_source_type,
const char *private_key_source,
@@ -1575,7 +1558,7 @@ int openssl_load_private_key(
const AskPasswordRequest *request,
EVP_PKEY **ret_private_key,
OpenSSLAskPasswordUI **ret_user_interface) {
-
+#if HAVE_OPENSSL
int r;
assert(private_key);
@@ -1589,18 +1572,21 @@ int openssl_load_private_key(
*ret_user_interface = NULL;
} else {
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
- r = openssl_ask_password_ui_new(&ui);
+ r = openssl_ask_password_ui_new(request, &ui);
if (r < 0)
return log_debug_errno(r, "Failed to allocate ask-password user interface: %m");
- ui->request = *request;
+ switch (private_key_source_type) {
- r = openssl_load_key_from_token(
- private_key_source_type,
- private_key_source,
- private_key,
- ui,
- ret_private_key);
+ case OPENSSL_KEY_SOURCE_ENGINE:
+ r = load_key_from_engine(private_key_source, private_key, ret_private_key);
+ break;
+ case OPENSSL_KEY_SOURCE_PROVIDER:
+ r = load_key_from_provider(private_key_source, private_key, ret_private_key);
+ break;
+ default:
+ assert_not_reached();
+ }
if (r < 0)
return log_debug_errno(
r,
@@ -1612,6 +1598,9 @@ int openssl_load_private_key(
}
return 0;
+#else
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
+#endif
}
int parse_openssl_key_source_argument(
diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h
index c9acd40f22..853aded2c7 100644
--- a/src/shared/openssl-util.h
+++ b/src/shared/openssl-util.h
@@ -55,6 +55,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ECDSA_SIG*, ECDSA_SIG_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free_all, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL);
@@ -134,59 +135,48 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s
int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);
-int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret_private_key);
-
#else
typedef struct X509 X509;
typedef struct EVP_PKEY EVP_PKEY;
+typedef struct EVP_MD EVP_MD;
typedef struct UI_METHOD UI_METHOD;
+typedef struct ASN1_TYPE ASN1_TYPE;
+typedef struct ASN1_STRING ASN1_STRING;
-static inline void *X509_free(X509 *p) {
+static inline void* X509_free(X509 *p) {
assert(p == NULL);
return NULL;
}
-static inline void *EVP_PKEY_free(EVP_PKEY *p) {
+static inline void* EVP_PKEY_free(EVP_PKEY *p) {
assert(p == NULL);
return NULL;
}
-static inline void* UI_destroy_method(UI_METHOD *p) {
+static inline void* ASN1_TYPE_free(ASN1_TYPE *p) {
assert(p == NULL);
return NULL;
}
-static inline int openssl_load_key_from_token(
- KeySourceType private_key_source_type,
- const char *private_key_source,
- const char *private_key,
- OpenSSLAskPasswordUI *ui,
- EVP_PKEY **ret_private_key) {
-
- return -EOPNOTSUPP;
+static inline void* ASN1_STRING_free(ASN1_STRING *p) {
+ assert(p == NULL);
+ return NULL;
}
#endif
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL);
-DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_TYPE*, ASN1_TYPE_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_STRING*, ASN1_STRING_free, NULL);
struct OpenSSLAskPasswordUI {
AskPasswordRequest request;
UI_METHOD *method;
};
-int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret);
-
-static inline OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
- if (!ui)
- return NULL;
-
- UI_destroy_method(ui->method);
- return mfree(ui);
-}
+OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL);
diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c
index 3893ffaf97..c50aad18f3 100644
--- a/src/shared/pe-binary.c
+++ b/src/shared/pe-binary.c
@@ -1,12 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include
#include
#include "alloc-util.h"
+#include "hexdecoct.h"
#include "log.h"
#include "pe-binary.h"
+#include "sort-util.h"
+#include "stat-util.h"
+#include "string-table.h"
#include "string-util.h"
-#include "uki.h"
bool pe_header_is_64bit(const PeHeader *h) {
assert(h);
@@ -284,3 +288,312 @@ bool pe_is_native(const PeHeader *pe_header) {
return false;
#endif
}
+
+/* Implements:
+ *
+ * https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
+ * → Section "Calculating the PE Image Hash"
+ */
+
+#if HAVE_OPENSSL
+static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
+ uint8_t buffer[64*1024];
+
+ log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
+
+ while (size > 0) {
+ size_t m = MIN(size, sizeof(buffer));
+ ssize_t n;
+
+ n = pread(fd, buffer, m, offset);
+ if (n < 0)
+ return log_debug_errno(errno, "Failed to read file for hashing: %m");
+ if ((size_t) n != m)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
+
+ if (EVP_DigestUpdate(md_ctx, buffer, m) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
+
+ offset += m;
+ size -= m;
+ }
+
+ return 0;
+}
+
+static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
+ return CMP(ASSERT_PTR(a)->PointerToRawData, ASSERT_PTR(b)->PointerToRawData);
+}
+#endif
+
+int pe_hash(int fd,
+ const EVP_MD *md,
+ void **ret_hash,
+ size_t *ret_hash_size) {
+#if HAVE_OPENSSL
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
+ _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
+ _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ const IMAGE_DATA_DIRECTORY *certificate_table;
+ struct stat st;
+ uint64_t p, q;
+ int r;
+
+ assert(fd >= 0);
+ assert(md);
+ assert(ret_hash_size);
+ assert(ret_hash);
+
+ if (fstat(fd, &st) < 0)
+ return log_debug_errno(errno, "Failed to stat file: %m");
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return log_debug_errno(r, "Not a regular file: %m");
+
+ r = pe_load_headers(fd, &dos_header, &pe_header);
+ if (r < 0)
+ return r;
+
+ r = pe_load_sections(fd, dos_header, pe_header, §ions);
+ if (r < 0)
+ return r;
+
+ certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
+ if (!certificate_table)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
+
+ mdctx = EVP_MD_CTX_new();
+ if (!mdctx)
+ return log_oom_debug();
+
+ if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
+
+ /* Everything from beginning of file to CheckSum field in PE header */
+ p = (uint64_t) dos_header->e_lfanew +
+ offsetof(PeHeader, optional.CheckSum);
+ r = hash_file(fd, mdctx, 0, p);
+ if (r < 0)
+ return r;
+ p += sizeof(le32_t);
+
+ /* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
+ q = (uint64_t) dos_header->e_lfanew +
+ PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
+ r = hash_file(fd, mdctx, p, q - p);
+ if (r < 0)
+ return r;
+ q += sizeof(IMAGE_DATA_DIRECTORY);
+
+ /* The rest of the header + the section table */
+ p = pe_header->optional.SizeOfHeaders;
+ if (p < q)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
+ r = hash_file(fd, mdctx, q, p - q);
+ if (r < 0)
+ return r;
+
+ /* Sort by location in file */
+ typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
+
+ FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
+ r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
+ if (r < 0)
+ return r;
+
+ p += section->SizeOfRawData;
+ }
+
+ if ((uint64_t) st.st_size > p) {
+
+ if (st.st_size - p < certificate_table->Size)
+ return log_debug_errno(errno, "No space for certificate table, refusing.");
+
+ r = hash_file(fd, mdctx, p, st.st_size - p - certificate_table->Size);
+ if (r < 0)
+ return r;
+
+ /* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */
+ if (st.st_size % 8 != 0 && EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
+ }
+
+ int hsz = EVP_MD_CTX_size(mdctx);
+ if (hsz < 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
+
+ unsigned hash_size = (unsigned) hsz;
+ _cleanup_free_ void *hash = malloc(hsz);
+ if (!hash)
+ return log_oom_debug();
+
+ if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
+
+ assert(hash_size == (unsigned) hsz);
+
+ *ret_hash = TAKE_PTR(hash);
+ *ret_hash_size = hash_size;
+
+ return 0;
+#else
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate PE hash.");
+#endif
+}
+
+int pe_checksum(int fd, uint32_t *ret) {
+ _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ struct stat st;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ if (fstat(fd, &st) < 0)
+ return log_debug_errno(errno, "Failed to stat file: %m");
+
+ r = pe_load_headers(fd, &dos_header, &pe_header);
+ if (r < 0)
+ return r;
+
+ uint32_t checksum = 0, checksum_offset = le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum);
+ size_t off = 0;
+ for (;;) {
+ uint16_t buf[32*1024];
+
+ ssize_t n = pread(fd, buf, sizeof(buf), off);
+ if (n == 0)
+ break;
+ if (n < 0)
+ return log_debug_errno(errno, "Failed to read from PE file: %m");
+ if (n % sizeof(uint16_t) != 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read from PE file");
+
+ for (size_t i = 0; i < (size_t) n / 2; i++) {
+ if (off + i >= checksum_offset && off + i < checksum_offset + sizeof(pe_header->optional.CheckSum))
+ continue;
+
+ uint16_t val = le16toh(buf[i]);
+
+ checksum += val;
+ checksum = (checksum >> 16) + (checksum & 0xffff);
+ }
+
+ off += n;
+ }
+
+ checksum = (checksum >> 16) + (checksum & 0xffff);
+ checksum += off;
+
+ *ret = checksum;
+ return 0;
+}
+
+#if HAVE_OPENSSL
+typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
+
+static void section_hash_array_done(SectionHashArray *array) {
+ assert(array);
+
+ for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
+ free((*array)[i]);
+}
+#endif
+
+int uki_hash(int fd,
+ const EVP_MD *md,
+ void* ret_hashes[static _UNIFIED_SECTION_MAX],
+ size_t *ret_hash_size) {
+#if HAVE_OPENSSL
+ _cleanup_(section_hash_array_done) SectionHashArray hashes = {};
+ _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
+ _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret_hashes);
+ assert(ret_hash_size);
+
+ r = pe_load_headers(fd, &dos_header, &pe_header);
+ if (r < 0)
+ return r;
+
+ r = pe_load_sections(fd, dos_header, pe_header, §ions);
+ if (r < 0)
+ return r;
+
+ int hsz = EVP_MD_size(md);
+ if (hsz < 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
+
+ FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
+ _cleanup_free_ char *n = NULL;
+ ssize_t i;
+
+ n = memdup_suffix0(section->Name, sizeof(section->Name));
+ if (!n)
+ return log_oom_debug();
+
+ i = string_table_lookup(unified_sections, _UNIFIED_SECTION_MAX, n);
+ if (i < 0)
+ continue;
+
+ if (hashes[i])
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
+
+ mdctx = EVP_MD_CTX_new();
+ if (!mdctx)
+ return log_oom_debug();
+
+ if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
+
+ r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData));
+ if (r < 0)
+ return r;
+
+ if (section->SizeOfRawData < section->VirtualSize) {
+ uint8_t zeroes[1024] = {};
+ size_t remaining = section->VirtualSize - section->SizeOfRawData;
+
+ while (remaining > 0) {
+ size_t sz = MIN(sizeof(zeroes), remaining);
+
+ if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
+
+ remaining -= sz;
+ }
+ }
+
+ hashes[i] = malloc(hsz);
+ if (!hashes[i])
+ return log_oom_debug();
+
+ unsigned hash_size = (unsigned) hsz;
+ if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
+
+ assert(hash_size == (unsigned) hsz);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *hs = NULL;
+
+ hs = hexmem(hashes[i], hsz);
+ log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs));
+ }
+ }
+
+ memcpy(ret_hashes, hashes, sizeof(hashes));
+ zero(hashes);
+ *ret_hash_size = (unsigned) hsz;
+
+ return 0;
+#else
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate UKI hash.");
+#endif
+}
diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h
index f508989185..eb0c8b3100 100644
--- a/src/shared/pe-binary.h
+++ b/src/shared/pe-binary.h
@@ -3,7 +3,12 @@
#include
+#include "openssl-util.h"
+#include "macro-fundamental.h"
#include "sparse-endian.h"
+#include "uki.h"
+
+#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
/* When naming things we try to stay close to the official Windows APIs as per:
* → https://learn.microsoft.com/en-us/windows/win32/debug/pe-format */
@@ -147,3 +152,9 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
bool pe_is_native(const PeHeader *pe_header);
+
+int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
+
+int pe_checksum(int fd, uint32_t *ret);
+
+int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);
diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py
index 02220dd983..ef4e9264c2 100755
--- a/src/ukify/ukify.py
+++ b/src/ukify/ukify.py
@@ -263,6 +263,7 @@ class UkifyConfig:
sections_by_name: dict[str, 'Section']
sign_kernel: bool
signing_engine: Optional[str]
+ signing_provider: Optional[str]
signtool: Optional[type['SignTool']]
splash: Optional[Path]
stub: Path
@@ -526,6 +527,45 @@ class SbSign(SignTool):
return 'No signature table present' in info
+class SystemdSbSign(SignTool):
+ @staticmethod
+ def sign(input_f: str, output_f: str, opts: UkifyConfig) -> None:
+ assert opts.sb_key is not None
+ assert opts.sb_cert is not None
+
+ tool = find_tool(
+ 'systemd-sbsign',
+ '/usr/lib/systemd/systemd-sbsign',
+ opts=opts,
+ msg='systemd-sbsign, required for signing, is not installed',
+ )
+ cmd = [
+ tool,
+ "sign",
+ '--private-key', opts.sb_key,
+ '--certificate', opts.sb_cert,
+ *(
+ ['--private-key-source', f'engine:{opts.signing_engine}']
+ if opts.signing_engine is not None
+ else []
+ ),
+ *(
+ ['--private-key-source', f'provider:{opts.signing_provider}']
+ if opts.signing_provider is not None
+ else []
+ ),
+ input_f,
+ '--output', output_f,
+ ] # fmt: skip
+
+ print('+', shell_join(cmd))
+ subprocess.check_call(cmd)
+
+ @staticmethod
+ def verify(opts: UkifyConfig) -> bool:
+ raise NotImplementedError('systemd-sbsign cannot yet verify if existing PE binaries are signed')
+
+
def parse_banks(s: str) -> list[str]:
banks = re.split(r',|\s+', s)
# TODO: do some sanity checking here
@@ -711,6 +751,10 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) ->
assert pub_key
extra += [f'--private-key-source=engine:{opts.signing_engine}']
extra += [f'--certificate={pub_key}']
+ elif opts.signing_provider is not None:
+ assert pub_key
+ extra += [f'--private-key-source=provider:{opts.signing_provider}']
+ extra += [f'--certificate={pub_key}']
elif pub_key:
extra += [f'--public-key={pub_key}']
extra += [f'--phase={phase_path}' for phase_path in group or ()]
@@ -965,9 +1009,9 @@ def make_uki(opts: UkifyConfig) -> None:
if pcrpkey is None:
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, we need to convert it to public key
- # format
- if opts.signing_engine is not None and Path(pcrpkey).exists():
+ # 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
@@ -1477,6 +1521,8 @@ class SignToolAction(argparse.Action):
setattr(namespace, 'signtool', SbSign)
elif values == 'pesign':
setattr(namespace, 'signtool', PeSign)
+ elif values == 'systemd-sbsign':
+ setattr(namespace, 'signtool', SystemdSbSign)
else:
raise ValueError(f"Unknown signtool '{values}' (this is unreachable)")
@@ -1622,9 +1668,15 @@ CONFIG_ITEMS = [
help='OpenSSL engine to use for signing',
config_key='UKI/SigningEngine',
),
+ ConfigItem(
+ '--signing-provider',
+ metavar='PROVIDER',
+ help='OpenSSL provider to use for signing',
+ config_key='UKI/SigningProvider',
+ ),
ConfigItem(
'--signtool',
- choices=('sbsign', 'pesign'),
+ choices=('sbsign', 'pesign', 'systemd-sbsign'),
action=SignToolAction,
dest='signtool',
help=(
@@ -1637,7 +1689,7 @@ CONFIG_ITEMS = [
ConfigItem(
'--secureboot-private-key',
dest='sb_key',
- help='required by --signtool=sbsign. Path to key file or engine-specific designation for SB signing',
+ help='required by --signtool=sbsign|systemd-sbsign. Path to key file or engine/provider designation for SB signing', # noqa: E501
config_key='UKI/SecureBootPrivateKey',
),
ConfigItem(
@@ -1686,7 +1738,7 @@ CONFIG_ITEMS = [
'--pcr-private-key',
dest='pcr_private_keys',
action='append',
- help='private part of the keypair or engine-specific designation for signing PCR signatures',
+ help='private part of the keypair or engine/provider designation for signing PCR signatures',
config_key='PCRSignature:/PCRPrivateKey',
config_push=ConfigItem.config_set_group,
),
@@ -1696,7 +1748,7 @@ CONFIG_ITEMS = [
metavar='PATH',
type=Path,
action='append',
- help='public part of the keypair or engine-specific designation for signing PCR signatures',
+ help='public part of the keypair or engine/provider designation for signing PCR signatures',
config_key='PCRSignature:/PCRPublicKey',
config_push=ConfigItem.config_set_group,
),
@@ -1927,7 +1979,10 @@ def finalize_options(opts: argparse.Namespace) -> None:
else:
opts.stub = Path(f'/usr/lib/systemd/boot/efi/addon{opts.efi_arch}.efi.stub')
- if opts.signing_engine is 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:
@@ -1940,11 +1995,12 @@ def finalize_options(opts: argparse.Namespace) -> None:
)
elif bool(opts.sb_key) and bool(opts.sb_cert):
# both param given, infer sbsign and in case it was given, ensure signtool=sbsign
- if opts.signtool and opts.signtool != SbSign:
+ if opts.signtool and opts.signtool not in (SbSign, SystemdSbSign):
raise ValueError(
f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=' # noqa: E501
)
- opts.signtool = SbSign
+ if not opts.signtool:
+ opts.signtool = SbSign
elif bool(opts.sb_cert_name):
# sb_cert_name given, infer pesign and in case it was given, ensure signtool=pesign
if opts.signtool and opts.signtool != PeSign:
@@ -1953,6 +2009,9 @@ def finalize_options(opts: argparse.Namespace) -> None:
)
opts.signtool = PeSign
+ if opts.signing_provider and opts.signtool != SystemdSbSign:
+ raise ValueError('--signing-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
diff --git a/test/units/TEST-74-AUX-UTILS.sbsign.sh b/test/units/TEST-74-AUX-UTILS.sbsign.sh
new file mode 100755
index 0000000000..891a2ae8af
--- /dev/null
+++ b/test/units/TEST-74-AUX-UTILS.sbsign.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/test-control.sh
+. "$(dirname "$0")"/test-control.sh
+
+if ! command -v /usr/lib/systemd/systemd-sbsign >/dev/null; then
+ echo "systemd-sbsign not found, skipping."
+ exit 0
+fi
+
+if [[ ! -d /usr/lib/systemd/boot/efi ]]; then
+ echo "systemd-boot is not installed, skipping."
+ exit 0
+fi
+
+cat >/tmp/openssl.conf </dev/null; then
+ echo "sbverify not found, skipping."
+ exit 0
+ fi
+
+ SD_BOOT="$(find /usr/lib/systemd/boot/efi/ -name "systemd-boot*.efi" | head -n1)"
+
+ (! sbverify --cert /tmp/sb.crt "$SD_BOOT")
+ /usr/lib/systemd/systemd-sbsign sign --certificate /tmp/sb.crt --private-key /tmp/sb.key --output /tmp/sdboot "$SD_BOOT"
+ sbverify --cert /tmp/sb.crt /tmp/sdboot
+
+ # Make sure appending signatures to an existing certificate table works as well.
+ /usr/lib/systemd/systemd-sbsign sign --certificate /tmp/sb.crt --private-key /tmp/sb.key --output /tmp/sdboot /tmp/sdboot
+ sbverify --cert /tmp/sb.crt /tmp/sdboot
+}
+
+testcase_validate_key() {
+ /usr/lib/systemd/systemd-sbsign validate-key --certificate /tmp/sb.crt --private-key /tmp/sb.key
+}
+
+run_testcases