From 112e26e33d87d581c6c64e8710e249f3edc7db22 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Thu, 6 Oct 2022 12:02:12 +0100 Subject: [PATCH] Make the CoSWID parser more full-featured --- libfwupdplugin/fu-coswid-common.c | 141 ++++++ libfwupdplugin/fu-coswid-common.h | 119 +++++ libfwupdplugin/fu-coswid-firmware.c | 548 ++++++++++++++++++++---- libfwupdplugin/fu-self-test.c | 2 +- libfwupdplugin/meson.build | 1 + libfwupdplugin/tests/coswid.builder.xml | 18 + libfwupdplugin/tests/uswid.bin | Bin 37 -> 85 bytes 7 files changed, 753 insertions(+), 76 deletions(-) create mode 100644 libfwupdplugin/fu-coswid-common.c create mode 100644 libfwupdplugin/fu-coswid-common.h create mode 100644 libfwupdplugin/tests/coswid.builder.xml diff --git a/libfwupdplugin/fu-coswid-common.c b/libfwupdplugin/fu-coswid-common.c new file mode 100644 index 000000000..9ebc6d863 --- /dev/null +++ b/libfwupdplugin/fu-coswid-common.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-coswid-common.h" + +FuCoswidEntityRole +fu_coswid_entity_role_from_string(const gchar *val) +{ + if (g_strcmp0(val, "tag-creator") == 0) + return FU_COSWID_ENTITY_ROLE_TAG_CREATOR; + if (g_strcmp0(val, "software-creator") == 0) + return FU_COSWID_ENTITY_ROLE_SOFTWARE_CREATOR; + if (g_strcmp0(val, "aggregator") == 0) + return FU_COSWID_ENTITY_ROLE_AGGREGATOR; + if (g_strcmp0(val, "distributor") == 0) + return FU_COSWID_ENTITY_ROLE_DISTRIBUTOR; + if (g_strcmp0(val, "licensor") == 0) + return FU_COSWID_ENTITY_ROLE_LICENSOR; + if (g_strcmp0(val, "maintainer") == 0) + return FU_COSWID_ENTITY_ROLE_MAINTAINER; + return FU_COSWID_ENTITY_ROLE_UNKNOWN; +} + +const gchar * +fu_coswid_entity_role_to_string(FuCoswidEntityRole val) +{ + if (val == FU_COSWID_ENTITY_ROLE_TAG_CREATOR) + return "tag-creator"; + if (val == FU_COSWID_ENTITY_ROLE_SOFTWARE_CREATOR) + return "software-creator"; + if (val == FU_COSWID_ENTITY_ROLE_AGGREGATOR) + return "aggregator"; + if (val == FU_COSWID_ENTITY_ROLE_DISTRIBUTOR) + return "distributor"; + if (val == FU_COSWID_ENTITY_ROLE_LICENSOR) + return "licensor"; + if (val == FU_COSWID_ENTITY_ROLE_MAINTAINER) + return "maintainer"; + return NULL; +} + +FuCoswidLinkRel +fu_coswid_link_rel_from_string(const gchar *val) +{ + if (g_strcmp0(val, "license") == 0) + return FU_COSWID_LINK_REL_LICENSE; + if (g_strcmp0(val, "compiler") == 0) + return FU_COSWID_LINK_REL_COMPILER; + if (g_strcmp0(val, "ancestor") == 0) + return FU_COSWID_LINK_REL_ANCESTOR; + if (g_strcmp0(val, "component") == 0) + return FU_COSWID_LINK_REL_COMPONENT; + if (g_strcmp0(val, "feature") == 0) + return FU_COSWID_LINK_REL_FEATURE; + if (g_strcmp0(val, "installationmedia") == 0) + return FU_COSWID_LINK_REL_INSTALLATIONMEDIA; + if (g_strcmp0(val, "packageinstaller") == 0) + return FU_COSWID_LINK_REL_PACKAGEINSTALLER; + if (g_strcmp0(val, "parent") == 0) + return FU_COSWID_LINK_REL_PARENT; + if (g_strcmp0(val, "patches") == 0) + return FU_COSWID_LINK_REL_PATCHES; + if (g_strcmp0(val, "requires") == 0) + return FU_COSWID_LINK_REL_REQUIRES; + if (g_strcmp0(val, "see-also") == 0) + return FU_COSWID_LINK_REL_SEE_ALSO; + if (g_strcmp0(val, "supersedes") == 0) + return FU_COSWID_LINK_REL_SUPERSEDES; + if (g_strcmp0(val, "supplemental") == 0) + return FU_COSWID_LINK_REL_SUPPLEMENTAL; + return FU_COSWID_LINK_REL_UNKNOWN; +} + +const gchar * +fu_coswid_link_rel_to_string(FuCoswidLinkRel val) +{ + if (val == FU_COSWID_LINK_REL_LICENSE) + return "license"; + if (val == FU_COSWID_LINK_REL_COMPILER) + return "compiler"; + if (val == FU_COSWID_LINK_REL_ANCESTOR) + return "ancestor"; + if (val == FU_COSWID_LINK_REL_COMPONENT) + return "component"; + if (val == FU_COSWID_LINK_REL_FEATURE) + return "feature"; + if (val == FU_COSWID_LINK_REL_INSTALLATIONMEDIA) + return "installationmedia"; + if (val == FU_COSWID_LINK_REL_PACKAGEINSTALLER) + return "packageinstaller"; + if (val == FU_COSWID_LINK_REL_PARENT) + return "parent"; + if (val == FU_COSWID_LINK_REL_PATCHES) + return "patches"; + if (val == FU_COSWID_LINK_REL_REQUIRES) + return "requires"; + if (val == FU_COSWID_LINK_REL_SEE_ALSO) + return "see-also"; + if (val == FU_COSWID_LINK_REL_SUPERSEDES) + return "supersedes"; + if (val == FU_COSWID_LINK_REL_SUPPLEMENTAL) + return "supplemental"; + return NULL; +} + +FuCoswidVersionScheme +fu_coswid_version_scheme_from_string(const gchar *val) +{ + if (g_strcmp0(val, "multipartnumeric") == 0) + return FU_COSWID_VERSION_SCHEME_MULTIPARTNUMERIC; + if (g_strcmp0(val, "multipartnumeric-suffix") == 0) + return FU_COSWID_VERSION_SCHEME_MULTIPARTNUMERIC_SUFFIX; + if (g_strcmp0(val, "alphanumeric") == 0) + return FU_COSWID_VERSION_SCHEME_ALPHANUMERIC; + if (g_strcmp0(val, "decimal") == 0) + return FU_COSWID_VERSION_SCHEME_DECIMAL; + if (g_strcmp0(val, "semver") == 0) + return FU_COSWID_VERSION_SCHEME_SEMVER; + return FU_COSWID_VERSION_SCHEME_UNKNOWN; +} + +const gchar * +fu_coswid_version_scheme_to_string(FuCoswidVersionScheme val) +{ + if (val == FU_COSWID_VERSION_SCHEME_MULTIPARTNUMERIC) + return "multipartnumeric"; + if (val == FU_COSWID_VERSION_SCHEME_MULTIPARTNUMERIC_SUFFIX) + return "multipartnumeric-suffix"; + if (val == FU_COSWID_VERSION_SCHEME_ALPHANUMERIC) + return "alphanumeric"; + if (val == FU_COSWID_VERSION_SCHEME_DECIMAL) + return "decimal"; + if (val == FU_COSWID_VERSION_SCHEME_SEMVER) + return "semver"; + return NULL; +} diff --git a/libfwupdplugin/fu-coswid-common.h b/libfwupdplugin/fu-coswid-common.h new file mode 100644 index 000000000..7906ce20c --- /dev/null +++ b/libfwupdplugin/fu-coswid-common.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +typedef enum { + FU_COSWID_TAG_TAG_ID, + FU_COSWID_TAG_SOFTWARE_NAME, + FU_COSWID_TAG_ENTITY, + FU_COSWID_TAG_EVIDENCE, + FU_COSWID_TAG_LINK, + FU_COSWID_TAG_SOFTWARE_META, + FU_COSWID_TAG_PAYLOAD, + FU_COSWID_TAG_HASH, + FU_COSWID_TAG_CORPUS, + FU_COSWID_TAG_PATCH, + FU_COSWID_TAG_MEDIA, + FU_COSWID_TAG_SUPPLEMENTAL, + FU_COSWID_TAG_TAG_VERSION, + FU_COSWID_TAG_SOFTWARE_VERSION, + FU_COSWID_TAG_VERSION_SCHEME, + FU_COSWID_TAG_LANG, + FU_COSWID_TAG_DIRECTORY, + FU_COSWID_TAG_FILE, + FU_COSWID_TAG_PROCESS, + FU_COSWID_TAG_RESOURCE, + FU_COSWID_TAG_SIZE, + FU_COSWID_TAG_FILE_VERSION, + FU_COSWID_TAG_KEY, + FU_COSWID_TAG_LOCATION, + FU_COSWID_TAG_FS_NAME, + FU_COSWID_TAG_ROOT, + FU_COSWID_TAG_PATH_ELEMENTS, + FU_COSWID_TAG_PROCESS_NAME, + FU_COSWID_TAG_PID, + FU_COSWID_TAG_TYPE, + FU_COSWID_TAG_MISSING30, /* not in the spec! */ + FU_COSWID_TAG_ENTITY_NAME, + FU_COSWID_TAG_REG_ID, + FU_COSWID_TAG_ROLE, + FU_COSWID_TAG_THUMBPRINT, + FU_COSWID_TAG_DATE, + FU_COSWID_TAG_DEVICE_ID, + FU_COSWID_TAG_ARTIFACT, + FU_COSWID_TAG_HREF, + FU_COSWID_TAG_OWNERSHIP, + FU_COSWID_TAG_REL, + FU_COSWID_TAG_MEDIA_TYPE, + FU_COSWID_TAG_USE, + FU_COSWID_TAG_ACTIVATION_STATUS, + FU_COSWID_TAG_CHANNEL_TYPE, + FU_COSWID_TAG_COLLOQUIAL_VERSION, + FU_COSWID_TAG_DESCRIPTION, + FU_COSWID_TAG_EDITION, + FU_COSWID_TAG_ENTITLEMENT_DATA_REQUIRED, + FU_COSWID_TAG_ENTITLEMENT_KEY, + FU_COSWID_TAG_GENERATOR, + FU_COSWID_TAG_PERSISTENT_ID, + FU_COSWID_TAG_PRODUCT, + FU_COSWID_TAG_PRODUCT_FAMILY, + FU_COSWID_TAG_REVISION, + FU_COSWID_TAG_SUMMARY, + FU_COSWID_TAG_UNSPSC_CODE, + FU_COSWID_TAG_UNSPSC_VERSION, +} FuCoswidTag; + +typedef enum { + FU_COSWID_VERSION_SCHEME_UNKNOWN, + FU_COSWID_VERSION_SCHEME_MULTIPARTNUMERIC, + FU_COSWID_VERSION_SCHEME_MULTIPARTNUMERIC_SUFFIX, + FU_COSWID_VERSION_SCHEME_ALPHANUMERIC, + FU_COSWID_VERSION_SCHEME_DECIMAL, + FU_COSWID_VERSION_SCHEME_SEMVER = 16384, +} FuCoswidVersionScheme; + +typedef enum { + FU_COSWID_LINK_REL_LICENSE = -2, + FU_COSWID_LINK_REL_COMPILER = -1, + FU_COSWID_LINK_REL_UNKNOWN = 0, + FU_COSWID_LINK_REL_ANCESTOR = 1, + FU_COSWID_LINK_REL_COMPONENT = 2, + FU_COSWID_LINK_REL_FEATURE = 3, + FU_COSWID_LINK_REL_INSTALLATIONMEDIA = 4, + FU_COSWID_LINK_REL_PACKAGEINSTALLER = 5, + FU_COSWID_LINK_REL_PARENT = 6, + FU_COSWID_LINK_REL_PATCHES = 7, + FU_COSWID_LINK_REL_REQUIRES = 8, + FU_COSWID_LINK_REL_SEE_ALSO = 9, + FU_COSWID_LINK_REL_SUPERSEDES = 10, + FU_COSWID_LINK_REL_SUPPLEMENTAL = 11, +} FuCoswidLinkRel; + +typedef enum { + FU_COSWID_ENTITY_ROLE_UNKNOWN, + FU_COSWID_ENTITY_ROLE_TAG_CREATOR, + FU_COSWID_ENTITY_ROLE_SOFTWARE_CREATOR, + FU_COSWID_ENTITY_ROLE_AGGREGATOR, + FU_COSWID_ENTITY_ROLE_DISTRIBUTOR, + FU_COSWID_ENTITY_ROLE_LICENSOR, + FU_COSWID_ENTITY_ROLE_MAINTAINER, +} FuCoswidEntityRole; + +FuCoswidEntityRole +fu_coswid_entity_role_from_string(const gchar *val); +const gchar * +fu_coswid_entity_role_to_string(FuCoswidEntityRole val); +FuCoswidLinkRel +fu_coswid_link_rel_from_string(const gchar *val); +const gchar * +fu_coswid_link_rel_to_string(FuCoswidLinkRel val); +FuCoswidVersionScheme +fu_coswid_version_scheme_from_string(const gchar *val); +const gchar * +fu_coswid_version_scheme_to_string(FuCoswidVersionScheme val); diff --git a/libfwupdplugin/fu-coswid-firmware.c b/libfwupdplugin/fu-coswid-firmware.c index 434131e9d..87fa75970 100644 --- a/libfwupdplugin/fu-coswid-firmware.c +++ b/libfwupdplugin/fu-coswid-firmware.c @@ -13,6 +13,7 @@ #endif #include "fu-common.h" +#include "fu-coswid-common.h" #include "fu-coswid-firmware.h" /** @@ -20,68 +21,50 @@ * * A coSWID SWID section. * - * See also: [class@FuUswidFirmware] + * See also: [class@FuCoswidFirmware] */ -G_DEFINE_TYPE(FuCoswidFirmware, fu_coswid_firmware, FU_TYPE_FIRMWARE) +typedef struct { + gchar *product; + gchar *summary; + gchar *colloquial_version; + FuCoswidVersionScheme version_scheme; + GPtrArray *links; /* of FuCoswidFirmwareLink */ + GPtrArray *entities; /* of FuCoswidFirmwareEntity */ +} FuCoswidFirmwarePrivate; -#define COSWID_GLOBAL_MAP_TAG_ID 0 -#define COSWID_GLOBAL_MAP_SOFTWARE_NAME 1 -#define COSWID_GLOBAL_MAP_ENTITY 2 -#define COSWID_GLOBAL_MAP_EVIDENCE 3 -#define COSWID_GLOBAL_MAP_LINK 4 -#define COSWID_GLOBAL_MAP_SOFTWARE_META 5 -#define COSWID_GLOBAL_MAP_PAYLOAD 6 -#define COSWID_GLOBAL_MAP_HASH 7 -#define COSWID_GLOBAL_MAP_CORPUS 8 -#define COSWID_GLOBAL_MAP_PATCH 9 -#define COSWID_GLOBAL_MAP_MEDIA 10 -#define COSWID_GLOBAL_MAP_SUPPLEMENTAL 11 -#define COSWID_GLOBAL_MAP_TAG_VERSION 12 -#define COSWID_GLOBAL_MAP_SOFTWARE_VERSION 13 -#define COSWID_GLOBAL_MAP_VERSION_SCHEME 14 -#define COSWID_GLOBAL_MAP_LANG 15 -#define COSWID_GLOBAL_MAP_DIRECTORY 16 -#define COSWID_GLOBAL_MAP_FILE 17 -#define COSWID_GLOBAL_MAP_PROCESS 18 -#define COSWID_GLOBAL_MAP_RESOURCE 19 -#define COSWID_GLOBAL_MAP_SIZE 20 -#define COSWID_GLOBAL_MAP_FILE_VERSION 21 -#define COSWID_GLOBAL_MAP_KEY 22 -#define COSWID_GLOBAL_MAP_LOCATION 23 -#define COSWID_GLOBAL_MAP_FS_NAME 24 -#define COSWID_GLOBAL_MAP_ROOT 25 -#define COSWID_GLOBAL_MAP_PATH_ELEMENTS 26 -#define COSWID_GLOBAL_MAP_PROCESS_NAME 27 -#define COSWID_GLOBAL_MAP_PID 28 -#define COSWID_GLOBAL_MAP_TYPE 29 -#define COSWID_GLOBAL_MAP_ENTITY_NAME 31 -#define COSWID_GLOBAL_MAP_REG_ID 32 -#define COSWID_GLOBAL_MAP_ROLE 33 -#define COSWID_GLOBAL_MAP_THUMBPRINT 34 -#define COSWID_GLOBAL_MAP_DATE 35 -#define COSWID_GLOBAL_MAP_DEVICE_ID 36 -#define COSWID_GLOBAL_MAP_ARTIFACT 37 -#define COSWID_GLOBAL_MAP_HREF 38 -#define COSWID_GLOBAL_MAP_OWNERSHIP 39 -#define COSWID_GLOBAL_MAP_REL 40 -#define COSWID_GLOBAL_MAP_MEDIA_TYPE 41 -#define COSWID_GLOBAL_MAP_USE 42 -#define COSWID_GLOBAL_MAP_ACTIVATION_STATUS 43 -#define COSWID_GLOBAL_MAP_CHANNEL_TYPE 44 -#define COSWID_GLOBAL_MAP_COLLOQUIAL_VERSION 45 -#define COSWID_GLOBAL_MAP_DESCRIPTION 46 -#define COSWID_GLOBAL_MAP_EDITION 47 -#define COSWID_GLOBAL_MAP_ENTITLEMENT_DATA_REQUIRED 48 -#define COSWID_GLOBAL_MAP_ENTITLEMENT_KEY 49 -#define COSWID_GLOBAL_MAP_GENERATOR 50 -#define COSWID_GLOBAL_MAP_PERSISTENT_ID 51 -#define COSWID_GLOBAL_MAP_PRODUCT 52 -#define COSWID_GLOBAL_MAP_PRODUCT_FAMILY 53 -#define COSWID_GLOBAL_MAP_REVISION 54 -#define COSWID_GLOBAL_MAP_SUMMARY 55 -#define COSWID_GLOBAL_MAP_UNSPSC_CODE 56 -#define COSWID_GLOBAL_MAP_UNSPSC_VERSION 57 +G_DEFINE_TYPE_WITH_PRIVATE(FuCoswidFirmware, fu_coswid_firmware, FU_TYPE_FIRMWARE) +#define GET_PRIVATE(o) (fu_coswid_firmware_get_instance_private(o)) + +typedef struct { + gchar *name; + gchar *regid; + FuCoswidEntityRole roles[6]; +} FuCoswidFirmwareEntity; + +typedef struct { + gchar *href; + FuCoswidLinkRel rel; +} FuCoswidFirmwareLink; + +static void +fu_coswid_firmware_entity_free(FuCoswidFirmwareEntity *entity) +{ + g_free(entity->name); + g_free(entity->regid); + g_free(entity); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareEntity, fu_coswid_firmware_entity_free) + +static void +fu_coswid_firmware_link_free(FuCoswidFirmwareLink *link) +{ + g_free(link->href); + g_free(link); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareLink, fu_coswid_firmware_link_free) #ifdef HAVE_CBOR G_DEFINE_AUTOPTR_CLEANUP_FUNC(cbor_item_t, cbor_intermediate_decref) @@ -93,6 +76,84 @@ fu_coswid_firmware_strndup(cbor_item_t *item) return NULL; return g_strndup((const gchar *)cbor_string_handle(item), cbor_string_length(item)); } + +static gboolean +fu_coswid_firmware_parse_meta(FuCoswidFirmware *self, cbor_item_t *item, GError **error) +{ + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + struct cbor_pair *pairs = cbor_map_handle(item); + + for (gsize i = 0; i < cbor_map_size(item); i++) { + FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); + if (tag_id == FU_COSWID_TAG_SUMMARY) { + priv->summary = fu_coswid_firmware_strndup(pairs[i].value); + } else if (tag_id == FU_COSWID_TAG_COLLOQUIAL_VERSION) { + priv->colloquial_version = fu_coswid_firmware_strndup(pairs[i].value); + } + } + + /* success */ + return TRUE; +} + +static gboolean +fu_coswid_firmware_parse_link(FuCoswidFirmware *self, cbor_item_t *item, GError **error) +{ + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + struct cbor_pair *pairs = cbor_map_handle(item); + g_autoptr(FuCoswidFirmwareLink) link = g_new0(FuCoswidFirmwareLink, 1); + + for (gsize i = 0; i < cbor_map_size(item); i++) { + FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); + if (tag_id == FU_COSWID_TAG_HREF) { + link->href = fu_coswid_firmware_strndup(pairs[i].value); + } else if (tag_id == FU_COSWID_TAG_REL) { + if (cbor_isa_negint(pairs[i].value)) + link->rel = (-1) - cbor_get_uint8(pairs[i].value); + else + link->rel = cbor_get_uint8(pairs[i].value); + } + } + + /* success */ + g_ptr_array_add(priv->links, g_steal_pointer(&link)); + return TRUE; +} + +static gboolean +fu_coswid_firmware_parse_entity(FuCoswidFirmware *self, cbor_item_t *item, GError **error) +{ + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + struct cbor_pair *pairs = cbor_map_handle(item); + guint entity_role_cnt = 0; + g_autoptr(FuCoswidFirmwareEntity) entity = g_new0(FuCoswidFirmwareEntity, 1); + + for (gsize i = 0; i < cbor_map_size(item); i++) { + FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); + if (tag_id == FU_COSWID_TAG_ENTITY_NAME) { + entity->name = fu_coswid_firmware_strndup(pairs[i].value); + } else if (tag_id == FU_COSWID_TAG_REG_ID) { + entity->regid = fu_coswid_firmware_strndup(pairs[i].value); + } else if (tag_id == FU_COSWID_TAG_ROLE) { + for (guint j = 0; j < cbor_array_size(pairs[i].value); j++) { + g_autoptr(cbor_item_t) value = cbor_array_get(pairs[i].value, j); + FuCoswidEntityRole role = cbor_get_uint8(value); + if (entity_role_cnt >= G_N_ELEMENTS(entity->roles)) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "too many roles"); + return FALSE; + } + entity->roles[entity_role_cnt++] = role; + } + } + } + + /* success */ + g_ptr_array_add(priv->entities, g_steal_pointer(&entity)); + return TRUE; +} #endif static gboolean @@ -103,6 +164,8 @@ fu_coswid_firmware_parse(FuFirmware *firmware, GError **error) { #ifdef HAVE_CBOR + FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); struct cbor_load_result result = {0x0}; struct cbor_pair *pairs = NULL; g_autoptr(cbor_item_t) item = NULL; @@ -128,10 +191,10 @@ fu_coswid_firmware_parse(FuFirmware *firmware, /* parse out anything interesting */ pairs = cbor_map_handle(item); for (gsize i = 0; i < cbor_map_size(item); i++) { - guint8 tag_id = cbor_get_uint8(pairs[i].key); + FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); /* identity can be specified as a string or in binary */ - if (tag_id == COSWID_GLOBAL_MAP_TAG_ID) { + if (tag_id == FU_COSWID_TAG_TAG_ID) { g_autofree gchar *str = NULL; if (cbor_isa_string(pairs[i].value)) { str = fu_coswid_firmware_strndup(pairs[i].value); @@ -143,12 +206,28 @@ fu_coswid_firmware_parse(FuFirmware *firmware, } if (str != NULL) fu_firmware_set_id(firmware, str); - } else if (tag_id == COSWID_GLOBAL_MAP_SOFTWARE_NAME) { - g_autofree gchar *str = fu_coswid_firmware_strndup(pairs[i].value); - fu_firmware_set_filename(firmware, str); - } else if (tag_id == COSWID_GLOBAL_MAP_SOFTWARE_VERSION) { + } else if (tag_id == FU_COSWID_TAG_SOFTWARE_NAME) { + priv->product = fu_coswid_firmware_strndup(pairs[i].value); + } else if (tag_id == FU_COSWID_TAG_SOFTWARE_VERSION) { g_autofree gchar *str = fu_coswid_firmware_strndup(pairs[i].value); fu_firmware_set_version(firmware, str); + } else if (tag_id == FU_COSWID_TAG_VERSION_SCHEME) { + priv->version_scheme = cbor_get_uint16(pairs[i].value); + } else if (tag_id == FU_COSWID_TAG_SOFTWARE_META) { + if (!fu_coswid_firmware_parse_meta(self, pairs[i].value, error)) + return FALSE; + } else if (tag_id == FU_COSWID_TAG_LINK) { + for (guint j = 0; j < cbor_array_size(pairs[i].value); j++) { + g_autoptr(cbor_item_t) value = cbor_array_get(pairs[i].value, j); + if (!fu_coswid_firmware_parse_link(self, value, error)) + return FALSE; + } + } else if (tag_id == FU_COSWID_TAG_ENTITY) { + for (guint j = 0; j < cbor_array_size(pairs[i].value); j++) { + g_autoptr(cbor_item_t) value = cbor_array_get(pairs[i].value, j); + if (!fu_coswid_firmware_parse_entity(self, value, error)) + return FALSE; + } } } @@ -163,31 +242,149 @@ fu_coswid_firmware_parse(FuFirmware *firmware, #endif } +#ifdef HAVE_CBOR +static void +fu_coswid_firmware_write_tag_string(cbor_item_t *root, FuCoswidTag tag, const gchar *item) +{ + g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); + g_autoptr(cbor_item_t) val = cbor_build_string(item); + cbor_map_add(root, (struct cbor_pair){.key = key, .value = val}); +} + +static void +fu_coswid_firmware_write_tag_bool(cbor_item_t *root, FuCoswidTag tag, gboolean item) +{ + g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); + g_autoptr(cbor_item_t) val = cbor_build_bool(item); + cbor_map_add(root, (struct cbor_pair){.key = key, .value = val}); +} + +static void +fu_coswid_firmware_write_tag_uint16(cbor_item_t *root, FuCoswidTag tag, guint16 item) +{ + g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); + g_autoptr(cbor_item_t) val = cbor_build_uint16(item); + cbor_map_add(root, (struct cbor_pair){.key = key, .value = val}); +} + +static void +fu_coswid_firmware_write_tag_int8(cbor_item_t *root, FuCoswidTag tag, gint8 item) +{ + g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); + g_autoptr(cbor_item_t) val = cbor_new_int8(); + if (item >= 0) { + cbor_set_uint8(val, item); + } else { + cbor_set_uint8(val, 0xFF - item); + cbor_mark_negint(val); + } + cbor_map_add(root, (struct cbor_pair){.key = key, .value = val}); +} + +static void +fu_coswid_firmware_write_tag_item(cbor_item_t *root, FuCoswidTag tag, cbor_item_t *item) +{ + g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); + cbor_map_add(root, (struct cbor_pair){.key = key, .value = item}); +} +#endif + static GBytes * fu_coswid_firmware_write(FuFirmware *firmware, GError **error) { #ifdef HAVE_CBOR + FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); gsize buflen; gsize bufsz = 0; g_autofree guchar *buf = NULL; g_autoptr(cbor_item_t) root = cbor_new_indefinite_map(); + g_autoptr(cbor_item_t) item_meta = cbor_new_indefinite_map(); /* preallocate the map structure */ + fu_coswid_firmware_write_tag_string(root, FU_COSWID_TAG_LANG, "en-US"); if (fu_firmware_get_id(firmware) != NULL) { - g_autoptr(cbor_item_t) key = cbor_build_uint8(COSWID_GLOBAL_MAP_TAG_ID); - g_autoptr(cbor_item_t) val = cbor_build_string(fu_firmware_get_id(firmware)); - cbor_map_add(root, (struct cbor_pair){.key = key, .value = val}); + fu_coswid_firmware_write_tag_string(root, + FU_COSWID_TAG_TAG_ID, + fu_firmware_get_id(firmware)); + } + fu_coswid_firmware_write_tag_bool(root, FU_COSWID_TAG_CORPUS, TRUE); + if (priv->product != NULL) { + fu_coswid_firmware_write_tag_string(root, + FU_COSWID_TAG_SOFTWARE_NAME, + priv->product); } if (fu_firmware_get_version(firmware) != NULL) { - g_autoptr(cbor_item_t) key = cbor_build_uint8(COSWID_GLOBAL_MAP_SOFTWARE_VERSION); - g_autoptr(cbor_item_t) val = cbor_build_string(fu_firmware_get_version(firmware)); - cbor_map_add(root, (struct cbor_pair){.key = key, .value = val}); + fu_coswid_firmware_write_tag_string(root, + FU_COSWID_TAG_SOFTWARE_VERSION, + fu_firmware_get_version(firmware)); } - if (fu_firmware_get_filename(firmware) != NULL) { - g_autoptr(cbor_item_t) key = cbor_build_uint8(COSWID_GLOBAL_MAP_SOFTWARE_NAME); - g_autoptr(cbor_item_t) val = cbor_build_string(fu_firmware_get_filename(firmware)); - cbor_map_add(root, (struct cbor_pair){.key = key, .value = val}); + if (priv->version_scheme != FU_COSWID_VERSION_SCHEME_UNKNOWN) { + fu_coswid_firmware_write_tag_uint16(root, + FU_COSWID_TAG_VERSION_SCHEME, + priv->version_scheme); } + fu_coswid_firmware_write_tag_item(root, FU_COSWID_TAG_SOFTWARE_META, item_meta); + fu_coswid_firmware_write_tag_string(item_meta, FU_COSWID_TAG_GENERATOR, PACKAGE_NAME); + if (priv->summary != NULL) { + fu_coswid_firmware_write_tag_string(item_meta, + FU_COSWID_TAG_SUMMARY, + priv->summary); + } + if (priv->colloquial_version != NULL) { + fu_coswid_firmware_write_tag_string(item_meta, + FU_COSWID_TAG_COLLOQUIAL_VERSION, + priv->colloquial_version); + } + + /* add entities */ + if (priv->entities->len > 0) { + g_autoptr(cbor_item_t) item_entities = cbor_new_indefinite_array(); + for (guint i = 0; i < priv->entities->len; i++) { + FuCoswidFirmwareEntity *entity = g_ptr_array_index(priv->entities, i); + g_autoptr(cbor_item_t) item_entity = cbor_new_indefinite_map(); + g_autoptr(cbor_item_t) item_roles = cbor_new_indefinite_array(); + if (entity->name != NULL) { + fu_coswid_firmware_write_tag_string(item_entity, + FU_COSWID_TAG_ENTITY_NAME, + entity->name); + } + if (entity->regid != NULL) { + fu_coswid_firmware_write_tag_string(item_entity, + FU_COSWID_TAG_REG_ID, + entity->regid); + } + for (guint j = 0; entity->roles[j] != FU_COSWID_ENTITY_ROLE_UNKNOWN; j++) { + g_autoptr(cbor_item_t) item_role = + cbor_build_uint8(entity->roles[j]); + cbor_array_push(item_roles, item_role); + } + fu_coswid_firmware_write_tag_item(item_entity, + FU_COSWID_TAG_ROLE, + item_roles); + cbor_array_push(item_entities, item_entity); + } + fu_coswid_firmware_write_tag_item(root, FU_COSWID_TAG_ENTITY, item_entities); + } + + /* add links */ + if (priv->links->len > 0) { + g_autoptr(cbor_item_t) item_links = cbor_new_indefinite_array(); + for (guint i = 0; i < priv->links->len; i++) { + FuCoswidFirmwareLink *link = g_ptr_array_index(priv->links, i); + g_autoptr(cbor_item_t) item_link = cbor_new_indefinite_map(); + if (link->href != NULL) { + fu_coswid_firmware_write_tag_string(item_link, + FU_COSWID_TAG_HREF, + link->href); + } + fu_coswid_firmware_write_tag_int8(item_link, FU_COSWID_TAG_REL, link->rel); + cbor_array_push(item_links, item_link); + } + fu_coswid_firmware_write_tag_item(root, FU_COSWID_TAG_LINK, item_links); + } + + /* serialize */ buflen = cbor_serialize_alloc(root, &buf, &bufsz); if (buflen > bufsz) { g_set_error_literal(error, @@ -206,17 +403,218 @@ fu_coswid_firmware_write(FuFirmware *firmware, GError **error) #endif } +static gboolean +fu_coswid_firmware_build_entity(FuCoswidFirmware *self, XbNode *n, GError **error) +{ + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + const gchar *tmp; + guint entity_role_cnt = 0; + FuCoswidEntityRole role; + g_autoptr(GPtrArray) roles = NULL; + g_autoptr(FuCoswidFirmwareEntity) entity = g_new0(FuCoswidFirmwareEntity, 1); + + /* these are required */ + tmp = xb_node_query_text(n, "name", error); + if (tmp == NULL) + return FALSE; + entity->name = g_strdup(tmp); + tmp = xb_node_query_text(n, "regid", error); + if (tmp == NULL) + return FALSE; + entity->regid = g_strdup(tmp); + + /* optional */ + roles = xb_node_query(n, "role", 0, NULL); + if (roles != NULL) { + for (guint i = 0; i < roles->len; i++) { + XbNode *c = g_ptr_array_index(roles, i); + tmp = xb_node_get_text(c); + role = fu_coswid_entity_role_from_string(tmp); + if (role == FU_COSWID_ENTITY_ROLE_UNKNOWN) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "failed to parse entity role %s", + tmp); + return FALSE; + } + if (entity_role_cnt >= G_N_ELEMENTS(entity->roles)) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "too many roles"); + return FALSE; + } + entity->roles[entity_role_cnt++] = role; + } + } + + /* success */ + g_ptr_array_add(priv->entities, g_steal_pointer(&entity)); + return TRUE; +} + +static gboolean +fu_coswid_firmware_build_link(FuCoswidFirmware *self, XbNode *n, GError **error) +{ + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + const gchar *tmp; + g_autoptr(FuCoswidFirmwareLink) link = g_new0(FuCoswidFirmwareLink, 1); + + /* required */ + tmp = xb_node_query_text(n, "href", error); + if (tmp == NULL) + return FALSE; + link->href = g_strdup(tmp); + + /* optional */ + tmp = xb_node_query_text(n, "rel", NULL); + if (tmp != NULL) { + link->rel = fu_coswid_link_rel_from_string(tmp); + if (link->rel == FU_COSWID_LINK_REL_UNKNOWN) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "failed to parse link rel %s", + tmp); + return FALSE; + } + } + + /* success */ + g_ptr_array_add(priv->links, g_steal_pointer(&link)); + return TRUE; +} + +static gboolean +fu_coswid_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) +{ + FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + const gchar *tmp; + g_autoptr(GPtrArray) links = NULL; + g_autoptr(GPtrArray) entities = NULL; + + /* simple properties */ + tmp = xb_node_query_text(n, "product", NULL); + if (tmp != NULL) + priv->product = g_strdup(tmp); + tmp = xb_node_query_text(n, "summary", NULL); + if (tmp != NULL) + priv->summary = g_strdup(tmp); + tmp = xb_node_query_text(n, "colloquial_version", NULL); + if (tmp != NULL) + priv->colloquial_version = g_strdup(tmp); + + tmp = xb_node_query_text(n, "version_scheme", NULL); + if (tmp != NULL) { + priv->version_scheme = fu_coswid_version_scheme_from_string(tmp); + if (priv->version_scheme == FU_COSWID_VERSION_SCHEME_UNKNOWN) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "failed to parse version_scheme %s", + tmp); + return FALSE; + } + } + + /* multiple links allowed */ + links = xb_node_query(n, "link", 0, NULL); + if (links != NULL) { + for (guint i = 0; i < links->len; i++) { + XbNode *c = g_ptr_array_index(links, i); + if (!fu_coswid_firmware_build_link(self, c, error)) + return FALSE; + } + } + + /* multiple entities allowed */ + entities = xb_node_query(n, "entity", 0, NULL); + if (entities != NULL) { + for (guint i = 0; i < entities->len; i++) { + XbNode *c = g_ptr_array_index(entities, i); + if (!fu_coswid_firmware_build_entity(self, c, error)) + return FALSE; + } + } + + /* success */ + return TRUE; +} + +static void +fu_coswid_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) +{ + FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + if (priv->version_scheme != FU_COSWID_VERSION_SCHEME_UNKNOWN) { + fu_xmlb_builder_insert_kv(bn, + "version_scheme", + fu_coswid_version_scheme_to_string(priv->version_scheme)); + } + fu_xmlb_builder_insert_kv(bn, "product", priv->product); + fu_xmlb_builder_insert_kv(bn, "summary", priv->summary); + fu_xmlb_builder_insert_kv(bn, "colloquial_version", priv->colloquial_version); + for (guint i = 0; i < priv->links->len; i++) { + FuCoswidFirmwareLink *link = g_ptr_array_index(priv->links, i); + g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "link", NULL); + fu_xmlb_builder_insert_kv(bc, "href", link->href); + if (link->rel != FU_COSWID_LINK_REL_UNKNOWN) { + fu_xmlb_builder_insert_kv(bc, + "rel", + fu_coswid_link_rel_to_string(link->rel)); + } + } + for (guint i = 0; i < priv->entities->len; i++) { + FuCoswidFirmwareEntity *entity = g_ptr_array_index(priv->entities, i); + g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "entity", NULL); + fu_xmlb_builder_insert_kv(bc, "name", entity->name); + fu_xmlb_builder_insert_kv(bc, "regid", entity->regid); + for (guint j = 0; entity->roles[j] != FU_COSWID_ENTITY_ROLE_UNKNOWN; j++) { + fu_xmlb_builder_insert_kv( + bc, + "role", + fu_coswid_entity_role_to_string(entity->roles[j])); + } + } +} + static void fu_coswid_firmware_init(FuCoswidFirmware *self) { + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + priv->version_scheme = FU_COSWID_VERSION_SCHEME_SEMVER; + priv->links = g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_link_free); + priv->entities = + g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_entity_free); +} + +static void +fu_coswid_firmware_finalize(GObject *object) +{ + FuCoswidFirmware *self = FU_COSWID_FIRMWARE(object); + FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); + + g_free(priv->product); + g_free(priv->summary); + g_free(priv->colloquial_version); + g_ptr_array_unref(priv->links); + g_ptr_array_unref(priv->entities); + + G_OBJECT_CLASS(fu_coswid_firmware_parent_class)->finalize(object); } static void fu_coswid_firmware_class_init(FuCoswidFirmwareClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); + object_class->finalize = fu_coswid_firmware_finalize; klass_firmware->parse = fu_coswid_firmware_parse; klass_firmware->write = fu_coswid_firmware_write; + klass_firmware->build = fu_coswid_firmware_build; + klass_firmware->export = fu_coswid_firmware_export; } /** diff --git a/libfwupdplugin/fu-self-test.c b/libfwupdplugin/fu-self-test.c index 151510dae..9b15f3da1 100644 --- a/libfwupdplugin/fu-self-test.c +++ b/libfwupdplugin/fu-self-test.c @@ -3319,7 +3319,7 @@ fu_firmware_builder_round_trip_func(void) #ifdef HAVE_CBOR {FU_TYPE_USWID_FIRMWARE, "uswid.builder.xml", - "cae8660d5acd5bb614d0410bc53dedaa1899aee1"}, + "b4631ebb64931da604500b9a7263225708195f54"}, #endif {G_TYPE_INVALID, NULL, NULL}}; g_type_ensure(FU_TYPE_COSWID_FIRMWARE); diff --git a/libfwupdplugin/meson.build b/libfwupdplugin/meson.build index a5fa1c1ef..71667b6bc 100644 --- a/libfwupdplugin/meson.build +++ b/libfwupdplugin/meson.build @@ -72,6 +72,7 @@ fwupdplugin_src = [ 'fu-ifd-firmware.c', # fuzzing 'fu-ifd-image.c', # fuzzing 'fu-uswid-firmware.c', # fuzzing + 'fu-coswid-common.c', # fuzzing 'fu-coswid-firmware.c', # fuzzing 'fu-efivar.c', 'fu-udev-device.c', diff --git a/libfwupdplugin/tests/coswid.builder.xml b/libfwupdplugin/tests/coswid.builder.xml new file mode 100644 index 000000000..d6f479fa2 --- /dev/null +++ b/libfwupdplugin/tests/coswid.builder.xml @@ -0,0 +1,18 @@ + + fwupd-efi:fwupdx64 + 1.4 + semver + fwupdx64 + EFI helpers to install system firmware + 1.3-3-g1d0c69f + + license + https://spdx.org/licenses/LGPL-2.0.html + + + Richard Hughes + hughsie.com + maintainer + tag-creator + + diff --git a/libfwupdplugin/tests/uswid.bin b/libfwupdplugin/tests/uswid.bin index d9d3fce892174073c6ad11783c9596f0db1258b3..90b27da90dfde5b95d4e641e49cf2dec17914fc4 100644 GIT binary patch literal 85 zcmWG`^7p;AOK;8MXH`L~-dV3_6lbtwU|`tKpPHJd8yd{