1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-19 22:50:17 +03:00

Merge 244efe9cef3f26d2b371a9147bb190b86d392a17 into fdab24bf6acc62d3011f9b5abdf834b4886642b2

This commit is contained in:
Daan De Meyer 2025-03-13 09:28:12 +07:00 committed by GitHub
commit 39dfc6cf50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 563 additions and 133 deletions

View File

@ -49,6 +49,17 @@
<xi:include href="version-info.xml" xpointer="v257"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>sign-secure-boot-database</option></term>
<listitem><para>Generates and signs a secure boot signature database. The signed secure boot
signature database will be written to the path specified with <option>--output=</option>. The secure
boot database identifier should be specified with the <option>--secure-boot-database=</option>
option.</para>
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -60,8 +71,9 @@
<varlistentry>
<term><option>--output=<replaceable>PATH</replaceable></option></term>
<listitem><para>Specifies the path where to write the signed PE binary or the data to be signed
offline when using the <option>--prepare-offline-signing</option> option.</para>
<listitem><para>Specifies the path where to write the signed PE binary, signed secure boot signature
database or the data to be signed offline when using the <option>--prepare-offline-signing</option>
option.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
@ -73,15 +85,16 @@
<term><option>--certificate-source=<replaceable>TYPE</replaceable>[:<replaceable>NAME</replaceable>]</option></term>
<listitem><para>Set the Secure Boot private key and certificate for use with the
<command>sign</command>. The <option>--certificate=</option> option takes a path to a PEM encoded
X.509 certificate or a URI that's passed to the OpenSSL provider configured with
<option>--certificate-source</option>. The <option>--certificate-source</option> takes one of
<literal>file</literal> or <literal>provider</literal>, with the latter being followed by a specific
provider identifier, separated with a colon, e.g. <literal>provider:pkcs11</literal>. The
<option>--private-key=</option> option can take a path or a URI that will be passed to the OpenSSL
engine or provider, as specified by <option>--private-key-source=</option> as a
<literal>type:name</literal> tuple, such as <literal>engine:pkcs11</literal>. The specified OpenSSL
signing engine or provider will be used to sign the PE binary.</para>
<command>sign</command> and <command>sign-secure-boot-database</command> commands. The
<option>--certificate=</option> option takes a path to a PEM encoded X.509 certificate or a URI
that's passed to the OpenSSL provider configured with <option>--certificate-source=</option>. The
<option>--certificate-source=</option> option takes one of <literal>file</literal> or
<literal>provider</literal>, with the latter being followed by a specific provider identifier,
separated with a colon, e.g. <literal>provider:pkcs11</literal>. The <option>--private-key=</option>
option can take a path or a URI that will be passed to the OpenSSL engine or provider, as specified
by <option>--private-key-source=</option> as a <literal>type:name</literal> tuple, such as
<literal>engine:pkcs11</literal>. The specified OpenSSL signing engine or provider will be used to
sign the PE binary or secure boot signature database.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
@ -91,9 +104,9 @@
<listitem><para>When this option is specified, the <command>sign</command> command writes the data
that should be signed to the path specified with <option>--output=</option> instead of writing the
signed PE binary. This data can then be signed out of band after which the signature can be attached
to the PE binary using the <option>--signed-data=</option> and
<option>--signed-data-signature=</option> options.</para>
signed PE binary or signed secure boot signature database. This data can then be signed out of band
after which the signature can be attached to the PE binary or secure boot signature database using
the <option>--signed-data=</option> and <option>--signed-data-signature=</option> options.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
@ -104,7 +117,19 @@
<listitem><para>Configure the signed data (as written to the path specified with
<option>--output=</option> when using the <option>--prepare-offline-signing</option> option) and
corresponding signature for the <command>sign</command> command.</para>
corresponding signature for the <command>sign</command> and
<command>sign-secure-boot-database</command> commands.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--secure-boot-database=PK|KEK|db|dbx</option></term>
<listitem><para>Specify the secure boot signature database that should be generated and signed with
the <command>sign-secure-boot-database</command> command. Takes one of the strings
<varname>PK</varname>, <varname>KEK</varname>, <varname>db</varname> or <varname>dbx</varname>.
</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>

View File

@ -582,7 +582,7 @@ static int efi_timestamp(EFI_TIME *ret) {
assert(ret);
r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
if (r != -ENXIO)
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
r = localtime_or_gmtime_usec(epoch != UINT64_MAX ? epoch : now(CLOCK_REALTIME), /*utc=*/ true, &tm);

View File

@ -7,6 +7,7 @@
#include "build.h"
#include "copy.h"
#include "efi-fundamental.h"
#include "efivars.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
@ -18,6 +19,7 @@
#include "pe-binary.h"
#include "pretty-print.h"
#include "stat-util.h"
#include "strv.h"
#include "tmpfile-util.h"
#include "verbs.h"
@ -31,6 +33,7 @@ static char *arg_private_key_source = NULL;
static bool arg_prepare_offline_signing = false;
static char *arg_signed_data = NULL;
static char *arg_signed_data_signature = NULL;
static char *arg_secure_boot_database = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_output, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
@ -40,6 +43,129 @@ STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_signed_data, freep);
STATIC_DESTRUCTOR_REGISTER(arg_signed_data_signature, freep);
typedef struct Context {
OpenSSLAskPasswordUI *ui;
EVP_PKEY *private_key;
X509 *certificate;
int srcfd;
struct stat srcfd_stat;
int dstfd;
char *tmp;
} Context;
#define CONTEXT_NULL (Context) { .srcfd = -EBADF, .dstfd = -EBADF }
static int context_populate(int argc, char *argv[], Context *ret) {
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
_cleanup_(X509_freep) X509 *certificate = NULL;
_cleanup_close_ int srcfd = -EBADF;
struct stat srcfd_stat = {};
_cleanup_(unlink_and_freep) char *tmp = NULL;
_cleanup_close_ int dstfd = -EBADF;
int r;
assert(argv);
assert(ret);
if (!arg_certificate)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No certificate specified, use --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);
if (arg_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) {
.tty_fd = -EBADF,
.id = "sbsign-private-key-pin",
.keyring = arg_private_key,
.credential = "sbsign.private-key-pin",
.until = USEC_INFINITY,
.hup_fd = -EBADF,
},
&private_key,
&ui);
if (r < 0)
return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
}
if (argc >= 2) {
srcfd = open(argv[1], O_RDONLY|O_CLOEXEC);
if (srcfd < 0)
return log_error_errno(errno, "Failed to open %s: %m", argv[1]);
if (fstat(srcfd, &srcfd_stat) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", argv[1]);
r = stat_verify_regular(&srcfd_stat);
if (r < 0)
return log_error_errno(r, "%s is not a regular file: %m", argv[1]);
}
if (arg_output) {
dstfd = open_tmpfile_linkable(arg_output, O_RDWR|O_CLOEXEC, &tmp);
if (dstfd < 0)
return log_error_errno(dstfd, "Failed to open temporary file: %m");
r = fchmod_umask(dstfd, 0666);
if (r < 0)
log_debug_errno(r, "Failed to change temporary file mode: %m");
}
*ret = (Context) {
.ui = TAKE_PTR(ui),
.private_key = TAKE_PTR(private_key),
.certificate = TAKE_PTR(certificate),
.srcfd = TAKE_FD(srcfd),
.srcfd_stat = srcfd_stat,
.tmp = TAKE_PTR(tmp),
.dstfd = TAKE_FD(dstfd),
};
return 0;
}
static void context_done(Context *context) {
assert(context);
context->ui = openssl_ask_password_ui_free(context->ui);
if (context->private_key) {
EVP_PKEY_free(context->private_key);
context->private_key = NULL;
}
if (context->certificate) {
X509_free(context->certificate);
context->certificate = NULL;
}
context->srcfd = safe_close(context->srcfd);
context->tmp = unlink_and_free(context->tmp);
context->dstfd = safe_close(context->dstfd);
}
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
int r;
@ -52,10 +178,13 @@ 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"
" sign-secure-boot-database\n"
" Generate and sign a UEFI Secure Boot database\n"
" for Secure Boot auto-enrollment\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
" --output Where to write the signed PE binary\n"
" --output=PATH Where to write the output\n"
" --certificate=PATH|URI\n"
" PEM certificate to use for signing, or a provider\n"
" specific designation if --certificate-source= is used\n"
@ -67,6 +196,13 @@ static int help(int argc, char *argv[], void *userdata) {
" --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"
" --prepare-offline-signing\n"
" Write the data that should be signed instead of the signed data"
" --signed-data=PATH Path to the data that was signed offline\n"
" --signed-data-signature=PATH\n"
" Path to the raw signature of the data that was signed offline\n"
" --secure-boot-database=PK|KEK|db|dbx\n"
" Which UEFI Secure Boot database to generate and sign\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@ -89,6 +225,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_PREPARE_OFFLINE_SIGNING,
ARG_SIGNED_DATA,
ARG_SIGNED_DATA_SIGNATURE,
ARG_SECURE_BOOT_DATABASE,
};
static const struct option options[] = {
@ -102,6 +239,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "prepare-offline-signing", no_argument, NULL, ARG_PREPARE_OFFLINE_SIGNING },
{ "signed-data", required_argument, NULL, ARG_SIGNED_DATA },
{ "signed-data-signature", required_argument, NULL, ARG_SIGNED_DATA_SIGNATURE },
{ "secure-boot-database", required_argument, NULL, ARG_SECURE_BOOT_DATABASE },
{}
};
@ -177,6 +315,12 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_SECURE_BOOT_DATABASE:
r = free_and_strdup(&arg_secure_boot_database, optarg);
if (r < 0)
return log_oom();
break;
case '?':
return -EINVAL;
@ -294,7 +438,7 @@ static int asn1_timestamp(ASN1_TIME **ret) {
assert(ret);
r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
if (r != -ENXIO)
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
if (epoch == UINT64_MAX) {
@ -333,7 +477,7 @@ static int pkcs7_new_with_attributes(
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new(certificate, private_key, &p7, &si);
if (r < 0)
return log_error_errno(r, "Failed to allocate PKCS# context: %m");
return log_error_errno(r, "Failed to allocate PKCS#7 context: %m");
if (signed_attributes) {
si->auth_attr = signed_attributes;
@ -373,7 +517,7 @@ static int pkcs7_new_with_attributes(
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to pkcs7 signer info: %s",
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to PKCS#7 signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
*ret_p7 = TAKE_PTR(p7);
@ -386,7 +530,7 @@ static int pkcs7_populate_data_bio(PKCS7* p7, const void *data, size_t size, BIO
_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",
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to create PKCS#7 data bio: %s",
ERR_error_string(ERR_get_error(), NULL));
int tag, class;
@ -399,7 +543,7 @@ static int pkcs7_populate_data_bio(PKCS7* p7, const void *data, size_t size, BIO
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",
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write to PKCS#7 data bio: %s",
ERR_error_string(ERR_get_error(), NULL));
*ret = TAKE_PTR(bio);
@ -437,9 +581,7 @@ static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *s
}
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;
_cleanup_(context_done) Context ctx = CONTEXT_NULL;
_cleanup_(x509_attribute_free_manyp) STACK_OF(X509_ATTRIBUTE) *signed_attributes = NULL;
_cleanup_(iovec_done) struct iovec signed_attributes_signature = {};
int r;
@ -447,10 +589,6 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
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 && !arg_signed_data_signature && !arg_prepare_offline_signing)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No private key or signed data signature specified, use --private-key= or --signed-data-signature=.");
@ -458,44 +596,9 @@ 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=");
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);
r = context_populate(argc, argv, &ctx);
if (r < 0)
return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
if (arg_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) {
.tty_fd = -EBADF,
.id = "sbsign-private-key-pin",
.keyring = arg_private_key,
.credential = "sbsign.private-key-pin",
.until = USEC_INFINITY,
.hup_fd = -EBADF,
},
&private_key,
&ui);
if (r < 0)
return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
}
return r;
if (arg_signed_data) {
_cleanup_free_ void *content = NULL;
@ -508,7 +611,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
const uint8_t *p = content;
if (!ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN)))
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse signed attributes: %s",
ERR_error_string(ERR_get_error(), NULL));
ERR_error_string(ERR_get_error(), NULL));
}
if (arg_signed_data_signature) {
@ -522,30 +625,9 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
signed_attributes_signature = IOVEC_MAKE(TAKE_PTR(content), contentsz);
}
_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_error_errno(errno, "Failed to stat %s: %m", argv[1]);
r = stat_verify_regular(&st);
if (r < 0)
return log_error_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(dstfd, "Failed to open temporary file: %m");
r = fchmod_umask(dstfd, 0666);
if (r < 0)
log_debug_errno(r, "Failed to change temporary file mode: %m");
_cleanup_free_ void *pehash = NULL;
size_t pehashsz;
r = pe_hash(srcfd, EVP_sha256(), &pehash, &pehashsz);
r = pe_hash(ctx.srcfd, EVP_sha256(), &pehash, &pehashsz);
if (r < 0)
return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
@ -557,18 +639,18 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new_with_attributes(certificate, private_key, signed_attributes, &p7, &si);
r = pkcs7_new_with_attributes(ctx.certificate, ctx.private_key, signed_attributes, &p7, &si);
if (r < 0)
return r;
TAKE_PTR(signed_attributes);
if (arg_prepare_offline_signing) {
_cleanup_(BIO_free_allp) BIO *bio = NULL;
r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio);
if (r < 0)
return r;
_cleanup_(BIO_free_allp) BIO *bio = NULL;
r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio);
if (r < 0)
return r;
if (arg_prepare_offline_signing) {
r = pkcs7_add_digest_attribute(p7, bio, si);
if (r < 0)
return r;
@ -579,11 +661,11 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert signed attributes ASN.1 to DER: %s",
ERR_error_string(ERR_get_error(), NULL));
r = loop_write(dstfd, abuf, alen);
r = loop_write(ctx.dstfd, abuf, alen);
if (r < 0)
return log_error_errno(r, "Failed to write PKCS#7 DER-encoded signed attributes blob to temporary file: %m");
r = link_tmpfile(dstfd, tmp, arg_output, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC);
r = link_tmpfile(ctx.dstfd, ctx.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);
@ -591,18 +673,18 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return 0;
}
if (iovec_is_set(&signed_attributes_signature))
ASN1_STRING_set0(si->enc_digest, TAKE_PTR(signed_attributes_signature.iov_base), signed_attributes_signature.iov_len);
else {
_cleanup_(BIO_free_allp) BIO *bio = NULL;
r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio);
if (r < 0)
return r;
if (iovec_is_set(&signed_attributes_signature)) {
ASN1_STRING_set0(si->enc_digest,
TAKE_PTR(signed_attributes_signature.iov_base),
signed_attributes_signature.iov_len);
if (PKCS7_dataFinal(p7, bio) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s",
if (PKCS7_signatureVerify(bio, p7, si, ctx.certificate) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "PKCS#7 signature validation failed: %s",
ERR_error_string(ERR_get_error(), NULL));
}
} else if (PKCS7_dataFinal(p7, bio) == 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)
@ -627,7 +709,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
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",
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 data: %s",
ERR_error_string(ERR_get_error(), NULL));
TAKE_PTR(p7c);
@ -635,12 +717,12 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
_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",
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS#7 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);
r = pe_load_headers(ctx.srcfd, &dos_header, &pe_header);
if (r < 0)
return log_error_errno(r, "Failed to load headers from PE file: %m");
@ -649,31 +731,31 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
if (!certificate_table)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
r = copy_bytes(srcfd, dstfd, UINT64_MAX, COPY_REFLINK);
r = copy_bytes(ctx.srcfd, ctx.dstfd, UINT64_MAX, COPY_REFLINK);
if (r < 0)
return log_error_errno(r, "Failed to copy %s to %s: %m", argv[1], tmp);
return log_error_errno(r, "Failed to copy %s to %s: %m", argv[1], ctx.tmp);
off_t end = st.st_size;
off_t end = ctx.srcfd_stat.st_size;
ssize_t n;
if (st.st_size % 8 != 0) {
if (ctx.srcfd_stat.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);
n = pwrite(ctx.dstfd, (const uint8_t[8]) {}, 8 - (ctx.srcfd_stat.st_size % 8), ctx.srcfd_stat.st_size);
if (n < 0)
return log_error_errno(errno, "Failed to write zero padding: %m");
if (n != 8 - (st.st_size % 8))
if (n != 8 - (ctx.srcfd_stat.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,
n = pwrite(ctx.dstfd,
&(WIN_CERTIFICATE) {
.wRevision = htole16(0x200),
.wCertificateType = htole16(0x0002), /* PKCS7 signedData */
.wCertificateType = htole16(0x0002), /* PKCS#7 signedData */
.dwLength = htole32(ROUND_UP(certsz, 8)),
},
sizeof(WIN_CERTIFICATE),
@ -685,7 +767,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
end += n;
n = pwrite(dstfd, sig, sigsz, end);
n = pwrite(ctx.dstfd, sig, sigsz, end);
if (n < 0)
return log_error_errno(errno, "Failed to write signature: %m");
if (n != sigsz)
@ -694,16 +776,16 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
end += n;
if (certsz % 8 != 0) {
n = pwrite(dstfd, (const uint8_t[8]) {}, 8 - (certsz % 8), end);
n = pwrite(ctx.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,
n = pwrite(ctx.dstfd,
&(IMAGE_DATA_DIRECTORY) {
.VirtualAddress = certificate_table->VirtualAddress ?: htole32(ROUND_UP(st.st_size, 8)),
.VirtualAddress = certificate_table->VirtualAddress ?: htole32(ROUND_UP(ctx.srcfd_stat.st_size, 8)),
.Size = htole32(le32toh(certificate_table->Size) + ROUND_UP(certsz, 8)),
},
sizeof(IMAGE_DATA_DIRECTORY),
@ -714,11 +796,11 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while updating PE certificate table.");
uint32_t checksum;
r = pe_checksum(dstfd, &checksum);
r = pe_checksum(ctx.dstfd, &checksum);
if (r < 0)
return log_error_errno(r, "Failed to calculate PE file checksum: %m");
n = pwrite(dstfd,
n = pwrite(ctx.dstfd,
&(le32_t) { htole32(checksum) },
sizeof(le32_t),
le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum));
@ -727,7 +809,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
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);
r = link_tmpfile(ctx.dstfd, ctx.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);
@ -735,10 +817,267 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return 0;
}
static int efi_timestamp(EFI_TIME *ret) {
uint64_t epoch = UINT64_MAX;
struct tm tm = {};
int r;
assert(ret);
r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
r = localtime_or_gmtime_usec(epoch != UINT64_MAX ? epoch : now(CLOCK_REALTIME), /*utc=*/ true, &tm);
if (r < 0)
return log_error_errno(r, "Failed to convert timestamp to calendar time: %m");
*ret = (EFI_TIME) {
.Year = 1900 + tm.tm_year,
/* tm_mon starts at 0, EFI_TIME months start at 1. */
.Month = tm.tm_mon + 1,
.Day = tm.tm_mday,
.Hour = tm.tm_hour,
.Minute = tm.tm_min,
.Second = tm.tm_sec,
};
return 0;
}
static int populate_secure_boot_database_bio(
const char16_t *db,
const EFI_GUID *guid,
uint32_t attrs,
const EFI_TIME *timestamp,
const EFI_SIGNATURE_LIST *siglist,
size_t siglistsz,
BIO **ret) {
assert(db);
assert(guid);
assert(timestamp);
assert(siglist);
assert(ret);
_cleanup_(BIO_freep) BIO *bio = NULL;
bio = BIO_new(BIO_s_mem());
if (!bio)
return log_oom();
/* Don't count the trailing NUL terminator. */
if (BIO_write(bio, db, char16_strsize(db) - sizeof(char16_t)) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable name to bio");
if (BIO_write(bio, guid, sizeof(*guid)) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable GUID to bio");
if (BIO_write(bio, &attrs, sizeof(attrs)) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable attributes to bio");
if (BIO_write(bio, timestamp, sizeof(*timestamp)) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write timestamp to bio");
if (BIO_write(bio, siglist, siglistsz) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write signature list to bio");
*ret = TAKE_PTR(bio);
return 0;
}
static int verb_sign_secure_boot_database(int argc, char *argv[], void *userdata) {
static const uint32_t attrs =
EFI_VARIABLE_NON_VOLATILE|
EFI_VARIABLE_BOOTSERVICE_ACCESS|
EFI_VARIABLE_RUNTIME_ACCESS|
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
_cleanup_(context_done) Context ctx = CONTEXT_NULL;
_cleanup_(iovec_done) struct iovec signed_data = {};
_cleanup_(iovec_done) struct iovec signed_data_signature = {};
int r;
if (!arg_private_key && !arg_signed_data_signature && !arg_prepare_offline_signing)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No private key or signed data signature specified, use --private-key= or --signed-data-signature=.");
if (!arg_output)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output=");
if (!arg_secure_boot_database)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No secure boot database identifier specified, use --secure-boot-database=");
if (!STR_IN_SET(arg_secure_boot_database, "PK", "KEK", "db", "dbx"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Secure Boot database identifier '%s' is not valid", arg_secure_boot_database);
r = context_populate(argc, argv, &ctx);
if (r < 0)
return r;
if (arg_signed_data) {
r = read_full_file(arg_signed_data, (char**) &signed_data.iov_base, &signed_data.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to read secure boot database signed data file '%s': %m", arg_signed_data);
}
if (arg_signed_data_signature) {
r = read_full_file(arg_signed_data_signature, (char**) &signed_data_signature.iov_base, &signed_data_signature.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to read secure boot database signature file '%s': %m", arg_signed_data_signature);
}
_cleanup_free_ uint8_t *dercert = NULL;
int dercertsz = i2d_X509(ctx.certificate, &dercert);
if (dercertsz < 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert X.509 certificate to DER: %s",
ERR_error_string(ERR_get_error(), NULL));
uint32_t siglistsz = offsetof(EFI_SIGNATURE_LIST, Signatures) + offsetof(EFI_SIGNATURE_DATA, SignatureData) + dercertsz;
/* We use malloc0() to zero-initialize the SignatureOwner field of Signatures[0]. */
_cleanup_free_ EFI_SIGNATURE_LIST *siglist = malloc0(siglistsz);
if (!siglist)
return log_oom();
*siglist = (EFI_SIGNATURE_LIST) {
.SignatureType = EFI_CERT_X509_GUID,
.SignatureListSize = siglistsz,
.SignatureSize = offsetof(EFI_SIGNATURE_DATA, SignatureData) + dercertsz,
};
memcpy(siglist->Signatures[0].SignatureData, dercert, dercertsz);
_cleanup_free_ char16_t *db16 = utf8_to_utf16(arg_secure_boot_database, SIZE_MAX);
if (!db16)
return log_oom();
EFI_TIME timestamp;
if (iovec_is_set(&signed_data)) {
/* Don't count the trailing NUL terminator. */
size_t db16sz = char16_strsize(db16) - sizeof(char16_t);
size_t expectedsz = db16sz + sizeof(EFI_GUID) + sizeof(attrs) + sizeof(timestamp) + siglistsz;
if (signed_data.iov_len != expectedsz)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
"The secure boot database signed data file size does not match the expected size (%s != %s)",
FORMAT_BYTES(signed_data.iov_len),
FORMAT_BYTES(expectedsz));
/* The signed data includes a timestamp which also has to go in the EFI variable descriptor
* which includes the signature and they have to match, so we extract the timestamp from the
* signed data so we can store it in the EFI variable descriptor later. */
size_t tsoffset = db16sz + sizeof(EFI_GUID) + sizeof(attrs);
memcpy_safe(&timestamp, (uint8_t*) signed_data.iov_base + tsoffset, sizeof(timestamp));
} else {
r = efi_timestamp(&timestamp);
if (r < 0)
return r;
}
EFI_GUID *guid = STR_IN_SET(arg_secure_boot_database, "PK", "KEK") ? &(EFI_GUID) EFI_GLOBAL_VARIABLE
: &(EFI_GUID) EFI_IMAGE_SECURITY_DATABASE_GUID;
_cleanup_(BIO_freep) BIO *bio = NULL;
r = populate_secure_boot_database_bio(db16, guid, attrs, &timestamp, siglist, siglistsz, &bio);
if (r < 0)
return r;
if (arg_prepare_offline_signing) {
char *buf;
long bufsz = BIO_get_mem_data(bio, &buf);
r = loop_write(ctx.dstfd, buf, bufsz);
if (r < 0)
return log_error_errno(r, "Failed to write secure boot database unsigned data blob to temporary file: %m");
r = link_tmpfile(ctx.dstfd, ctx.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 secure boot database unsigned data blob to %s", arg_output);
return 0;
}
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new(ctx.certificate, ctx.private_key, &p7, &si);
if (r < 0)
return log_error_errno(r, "Failed to allocate PKCS#7 context: %m");
if (PKCS7_set_detached(p7, true) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 detached attribute: %s",
ERR_error_string(ERR_get_error(), NULL));
_cleanup_(BIO_free_allp) BIO *p7bio = PKCS7_dataInit(p7, NULL);
if (!p7bio)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to create PKCS#7 data bio: %s",
ERR_error_string(ERR_get_error(), NULL));
if (SMIME_crlf_copy(bio, p7bio, PKCS7_BINARY) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to copy unsigned data to PKCS#7 data bio: %s",
ERR_error_string(ERR_get_error(), NULL));
if (iovec_is_set(&signed_data_signature)) {
ASN1_STRING_set0(si->enc_digest,
TAKE_PTR(signed_data_signature.iov_base),
signed_data_signature.iov_len);
if (PKCS7_signatureVerify(p7bio, p7, si, ctx.certificate) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "PKCS#7 signature validation failed: %s",
ERR_error_string(ERR_get_error(), NULL));
} else if (PKCS7_dataFinal(p7, p7bio) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign PKCS#7 data: %s",
ERR_error_string(ERR_get_error(), NULL));
_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 PKCS#7 signature to DER: %s",
ERR_error_string(ERR_get_error(), NULL));
size_t authsz = offsetof(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsz;
_cleanup_free_ EFI_VARIABLE_AUTHENTICATION_2 *auth = malloc(authsz);
if (!auth)
return log_oom();
*auth = (EFI_VARIABLE_AUTHENTICATION_2) {
.TimeStamp = timestamp,
.AuthInfo = {
.Hdr = {
.dwLength = offsetof(WIN_CERTIFICATE_UEFI_GUID, CertData) + sigsz,
.wRevision = 0x0200,
.wCertificateType = 0x0EF1, /* WIN_CERT_TYPE_EFI_GUID */
},
.CertType = EFI_CERT_TYPE_PKCS7_GUID,
}
};
memcpy(auth->AuthInfo.CertData, sig, sigsz);
r = loop_write(ctx.dstfd, auth, authsz);
if (r < 0)
return log_error_errno(r, "Failed to write authentication descriptor to secure boot database file: %m");
r = loop_write(ctx.dstfd, siglist, siglistsz);
if (r < 0)
return log_error_errno(r, "Failed to write signature list to secure boot database file: %m");
r = link_tmpfile(ctx.dstfd, ctx.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 secure boot database to %s", arg_output);
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 },
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "sign", 2, 2, 0, verb_sign },
{ "sign-secure-boot-database", 1, 1, 0, verb_sign_secure_boot_database },
{}
};
int r;
@ -749,7 +1088,7 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
return dispatch_verb(argc, argv, verbs, NULL);
return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL);
}
DEFINE_MAIN_FUNCTION(run);

View File

@ -43,8 +43,8 @@ testcase_sign_systemd_boot() {
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
@ -60,18 +60,84 @@ testcase_sign_systemd_boot_offline() {
fi
SD_BOOT="$(find /usr/lib/systemd/boot/efi/ -name "systemd-boot*.efi" | head -n1)"
(! sbverify --cert /tmp/sb.crt "$SD_BOOT")
export SOURCE_DATE_EPOCH="123"
/usr/lib/systemd/systemd-sbsign sign --certificate /tmp/sb.crt --output /tmp/signed-data.bin --prepare-offline-signing "$SD_BOOT"
openssl dgst -sha256 -sign /tmp/sb.key -out /tmp/signed-data.sig /tmp/signed-data.bin
/usr/lib/systemd/systemd-sbsign \
sign \
--certificate /tmp/sb.crt \
--output /tmp/sdboot \
--private-key /tmp/sb.key \
--output /tmp/sdboot-signed-online \
"$SD_BOOT"
sbverify --cert /tmp/sb.crt /tmp/sdboot-signed-online
/usr/lib/systemd/systemd-sbsign \
sign \
--certificate /tmp/sb.crt \
--output /tmp/signed-data.bin \
--prepare-offline-signing \
"$SD_BOOT"
openssl dgst -sha256 -sign /tmp/sb.key -out /tmp/signed-data.sig /tmp/signed-data.bin
# Make sure systemd-sbsign can't pick up the timestamp from the environment when
# attaching the signature.
unset SOURCE_DATE_EPOCH
/usr/lib/systemd/systemd-sbsign \
sign \
--certificate /tmp/sb.crt \
--output /tmp/sdboot-signed-offline \
--signed-data /tmp/signed-data.bin \
--signed-data-signature /tmp/signed-data.sig \
"$SD_BOOT"
sbverify --cert /tmp/sb.crt /tmp/sdboot
sbverify --cert /tmp/sb.crt /tmp/sdboot-signed-offline
cmp /tmp/sdboot-signed-online /tmp/sdboot-signed-offline
}
testcase_sign_secure_boot_database() {
/usr/lib/systemd/systemd-sbsign \
sign-secure-boot-database \
--certificate /tmp/sb.crt \
--private-key /tmp/sb.key \
--output /tmp/PK.signed \
--secure-boot-database PK
}
testcase_sign_secure_boot_database_offline() {
export SOURCE_DATE_EPOCH="123"
/usr/lib/systemd/systemd-sbsign \
sign-secure-boot-database \
--certificate /tmp/sb.crt \
--private-key /tmp/sb.key \
--output /tmp/PK.signed-online \
--secure-boot-database PK
/usr/lib/systemd/systemd-sbsign \
sign-secure-boot-database \
--certificate /tmp/sb.crt \
--output /tmp/signed-data.bin \
--prepare-offline-signing \
--secure-boot-database PK
openssl dgst -sha256 -sign /tmp/sb.key -out /tmp/signed-data.sig /tmp/signed-data.bin
# Make sure systemd-sbsign can't pick up the timestamp from the environment when
# attaching the signature.
unset SOURCE_DATE_EPOCH
/usr/lib/systemd/systemd-sbsign \
sign-secure-boot-database \
--certificate /tmp/sb.crt \
--output /tmp/PK.signed-offline \
--signed-data /tmp/signed-data.bin \
--signed-data-signature /tmp/signed-data.sig \
--secure-boot-database PK
cmp /tmp/PK.signed-offline /tmp/PK.signed-online
}
run_testcases