diff --git a/man/systemd-sbsign.xml b/man/systemd-sbsign.xml
index 72b286256a3..53c90820162 100644
--- a/man/systemd-sbsign.xml
+++ b/man/systemd-sbsign.xml
@@ -49,6 +49,17 @@
+
+
+
+ Generates and signs a secure boot signature database. The signed secure boot
+ signature database will be written to the path specified with . The secure
+ boot database identifier should be specified with the
+ option.
+
+
+
+
@@ -60,8 +71,9 @@
- Specifies the path where to write the signed PE binary or the data to be signed
- offline when using the option.
+ 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.
@@ -73,15 +85,16 @@
Set the Secure Boot private key and certificate for use with the
- sign. The option takes a path to a PEM encoded
- X.509 certificate or a URI that's passed to the OpenSSL provider configured with
- . The takes one of
- file or provider, with the latter being followed by a specific
- provider identifier, separated with a colon, e.g. provider:pkcs11. The
- option can take a path or a URI that will be passed to the OpenSSL
- engine or provider, as specified by as a
- type:name tuple, such as engine:pkcs11. The specified OpenSSL
- signing engine or provider will be used to sign the PE binary.
+ sign and sign-secure-boot-database commands. The
+ option takes a path to a PEM encoded X.509 certificate or a URI
+ that's passed to the OpenSSL provider configured with . The
+ option takes one of file or
+ provider, with the latter being followed by a specific provider identifier,
+ separated with a colon, e.g. provider:pkcs11. The
+ option can take a path or a URI that will be passed to the OpenSSL engine or provider, as specified
+ by as a type:name tuple, such as
+ engine:pkcs11. The specified OpenSSL signing engine or provider will be used to
+ sign the PE binary or secure boot signature database.
@@ -91,9 +104,9 @@
When this option is specified, the sign command writes the data
that should be signed to the path specified with 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 and
- options.
+ 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 and options.
@@ -104,7 +117,19 @@
Configure the signed data (as written to the path specified with
when using the option) and
- corresponding signature for the sign command.
+ corresponding signature for the sign and
+ sign-secure-boot-database commands.
+
+
+
+
+
+
+
+ Specify the secure boot signature database that should be generated and signed with
+ the sign-secure-boot-database command. Takes one of the strings
+ PK, KEK, db or dbx.
+
diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c
index 4171e4aeee0..cbff2a131a8 100644
--- a/src/bootctl/bootctl-install.c
+++ b/src/bootctl/bootctl-install.c
@@ -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);
diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c
index b2f0d861ce1..a0c8c3758f0 100644
--- a/src/sbsign/sbsign.c
+++ b/src/sbsign/sbsign.c
@@ -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(×tamp, (uint8_t*) signed_data.iov_base + tsoffset, sizeof(timestamp));
+ } else {
+ r = efi_timestamp(×tamp);
+ 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, ×tamp, 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);
diff --git a/test/units/TEST-74-AUX-UTILS.sbsign.sh b/test/units/TEST-74-AUX-UTILS.sbsign.sh
index d7bd4a2c808..addeac5d95e 100755
--- a/test/units/TEST-74-AUX-UTILS.sbsign.sh
+++ b/test/units/TEST-74-AUX-UTILS.sbsign.sh
@@ -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