Introduce systemd-sbsign to do secure boot signing (#35021)
Currently in mkosi and ukify we use sbsigntools to do secure boot signing. This has multiple issues: - sbsigntools is practically unmaintained, sbvarsign is completely broken with the latest gnu-efi when built without -fshort-wchar and upstream has completely ignored my bug report about this. - sbsigntools only supports openssl engines and not the new providers API. - sbsigntools doesn't allow us to cache hardware token pins in the kernel keyring like we do nowadays when we sign stuff ourselves in systemd-repart or systemd-measure There are alternative tools like sbctl and pesign but these do not support caching hardware token pins in the kernel keyring either. To get around the issues with sbsigntools, let's introduce our own tool systemd-sbsign to do secure boot signing. This allows us to take advantage of our own openssl infra so that hardware token pins are cached in the kernel keyring as expected and we get openssl provider support as well.
This commit is contained in:
+76
-87
@@ -24,6 +24,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
|
||||
REENABLE_WARNING;
|
||||
# endif
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
|
||||
|
||||
/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error
|
||||
* string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL
|
||||
* errors." This logs at level debug. Returns -EIO (or -ENOMEM). */
|
||||
@@ -1315,12 +1317,10 @@ int pkey_generate_volume_keys(
|
||||
static int load_key_from_provider(
|
||||
const char *provider,
|
||||
const char *private_key_uri,
|
||||
OpenSSLAskPasswordUI *ui,
|
||||
EVP_PKEY **ret) {
|
||||
|
||||
assert(provider);
|
||||
assert(private_key_uri);
|
||||
assert(ui);
|
||||
assert(ret);
|
||||
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
@@ -1333,8 +1333,8 @@ static int load_key_from_provider(
|
||||
|
||||
_cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open(
|
||||
private_key_uri,
|
||||
ui->method,
|
||||
&ui->request,
|
||||
/*ui_method=*/ NULL,
|
||||
/*ui_method=*/ NULL,
|
||||
/* post_process= */ NULL,
|
||||
/* post_process_data= */ NULL);
|
||||
if (!store)
|
||||
@@ -1356,10 +1356,9 @@ static int load_key_from_provider(
|
||||
#endif
|
||||
}
|
||||
|
||||
static int load_key_from_engine(const char *engine, const char *private_key_uri, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret) {
|
||||
static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) {
|
||||
assert(engine);
|
||||
assert(private_key_uri);
|
||||
assert(ui);
|
||||
assert(ret);
|
||||
|
||||
#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
|
||||
@@ -1371,13 +1370,7 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri,
|
||||
if (ENGINE_init(e) == 0)
|
||||
return log_openssl_errors("Failed to initialize signing engine '%s'", engine);
|
||||
|
||||
if (ENGINE_ctrl(e, ENGINE_CTRL_SET_USER_INTERFACE, /*i=*/ 0, ui->method, /*f=*/ NULL) <= 0)
|
||||
return log_openssl_errors("Failed to set engine user interface");
|
||||
|
||||
if (ENGINE_ctrl(e, ENGINE_CTRL_SET_CALLBACK_DATA, /*i=*/ 0, &ui->request, /*f=*/ NULL) <= 0)
|
||||
return log_openssl_errors("Failed to set engine user interface data");
|
||||
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, ui->method, &ui->request);
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, /*ui_method=*/ NULL, /*callback_data=*/ NULL);
|
||||
if (!private_key)
|
||||
return log_openssl_errors("Failed to load private key from '%s'", private_key_uri);
|
||||
REENABLE_WARNING;
|
||||
@@ -1390,36 +1383,13 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri,
|
||||
#endif
|
||||
}
|
||||
|
||||
int openssl_load_key_from_token(
|
||||
KeySourceType private_key_source_type,
|
||||
const char *private_key_source,
|
||||
const char *private_key,
|
||||
OpenSSLAskPasswordUI *ui,
|
||||
EVP_PKEY **ret_private_key) {
|
||||
|
||||
assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER));
|
||||
assert(private_key_source);
|
||||
assert(ui);
|
||||
assert(private_key);
|
||||
|
||||
switch (private_key_source_type) {
|
||||
|
||||
case OPENSSL_KEY_SOURCE_ENGINE:
|
||||
return load_key_from_engine(private_key_source, private_key, ui, ret_private_key);
|
||||
case OPENSSL_KEY_SOURCE_PROVIDER:
|
||||
return load_key_from_provider(private_key_source, private_key, ui, ret_private_key);
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
|
||||
int r;
|
||||
|
||||
switch(UI_get_string_type(uis)) {
|
||||
case UIT_PROMPT: {
|
||||
/* If no ask password request was configured use the default openssl UI. */
|
||||
AskPasswordRequest *req = UI_get0_user_data(ui);
|
||||
AskPasswordRequest *req = (AskPasswordRequest*) UI_method_get_ex_data(UI_get_method(ui), 0);
|
||||
if (!req)
|
||||
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
|
||||
|
||||
@@ -1448,10 +1418,41 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
|
||||
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) {
|
||||
#if HAVE_OPENSSL
|
||||
static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
|
||||
_cleanup_(erase_and_freep) char *rawkey = NULL;
|
||||
_cleanup_(BIO_freep) BIO *kb = NULL;
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
|
||||
size_t rawkeysz;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(ret);
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
|
||||
NULL,
|
||||
&rawkey, &rawkeysz);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read key file '%s': %m", path);
|
||||
|
||||
kb = BIO_new_mem_buf(rawkey, rawkeysz);
|
||||
if (!kb)
|
||||
return log_oom_debug();
|
||||
|
||||
pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
|
||||
if (!pk)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(pk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) {
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password");
|
||||
@@ -1467,12 +1468,31 @@ int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) {
|
||||
|
||||
*ui = (OpenSSLAskPasswordUI) {
|
||||
.method = TAKE_PTR(method),
|
||||
.request = *request,
|
||||
};
|
||||
|
||||
UI_set_default_method(ui->method);
|
||||
|
||||
if (UI_method_set_ex_data(ui->method, 0, &ui->request) == 0)
|
||||
return log_openssl_errors("Failed to set extra data for UI method");
|
||||
|
||||
*ret = TAKE_PTR(ui);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
|
||||
#if HAVE_OPENSSL
|
||||
if (!ui)
|
||||
return NULL;
|
||||
|
||||
assert(UI_get_default_method() == ui->method);
|
||||
UI_set_default_method(UI_OpenSSL());
|
||||
UI_destroy_method(ui->method);
|
||||
return mfree(ui);
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot create ask-password user interface.");
|
||||
assert(ui == NULL);
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1531,43 +1551,6 @@ int openssl_load_x509_certificate(const char *path, X509 **ret) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(erase_and_freep) char *rawkey = NULL;
|
||||
_cleanup_(BIO_freep) BIO *kb = NULL;
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
|
||||
size_t rawkeysz;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(ret);
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
|
||||
NULL,
|
||||
&rawkey, &rawkeysz);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read key file '%s': %m", path);
|
||||
|
||||
kb = BIO_new_mem_buf(rawkey, rawkeysz);
|
||||
if (!kb)
|
||||
return log_oom_debug();
|
||||
|
||||
pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
|
||||
if (!pk)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(pk);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int openssl_load_private_key(
|
||||
KeySourceType private_key_source_type,
|
||||
const char *private_key_source,
|
||||
@@ -1575,7 +1558,7 @@ int openssl_load_private_key(
|
||||
const AskPasswordRequest *request,
|
||||
EVP_PKEY **ret_private_key,
|
||||
OpenSSLAskPasswordUI **ret_user_interface) {
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
int r;
|
||||
|
||||
assert(private_key);
|
||||
@@ -1589,18 +1572,21 @@ int openssl_load_private_key(
|
||||
*ret_user_interface = NULL;
|
||||
} else {
|
||||
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
|
||||
r = openssl_ask_password_ui_new(&ui);
|
||||
r = openssl_ask_password_ui_new(request, &ui);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to allocate ask-password user interface: %m");
|
||||
|
||||
ui->request = *request;
|
||||
switch (private_key_source_type) {
|
||||
|
||||
r = openssl_load_key_from_token(
|
||||
private_key_source_type,
|
||||
private_key_source,
|
||||
private_key,
|
||||
ui,
|
||||
ret_private_key);
|
||||
case OPENSSL_KEY_SOURCE_ENGINE:
|
||||
r = load_key_from_engine(private_key_source, private_key, ret_private_key);
|
||||
break;
|
||||
case OPENSSL_KEY_SOURCE_PROVIDER:
|
||||
r = load_key_from_provider(private_key_source, private_key, ret_private_key);
|
||||
break;
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(
|
||||
r,
|
||||
@@ -1612,6 +1598,9 @@ int openssl_load_private_key(
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int parse_openssl_key_source_argument(
|
||||
|
||||
+13
-23
@@ -55,6 +55,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ECDSA_SIG*, ECDSA_SIG_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free_all, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL);
|
||||
|
||||
@@ -134,59 +135,48 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s
|
||||
|
||||
int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);
|
||||
|
||||
int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret_private_key);
|
||||
|
||||
#else
|
||||
|
||||
typedef struct X509 X509;
|
||||
typedef struct EVP_PKEY EVP_PKEY;
|
||||
typedef struct EVP_MD EVP_MD;
|
||||
typedef struct UI_METHOD UI_METHOD;
|
||||
typedef struct ASN1_TYPE ASN1_TYPE;
|
||||
typedef struct ASN1_STRING ASN1_STRING;
|
||||
|
||||
static inline void *X509_free(X509 *p) {
|
||||
static inline void* X509_free(X509 *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void *EVP_PKEY_free(EVP_PKEY *p) {
|
||||
static inline void* EVP_PKEY_free(EVP_PKEY *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void* UI_destroy_method(UI_METHOD *p) {
|
||||
static inline void* ASN1_TYPE_free(ASN1_TYPE *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int openssl_load_key_from_token(
|
||||
KeySourceType private_key_source_type,
|
||||
const char *private_key_source,
|
||||
const char *private_key,
|
||||
OpenSSLAskPasswordUI *ui,
|
||||
EVP_PKEY **ret_private_key) {
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
static inline void* ASN1_STRING_free(ASN1_STRING *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_TYPE*, ASN1_TYPE_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_STRING*, ASN1_STRING_free, NULL);
|
||||
|
||||
struct OpenSSLAskPasswordUI {
|
||||
AskPasswordRequest request;
|
||||
UI_METHOD *method;
|
||||
};
|
||||
|
||||
int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret);
|
||||
|
||||
static inline OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
|
||||
if (!ui)
|
||||
return NULL;
|
||||
|
||||
UI_destroy_method(ui->method);
|
||||
return mfree(ui);
|
||||
}
|
||||
OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL);
|
||||
|
||||
|
||||
+314
-1
@@ -1,12 +1,16 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "log.h"
|
||||
#include "pe-binary.h"
|
||||
#include "sort-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
#include "uki.h"
|
||||
|
||||
bool pe_header_is_64bit(const PeHeader *h) {
|
||||
assert(h);
|
||||
@@ -284,3 +288,312 @@ bool pe_is_native(const PeHeader *pe_header) {
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Implements:
|
||||
*
|
||||
* https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
|
||||
* → Section "Calculating the PE Image Hash"
|
||||
*/
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
|
||||
uint8_t buffer[64*1024];
|
||||
|
||||
log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
|
||||
|
||||
while (size > 0) {
|
||||
size_t m = MIN(size, sizeof(buffer));
|
||||
ssize_t n;
|
||||
|
||||
n = pread(fd, buffer, m, offset);
|
||||
if (n < 0)
|
||||
return log_debug_errno(errno, "Failed to read file for hashing: %m");
|
||||
if ((size_t) n != m)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
|
||||
|
||||
if (EVP_DigestUpdate(md_ctx, buffer, m) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
|
||||
offset += m;
|
||||
size -= m;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
|
||||
return CMP(ASSERT_PTR(a)->PointerToRawData, ASSERT_PTR(b)->PointerToRawData);
|
||||
}
|
||||
#endif
|
||||
|
||||
int pe_hash(int fd,
|
||||
const EVP_MD *md,
|
||||
void **ret_hash,
|
||||
size_t *ret_hash_size) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
const IMAGE_DATA_DIRECTORY *certificate_table;
|
||||
struct stat st;
|
||||
uint64_t p, q;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(md);
|
||||
assert(ret_hash_size);
|
||||
assert(ret_hash);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat file: %m");
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Not a regular file: %m");
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pe_load_sections(fd, dos_header, pe_header, §ions);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
|
||||
if (!certificate_table)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
|
||||
|
||||
mdctx = EVP_MD_CTX_new();
|
||||
if (!mdctx)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
|
||||
|
||||
/* Everything from beginning of file to CheckSum field in PE header */
|
||||
p = (uint64_t) dos_header->e_lfanew +
|
||||
offsetof(PeHeader, optional.CheckSum);
|
||||
r = hash_file(fd, mdctx, 0, p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
p += sizeof(le32_t);
|
||||
|
||||
/* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
|
||||
q = (uint64_t) dos_header->e_lfanew +
|
||||
PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
|
||||
r = hash_file(fd, mdctx, p, q - p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
q += sizeof(IMAGE_DATA_DIRECTORY);
|
||||
|
||||
/* The rest of the header + the section table */
|
||||
p = pe_header->optional.SizeOfHeaders;
|
||||
if (p < q)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
|
||||
r = hash_file(fd, mdctx, q, p - q);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Sort by location in file */
|
||||
typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p += section->SizeOfRawData;
|
||||
}
|
||||
|
||||
if ((uint64_t) st.st_size > p) {
|
||||
|
||||
if (st.st_size - p < certificate_table->Size)
|
||||
return log_debug_errno(errno, "No space for certificate table, refusing.");
|
||||
|
||||
r = hash_file(fd, mdctx, p, st.st_size - p - certificate_table->Size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */
|
||||
if (st.st_size % 8 != 0 && EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
}
|
||||
|
||||
int hsz = EVP_MD_CTX_size(mdctx);
|
||||
if (hsz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
|
||||
|
||||
unsigned hash_size = (unsigned) hsz;
|
||||
_cleanup_free_ void *hash = malloc(hsz);
|
||||
if (!hash)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
|
||||
|
||||
assert(hash_size == (unsigned) hsz);
|
||||
|
||||
*ret_hash = TAKE_PTR(hash);
|
||||
*ret_hash_size = hash_size;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate PE hash.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int pe_checksum(int fd, uint32_t *ret) {
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat file: %m");
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uint32_t checksum = 0, checksum_offset = le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum);
|
||||
size_t off = 0;
|
||||
for (;;) {
|
||||
uint16_t buf[32*1024];
|
||||
|
||||
ssize_t n = pread(fd, buf, sizeof(buf), off);
|
||||
if (n == 0)
|
||||
break;
|
||||
if (n < 0)
|
||||
return log_debug_errno(errno, "Failed to read from PE file: %m");
|
||||
if (n % sizeof(uint16_t) != 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read from PE file");
|
||||
|
||||
for (size_t i = 0; i < (size_t) n / 2; i++) {
|
||||
if (off + i >= checksum_offset && off + i < checksum_offset + sizeof(pe_header->optional.CheckSum))
|
||||
continue;
|
||||
|
||||
uint16_t val = le16toh(buf[i]);
|
||||
|
||||
checksum += val;
|
||||
checksum = (checksum >> 16) + (checksum & 0xffff);
|
||||
}
|
||||
|
||||
off += n;
|
||||
}
|
||||
|
||||
checksum = (checksum >> 16) + (checksum & 0xffff);
|
||||
checksum += off;
|
||||
|
||||
*ret = checksum;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
|
||||
|
||||
static void section_hash_array_done(SectionHashArray *array) {
|
||||
assert(array);
|
||||
|
||||
for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
|
||||
free((*array)[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
int uki_hash(int fd,
|
||||
const EVP_MD *md,
|
||||
void* ret_hashes[static _UNIFIED_SECTION_MAX],
|
||||
size_t *ret_hash_size) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(section_hash_array_done) SectionHashArray hashes = {};
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret_hashes);
|
||||
assert(ret_hash_size);
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pe_load_sections(fd, dos_header, pe_header, §ions);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
int hsz = EVP_MD_size(md);
|
||||
if (hsz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
|
||||
_cleanup_free_ char *n = NULL;
|
||||
ssize_t i;
|
||||
|
||||
n = memdup_suffix0(section->Name, sizeof(section->Name));
|
||||
if (!n)
|
||||
return log_oom_debug();
|
||||
|
||||
i = string_table_lookup(unified_sections, _UNIFIED_SECTION_MAX, n);
|
||||
if (i < 0)
|
||||
continue;
|
||||
|
||||
if (hashes[i])
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
|
||||
|
||||
mdctx = EVP_MD_CTX_new();
|
||||
if (!mdctx)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
|
||||
|
||||
r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (section->SizeOfRawData < section->VirtualSize) {
|
||||
uint8_t zeroes[1024] = {};
|
||||
size_t remaining = section->VirtualSize - section->SizeOfRawData;
|
||||
|
||||
while (remaining > 0) {
|
||||
size_t sz = MIN(sizeof(zeroes), remaining);
|
||||
|
||||
if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
|
||||
remaining -= sz;
|
||||
}
|
||||
}
|
||||
|
||||
hashes[i] = malloc(hsz);
|
||||
if (!hashes[i])
|
||||
return log_oom_debug();
|
||||
|
||||
unsigned hash_size = (unsigned) hsz;
|
||||
if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
|
||||
|
||||
assert(hash_size == (unsigned) hsz);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *hs = NULL;
|
||||
|
||||
hs = hexmem(hashes[i], hsz);
|
||||
log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs));
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(ret_hashes, hashes, sizeof(hashes));
|
||||
zero(hashes);
|
||||
*ret_hash_size = (unsigned) hsz;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate UKI hash.");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "openssl-util.h"
|
||||
#include "macro-fundamental.h"
|
||||
#include "sparse-endian.h"
|
||||
#include "uki.h"
|
||||
|
||||
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
|
||||
|
||||
/* When naming things we try to stay close to the official Windows APIs as per:
|
||||
* → https://learn.microsoft.com/en-us/windows/win32/debug/pe-format */
|
||||
@@ -147,3 +152,9 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
|
||||
bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
|
||||
|
||||
bool pe_is_native(const PeHeader *pe_header);
|
||||
|
||||
int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
|
||||
|
||||
int pe_checksum(int fd, uint32_t *ret);
|
||||
|
||||
int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);
|
||||
|
||||
Reference in New Issue
Block a user