mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 13:27:45 +00:00
sbsign: move the Authenticode code to src/shared
ukify signs UKIs and kernels the same way systemd-sbsign does. Move the ASN.1 types and the PKCS#7 helpers from sbsign into src/shared/authenticode.c, and add authenticode_sign_pe(), which signs a PE file in place. Preparation for rewriting ukify in C.
This commit is contained in:
1 parent
a5ffbaed3e
commit
9c76ca4cec
9 files changed
+574
-467
No files matched your search
@@ -1,125 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <openssl/asn1t.h>
|
||||
|
||||
#include "authenticode.h"
|
||||
#include "crypto-util.h"
|
||||
|
||||
/* OpenSSL's ASN1_SEQUENCE/ASN1_CHOICE/IMPLEMENT_ASN1_FUNCTIONS macros expand to code that references
|
||||
* libcrypto symbols directly, which would force us to link this object against libcrypto and defeat the
|
||||
* dlopen approach used everywhere else. We work around that in two different ways depending on where the
|
||||
* reference appears in the macro expansion:
|
||||
*
|
||||
* - ASN1_item_new/ASN1_item_free/ASN1_item_d2i/ASN1_item_i2d are called from the bodies of the functions
|
||||
* generated by IMPLEMENT_ASN1_FUNCTIONS. We can simply #define them to the matching sym_* variants, so
|
||||
* the generated bodies end up calling our dlopen'd pointers.
|
||||
*
|
||||
* - ASN1_ANY_it/ASN1_BIT_STRING_it/... (the "_it" getters) are embedded as function pointers in the
|
||||
* static const ASN1_ADB / ASN1_TEMPLATE tables emitted by the ASN1_SEQUENCE/ASN1_CHOICE macros. Static
|
||||
* initializers can only contain constant expressions, so we can't point them at the sym_* variables
|
||||
* (which are non-const globals filled in at dlopen time). Instead we define static trampoline
|
||||
* functions (openssl_ASN1_*_it) whose addresses are constant, each forwarding to the corresponding
|
||||
* sym_* pointer at runtime, and #define the OpenSSL names to our trampolines before the tables are
|
||||
* emitted.
|
||||
*
|
||||
* Note that this file must only call these redefined macros indirectly, via the IMPLEMENT_ASN1_FUNCTIONS
|
||||
* expansions, and callers of those generated wrappers (e.g. sbsign.c) must have dlopen_libcrypto()
|
||||
* before invoking them, otherwise the sym_* pointers will still be NULL. */
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_ANY_it(void) {
|
||||
assert(sym_ASN1_ANY_it);
|
||||
return sym_ASN1_ANY_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_BIT_STRING_it(void) {
|
||||
assert(sym_ASN1_BIT_STRING_it);
|
||||
return sym_ASN1_BIT_STRING_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_BMPSTRING_it(void) {
|
||||
assert(sym_ASN1_BMPSTRING_it);
|
||||
return sym_ASN1_BMPSTRING_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_IA5STRING_it(void) {
|
||||
assert(sym_ASN1_IA5STRING_it);
|
||||
return sym_ASN1_IA5STRING_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_OBJECT_it(void) {
|
||||
assert(sym_ASN1_OBJECT_it);
|
||||
return sym_ASN1_OBJECT_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_OCTET_STRING_it(void) {
|
||||
assert(sym_ASN1_OCTET_STRING_it);
|
||||
return sym_ASN1_OCTET_STRING_it();
|
||||
}
|
||||
|
||||
#define ASN1_item_new sym_ASN1_item_new
|
||||
#define ASN1_item_free sym_ASN1_item_free
|
||||
#define ASN1_item_d2i sym_ASN1_item_d2i
|
||||
#define ASN1_item_i2d sym_ASN1_item_i2d
|
||||
#define ASN1_ANY_it openssl_ASN1_ANY_it
|
||||
#define ASN1_BIT_STRING_it openssl_ASN1_BIT_STRING_it
|
||||
#define ASN1_BMPSTRING_it openssl_ASN1_BMPSTRING_it
|
||||
#define ASN1_IA5STRING_it openssl_ASN1_IA5STRING_it
|
||||
#define ASN1_OBJECT_it openssl_ASN1_OBJECT_it
|
||||
#define ASN1_OCTET_STRING_it openssl_ASN1_OCTET_STRING_it
|
||||
|
||||
ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
|
||||
ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
|
||||
ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
|
||||
} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
ASN1_SEQUENCE(AlgorithmIdentifier) = {
|
||||
ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
|
||||
ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
|
||||
} ASN1_SEQUENCE_END(AlgorithmIdentifier)
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier);
|
||||
|
||||
ASN1_SEQUENCE(DigestInfo) = {
|
||||
ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
|
||||
ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
|
||||
} ASN1_SEQUENCE_END(DigestInfo);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(DigestInfo);
|
||||
|
||||
ASN1_SEQUENCE(SpcIndirectDataContent) = {
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
|
||||
} ASN1_SEQUENCE_END(SpcIndirectDataContent);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
@@ -1,80 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#ifndef SYSTEMD_CFLAGS_MARKER_LIBOPENSSL
|
||||
# error "missing libopenssl_cflags in meson dependency."
|
||||
#endif
|
||||
|
||||
#include <openssl/asn1.h>
|
||||
|
||||
#include "forward.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);
|
||||
|
||||
typedef struct {
|
||||
ASN1_OBJECT *algorithm;
|
||||
ASN1_TYPE *parameters;
|
||||
} AlgorithmIdentifier;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier);
|
||||
|
||||
typedef struct {
|
||||
AlgorithmIdentifier *digestAlgorithm;
|
||||
ASN1_OCTET_STRING *digest;
|
||||
} DigestInfo;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(DigestInfo);
|
||||
|
||||
typedef struct {
|
||||
SpcAttributeTypeAndOptionalValue *data;
|
||||
DigestInfo *messageDigest;
|
||||
} SpcIndirectDataContent;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent);
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_BMPSTRING *unicode;
|
||||
ASN1_IA5STRING *ascii;
|
||||
} value;
|
||||
} SpcString;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcString);
|
||||
|
||||
typedef struct {
|
||||
ASN1_OCTET_STRING *classId;
|
||||
ASN1_OCTET_STRING *serializedData;
|
||||
} SpcSerializedObject;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcSerializedObject);
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_IA5STRING *url;
|
||||
SpcSerializedObject *moniker;
|
||||
SpcString *file;
|
||||
} value;
|
||||
} SpcLink;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcLink);
|
||||
|
||||
typedef struct {
|
||||
ASN1_BIT_STRING *flags;
|
||||
SpcLink *file;
|
||||
} SpcPeImageData;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcPeImageData);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL);
|
||||
@@ -6,10 +6,7 @@ executables += [
|
||||
'conditions' : [
|
||||
'HAVE_OPENSSL',
|
||||
],
|
||||
'sources' : files(
|
||||
'sbsign.c',
|
||||
'authenticode.c',
|
||||
),
|
||||
'sources' : files('sbsign.c'),
|
||||
'dependencies' : libopenssl_cflags,
|
||||
},
|
||||
]
|
||||
+14
-258
@@ -15,7 +15,6 @@
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "install-file.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
@@ -146,225 +145,6 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* 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[] = {
|
||||
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 = sym_ASN1_BMPSTRING_new();
|
||||
if (!link->value.file->value.unicode)
|
||||
return log_oom();
|
||||
|
||||
if (sym_ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to set ASN1 string");
|
||||
|
||||
_cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new();
|
||||
if (!peid)
|
||||
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_openssl_errors(LOG_ERR, "Failed to convert SpcPeImageData to BER");
|
||||
|
||||
_cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new();
|
||||
idc->data->value = sym_ASN1_TYPE_new();
|
||||
if (!idc->data->value)
|
||||
return log_oom();
|
||||
|
||||
idc->data->value->type = V_ASN1_SEQUENCE;
|
||||
idc->data->value->value.sequence = sym_ASN1_STRING_new();
|
||||
if (!idc->data->value->value.sequence)
|
||||
return log_oom();
|
||||
|
||||
idc->data->type = sym_OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1);
|
||||
if (!idc->data->type)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to get SpcPeImageData object");
|
||||
|
||||
if (!sym_ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz))
|
||||
return log_openssl_errors(LOG_ERR, "Failed to set ASN1_STRING data.");
|
||||
|
||||
idc->messageDigest->digestAlgorithm->algorithm = sym_OBJ_nid2obj(NID_sha256);
|
||||
if (!idc->messageDigest->digestAlgorithm->algorithm)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to get SHA256 object");
|
||||
|
||||
idc->messageDigest->digestAlgorithm->parameters = sym_ASN1_TYPE_new();
|
||||
if (!idc->messageDigest->digestAlgorithm->parameters)
|
||||
return log_oom();
|
||||
|
||||
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
|
||||
|
||||
if (sym_ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to set digest");
|
||||
|
||||
_cleanup_free_ uint8_t *idcraw = NULL;
|
||||
int idcrawsz = i2d_SpcIndirectDataContent(idc, &idcraw);
|
||||
if (idcrawsz < 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to convert SpcIndirectDataContent to BER");
|
||||
|
||||
*ret_idc = TAKE_PTR(idcraw);
|
||||
*ret_idcsz = (size_t) idcrawsz;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asn1_timestamp(ASN1_TIME **ret) {
|
||||
ASN1_TIME *time;
|
||||
|
||||
assert(ret);
|
||||
|
||||
usec_t epoch = parse_source_date_epoch();
|
||||
|
||||
if (epoch == USEC_INFINITY) {
|
||||
time = sym_X509_gmtime_adj(NULL, 0);
|
||||
if (!time)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to get current time");
|
||||
} else {
|
||||
time = sym_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,
|
||||
const char *hash_algorithm,
|
||||
STACK_OF(X509_ATTRIBUTE) *signed_attributes,
|
||||
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(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, hash_algorithm, &p7, &si);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate PKCS# context: %m");
|
||||
|
||||
if (signed_attributes) {
|
||||
si->auth_attr = signed_attributes;
|
||||
|
||||
*ret_p7 = TAKE_PTR(p7);
|
||||
*ret_si = TAKE_PTR(si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add an empty SMIMECAP attribute to indicate we don't have any SMIME capabilities. */
|
||||
_cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = (STACK_OF(X509_ALGOR)*) sym_OPENSSL_sk_new_null();
|
||||
if (!smcap)
|
||||
return log_oom();
|
||||
|
||||
if (sym_PKCS7_add_attrib_smimecap(si, smcap) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to add smimecap signed attribute to signer info");
|
||||
|
||||
if (sym_PKCS7_add_attrib_content_type(si, NULL) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to add content type signed attribute to signer info");
|
||||
|
||||
_cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL;
|
||||
r = asn1_timestamp(&time);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sym_PKCS7_add0_attrib_signing_time(si, time) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to add signing time signed attribute to signer info");
|
||||
|
||||
TAKE_PTR(time);
|
||||
|
||||
ASN1_OBJECT *idc = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
|
||||
if (!idc)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to get SpcIndirectDataContent object");
|
||||
|
||||
if (sym_PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to add signed attribute to pkcs7 signer info");
|
||||
|
||||
*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 = sym_PKCS7_dataInit(p7, NULL);
|
||||
if (!bio)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to create PKCS7 data bio");
|
||||
|
||||
int tag, class;
|
||||
long psz;
|
||||
const uint8_t *p = data;
|
||||
|
||||
/* This function weirdly enough reports errors by setting the 0x80 bit in its return value. */
|
||||
if (sym_ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to parse ASN.1 object");
|
||||
|
||||
if (sym_BIO_write(bio, p, psz) < 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to write to PKCS7 data bio");
|
||||
|
||||
*ret = TAKE_PTR(bio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *si) {
|
||||
assert(p7);
|
||||
assert(data);
|
||||
assert(si);
|
||||
|
||||
BIO *mdbio = sym_BIO_find_type(data, BIO_TYPE_MD);
|
||||
if (!mdbio)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to find digest bio");
|
||||
|
||||
EVP_MD_CTX *mdc;
|
||||
if (sym_BIO_get_md_ctx(mdbio, &mdc) <= 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to get digest context from bio");
|
||||
|
||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||
unsigned digestsz;
|
||||
|
||||
if (sym_EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to get digest");
|
||||
|
||||
if (sym_PKCS7_add1_attrib_digest(si, digest, digestsz) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to add PKCS9 message digest signed attribute to signer info");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB(verb_sign, "sign", "EXEFILE\0", 2, 2, 0,
|
||||
"Sign the given binary for EFI Secure Boot");
|
||||
static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
@@ -477,27 +257,27 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
|
||||
_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);
|
||||
r = authenticode_spc_indirect_data_content_new(pehash, pehashsz, &idcraw, &idcrawsz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to create SpcIndirectDataContent: %m");
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
|
||||
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
|
||||
r = pkcs7_new_with_attributes(certificate, private_key, /* hash_algorithm= */ NULL, signed_attributes, &p7, &si);
|
||||
r = authenticode_pkcs7_new(certificate, private_key, signed_attributes, &p7, &si);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to create PKCS#7 signing context: %m");
|
||||
|
||||
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);
|
||||
r = authenticode_pkcs7_data_bio(p7, idcraw, idcrawsz, &bio);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to populate PKCS#7 data: %m");
|
||||
|
||||
r = pkcs7_add_digest_attribute(p7, bio, si);
|
||||
r = authenticode_pkcs7_add_digest_attribute(bio, si);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to add digest attribute: %m");
|
||||
|
||||
_cleanup_free_ unsigned char *abuf = NULL;
|
||||
int alen = sym_ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, sym_PKCS7_ATTR_SIGN_it());
|
||||
@@ -520,43 +300,19 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
sym_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);
|
||||
r = authenticode_pkcs7_data_bio(p7, idcraw, idcrawsz, &bio);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to populate PKCS#7 data: %m");
|
||||
|
||||
if (sym_PKCS7_dataFinal(p7, bio) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to sign data");
|
||||
}
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7c = sym_PKCS7_new();
|
||||
if (!p7c)
|
||||
return log_oom();
|
||||
|
||||
p7c->type = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
|
||||
if (!p7c->type)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to get SpcIndirectDataContent object");
|
||||
|
||||
p7c->d.other = sym_ASN1_TYPE_new();
|
||||
if (!p7c->d.other)
|
||||
return log_oom();
|
||||
|
||||
p7c->d.other->type = V_ASN1_SEQUENCE;
|
||||
p7c->d.other->value.sequence = sym_ASN1_STRING_new();
|
||||
if (!p7c->d.other->value.sequence)
|
||||
return log_oom();
|
||||
|
||||
if (sym_ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to set ASN1 string");
|
||||
|
||||
if (sym_PKCS7_set_content(p7, p7c) == 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to set PKCS7 data");
|
||||
|
||||
TAKE_PTR(p7c);
|
||||
|
||||
_cleanup_free_ uint8_t *sig = NULL;
|
||||
int sigsz = sym_i2d_PKCS7(p7, &sig);
|
||||
if (sigsz < 0)
|
||||
return log_openssl_errors(LOG_ERR, "Failed to convert PKCS7 signature to DER");
|
||||
size_t sigsz;
|
||||
r = authenticode_pkcs7_finalize(p7, idcraw, idcrawsz, &sig, &sigsz);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to finalize PKCS#7 signature: %m");
|
||||
|
||||
r = copy_bytes(srcfd, dstfd, UINT64_MAX, /* copy_flags= */ 0);
|
||||
if (r < 0)
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <openssl/asn1t.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "authenticode.h"
|
||||
#include "crypto-util.h"
|
||||
#include "install-file.h"
|
||||
#include "log.h"
|
||||
#include "pe-binary.h"
|
||||
#include "pe-writer.h"
|
||||
#include "time-util.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;
|
||||
|
||||
typedef struct {
|
||||
ASN1_OBJECT *algorithm;
|
||||
ASN1_TYPE *parameters;
|
||||
} AlgorithmIdentifier;
|
||||
|
||||
typedef struct {
|
||||
AlgorithmIdentifier *digestAlgorithm;
|
||||
ASN1_OCTET_STRING *digest;
|
||||
} DigestInfo;
|
||||
|
||||
typedef struct {
|
||||
SpcAttributeTypeAndOptionalValue *data;
|
||||
DigestInfo *messageDigest;
|
||||
} SpcIndirectDataContent;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_BMPSTRING *unicode;
|
||||
ASN1_IA5STRING *ascii;
|
||||
} value;
|
||||
} SpcString;
|
||||
|
||||
typedef struct {
|
||||
ASN1_OCTET_STRING *classId;
|
||||
ASN1_OCTET_STRING *serializedData;
|
||||
} SpcSerializedObject;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_IA5STRING *url;
|
||||
SpcSerializedObject *moniker;
|
||||
SpcString *file;
|
||||
} value;
|
||||
} SpcLink;
|
||||
|
||||
typedef struct {
|
||||
ASN1_BIT_STRING *flags;
|
||||
SpcLink *file;
|
||||
} SpcPeImageData;
|
||||
|
||||
/* OpenSSL's ASN1_SEQUENCE/ASN1_CHOICE/IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS macros expand to code that
|
||||
* references libcrypto symbols directly, which would force us to link this object against libcrypto and
|
||||
* defeat the dlopen approach used everywhere else. We work around that in two different ways depending on
|
||||
* where the reference appears in the macro expansion:
|
||||
*
|
||||
* - ASN1_item_new/ASN1_item_free are called from the bodies of the functions generated by
|
||||
* IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS. We #define them to the matching sym_* variants, so
|
||||
* the generated bodies end up calling our dlopen'd pointers.
|
||||
*
|
||||
* - ASN1_ANY_it/ASN1_BIT_STRING_it/... (the "_it" getters) are embedded as function pointers in the
|
||||
* static const ASN1_ADB / ASN1_TEMPLATE tables emitted by the ASN1_SEQUENCE/ASN1_CHOICE macros. Static
|
||||
* initializers can only contain constant expressions, so we can't point them at the sym_* variables
|
||||
* (which are non-const globals filled in at dlopen time). Instead we define static trampoline
|
||||
* functions (openssl_ASN1_*_it) whose addresses are constant, each forwarding to the corresponding
|
||||
* sym_* pointer at runtime, and #define the OpenSSL names to our trampolines before the tables are
|
||||
* emitted.
|
||||
*
|
||||
* A function in this file must call dlopen_libcrypto() before it calls SpcLink_new() or any other generated
|
||||
* function. Otherwise the sym_* pointers are still NULL and the call crashes.
|
||||
*
|
||||
* The ASN.1 items and functions are static, so that libsystemd-shared doesn't export them. GCC and clang
|
||||
* fail the build on an unused static function because of -Werror=unused-function. For that reason, we only
|
||||
* generate the allocation functions that this file calls, and we call sym_ASN1_item_i2d() directly instead
|
||||
* of generating i2d_*() functions. */
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_ANY_it(void) {
|
||||
assert(sym_ASN1_ANY_it);
|
||||
return sym_ASN1_ANY_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_BIT_STRING_it(void) {
|
||||
assert(sym_ASN1_BIT_STRING_it);
|
||||
return sym_ASN1_BIT_STRING_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_BMPSTRING_it(void) {
|
||||
assert(sym_ASN1_BMPSTRING_it);
|
||||
return sym_ASN1_BMPSTRING_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_IA5STRING_it(void) {
|
||||
assert(sym_ASN1_IA5STRING_it);
|
||||
return sym_ASN1_IA5STRING_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_OBJECT_it(void) {
|
||||
assert(sym_ASN1_OBJECT_it);
|
||||
return sym_ASN1_OBJECT_it();
|
||||
}
|
||||
|
||||
static const ASN1_ITEM* openssl_ASN1_OCTET_STRING_it(void) {
|
||||
assert(sym_ASN1_OCTET_STRING_it);
|
||||
return sym_ASN1_OCTET_STRING_it();
|
||||
}
|
||||
|
||||
#define ASN1_item_new sym_ASN1_item_new
|
||||
#define ASN1_item_free sym_ASN1_item_free
|
||||
#define ASN1_ANY_it openssl_ASN1_ANY_it
|
||||
#define ASN1_BIT_STRING_it openssl_ASN1_BIT_STRING_it
|
||||
#define ASN1_BMPSTRING_it openssl_ASN1_BMPSTRING_it
|
||||
#define ASN1_IA5STRING_it openssl_ASN1_IA5STRING_it
|
||||
#define ASN1_OBJECT_it openssl_ASN1_OBJECT_it
|
||||
#define ASN1_OCTET_STRING_it openssl_ASN1_OCTET_STRING_it
|
||||
|
||||
ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
|
||||
ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
|
||||
ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
|
||||
} static_ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
ASN1_SEQUENCE(AlgorithmIdentifier) = {
|
||||
ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
|
||||
ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
|
||||
} static_ASN1_SEQUENCE_END(AlgorithmIdentifier)
|
||||
|
||||
ASN1_SEQUENCE(DigestInfo) = {
|
||||
ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
|
||||
ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
|
||||
} static_ASN1_SEQUENCE_END(DigestInfo);
|
||||
|
||||
ASN1_SEQUENCE(SpcIndirectDataContent) = {
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
|
||||
} static_ASN1_SEQUENCE_END(SpcIndirectDataContent);
|
||||
|
||||
IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS(SpcIndirectDataContent);
|
||||
|
||||
ASN1_CHOICE(SpcString) = {
|
||||
ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0),
|
||||
ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1)
|
||||
} static_ASN1_CHOICE_END(SpcString);
|
||||
|
||||
ASN1_SEQUENCE(SpcSerializedObject) = {
|
||||
ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING),
|
||||
ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING)
|
||||
} static_ASN1_SEQUENCE_END(SpcSerializedObject);
|
||||
|
||||
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)
|
||||
} static_ASN1_CHOICE_END(SpcLink);
|
||||
|
||||
IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS(SpcLink);
|
||||
|
||||
ASN1_SEQUENCE(SpcPeImageData) = {
|
||||
ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING),
|
||||
ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0)
|
||||
} static_ASN1_SEQUENCE_END(SpcPeImageData)
|
||||
|
||||
IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS(SpcPeImageData);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL);
|
||||
|
||||
int authenticode_spc_indirect_data_content_new(const void *digest, size_t digest_size, uint8_t **ret, size_t *ret_size) {
|
||||
int r;
|
||||
|
||||
assert(digest);
|
||||
assert(ret);
|
||||
assert(ret_size);
|
||||
|
||||
r = dlopen_libcrypto(LOG_DEBUG);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* See the authenticode spec https://aka.ms/AuthenticodeSpec for more information on the individual
|
||||
* fields. */
|
||||
|
||||
/* <<<Obsolete>>> 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_debug();
|
||||
|
||||
link->type = 2;
|
||||
link->value.file = (SpcString*) sym_ASN1_item_new(ASN1_ITEM_rptr(SpcString));
|
||||
if (!link->value.file)
|
||||
return log_oom_debug();
|
||||
|
||||
link->value.file->type = 0;
|
||||
link->value.file->value.unicode = sym_ASN1_BMPSTRING_new();
|
||||
if (!link->value.file->value.unicode)
|
||||
return log_oom_debug();
|
||||
|
||||
if (sym_ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to set ASN1 string");
|
||||
|
||||
_cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new();
|
||||
if (!peid)
|
||||
return log_oom_debug();
|
||||
|
||||
peid->file = TAKE_PTR(link);
|
||||
|
||||
_cleanup_free_ uint8_t *peidraw = NULL;
|
||||
int peidrawsz = sym_ASN1_item_i2d((ASN1_VALUE*) peid, &peidraw, ASN1_ITEM_rptr(SpcPeImageData));
|
||||
if (peidrawsz < 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to convert SpcPeImageData to BER");
|
||||
|
||||
_cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new();
|
||||
if (!idc)
|
||||
return log_oom_debug();
|
||||
|
||||
idc->data->value = sym_ASN1_TYPE_new();
|
||||
if (!idc->data->value)
|
||||
return log_oom_debug();
|
||||
|
||||
idc->data->value->type = V_ASN1_SEQUENCE;
|
||||
idc->data->value->value.sequence = sym_ASN1_STRING_new();
|
||||
if (!idc->data->value->value.sequence)
|
||||
return log_oom_debug();
|
||||
|
||||
idc->data->type = sym_OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1);
|
||||
if (!idc->data->type)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to get SpcPeImageData object");
|
||||
|
||||
if (!sym_ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to set ASN1_STRING data");
|
||||
|
||||
idc->messageDigest->digestAlgorithm->algorithm = sym_OBJ_nid2obj(NID_sha256);
|
||||
if (!idc->messageDigest->digestAlgorithm->algorithm)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to get SHA256 object");
|
||||
|
||||
idc->messageDigest->digestAlgorithm->parameters = sym_ASN1_TYPE_new();
|
||||
if (!idc->messageDigest->digestAlgorithm->parameters)
|
||||
return log_oom_debug();
|
||||
|
||||
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
|
||||
|
||||
if (sym_ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digest_size) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to set digest");
|
||||
|
||||
_cleanup_free_ uint8_t *idcraw = NULL;
|
||||
int idcrawsz = sym_ASN1_item_i2d((ASN1_VALUE*) idc, &idcraw, ASN1_ITEM_rptr(SpcIndirectDataContent));
|
||||
if (idcrawsz < 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to convert SpcIndirectDataContent to BER");
|
||||
|
||||
*ret = TAKE_PTR(idcraw);
|
||||
*ret_size = (size_t) idcrawsz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asn1_timestamp(ASN1_TIME **ret) {
|
||||
ASN1_TIME *time;
|
||||
|
||||
assert(ret);
|
||||
|
||||
usec_t epoch = parse_source_date_epoch();
|
||||
|
||||
if (epoch == USEC_INFINITY) {
|
||||
time = sym_X509_gmtime_adj(NULL, 0);
|
||||
if (!time)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to get current time");
|
||||
} else {
|
||||
time = sym_ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC));
|
||||
if (!time)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
*ret = time;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int authenticode_pkcs7_new(
|
||||
X509 *certificate,
|
||||
EVP_PKEY *private_key,
|
||||
STACK_OF(X509_ATTRIBUTE) *signed_attributes,
|
||||
PKCS7 **ret_p7,
|
||||
PKCS7_SIGNER_INFO **ret_si) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(certificate);
|
||||
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, /* hash_algorithm= */ NULL, &p7, &si);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to allocate PKCS#7 context: %m");
|
||||
|
||||
if (signed_attributes) {
|
||||
si->auth_attr = signed_attributes;
|
||||
|
||||
*ret_p7 = TAKE_PTR(p7);
|
||||
*ret_si = TAKE_PTR(si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add an empty SMIMECAP attribute to indicate we don't have any SMIME capabilities. */
|
||||
_cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = (STACK_OF(X509_ALGOR)*) sym_OPENSSL_sk_new_null();
|
||||
if (!smcap)
|
||||
return log_oom_debug();
|
||||
|
||||
if (sym_PKCS7_add_attrib_smimecap(si, smcap) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add smimecap signed attribute to signer info");
|
||||
|
||||
if (sym_PKCS7_add_attrib_content_type(si, NULL) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add content type signed attribute to signer info");
|
||||
|
||||
_cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL;
|
||||
r = asn1_timestamp(&time);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sym_PKCS7_add0_attrib_signing_time(si, time) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add signing time signed attribute to signer info");
|
||||
|
||||
TAKE_PTR(time);
|
||||
|
||||
ASN1_OBJECT *idc = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
|
||||
if (!idc)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to get SpcIndirectDataContent object");
|
||||
|
||||
if (sym_PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add signed attribute to pkcs7 signer info");
|
||||
|
||||
*ret_p7 = TAKE_PTR(p7);
|
||||
*ret_si = TAKE_PTR(si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int authenticode_pkcs7_data_bio(PKCS7 *p7, const void *idc, size_t idc_size, BIO **ret) {
|
||||
assert(p7);
|
||||
assert(idc);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(BIO_free_allp) BIO *bio = sym_PKCS7_dataInit(p7, NULL);
|
||||
if (!bio)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to create PKCS7 data bio");
|
||||
|
||||
int tag, class;
|
||||
long psz;
|
||||
const uint8_t *p = idc;
|
||||
|
||||
/* ASN1_get_object() reports errors by setting the 0x80 bit in its return value. */
|
||||
if (sym_ASN1_get_object(&p, &psz, &tag, &class, idc_size) & 0x80)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to parse ASN.1 object");
|
||||
|
||||
if (sym_BIO_write(bio, p, psz) < 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to write to PKCS7 data bio");
|
||||
|
||||
*ret = TAKE_PTR(bio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int authenticode_pkcs7_add_digest_attribute(BIO *data, PKCS7_SIGNER_INFO *si) {
|
||||
assert(data);
|
||||
assert(si);
|
||||
|
||||
BIO *mdbio = sym_BIO_find_type(data, BIO_TYPE_MD);
|
||||
if (!mdbio)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to find digest bio");
|
||||
|
||||
EVP_MD_CTX *mdc;
|
||||
if (sym_BIO_get_md_ctx(mdbio, &mdc) <= 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to get digest context from bio");
|
||||
|
||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||
unsigned digestsz;
|
||||
|
||||
if (sym_EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to get digest");
|
||||
|
||||
if (sym_PKCS7_add1_attrib_digest(si, digest, digestsz) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add PKCS9 message digest signed attribute to signer info");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int authenticode_pkcs7_finalize(PKCS7 *p7, const void *idc, size_t idc_size, uint8_t **ret, size_t *ret_size) {
|
||||
assert(p7);
|
||||
assert(idc);
|
||||
assert(ret);
|
||||
assert(ret_size);
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7c = sym_PKCS7_new();
|
||||
if (!p7c)
|
||||
return log_oom_debug();
|
||||
|
||||
p7c->type = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
|
||||
if (!p7c->type)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to get SpcIndirectDataContent object");
|
||||
|
||||
p7c->d.other = sym_ASN1_TYPE_new();
|
||||
if (!p7c->d.other)
|
||||
return log_oom_debug();
|
||||
|
||||
p7c->d.other->type = V_ASN1_SEQUENCE;
|
||||
p7c->d.other->value.sequence = sym_ASN1_STRING_new();
|
||||
if (!p7c->d.other->value.sequence)
|
||||
return log_oom_debug();
|
||||
|
||||
if (sym_ASN1_STRING_set(p7c->d.other->value.sequence, idc, idc_size) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to set ASN1 string");
|
||||
|
||||
if (sym_PKCS7_set_content(p7, p7c) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to set PKCS7 data");
|
||||
|
||||
TAKE_PTR(p7c);
|
||||
|
||||
_cleanup_free_ uint8_t *sig = NULL;
|
||||
int sigsz = sym_i2d_PKCS7(p7, &sig);
|
||||
if (sigsz < 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to convert PKCS7 signature to DER");
|
||||
|
||||
*ret = TAKE_PTR(sig);
|
||||
*ret_size = sigsz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int authenticode_sign_pe(int fd, X509 *certificate, EVP_PKEY *private_key) {
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(certificate);
|
||||
assert(private_key);
|
||||
|
||||
r = dlopen_libcrypto(LOG_DEBUG);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ void *pehash = NULL;
|
||||
size_t pehashsz;
|
||||
r = pe_hash(fd, sym_EVP_sha256(), &pehash, &pehashsz);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to hash PE binary: %m");
|
||||
|
||||
_cleanup_free_ uint8_t *idc = NULL;
|
||||
size_t idcsz = 0; /* avoid false maybe-uninitialized warning */
|
||||
r = authenticode_spc_indirect_data_content_new(pehash, pehashsz, &idc, &idcsz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
|
||||
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
|
||||
r = authenticode_pkcs7_new(certificate, private_key, /* signed_attributes= */ NULL, &p7, &si);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(BIO_free_allp) BIO *bio = NULL;
|
||||
r = authenticode_pkcs7_data_bio(p7, idc, idcsz, &bio);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sym_PKCS7_dataFinal(p7, bio) == 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to sign data");
|
||||
|
||||
_cleanup_free_ uint8_t *sig = NULL;
|
||||
size_t sigsz;
|
||||
r = authenticode_pkcs7_finalize(p7, idc, idcsz, &sig, &sigsz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return pe_append_signature(fd, sig, sigsz);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#ifndef SYSTEMD_CFLAGS_MARKER_LIBOPENSSL
|
||||
# error "missing libopenssl_cflags in meson dependency."
|
||||
#endif
|
||||
|
||||
#include "crypto-util.h"
|
||||
#include "forward.h"
|
||||
|
||||
int authenticode_spc_indirect_data_content_new(const void *digest, size_t digest_size, uint8_t **ret, size_t *ret_size);
|
||||
int authenticode_pkcs7_new(
|
||||
X509 *certificate,
|
||||
EVP_PKEY *private_key,
|
||||
STACK_OF(X509_ATTRIBUTE) *signed_attributes,
|
||||
PKCS7 **ret_p7,
|
||||
PKCS7_SIGNER_INFO **ret_si);
|
||||
int authenticode_pkcs7_data_bio(PKCS7 *p7, const void *idc, size_t idc_size, BIO **ret);
|
||||
int authenticode_pkcs7_add_digest_attribute(BIO *data, PKCS7_SIGNER_INFO *si);
|
||||
int authenticode_pkcs7_finalize(PKCS7 *p7, const void *idc, size_t idc_size, uint8_t **ret, size_t *ret_size);
|
||||
|
||||
int authenticode_sign_pe(int fd, X509 *certificate, EVP_PKEY *private_key);
|
||||
@@ -325,6 +325,10 @@ if conf.get('ENABLE_REPORT') == 1
|
||||
endif
|
||||
endif
|
||||
|
||||
if conf.get('HAVE_OPENSSL') == 1
|
||||
shared_sources += files('authenticode.c')
|
||||
endif
|
||||
|
||||
generate_ip_protocol_list = files('generate-ip-protocol-list.sh')
|
||||
ip_protocol_list_txt = custom_target(
|
||||
input : [generate_ip_protocol_list, ipproto_sources],
|
||||
|
||||
@@ -487,6 +487,7 @@ executables += [
|
||||
},
|
||||
test_template + {
|
||||
'sources' : files('test-pe-writer.c'),
|
||||
'dependencies' : libopenssl_cflags,
|
||||
},
|
||||
test_template + {
|
||||
'sources' : files('test-pkcs7-util.c'),
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include "string-util.h"
|
||||
#include "tests.h"
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
# include "authenticode.h"
|
||||
# include "crypto-util.h"
|
||||
#endif
|
||||
|
||||
#define PE_OFFSET 0x40U
|
||||
#define OPTIONAL_HEADER_SIZE 0xf0U
|
||||
#define SECTION_TABLE_OFFSET (PE_OFFSET + offsetof(PeHeader, optional) + OPTIONAL_HEADER_SIZE)
|
||||
@@ -601,4 +606,45 @@ TEST(signature_unaligned_input) {
|
||||
assert_zero(fd, STUB_SIZE + 3, 5);
|
||||
}
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
TEST(authenticode_sign_pe) {
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *key = NULL;
|
||||
_cleanup_(X509_freep) X509 *cert = NULL;
|
||||
|
||||
if (dlopen_libcrypto(LOG_DEBUG) < 0)
|
||||
return (void) log_tests_skipped("libcrypto not available");
|
||||
|
||||
ASSERT_OK(rsa_pkey_new(2048, &key));
|
||||
ASSERT_OK(x509_new_self_signed(key, "systemd test", 30, &cert));
|
||||
|
||||
_cleanup_free_ uint8_t *stub = build_stub(STUB_SIZE, 0x400);
|
||||
_cleanup_close_ int fd = ASSERT_OK(memfd_new("stub"));
|
||||
ASSERT_OK_EQ_ERRNO(pwrite(fd, stub, STUB_SIZE, 0), (ssize_t) STUB_SIZE);
|
||||
uint64_t unsigned_size = ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_END));
|
||||
|
||||
_cleanup_free_ void *hash = NULL, *signed_hash = NULL;
|
||||
size_t hash_size, signed_hash_size;
|
||||
ASSERT_OK(pe_hash(fd, sym_EVP_sha256(), &hash, &hash_size));
|
||||
|
||||
ASSERT_OK(authenticode_sign_pe(fd, cert, key));
|
||||
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
ASSERT_OK(pe_load_headers_and_sections(fd, &pe_header, /* ret_sections= */ NULL));
|
||||
ASSERT_TRUE(pe_header_is_signed(pe_header));
|
||||
|
||||
const IMAGE_DATA_DIRECTORY *certificate_table =
|
||||
pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
|
||||
ASSERT_EQ(le32toh(certificate_table->VirtualAddress), unsigned_size);
|
||||
ASSERT_OK_EQ_ERRNO(lseek(fd, 0, SEEK_END), (off_t) (unsigned_size + le32toh(certificate_table->Size)));
|
||||
|
||||
uint32_t checksum;
|
||||
ASSERT_OK(pe_checksum(fd, &checksum));
|
||||
ASSERT_EQ(le32toh(pe_header->optional.CheckSum), checksum);
|
||||
|
||||
/* The Authenticode hash does not cover the certificate table, so signing does not change it. */
|
||||
ASSERT_OK(pe_hash(fd, sym_EVP_sha256(), &signed_hash, &signed_hash_size));
|
||||
ASSERT_EQ(memcmp_nn(signed_hash, signed_hash_size, hash, hash_size), 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
Reference in new issue
Block a user