diff --git a/docs/hsi.md b/docs/hsi.md
index e097f5dd1..31ea55d63 100644
--- a/docs/hsi.md
+++ b/docs/hsi.md
@@ -350,6 +350,33 @@ To meet HSI-1 on systems that run this test, the result must be `locked`. *[v1.5
- [Chromium documentation for Intel ME](https://chromium.googlesource.com/chromiumos/third_party/flashrom/+/master/Documentation/mysteries_intel.txt)
+
+
+### [ME BootGuard Platform Key](#org.fwupd.hsi.Mei.BootGuardPlatformKey)
+
+The BootGuard Platform Key is fused into the CPU PCH during manufacturing by the OEM.
+
+At bootup, an authenticated code module computes a hash of the Platform Key and and compares it
+with the one stored in field-programmable fuses.
+If the key matches the ACM will pass control to the firmware, otherwise the boot process will stop.
+
+In 2022 a number of Platform **secret** Keys were leaked by Lenovo and confirmed by Intel.
+
+**Impact:** A custom system firmware can be signed using the leaked private key to completely
+disable UEFI Secure Boot and allow complete persistent compromise of the affected machine.
+
+**Possible results:**
+
+- `valid`: device uses a BootGuard Platform Key that is not known to be compromised
+- `not-valid`: device uses a key that is compromised
+
+To meet HSI-1 on systems that run this test, the result must be `valid`. *[v1.8.7]*
+
+**References:**
+
+- [Intel leak checker](https://github.com/phretor/intel-leak-checker/)
+- [Tom's Hardware Article](https://www.tomshardware.com/news/intel-confirms-6gb-alder-lake-bios-source-code-leak-new-details-emerge)
+
### [CSME Version](#org.fwupd.hsi.Mei.Version)
diff --git a/libfwupd/fwupd-security-attr-private.h b/libfwupd/fwupd-security-attr-private.h
index f6025beac..5057f2546 100644
--- a/libfwupd/fwupd-security-attr-private.h
+++ b/libfwupd/fwupd-security-attr-private.h
@@ -166,6 +166,14 @@ G_BEGIN_DECLS
* Since: 1.5.0
**/
#define FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP "org.fwupd.hsi.Mei.OverrideStrap"
+/**
+ * FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST:
+ *
+ * Host Security ID attribute for Intel ME Key Manifest
+ *
+ * Since: 1.8.7
+ **/
+#define FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST "org.fwupd.hsi.Mei.KeyManifest"
/**
* FWUPD_SECURITY_ATTR_ID_MEI_VERSION:
*
diff --git a/libfwupdplugin/fu-mei-device.c b/libfwupdplugin/fu-mei-device.c
index efd245ab9..2ec97c9cf 100644
--- a/libfwupdplugin/fu-mei-device.c
+++ b/libfwupdplugin/fu-mei-device.c
@@ -157,6 +157,8 @@ fu_mei_device_connect(FuMeiDevice *self, guchar req_protocol_version, GError **e
if (!fwupd_guid_from_string(priv->uuid, &guid_le, FWUPD_GUID_FLAG_MIXED_ENDIAN, error))
return FALSE;
+ if (g_getenv("FU_MEI_DEVICE_DEBUG") != NULL)
+ fu_dump_raw(G_LOG_DOMAIN, "guid_le", (guint8 *)&guid_le, sizeof(guid_le));
memcpy(&data.in_client_uuid, &guid_le, sizeof(guid_le));
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
IOCTL_MEI_CONNECT_CLIENT,
diff --git a/libfwupdplugin/fu-security-attrs.c b/libfwupdplugin/fu-security-attrs.c
index 739aef07c..ed77368e6 100644
--- a/libfwupdplugin/fu-security-attrs.c
+++ b/libfwupdplugin/fu-security-attrs.c
@@ -330,6 +330,7 @@ static struct {
{FWUPD_SECURITY_ATTR_ID_IOMMU, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT},
{FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL},
{FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL},
+ {FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL},
{FWUPD_SECURITY_ATTR_ID_MEI_VERSION, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL},
{FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL},
{FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT},
diff --git a/plugins/intel-me/README.md b/plugins/intel-me/README.md
new file mode 100644
index 000000000..5657a98b0
--- /dev/null
+++ b/plugins/intel-me/README.md
@@ -0,0 +1,19 @@
+# Intel ME
+
+## Introduction
+
+This plugin is used to talk to the Intel ME device, typically CSME.
+
+It allows us to get the Platform Key as used for BootGuard.
+
+## GUID Generation
+
+These devices use the existing GUIDs provided by the ME host interfaces.
+
+## Vendor ID Security
+
+The devices are not upgradable and thus require no vendor ID set.
+
+## External Interface Access
+
+This plugin requires `ioctl(IOCTL_MEI_CONNECT_CLIENT)` to `/dev/mei0`.
diff --git a/plugins/intel-me/fu-intel-me-common.c b/plugins/intel-me/fu-intel-me-common.c
new file mode 100644
index 000000000..2846760a7
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-common.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include "fu-intel-me-common.h"
+
+gboolean
+fu_intel_me_mkhi_result_to_error(FuMkhiResult result, GError **error)
+{
+ if (result == MKHI_STATUS_SUCCESS)
+ return TRUE;
+
+ switch (result) {
+ case MKHI_STATUS_NOT_SUPPORTED:
+ case MKHI_STATUS_NOT_AVAILABLE:
+ case MKHI_STATUS_NOT_SET:
+ g_set_error(error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "not supported [0x%x]",
+ result);
+ break;
+ default:
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic failure [0x%x]", result);
+ break;
+ }
+ return FALSE;
+}
+
+gboolean
+fu_intel_me_mkhi_verify_header(const FuMkhiHeader hdr_req,
+ const FuMkhiHeader hdr_res,
+ GError **error)
+{
+ if (hdr_req.group_id != hdr_res.group_id) {
+ g_set_error(error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "invalid response group ID, requested 0x%x and got 0x%x",
+ hdr_req.group_id,
+ hdr_res.group_id);
+ return FALSE;
+ }
+ if (hdr_req.command != hdr_res.command) {
+ g_set_error(error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "invalid response command, requested 0x%x and got 0x%x",
+ (guint)hdr_req.command,
+ (guint)hdr_res.command);
+ return FALSE;
+ }
+ if (!hdr_res.is_resp) {
+ g_set_error_literal(error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "invalid response group ID, not a response!");
+ return FALSE;
+ }
+ return fu_intel_me_mkhi_result_to_error(hdr_res.result, error);
+}
+
+GString *
+fu_intel_me_convert_checksum(GByteArray *buf, GError **error)
+{
+ gboolean seen_non00_data = FALSE;
+ gboolean seen_nonff_data = FALSE;
+ g_autoptr(GString) checksum = g_string_new(NULL);
+
+ /* create checksum, but only if non-zero and set */
+ for (gsize i = 0; i < buf->len; i++) {
+ if (!seen_non00_data && buf->data[i] != 0x00)
+ seen_non00_data = TRUE;
+ if (!seen_nonff_data && buf->data[i] != 0xFF)
+ seen_nonff_data = TRUE;
+ g_string_append_printf(checksum, "%02x", buf->data[i]);
+ }
+ if (!seen_non00_data) {
+ g_set_error_literal(error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_INITIALIZED,
+ "buffer was all 0x00");
+ return NULL;
+ }
+ if (!seen_nonff_data) {
+ g_set_error_literal(error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_INITIALIZED,
+ "buffer was all 0xFF");
+ return NULL;
+ }
+
+ /* success */
+ return g_steal_pointer(&checksum);
+}
diff --git a/plugins/intel-me/fu-intel-me-common.h b/plugins/intel-me/fu-intel-me-common.h
new file mode 100644
index 000000000..d886cfa88
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-common.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include
+
+typedef struct __attribute__((packed)) {
+ guint8 group_id;
+ guint8 command : 7;
+ guint8 is_resp : 1;
+ guint8 rsvd;
+ guint8 result;
+} FuMkhiHeader;
+
+typedef enum {
+ MKHI_GROUP_ID_CBM,
+ MKHI_GROUP_ID_PM, /* no longer used */
+ MKHI_GROUP_ID_PWD,
+ MKHI_GROUP_ID_FWCAPS,
+ MKHI_GROUP_ID_APP, /* no longer used */
+ MKHI_GROUP_ID_FWUPDATE, /* for manufacturing downgrade */
+ MKHI_GROUP_ID_FIRMWARE_UPDATE,
+ MKHI_GROUP_ID_BIST,
+ MKHI_GROUP_ID_MDES,
+ MKHI_GROUP_ID_ME_DBG,
+ MKHI_GROUP_ID_MCA, /* sometimes called "FPF" */
+ MKHI_GROUP_ID_GEN = 0xFF
+} FuMkhiGroupId;
+
+#define MCA_READ_FILE_EX 0x02
+#define MCA_READ_FILE_EX_CMD 0x0A
+
+typedef enum {
+ MKHI_STATUS_SUCCESS,
+ MKHI_STATUS_INVALID_STATE,
+ MKHI_STATUS_MESSAGE_SKIPPED,
+ MKHI_STATUS_SIZE_ERROR = 0x05,
+ MKHI_STATUS_NOT_SET = 0x0F, /* guessed */
+ MKHI_STATUS_NOT_AVAILABLE = 0x18, /* guessed */
+ MKHI_STATUS_INVALID_ACCESS = 0x84,
+ MKHI_STATUS_INVALID_PARAMS = 0x85,
+ MKHI_STATUS_NOT_READY = 0x88,
+ MKHI_STATUS_NOT_SUPPORTED = 0x89,
+ MKHI_STATUS_INVALID_ADDRESS = 0x8C,
+ MKHI_STATUS_INVALID_COMMAND = 0x8D,
+ MKHI_STATUS_FAILURE = 0x9E,
+ MKHI_STATUS_INVALID_RESOURCE = 0xE4,
+ MKHI_STATUS_RESOURCE_IN_USE = 0xE5,
+ MKHI_STATUS_NO_RESOURCE = 0xE6,
+ MKHI_STATUS_GENERAL_ERROR = 0xFF
+} FuMkhiResult;
+
+GString *
+fu_intel_me_convert_checksum(GByteArray *buf, GError **error);
+gboolean
+fu_intel_me_mkhi_result_to_error(FuMkhiResult result, GError **error);
+gboolean
+fu_intel_me_mkhi_verify_header(const FuMkhiHeader hdr_req,
+ const FuMkhiHeader hdr_res,
+ GError **error);
diff --git a/plugins/intel-me/fu-intel-me-heci-device.c b/plugins/intel-me/fu-intel-me-heci-device.c
new file mode 100644
index 000000000..89f17b109
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-heci-device.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include
+
+#include "fu-intel-me-common.h"
+#include "fu-intel-me-heci-device.h"
+
+G_DEFINE_TYPE(FuIntelMeHeciDevice, fu_intel_me_heci_device, FU_TYPE_MEI_DEVICE)
+
+#define FU_INTEL_ME_HECI_DEVICE_TIMEOUT 200 /* ms */
+
+static gboolean
+fu_intel_me_heci_device_open(FuDevice *device, GError **error)
+{
+ /* open then create context */
+ if (!FU_DEVICE_CLASS(fu_intel_me_heci_device_parent_class)->open(device, error))
+ return FALSE;
+ return fu_mei_device_connect(FU_MEI_DEVICE(device), 0, error);
+}
+
+static GByteArray *
+fu_intel_me_heci_device_read_file_response(GByteArray *buf_res, guint32 datasz_req, GError **error)
+{
+ guint32 datasz_res = 0;
+ g_autoptr(GByteArray) buf = g_byte_array_new();
+
+ /* verify payload size */
+ if (!fu_memread_uint32_safe(buf_res->data,
+ buf_res->len,
+ sizeof(FuMkhiHeader),
+ &datasz_res,
+ G_LITTLE_ENDIAN,
+ error))
+ return NULL;
+ if (datasz_res > datasz_req || datasz_res == 0x0) {
+ g_set_error(error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "invalid response data size, requested 0x%x and got 0x%x",
+ datasz_req,
+ datasz_res);
+ return NULL;
+ }
+
+ /* copy out payload */
+ for (gsize i = 0; i < datasz_res; i++) {
+ guint8 tmp = 0;
+ if (!fu_memread_uint8_safe(buf_res->data,
+ buf_res->len,
+ sizeof(FuMkhiHeader) + sizeof(guint32) + i,
+ &tmp,
+ error))
+ return NULL;
+ fu_byte_array_append_uint8(buf, tmp);
+ }
+
+ /* success */
+ return g_steal_pointer(&buf);
+}
+
+GByteArray *
+fu_intel_me_heci_device_read_file(FuIntelMeHeciDevice *self, const gchar *filename, GError **error)
+{
+ FuMkhiHeader hdr_res = {0};
+ gsize filenamesz = strlen(filename);
+ guint datasz_req = 0x80;
+ g_autoptr(GByteArray) buf_fn = g_byte_array_new();
+ g_autoptr(GByteArray) buf_req = g_byte_array_new();
+ g_autoptr(GByteArray) buf_res = g_byte_array_new();
+ const FuMkhiHeader hdr_req = {.group_id = MKHI_GROUP_ID_MCA, .command = MCA_READ_FILE_EX};
+
+ /* filename must be a specific size */
+ fu_byte_array_set_size(buf_fn, 0x40, 0x0);
+ if (!fu_memcpy_safe(buf_fn->data,
+ buf_fn->len - 1,
+ 0x0,
+ (const guint8 *)filename,
+ filenamesz,
+ 0x0,
+ filenamesz,
+ error))
+ return NULL;
+
+ /* request */
+ g_byte_array_append(buf_req, (const guint8 *)&hdr_req, sizeof(hdr_req));
+ g_byte_array_append(buf_req, buf_fn->data, buf_fn->len); /* Filename */
+ fu_byte_array_append_uint32(buf_req, 0x0, G_LITTLE_ENDIAN); /* Offset */
+ fu_byte_array_append_uint32(buf_req, datasz_req, G_LITTLE_ENDIAN); /* DataSize */
+ fu_byte_array_append_uint8(buf_req, (1 << 3)); /* Flags?? */
+ if (!fu_mei_device_write(FU_MEI_DEVICE(self),
+ buf_req->data,
+ buf_req->len,
+ FU_INTEL_ME_HECI_DEVICE_TIMEOUT,
+ error))
+ return NULL;
+
+ /* response */
+ fu_byte_array_set_size(buf_res, sizeof(hdr_res) + sizeof(guint32) + datasz_req, 0x0);
+ if (!fu_mei_device_read(FU_MEI_DEVICE(self),
+ buf_res->data,
+ buf_res->len,
+ NULL,
+ FU_INTEL_ME_HECI_DEVICE_TIMEOUT,
+ error))
+ return NULL;
+
+ /* verify header */
+ if (!fu_memcpy_safe((guint8 *)&hdr_res,
+ sizeof(hdr_res),
+ 0x0, /* dst */
+ buf_res->data,
+ buf_res->len,
+ 0x0, /* src */
+ sizeof(hdr_req),
+ error))
+ return NULL;
+ if (!fu_intel_me_mkhi_verify_header(hdr_req, hdr_res, error))
+ return NULL;
+ return fu_intel_me_heci_device_read_file_response(buf_res, datasz_req, error);
+}
+
+GByteArray *
+fu_intel_me_heci_device_read_file_ex(FuIntelMeHeciDevice *self,
+ guint32 file_id,
+ guint32 section,
+ guint32 datasz_req,
+ GError **error)
+{
+ FuMkhiHeader hdr_res = {0};
+ g_autoptr(GByteArray) buf_req = g_byte_array_new();
+ g_autoptr(GByteArray) buf_res = g_byte_array_new();
+ const FuMkhiHeader hdr_req = {.group_id = MKHI_GROUP_ID_MCA,
+ .command = MCA_READ_FILE_EX_CMD};
+
+ /* request */
+ g_byte_array_append(buf_req, (const guint8 *)&hdr_req, sizeof(hdr_req));
+ fu_byte_array_append_uint32(buf_req, file_id, G_LITTLE_ENDIAN); /* FileId */
+ fu_byte_array_append_uint32(buf_req, 0x0, G_LITTLE_ENDIAN); /* Offset */
+ fu_byte_array_append_uint32(buf_req, datasz_req, G_LITTLE_ENDIAN); /* DataSize */
+ fu_byte_array_append_uint8(buf_req, section); /* Flags */
+ if (!fu_mei_device_write(FU_MEI_DEVICE(self),
+ buf_req->data,
+ buf_req->len,
+ FU_INTEL_ME_HECI_DEVICE_TIMEOUT,
+ error))
+ return NULL;
+
+ /* response */
+ fu_byte_array_set_size(buf_res, sizeof(hdr_res) + sizeof(guint32) + datasz_req, 0x0);
+ if (!fu_mei_device_read(FU_MEI_DEVICE(self),
+ buf_res->data,
+ buf_res->len,
+ NULL,
+ FU_INTEL_ME_HECI_DEVICE_TIMEOUT,
+ error))
+ return NULL;
+
+ /* verify header */
+ if (!fu_memcpy_safe((guint8 *)&hdr_res,
+ sizeof(hdr_res),
+ 0x0, /* dst */
+ buf_res->data,
+ buf_res->len,
+ 0x0, /* src */
+ sizeof(hdr_req),
+ error))
+ return NULL;
+ if (!fu_intel_me_mkhi_verify_header(hdr_req, hdr_res, error))
+ return NULL;
+ return fu_intel_me_heci_device_read_file_response(buf_res, datasz_req, error);
+}
+
+static void
+fu_intel_me_heci_device_init(FuIntelMeHeciDevice *self)
+{
+ fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
+ fu_device_add_icon(FU_DEVICE(self), "computer");
+}
+
+static void
+fu_intel_me_heci_device_class_init(FuIntelMeHeciDeviceClass *klass)
+{
+ FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
+ klass_device->open = fu_intel_me_heci_device_open;
+}
diff --git a/plugins/intel-me/fu-intel-me-heci-device.h b/plugins/intel-me/fu-intel-me-heci-device.h
new file mode 100644
index 000000000..bf797335a
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-heci-device.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include
+
+#define FU_TYPE_INTEL_ME_HECI_DEVICE (fu_intel_me_heci_device_get_type())
+G_DECLARE_DERIVABLE_TYPE(FuIntelMeHeciDevice,
+ fu_intel_me_heci_device,
+ FU,
+ INTEL_ME_HECI_DEVICE,
+ FuMeiDevice)
+
+struct _FuIntelMeHeciDeviceClass {
+ FuMeiDeviceClass parent_class;
+};
+
+GByteArray *
+fu_intel_me_heci_device_read_file(FuIntelMeHeciDevice *self, const gchar *filename, GError **error);
+GByteArray *
+fu_intel_me_heci_device_read_file_ex(FuIntelMeHeciDevice *self,
+ guint32 file_id,
+ guint32 section,
+ guint32 datasz_req,
+ GError **error);
diff --git a/plugins/intel-me/fu-intel-me-mca-device.c b/plugins/intel-me/fu-intel-me-mca-device.c
new file mode 100644
index 000000000..e3b74a4ae
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-mca-device.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include "fu-intel-me-common.h"
+#include "fu-intel-me-mca-device.h"
+
+struct _FuIntelMeMcaDevice {
+ FuIntelMeHeciDevice parent_instance;
+ gboolean using_leaked_km;
+};
+
+G_DEFINE_TYPE(FuIntelMeMcaDevice, fu_intel_me_mca_device, FU_TYPE_INTEL_ME_HECI_DEVICE)
+
+#define MCA_SECTION_ME 0x00 /* OEM Public Key Hash ME FW */
+#define MCA_SECTION_UEP 0x04 /* OEM Public Key Hash UEP */
+#define MCA_SECTION_FPF 0x08 /* OEM Public Key Hash FPF */
+
+static const gchar *
+fu_intel_me_mca_device_section_to_string(guint8 section)
+{
+ if (section == MCA_SECTION_ME)
+ return "ME";
+ if (section == MCA_SECTION_UEP)
+ return "UEP";
+ if (section == MCA_SECTION_FPF)
+ return "FPF";
+ return NULL;
+}
+
+static gboolean
+fu_intel_me_mca_device_add_checksum_for_id(FuIntelMeMcaDevice *self,
+ guint32 file_id,
+ guint32 section,
+ GError **error)
+{
+ g_autoptr(GByteArray) buf = NULL;
+ g_autoptr(GString) checksum = NULL;
+
+ /*
+ * Call READ_FILE_EX with a larger-than-required data size -- which hopefully works when
+ * SHA512 results start being returned too.
+ *
+ * CometLake: 0x20 (SHA256)
+ * TigerLake: 0x30 (SHA384)
+ */
+ buf = fu_intel_me_heci_device_read_file_ex(FU_INTEL_ME_HECI_DEVICE(self),
+ file_id,
+ section,
+ 0x40,
+ error);
+ if (buf == NULL)
+ return FALSE;
+
+ /* convert into checksum, but only if non-zero and set */
+ checksum = fu_intel_me_convert_checksum(buf, error);
+ if (checksum == NULL)
+ return FALSE;
+ fu_device_add_checksum(FU_DEVICE(self), checksum->str);
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+fu_intel_me_mca_device_setup(FuDevice *device, GError **error)
+{
+ FuIntelMeMcaDevice *self = FU_INTEL_ME_MCA_DEVICE(device);
+ const guint32 sections[] = {MCA_SECTION_FPF, MCA_SECTION_UEP, MCA_SECTION_ME, G_MAXUINT32};
+ const guint32 file_ids[] = {0x40002300, /* CometLake: OEM Public Key Hash */
+ 0x40005B00, /* TigerLake: 1st OEM Public Key Hash */
+ 0x40005C00 /* TigerLake: 2nd OEM Public Key Hash */,
+ G_MAXUINT32};
+ const gchar *leaked_kms[] = {"05a92e16da51d10882bfa7e3ba449184ce48e94fa9903e07983d2112ab"
+ "54ecf20fbb07512cea2c13b167c0e252c6a704",
+ "2e357bca116cf3da637bb5803be3550873eddb5a4431a49df1770aca83"
+ "5d94853b458239d207653dce277910d9e5aa0b",
+ "b52a825cf0be60027f12a226226b055ed68efaa9273695d45d859c0ed3"
+ "3d063143974f4b4c59fabfc5afeadab0b00f09",
+ NULL};
+
+ /* look for all the possible OEM Public Key hashes using the CML+ method */
+ for (guint i = 0; file_ids[i] != G_MAXUINT32; i++) {
+ for (guint j = 0; sections[j] != G_MAXUINT32; j++) {
+ g_autoptr(GError) error_local = NULL;
+ if (!fu_intel_me_mca_device_add_checksum_for_id(self,
+ file_ids[i],
+ sections[j],
+ &error_local)) {
+ if (g_error_matches(error_local,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED) ||
+ g_error_matches(error_local,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_INITIALIZED)) {
+ continue;
+ }
+ g_warning("failed to get public key using file-id 0x%x, "
+ "section %s [0x%x]: %s",
+ file_ids[i],
+ fu_intel_me_mca_device_section_to_string(sections[j]),
+ sections[j],
+ error_local->message);
+ }
+ }
+ }
+
+ /* no point even adding */
+ if (fu_device_get_checksums(device)->len == 0) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "no OEM public keys found");
+ return FALSE;
+ }
+
+ /* check for any of the leaked keys */
+ for (guint i = 0; leaked_kms[i] != NULL; i++) {
+ if (fu_device_has_checksum(self, leaked_kms[i])) {
+ self->using_leaked_km = TRUE;
+ break;
+ }
+ }
+
+ /* success */
+ return TRUE;
+}
+
+static void
+fu_intel_me_mca_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs)
+{
+ FuIntelMeMcaDevice *self = FU_INTEL_ME_MCA_DEVICE(device);
+ g_autoptr(FwupdSecurityAttr) attr = NULL;
+
+ /* create attr */
+ attr =
+ fu_device_security_attr_new(FU_DEVICE(self), FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST);
+ fu_security_attrs_append(attrs, attr);
+
+ /* verify keys */
+ if (fu_device_get_checksums(device)->len == 0) {
+ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA);
+ return;
+ }
+ if (self->using_leaked_km) {
+ fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID);
+ return;
+ }
+
+ /* success */
+ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS);
+ fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID);
+}
+
+static void
+fu_intel_me_mca_device_init(FuIntelMeMcaDevice *self)
+{
+ fu_device_set_logical_id(FU_DEVICE(self), "MCA");
+ fu_device_set_name(FU_DEVICE(self), "BootGuard Configuration");
+ fu_device_add_parent_guid(FU_DEVICE(self), "main-system-firmware");
+}
+
+static void
+fu_intel_me_mca_device_class_init(FuIntelMeMcaDeviceClass *klass)
+{
+ FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
+ klass_device->setup = fu_intel_me_mca_device_setup;
+ klass_device->add_security_attrs = fu_intel_me_mca_device_add_security_attrs;
+}
diff --git a/plugins/intel-me/fu-intel-me-mca-device.h b/plugins/intel-me/fu-intel-me-mca-device.h
new file mode 100644
index 000000000..36efebae6
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-mca-device.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include
+
+#include "fu-intel-me-heci-device.h"
+
+#define FU_TYPE_INTEL_ME_MCA_DEVICE (fu_intel_me_mca_device_get_type())
+G_DECLARE_FINAL_TYPE(FuIntelMeMcaDevice,
+ fu_intel_me_mca_device,
+ FU,
+ INTEL_ME_MCA_DEVICE,
+ FuIntelMeHeciDevice)
diff --git a/plugins/intel-me/fu-intel-me-mkhi-device.c b/plugins/intel-me/fu-intel-me-mkhi-device.c
new file mode 100644
index 000000000..66f364531
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-mkhi-device.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include "fu-intel-me-common.h"
+#include "fu-intel-me-mkhi-device.h"
+
+struct _FuIntelMeMkhiDevice {
+ FuIntelMeHeciDevice parent_instance;
+};
+
+G_DEFINE_TYPE(FuIntelMeMkhiDevice, fu_intel_me_mkhi_device, FU_TYPE_INTEL_ME_HECI_DEVICE)
+
+static gboolean
+fu_intel_me_mkhi_device_add_checksum_for_filename(FuIntelMeMkhiDevice *self,
+ const gchar *filename,
+ GError **error)
+{
+ g_autoptr(GByteArray) buf = NULL;
+ g_autoptr(GString) checksum = NULL;
+
+ /* read from the MFS */
+ buf = fu_intel_me_heci_device_read_file(FU_INTEL_ME_HECI_DEVICE(self), filename, error);
+ if (buf == NULL)
+ return FALSE;
+
+ /* convert into checksum, but only if non-zero and set */
+ checksum = fu_intel_me_convert_checksum(buf, error);
+ if (checksum == NULL)
+ return FALSE;
+ fu_device_add_checksum(FU_DEVICE(self), checksum->str);
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+fu_intel_me_mkhi_device_setup(FuDevice *device, GError **error)
+{
+ FuIntelMeMkhiDevice *self = FU_INTEL_ME_MKHI_DEVICE(device);
+ const gchar *fns[] = {"/fpf/OemCred", NULL};
+
+ /* this is the legacy way to get the hash, which is removed in newer ME versions due to
+ * possible path traversal attacks */
+ for (guint i = 0; fns[i] != NULL; i++) {
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GByteArray) buf = NULL;
+ if (!fu_intel_me_mkhi_device_add_checksum_for_filename(self,
+ fns[i],
+ &error_local)) {
+ if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
+ continue;
+ }
+ g_warning("failed to get public key using %s: %s",
+ fns[i],
+ error_local->message);
+ }
+ }
+
+ /* no point even adding */
+ if (fu_device_get_checksums(device)->len == 0) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "no OEM public keys found");
+ return FALSE;
+ }
+
+ /* success */
+ return TRUE;
+}
+
+static void
+fu_intel_me_mkhi_device_init(FuIntelMeMkhiDevice *self)
+{
+ fu_device_set_logical_id(FU_DEVICE(self), "MKHI");
+ fu_device_set_name(FU_DEVICE(self), "BootGuard Configuration");
+ fu_device_add_parent_guid(FU_DEVICE(self), "main-system-firmware");
+}
+
+static void
+fu_intel_me_mkhi_device_class_init(FuIntelMeMkhiDeviceClass *klass)
+{
+ FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
+ klass_device->setup = fu_intel_me_mkhi_device_setup;
+}
diff --git a/plugins/intel-me/fu-intel-me-mkhi-device.h b/plugins/intel-me/fu-intel-me-mkhi-device.h
new file mode 100644
index 000000000..394030fed
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-mkhi-device.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include
+
+#include "fu-intel-me-heci-device.h"
+
+#define FU_TYPE_INTEL_ME_MKHI_DEVICE (fu_intel_me_mkhi_device_get_type())
+G_DECLARE_FINAL_TYPE(FuIntelMeMkhiDevice,
+ fu_intel_me_mkhi_device,
+ FU,
+ INTEL_ME_MKHI_DEVICE,
+ FuIntelMeHeciDevice)
diff --git a/plugins/intel-me/fu-intel-me-plugin.c b/plugins/intel-me/fu-intel-me-plugin.c
new file mode 100644
index 000000000..68f3139ac
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-plugin.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include "fu-intel-me-mca-device.h"
+#include "fu-intel-me-mkhi-device.h"
+#include "fu-intel-me-plugin.h"
+
+struct _FuIntelMePlugin {
+ FuPlugin parent_instance;
+};
+
+G_DEFINE_TYPE(FuIntelMePlugin, fu_intel_me_plugin, FU_TYPE_PLUGIN)
+
+static void
+fu_intel_me_plugin_init(FuIntelMePlugin *self)
+{
+}
+
+static void
+fu_intel_me_plugin_constructed(GObject *obj)
+{
+ FuPlugin *plugin = FU_PLUGIN(obj);
+ fu_plugin_add_udev_subsystem(plugin, "mei");
+ fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_ME_MCA_DEVICE);
+ fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_ME_MKHI_DEVICE);
+}
+
+static void
+fu_intel_me_plugin_class_init(FuIntelMePluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ object_class->constructed = fu_intel_me_plugin_constructed;
+}
diff --git a/plugins/intel-me/fu-intel-me-plugin.h b/plugins/intel-me/fu-intel-me-plugin.h
new file mode 100644
index 000000000..c5dc9c3b3
--- /dev/null
+++ b/plugins/intel-me/fu-intel-me-plugin.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (C) 2022 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include
+
+G_DECLARE_FINAL_TYPE(FuIntelMePlugin, fu_intel_me_plugin, FU, INTEL_ME_PLUGIN, FuPlugin)
diff --git a/plugins/intel-me/intel-me.quirk b/plugins/intel-me/intel-me.quirk
new file mode 100644
index 000000000..f65dc13ef
--- /dev/null
+++ b/plugins/intel-me/intel-me.quirk
@@ -0,0 +1,9 @@
+# MKHI (legacy)
+[8e6a6715-9abc-4043-88ef-9e39c6f63e0f]
+Plugin = intel_me
+GType = FuIntelMeMkhiDevice
+
+# MCA?
+[dd17041c-09ea-4b17-a271-5b989867ec65]
+Plugin = intel_me
+GType = FuIntelMeMcaDevice
diff --git a/plugins/intel-me/meson.build b/plugins/intel-me/meson.build
new file mode 100644
index 000000000..2efa22c6f
--- /dev/null
+++ b/plugins/intel-me/meson.build
@@ -0,0 +1,18 @@
+if get_option('plugin_amt').disable_auto_if(host_machine.system() != 'linux').allowed()
+cargs = ['-DG_LOG_DOMAIN="FuPluginIntelMe"']
+
+plugin_quirks += files('intel-me.quirk')
+plugin_builtins += static_library('fu_plugin_intel_me',
+ sources: [
+ 'fu-intel-me-common.c',
+ 'fu-intel-me-plugin.c',
+ 'fu-intel-me-heci-device.c',
+ 'fu-intel-me-mca-device.c',
+ 'fu-intel-me-mkhi-device.c',
+ ],
+ include_directories: plugin_incdirs,
+ link_with: plugin_libs,
+ c_args: cargs,
+ dependencies: plugin_deps,
+)
+endif
diff --git a/plugins/meson.build b/plugins/meson.build
index 703b20c43..39a759fe2 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -52,6 +52,7 @@ subdir('genesys')
subdir('goodix-moc')
subdir('gpio')
subdir('hailuck')
+subdir('intel-me')
subdir('intel-spi')
subdir('intel-usb4')
subdir('iommu')
diff --git a/src/fu-security-attr-common.c b/src/fu-security-attr-common.c
index 361a5e321..11626c448 100644
--- a/src/fu-security-attr-common.c
+++ b/src/fu-security-attr-common.c
@@ -146,6 +146,11 @@ fu_security_attr_get_name(FwupdSecurityAttr *attr)
* end users on consumer boards */
return g_strdup(_("MEI override"));
}
+ if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) {
+ /* TRANSLATORS: Title: MEI = Intel Management Engine, and key refer
+ * to the private/public key used to secure loading of firmware */
+ return g_strdup(_("MEI key manifest"));
+ }
if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) {
/* TRANSLATORS: Title: MEI = Intel Management Engine */
const gchar *kind = fwupd_security_attr_get_metadata(attr, "kind");
@@ -322,6 +327,11 @@ fu_security_attr_get_title(FwupdSecurityAttr *attr)
* boards */
return _("Intel Management Engine Override");
}
+ if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) {
+ /* TRANSLATORS: Title: MEI = Intel Management Engine, and key refers
+ * to the private/public key used to secure loading of firmware */
+ return g_strdup(_("MEI Key Manifest"));
+ }
if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) {
/* TRANSLATORS: Title: MEI = Intel Management Engine */
return _("Intel Management Engine Version");
@@ -496,6 +506,11 @@ fu_security_attr_get_description(FwupdSecurityAttr *attr)
return _("Intel Management Engine Override disables checks for device software "
"tampering.");
}
+ if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) {
+ /* TRANSLATORS: longer description */
+ return _("The Intel Management Engine Key Manifest must be valid so "
+ "that the device firmware can be trusted by the CPU.");
+ }
if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) {
/* TRANSLATORS: longer description */
return _("The Intel Management Engine controls device components and needs "