mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-05 17:19:13 +00:00
297 lines
7.2 KiB
C
297 lines
7.2 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
#ifdef HAVE_TSS2
|
|
#include <tss2/tss2_esys.h>
|
|
#endif
|
|
|
|
#include "fu-uefi-pcrs.h"
|
|
|
|
typedef struct {
|
|
guint idx;
|
|
gchar *checksum;
|
|
} FuUefiPcrItem;
|
|
|
|
struct _FuUefiPcrs {
|
|
GObject parent_instance;
|
|
GPtrArray *items; /* of FuUefiPcrItem */
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuUefiPcrs, fu_uefi_pcrs, G_TYPE_OBJECT)
|
|
|
|
#ifdef HAVE_TSS2
|
|
static void
|
|
Esys_Finalize_autoptr_cleanup(ESYS_CONTEXT *esys_context)
|
|
{
|
|
Esys_Finalize(&esys_context);
|
|
}
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup)
|
|
#endif
|
|
|
|
static gboolean
|
|
_g_string_isxdigit(GString *str)
|
|
{
|
|
for (gsize i = 0; i < str->len; i++) {
|
|
if (!g_ascii_isxdigit(str->str[i]))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_uefi_pcrs_parse_line(const gchar *line, gpointer user_data)
|
|
{
|
|
FuUefiPcrs *self = FU_UEFI_PCRS(user_data);
|
|
FuUefiPcrItem *item;
|
|
guint64 idx;
|
|
g_autofree gchar *idxstr = NULL;
|
|
g_auto(GStrv) split = NULL;
|
|
g_autoptr(GString) str = NULL;
|
|
|
|
/* split into index:hash */
|
|
if (line == NULL || line[0] == '\0')
|
|
return;
|
|
split = g_strsplit(line, ":", -1);
|
|
if (g_strv_length(split) != 2) {
|
|
g_debug("unexpected format, skipping: %s", line);
|
|
return;
|
|
}
|
|
|
|
/* get index */
|
|
idxstr = fu_common_strstrip(split[0]);
|
|
idx = fu_common_strtoull(idxstr);
|
|
if (idx > 64) {
|
|
g_debug("unexpected index, skipping: %s", idxstr);
|
|
return;
|
|
}
|
|
|
|
/* parse hash */
|
|
str = g_string_new(split[1]);
|
|
fu_common_string_replace(str, " ", "");
|
|
if ((str->len != 40 && str->len != 64) || !_g_string_isxdigit(str)) {
|
|
g_debug("not SHA-1 or SHA-256, skipping: %s", split[1]);
|
|
return;
|
|
}
|
|
g_string_ascii_down(str);
|
|
item = g_new0(FuUefiPcrItem, 1);
|
|
item->idx = idx;
|
|
item->checksum = g_string_free(g_steal_pointer(&str), FALSE);
|
|
g_ptr_array_add(self->items, item);
|
|
g_debug("added PCR-%02u=%s", item->idx, item->checksum);
|
|
}
|
|
|
|
static gboolean
|
|
fu_uefi_pcrs_setup_tpm12(FuUefiPcrs *self, const gchar *fn_pcrs, GError **error)
|
|
{
|
|
g_auto(GStrv) lines = NULL;
|
|
g_autofree gchar *buf_pcrs = NULL;
|
|
|
|
/* get entire contents */
|
|
if (!g_file_get_contents(fn_pcrs, &buf_pcrs, NULL, error))
|
|
return FALSE;
|
|
|
|
/* find PCR lines */
|
|
lines = g_strsplit(buf_pcrs, "\n", -1);
|
|
for (guint i = 0; lines[i] != NULL; i++) {
|
|
if (g_str_has_prefix(lines[i], "PCR-"))
|
|
fu_uefi_pcrs_parse_line(lines[i] + 4, self);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_uefi_pcrs_setup_tpm20(FuUefiPcrs *self, GError **error)
|
|
{
|
|
#ifdef HAVE_TSS2
|
|
TSS2_RC rc;
|
|
g_autoptr(ESYS_CONTEXT) ctx = NULL;
|
|
g_autofree TPMS_CAPABILITY_DATA *capability_data = NULL;
|
|
TPML_PCR_SELECTION pcr_selection_in = {
|
|
0,
|
|
};
|
|
g_autofree TPML_DIGEST *pcr_values = NULL;
|
|
|
|
/* suppress warning messages about missing TCTI libraries for tpm2-tss <2.3 */
|
|
if (g_getenv("FWUPD_UEFI_VERBOSE") == NULL) {
|
|
g_setenv("TSS2_LOG", "esys+error,tcti+none", FALSE);
|
|
}
|
|
|
|
rc = Esys_Initialize(&ctx, NULL, NULL);
|
|
if (rc != TSS2_RC_SUCCESS) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"failed to initialize TPM library");
|
|
return FALSE;
|
|
}
|
|
rc = Esys_Startup(ctx, TPM2_SU_CLEAR);
|
|
if (rc != TSS2_RC_SUCCESS) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"failed to initialize TPM");
|
|
return FALSE;
|
|
}
|
|
|
|
/* get hash algorithms supported by the TPM */
|
|
rc = Esys_GetCapability(ctx,
|
|
ESYS_TR_NONE,
|
|
ESYS_TR_NONE,
|
|
ESYS_TR_NONE,
|
|
TPM2_CAP_PCRS,
|
|
0,
|
|
1,
|
|
NULL,
|
|
&capability_data);
|
|
if (rc != TSS2_RC_SUCCESS) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"failed to get hash algorithms supported by TPM");
|
|
return FALSE;
|
|
}
|
|
|
|
/* fetch PCR 0 for every supported hash algorithm */
|
|
pcr_selection_in.count = capability_data->data.assignedPCR.count;
|
|
for (guint i = 0; i < pcr_selection_in.count; i++) {
|
|
pcr_selection_in.pcrSelections[i].hash =
|
|
capability_data->data.assignedPCR.pcrSelections[i].hash;
|
|
pcr_selection_in.pcrSelections[i].sizeofSelect =
|
|
capability_data->data.assignedPCR.pcrSelections[i].sizeofSelect;
|
|
pcr_selection_in.pcrSelections[i].pcrSelect[0] = 0b00000001;
|
|
}
|
|
|
|
rc = Esys_PCR_Read(ctx,
|
|
ESYS_TR_NONE,
|
|
ESYS_TR_NONE,
|
|
ESYS_TR_NONE,
|
|
&pcr_selection_in,
|
|
NULL,
|
|
NULL,
|
|
&pcr_values);
|
|
if (rc != TSS2_RC_SUCCESS) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"failed to read PCR values from TPM");
|
|
return FALSE;
|
|
}
|
|
|
|
for (guint i = 0; i < pcr_values->count; i++) {
|
|
FuUefiPcrItem *item;
|
|
g_autoptr(GString) str = NULL;
|
|
gboolean valid = FALSE;
|
|
|
|
str = g_string_new(NULL);
|
|
for (guint j = 0; j < pcr_values->digests[i].size; j++) {
|
|
gint64 val = pcr_values->digests[i].buffer[j];
|
|
if (val > 0)
|
|
valid = TRUE;
|
|
g_string_append_printf(str, "%02x", pcr_values->digests[i].buffer[j]);
|
|
}
|
|
if (valid) {
|
|
item = g_new0(FuUefiPcrItem, 1);
|
|
item->idx =
|
|
0; /* constant PCR index 0, since we only read this single PCR */
|
|
item->checksum = g_string_free(g_steal_pointer(&str), FALSE);
|
|
g_ptr_array_add(self->items, item);
|
|
g_debug("added PCR-%02u=%s", item->idx, item->checksum);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_pcrs_setup(FuUefiPcrs *self, GError **error)
|
|
{
|
|
g_autofree gchar *devpath = NULL;
|
|
g_autofree gchar *sysfstpmdir = NULL;
|
|
g_autofree gchar *fn_pcrs = NULL;
|
|
|
|
g_return_val_if_fail(FU_IS_UEFI_PCRS(self), FALSE);
|
|
|
|
/* look for TPM 1.2 */
|
|
sysfstpmdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_TPM);
|
|
devpath = g_build_filename(sysfstpmdir, "tpm0", NULL);
|
|
fn_pcrs = g_build_filename(devpath, "pcrs", NULL);
|
|
if (g_file_test(fn_pcrs, G_FILE_TEST_EXISTS) && g_getenv("FWUPD_FORCE_TPM2") == NULL) {
|
|
if (!fu_uefi_pcrs_setup_tpm12(self, fn_pcrs, error))
|
|
return FALSE;
|
|
|
|
/* assume TPM 2.0 */
|
|
} else {
|
|
if (!fu_uefi_pcrs_setup_tpm20(self, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* check we got anything */
|
|
if (self->items->len == 0) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"no TPMxx measurements found");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
GPtrArray *
|
|
fu_uefi_pcrs_get_checksums(FuUefiPcrs *self, guint idx)
|
|
{
|
|
g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free);
|
|
g_return_val_if_fail(FU_IS_UEFI_PCRS(self), NULL);
|
|
for (guint i = 0; i < self->items->len; i++) {
|
|
FuUefiPcrItem *item = g_ptr_array_index(self->items, i);
|
|
if (item->idx == idx)
|
|
g_ptr_array_add(array, g_strdup(item->checksum));
|
|
}
|
|
return g_steal_pointer(&array);
|
|
}
|
|
|
|
static void
|
|
fu_uefi_pcrs_item_free(FuUefiPcrItem *item)
|
|
{
|
|
g_free(item->checksum);
|
|
g_free(item);
|
|
}
|
|
|
|
static void
|
|
fu_uefi_pcrs_finalize(GObject *object)
|
|
{
|
|
FuUefiPcrs *self = FU_UEFI_PCRS(object);
|
|
g_ptr_array_unref(self->items);
|
|
G_OBJECT_CLASS(fu_uefi_pcrs_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
fu_uefi_pcrs_class_init(FuUefiPcrsClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
object_class->finalize = fu_uefi_pcrs_finalize;
|
|
}
|
|
|
|
static void
|
|
fu_uefi_pcrs_init(FuUefiPcrs *self)
|
|
{
|
|
self->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_uefi_pcrs_item_free);
|
|
}
|
|
|
|
FuUefiPcrs *
|
|
fu_uefi_pcrs_new(void)
|
|
{
|
|
FuUefiPcrs *self;
|
|
self = g_object_new(FU_TYPE_UEFI_PCRS, NULL);
|
|
return FU_UEFI_PCRS(self);
|
|
}
|