mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
sbsign: Split out functions and switch to lower level APIs
Preparation for adding offline signing support. Some additional features and fixes are included as well: - We make sure to add an empty SMIMECAP attribute instead of a populated one to mimick pesign more. - We switch to PKCS7_dataFinal() instead of PKCS7_final() as all that the latter does is an unnecessary copy before calling PKCS7_dataFinal(). - We add support for passing in the signing time via $SOURCE_DATE_EPOCH.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include "build.h"
|
||||
#include "copy.h"
|
||||
#include "efi-fundamental.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
@@ -158,111 +159,13 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
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;
|
||||
static int spc_indirect_data_content_new(const void *digest, size_t digestsz, uint8_t **ret_idc, size_t *ret_idcsz) {
|
||||
assert(digest);
|
||||
assert(ret_idc);
|
||||
assert(ret_idcsz);
|
||||
|
||||
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=");
|
||||
|
||||
if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) {
|
||||
r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = openssl_load_x509_certificate(
|
||||
arg_certificate_source_type,
|
||||
arg_certificate_source,
|
||||
arg_certificate,
|
||||
&certificate);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
|
||||
|
||||
if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
|
||||
r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
|
||||
if (r < 0)
|
||||
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);
|
||||
|
||||
_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_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 *hash = NULL;
|
||||
size_t hashsz;
|
||||
r = pe_hash(srcfd, EVP_sha256(), &hash, &hashsz);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
|
||||
/* This function allocates and populates a new SpcIndirectDataContent object. See the authenticode
|
||||
* spec https://aka.ms/AuthenticodeSpec for more information on the individual fields. */
|
||||
|
||||
/* <<<Obsolete>>> in unicode bytes. */
|
||||
static const uint8_t obsolete[] = {
|
||||
@@ -330,7 +233,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
||||
|
||||
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
|
||||
|
||||
if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashsz) == 0)
|
||||
if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
@@ -340,6 +243,98 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcIndirectDataContent to BER: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
*ret_idc = TAKE_PTR(idcraw);
|
||||
*ret_idcsz = (size_t) idcrawsz;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asn1_timestamp(ASN1_TIME **ret) {
|
||||
ASN1_TIME *time;
|
||||
uint64_t epoch = UINT64_MAX;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
|
||||
if (r != -ENXIO)
|
||||
log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
|
||||
|
||||
if (epoch == UINT64_MAX) {
|
||||
time = X509_gmtime_adj(NULL, 0);
|
||||
if (!time)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get current time: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
} else {
|
||||
time = ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC));
|
||||
if (!time)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(time);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pkcs7_new_with_attributes(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) {
|
||||
int r;
|
||||
|
||||
/* This function sets up a new PKCS#7 signing context with the signed attributes required for
|
||||
* authenticode signing. */
|
||||
|
||||
assert(certificate);
|
||||
assert(private_key);
|
||||
assert(ret_p7);
|
||||
assert(ret_si);
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
|
||||
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");
|
||||
|
||||
/* Add an empty SMIMECAP attribute to indicate we don't have any SMIME capabilities. */
|
||||
|
||||
_cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = sk_X509_ALGOR_new_null();
|
||||
if (!smcap)
|
||||
return log_oom();
|
||||
|
||||
if (PKCS7_add_attrib_smimecap(si, smcap) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add smimecap signed attribute to signer info: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (PKCS7_add_attrib_content_type(si, NULL) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add content type signed attribute to signer info: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL;
|
||||
r = asn1_timestamp(&time);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (PKCS7_add0_attrib_signing_time(si, time) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signing time signed attribute to signer info: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
TAKE_PTR(time);
|
||||
|
||||
ASN1_OBJECT *idc = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
|
||||
if (!idc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
|
||||
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",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
*ret_p7 = TAKE_PTR(p7);
|
||||
*ret_si = TAKE_PTR(si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pkcs7_populate_data_bio(PKCS7* p7, const void *data, size_t size, BIO **ret) {
|
||||
assert(ret);
|
||||
|
||||
_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",
|
||||
@@ -347,10 +342,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
||||
|
||||
int tag, class;
|
||||
long psz;
|
||||
const uint8_t *p = idcraw;
|
||||
const uint8_t *p = data;
|
||||
|
||||
/* 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)
|
||||
if (ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
@@ -358,7 +353,113 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
||||
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)
|
||||
*ret = TAKE_PTR(bio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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=");
|
||||
|
||||
if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) {
|
||||
r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = openssl_load_x509_certificate(
|
||||
arg_certificate_source_type,
|
||||
arg_certificate_source,
|
||||
arg_certificate,
|
||||
&certificate);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
|
||||
|
||||
if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
|
||||
r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
|
||||
if (r < 0)
|
||||
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);
|
||||
|
||||
_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);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
|
||||
|
||||
_cleanup_free_ uint8_t *idcraw = NULL;
|
||||
size_t idcrawsz = 0; /* avoid false maybe-uninitialized warning */
|
||||
r = spc_indirect_data_content_new(pehash, pehashsz, &idcraw, &idcrawsz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
|
||||
PKCS7_SIGNER_INFO *si;
|
||||
r = pkcs7_new_with_attributes(certificate, private_key, &p7, &si);
|
||||
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 (PKCS7_dataFinal(p7, bio) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
@@ -366,7 +467,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
||||
if (!p7c)
|
||||
return log_oom();
|
||||
|
||||
p7c->type = OBJ_nid2obj(idcnid);
|
||||
p7c->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
|
||||
if (!p7c->type)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
@@ -68,6 +68,17 @@ 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);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_TIME*, ASN1_TIME_free, NULL);
|
||||
|
||||
static inline STACK_OF(X509_ALGOR) *x509_algor_free_many(STACK_OF(X509_ALGOR) *attrs) {
|
||||
if (!attrs)
|
||||
return NULL;
|
||||
|
||||
sk_X509_ALGOR_pop_free(attrs, X509_ALGOR_free);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ALGOR)*, x509_algor_free_many, NULL);
|
||||
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL);
|
||||
|
||||
Reference in New Issue
Block a user