Coverage Report

Created: 2026-08-15 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-efi-x509-signature.c
Line
Count
Source
1
/*
2
 * Copyright 2025 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#ifdef HAVE_GNUTLS
10
#include <gnutls/abstract.h>
11
#include <gnutls/crypto.h>
12
#endif
13
14
#include "fu-common.h"
15
#include "fu-efi-signature-private.h"
16
#include "fu-efi-x509-signature-private.h"
17
#include "fu-input-stream.h"
18
#include "fu-string.h"
19
#include "fu-version-common.h"
20
#include "fu-x509-certificate.h"
21
22
/**
23
 * FuEfiX509Signature:
24
 *
25
 * A X.509 certificate as found in an `EFI_SIGNATURE_LIST`.
26
 *
27
 * See also: [class@FuFirmware]
28
 */
29
30
struct _FuEfiX509Signature {
31
  FuEfiSignature parent_instance;
32
  gchar *issuer;
33
  gchar *subject;
34
  gchar *subject_name;
35
  gchar *subject_vendor;
36
};
37
38
0
G_DEFINE_TYPE(FuEfiX509Signature, fu_efi_x509_signature, FU_TYPE_EFI_SIGNATURE)
39
0
40
0
static void
41
0
fu_efi_x509_signature_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
42
0
{
43
0
  FuEfiX509Signature *self = FU_EFI_X509_SIGNATURE(firmware);
44
0
  fu_xmlb_builder_insert_kv(bn, "issuer", self->issuer);
45
0
  fu_xmlb_builder_insert_kv(bn, "subject", self->subject);
46
0
  fu_xmlb_builder_insert_kv(bn, "subject_name", self->subject_name);
47
0
  fu_xmlb_builder_insert_kv(bn, "subject_vendor", self->subject_vendor);
48
0
}
49
50
/**
51
 * fu_efi_x509_signature_get_issuer:
52
 * @self: A #FuEfiX509Signature
53
 *
54
 * Returns the certificate issuer.
55
 *
56
 * Returns: string, or %NULL for unset
57
 *
58
 * Since: 2.0.8
59
 **/
60
const gchar *
61
fu_efi_x509_signature_get_issuer(FuEfiX509Signature *self)
62
0
{
63
0
  g_return_val_if_fail(FU_IS_EFI_X509_SIGNATURE(self), NULL);
64
0
  return self->issuer;
65
0
}
66
67
/* private */
68
void
69
fu_efi_x509_signature_set_issuer(FuEfiX509Signature *self, const gchar *issuer)
70
0
{
71
0
  g_return_if_fail(FU_IS_EFI_X509_SIGNATURE(self));
72
0
  if (g_strcmp0(issuer, self->issuer) == 0)
73
0
    return;
74
0
  g_free(self->issuer);
75
0
  self->issuer = g_strdup(issuer);
76
0
}
77
78
static gchar *
79
fu_efi_x509_signature_normalize_vendor(const gchar *text)
80
0
{
81
0
  GString *str = g_string_new(text);
82
0
  struct {
83
0
    const gchar *search;
84
0
    const gchar *replace;
85
0
  } dmi_map[] = {
86
0
      {"ASUSTeK MotherBoard", "ASUSTeK"},
87
0
      {"ASUSTeK Notebook", "ASUSTeK"},
88
0
      {"Canonical Ltd.", "Canonical"},
89
0
      {"Dell Inc.", "Dell"},
90
0
      {"HP Inc.", "HP"},
91
0
      {"Hughski Ltd.", "Hughski"},
92
0
      {"Lenovo(Beijing) Ltd", "Lenovo"},
93
0
      {"Lenovo Ltd.", "Lenovo"},
94
0
      {"LG Electronics inc.", "LG"},
95
0
      {"Microsoft Corporation", "Microsoft"},
96
0
      {"KEK 2K CA", "KEK CA"},
97
0
      {"KEK 3K CA", "KEK CA"},
98
0
  };
99
100
  /* make the certificate match DMI for LVFS permissions */
101
0
  for (guint i = 0; i < G_N_ELEMENTS(dmi_map); i++)
102
0
    g_string_replace(str, dmi_map[i].search, dmi_map[i].replace, 0);
103
0
  return g_string_free(str, FALSE);
104
0
}
105
106
static void
107
fu_efi_x509_signature_set_subject_vendor(FuEfiX509Signature *self, const gchar *vendor)
108
0
{
109
0
  self->subject_vendor = fu_efi_x509_signature_normalize_vendor(vendor);
110
0
}
111
112
static void
113
fu_efi_x509_signature_set_subject_name(FuEfiX509Signature *self, const gchar *name)
114
0
{
115
0
  g_autoptr(GString) str = g_string_new(name);
116
117
  /* remove any year suffix */
118
0
  if (str->len >= 5) {
119
0
    guint64 version_raw = 0;
120
0
    if (fu_strtoull(str->str + str->len - 4,
121
0
        &version_raw,
122
0
        1982,
123
0
        2099,
124
0
        FU_INTEGER_BASE_10,
125
0
        NULL)) {
126
0
      g_string_truncate(str, str->len - 5);
127
0
      fu_firmware_set_version_raw(FU_FIRMWARE(self), version_raw);
128
0
    }
129
0
  }
130
0
  self->subject_name = fu_efi_x509_signature_normalize_vendor(str->str);
131
0
}
132
133
/* private */
134
gchar *
135
fu_efi_x509_signature_build_dedupe_key(FuEfiX509Signature *self)
136
0
{
137
0
  g_return_val_if_fail(FU_IS_EFI_X509_SIGNATURE(self), NULL);
138
139
  /* in 2023 Microsoft renamed "Microsoft Windows Production PCA" -> "Windows UEFI CA" */
140
0
  if (g_strcmp0(self->subject_vendor, "Microsoft") == 0 &&
141
0
      g_strcmp0(self->subject_name, "Microsoft Windows Production PCA") == 0) {
142
0
    return g_strdup("Microsoft:Windows UEFI CA");
143
0
  }
144
0
  return g_strdup_printf("%s:%s", self->subject_vendor, self->subject_name);
145
0
}
146
147
/* private */
148
void
149
fu_efi_x509_signature_set_subject(FuEfiX509Signature *self, const gchar *subject)
150
0
{
151
0
  g_return_if_fail(FU_IS_EFI_X509_SIGNATURE(self));
152
0
  if (g_strcmp0(subject, self->subject) == 0)
153
0
    return;
154
0
  g_free(self->subject);
155
0
  self->subject = g_strdup(subject);
156
157
  /* parse out two keys things we need */
158
0
  if (subject != NULL) {
159
0
    g_auto(GStrv) attrs = g_strsplit(subject, ",", -1);
160
0
    for (guint i = 0; attrs[i] != NULL; i++) {
161
0
      if (g_str_has_prefix(attrs[i], "O=")) {
162
0
        fu_efi_x509_signature_set_subject_vendor(self, attrs[i] + 2);
163
0
        continue;
164
0
      }
165
0
      if (g_str_has_prefix(attrs[i], "CN=")) {
166
0
        fu_efi_x509_signature_set_subject_name(self, attrs[i] + 3);
167
0
        continue;
168
0
      }
169
0
    }
170
0
  }
171
0
}
172
173
/**
174
 * fu_efi_x509_signature_get_subject:
175
 * @self: A #FuEfiX509Signature
176
 *
177
 * Returns the certificate subject.
178
 *
179
 * Returns: string, or %NULL for unset
180
 *
181
 * Since: 2.0.8
182
 **/
183
const gchar *
184
fu_efi_x509_signature_get_subject(FuEfiX509Signature *self)
185
0
{
186
0
  g_return_val_if_fail(FU_IS_EFI_X509_SIGNATURE(self), NULL);
187
0
  return self->subject;
188
0
}
189
190
/**
191
 * fu_efi_x509_signature_get_subject_name:
192
 * @self: A #FuEfiX509Signature
193
 *
194
 * Returns the certificate subject name, with any suffixed version removed.
195
 *
196
 * Returns: string, or %NULL for unset
197
 *
198
 * Since: 2.0.8
199
 **/
200
const gchar *
201
fu_efi_x509_signature_get_subject_name(FuEfiX509Signature *self)
202
0
{
203
0
  g_return_val_if_fail(FU_IS_EFI_X509_SIGNATURE(self), NULL);
204
0
  return self->subject_name;
205
0
}
206
207
/**
208
 * fu_efi_x509_signature_get_subject_vendor:
209
 * @self: A #FuEfiX509Signature
210
 *
211
 * Returns the certificate subject name, with any suffixed version removed.
212
 *
213
 * Returns: string, or %NULL for unset
214
 *
215
 * Since: 2.0.8
216
 **/
217
const gchar *
218
fu_efi_x509_signature_get_subject_vendor(FuEfiX509Signature *self)
219
0
{
220
0
  g_return_val_if_fail(FU_IS_EFI_X509_SIGNATURE(self), NULL);
221
0
  return self->subject_vendor;
222
0
}
223
224
static gboolean
225
fu_efi_x509_signature_parse(FuFirmware *firmware,
226
          FuInputStream *stream,
227
          FuFirmwareParseFlags flags,
228
          GError **error)
229
0
{
230
0
  FuEfiX509Signature *self = FU_EFI_X509_SIGNATURE(firmware);
231
0
  g_autoptr(FuX509Certificate) crt = fu_x509_certificate_new();
232
0
  g_autoptr(GBytes) blob = NULL;
233
234
  /* set bytes */
235
0
  if (!FU_FIRMWARE_CLASS(fu_efi_x509_signature_parent_class)
236
0
     ->parse(firmware, stream, flags, error))
237
0
    return FALSE;
238
239
  /* parse certificate */
240
0
  blob = fu_firmware_get_bytes(firmware, error);
241
0
  if (blob == NULL)
242
0
    return FALSE;
243
0
  if (!fu_firmware_parse_bytes(FU_FIRMWARE(crt), blob, 0x0, flags, error))
244
0
    return FALSE;
245
0
  fu_firmware_set_id(firmware, fu_firmware_get_id(FU_FIRMWARE(crt)));
246
0
  fu_efi_x509_signature_set_issuer(self, fu_x509_certificate_get_issuer(crt));
247
0
  fu_efi_x509_signature_set_subject(self, fu_x509_certificate_get_subject(crt));
248
249
  /* no year in the subject, fall back */
250
0
  if (fu_firmware_get_version_raw(FU_FIRMWARE(self)) == 0) {
251
0
    g_autoptr(GDateTime) dt = fu_x509_certificate_get_activation_time(crt);
252
0
    if (dt != NULL) {
253
0
      g_debug("falling back to activation time %u",
254
0
        (guint)g_date_time_get_year(dt));
255
0
      fu_firmware_set_version_raw(FU_FIRMWARE(self), g_date_time_get_year(dt));
256
0
    }
257
0
  }
258
259
  /* set something plausible */
260
0
  if (fu_firmware_get_filename(firmware) == NULL &&
261
0
      fu_x509_certificate_get_subject(crt) != NULL) {
262
0
    g_autofree gchar *filename = g_strdup_printf("%s_%s.der",
263
0
                   fu_firmware_get_id(FU_FIRMWARE(crt)),
264
0
                   fu_x509_certificate_get_subject(crt));
265
0
    fu_firmware_set_filename(firmware, filename);
266
0
  }
267
268
  /* success */
269
0
  return TRUE;
270
0
}
271
272
static gchar *
273
fu_efi_x509_signature_convert_version(FuFirmware *firmware, guint64 version_raw)
274
0
{
275
0
  return fu_version_from_uint64(version_raw, fu_firmware_get_version_format(firmware));
276
0
}
277
278
static void
279
fu_efi_x509_signature_init(FuEfiX509Signature *self)
280
0
{
281
0
  fu_efi_signature_set_kind(FU_EFI_SIGNATURE(self), FU_EFI_SIGNATURE_KIND_X509);
282
0
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_NUMBER);
283
0
}
284
285
static void
286
fu_efi_x509_signature_finalize(GObject *obj)
287
0
{
288
0
  FuEfiX509Signature *self = FU_EFI_X509_SIGNATURE(obj);
289
0
  g_free(self->issuer);
290
0
  g_free(self->subject);
291
0
  g_free(self->subject_name);
292
0
  g_free(self->subject_vendor);
293
0
  G_OBJECT_CLASS(fu_efi_x509_signature_parent_class)->finalize(obj);
294
0
}
295
296
static void
297
fu_efi_x509_signature_class_init(FuEfiX509SignatureClass *klass)
298
0
{
299
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
300
0
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
301
0
  object_class->finalize = fu_efi_x509_signature_finalize;
302
0
  firmware_class->export = fu_efi_x509_signature_export;
303
0
  firmware_class->parse = fu_efi_x509_signature_parse;
304
0
  firmware_class->convert_version = fu_efi_x509_signature_convert_version;
305
0
}
306
307
/**
308
 * fu_efi_x509_signature_new:
309
 *
310
 * Creates a new #FuEfiX509Signature.
311
 *
312
 * Returns: (transfer full): object
313
 *
314
 * Since: 2.0.8
315
 **/
316
FuEfiX509Signature *
317
fu_efi_x509_signature_new(void)
318
0
{
319
0
  return g_object_new(FU_TYPE_EFI_X509_SIGNATURE, NULL);
320
0
}