Coverage Report

Created: 2026-07-16 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-x509-certificate.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
#include <gnutls/x509.h>
13
#endif
14
15
#include "fu-common.h"
16
#include "fu-input-stream.h"
17
#include "fu-string.h"
18
#include "fu-x509-certificate.h"
19
20
#ifdef HAVE_GNUTLS
21
static void
22
fu_x509_certificate_gnutls_datum_deinit(gnutls_datum_t *d)
23
{
24
  gnutls_free(d->data);
25
  gnutls_free(d);
26
}
27
28
static void
29
fu_x509_certificate_gnutls_datum_clear(gnutls_datum_t *d)
30
{
31
  gnutls_free(d->data);
32
}
33
34
#pragma clang diagnostic push
35
#pragma clang diagnostic ignored "-Wunused-function"
36
G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_datum_t, fu_x509_certificate_gnutls_datum_deinit)
37
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(gnutls_datum_t, fu_x509_certificate_gnutls_datum_clear)
38
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_crt_t, gnutls_x509_crt_deinit, NULL)
39
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_privkey_t, gnutls_x509_privkey_deinit, NULL)
40
#pragma clang diagnostic pop
41
#endif
42
43
/**
44
 * FuX509Certificate:
45
 *
46
 * An X.509 certificate.
47
 *
48
 * See also: [class@FuFirmware]
49
 */
50
51
struct _FuX509Certificate {
52
  FuFirmware parent_instance;
53
  gchar *issuer;
54
  gchar *subject;
55
  GDateTime *activation_time;
56
};
57
58
0
G_DEFINE_TYPE(FuX509Certificate, fu_x509_certificate, FU_TYPE_FIRMWARE)
59
0
60
0
static void
61
0
fu_x509_certificate_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
62
0
{
63
0
  FuX509Certificate *self = FU_X509_CERTIFICATE(firmware);
64
0
  fu_xmlb_builder_insert_kv(bn, "issuer", self->issuer);
65
0
  fu_xmlb_builder_insert_kv(bn, "subject", self->subject);
66
0
}
67
68
/**
69
 * fu_x509_certificate_get_issuer:
70
 * @self: A #FuX509Certificate
71
 *
72
 * Returns the certificate issuer.
73
 *
74
 * Returns: string, or %NULL for unset
75
 *
76
 * Since: 2.0.9
77
 **/
78
const gchar *
79
fu_x509_certificate_get_issuer(FuX509Certificate *self)
80
0
{
81
0
  g_return_val_if_fail(FU_IS_X509_CERTIFICATE(self), NULL);
82
0
  return self->issuer;
83
0
}
84
85
/**
86
 * fu_x509_certificate_set_issuer:
87
 * @self: A #FuX509Certificate
88
 * @issuer: the certificate issuer
89
 *
90
 * Sets the certificate issuer.
91
 *
92
 * Since: 2.1.5
93
 **/
94
void
95
fu_x509_certificate_set_issuer(FuX509Certificate *self, const gchar *issuer)
96
0
{
97
0
  g_return_if_fail(FU_IS_X509_CERTIFICATE(self));
98
0
  if (g_strcmp0(issuer, self->issuer) == 0)
99
0
    return;
100
0
  g_free(self->issuer);
101
0
  self->issuer = g_strdup(issuer);
102
0
}
103
104
/**
105
 * fu_x509_certificate_set_subject:
106
 * @self: A #FuX509Certificate
107
 * @subject: the certificate subject
108
 *
109
 * Sets the certificate subject.
110
 *
111
 * Since: 2.0.13
112
 **/
113
void
114
fu_x509_certificate_set_subject(FuX509Certificate *self, const gchar *subject)
115
0
{
116
0
  g_return_if_fail(FU_IS_X509_CERTIFICATE(self));
117
0
  if (g_strcmp0(subject, self->subject) == 0)
118
0
    return;
119
0
  g_free(self->subject);
120
0
  self->subject = g_strdup(subject);
121
0
}
122
123
/**
124
 * fu_x509_certificate_set_activation_time:
125
 * @self: A #FuX509Certificate
126
 * @activation_time: the activation time as a UNIX epoch
127
 *
128
 * Sets the certificate activation time.
129
 *
130
 * Since: 2.1.5
131
 **/
132
void
133
fu_x509_certificate_set_activation_time(FuX509Certificate *self, gint64 activation_time)
134
0
{
135
0
  g_return_if_fail(FU_IS_X509_CERTIFICATE(self));
136
0
  if (self->activation_time != NULL)
137
0
    g_date_time_unref(self->activation_time);
138
0
  self->activation_time = g_date_time_new_from_unix_utc(activation_time);
139
0
}
140
141
/**
142
 * fu_x509_certificate_get_subject:
143
 * @self: A #FuX509Certificate
144
 *
145
 * Returns the certificate subject.
146
 *
147
 * Returns: string, or %NULL for unset
148
 *
149
 * Since: 2.0.9
150
 **/
151
const gchar *
152
fu_x509_certificate_get_subject(FuX509Certificate *self)
153
0
{
154
0
  g_return_val_if_fail(FU_IS_X509_CERTIFICATE(self), NULL);
155
0
  return self->subject;
156
0
}
157
158
/**
159
 * fu_x509_certificate_get_activation_time:
160
 * @self: A #FuX509Certificate
161
 *
162
 * Returns the certificate activation time.
163
 *
164
 * Returns: (transfer full): a #GDateTime, or %NULL for unset
165
 *
166
 * Since: 2.0.11
167
 **/
168
GDateTime *
169
fu_x509_certificate_get_activation_time(FuX509Certificate *self)
170
0
{
171
0
  g_return_val_if_fail(FU_IS_X509_CERTIFICATE(self), NULL);
172
0
  if (self->activation_time == NULL)
173
0
    return NULL;
174
0
  return g_date_time_ref(self->activation_time);
175
0
}
176
177
static gboolean
178
fu_x509_certificate_parse(FuFirmware *firmware,
179
        FuInputStream *stream,
180
        FuFirmwareParseFlags flags,
181
        GError **error)
182
0
{
183
#ifdef HAVE_GNUTLS
184
  FuX509Certificate *self = FU_X509_CERTIFICATE(firmware);
185
  gchar buf[1024] = {'\0'};
186
  guchar key_id[32] = {'\0'};
187
  gsize key_idsz = sizeof(key_id);
188
  gnutls_datum_t d = {0};
189
  gnutls_x509_dn_t dn = {0x0};
190
  gint64 ts;
191
  gsize bufsz = sizeof(buf);
192
  int rc;
193
  g_auto(gnutls_x509_crt_t) crt = NULL;
194
  g_autoptr(gnutls_datum_t) subject = NULL;
195
  g_autoptr(GBytes) blob = NULL;
196
  g_autoptr(GString) key_idstr = g_string_new(NULL);
197
198
  /* parse certificate */
199
  blob = fu_input_stream_read_bytes(stream, 0x0, G_MAXSIZE, NULL, error);
200
  if (blob == NULL)
201
    return FALSE;
202
  d.size = g_bytes_get_size(blob);
203
  d.data = (unsigned char *)g_bytes_get_data(blob, NULL);
204
205
  rc = gnutls_x509_crt_init(&crt);
206
  if (rc < 0) {
207
    g_set_error(error,
208
          FWUPD_ERROR,
209
          FWUPD_ERROR_INVALID_DATA,
210
          "crt_init: %s [%i]",
211
          gnutls_strerror(rc),
212
          rc);
213
    return FALSE;
214
  }
215
  rc = gnutls_x509_crt_import(crt, &d, GNUTLS_X509_FMT_DER);
216
  if (rc < 0) {
217
    g_set_error(error,
218
          FWUPD_ERROR,
219
          FWUPD_ERROR_INVALID_DATA,
220
          "crt_import: %s [%i]",
221
          gnutls_strerror(rc),
222
          rc);
223
    return FALSE;
224
  }
225
226
  /* issuer */
227
  if (gnutls_x509_crt_get_issuer_dn(crt, buf, &bufsz) == GNUTLS_E_SUCCESS) {
228
    g_autofree gchar *str = fu_strsafe((const gchar *)buf, bufsz);
229
    fu_x509_certificate_set_issuer(self, str);
230
  }
231
232
  /* subject */
233
  subject = (gnutls_datum_t *)gnutls_calloc(1, sizeof(gnutls_datum_t));
234
  if (gnutls_x509_crt_get_subject(crt, &dn) == GNUTLS_E_SUCCESS) {
235
    g_autofree gchar *str = NULL;
236
    if (gnutls_x509_dn_get_str(dn, subject) != GNUTLS_E_SUCCESS) {
237
      g_set_error_literal(error,
238
              FWUPD_ERROR,
239
              FWUPD_ERROR_INVALID_DATA,
240
              "failed to get subject string");
241
      return FALSE;
242
    }
243
    str = fu_strsafe((const gchar *)subject->data, subject->size);
244
    fu_x509_certificate_set_subject(self, str);
245
  }
246
247
  /* activation_time */
248
  ts = (gint64)gnutls_x509_crt_get_activation_time(crt);
249
  if (ts == -1) {
250
    g_set_error_literal(error,
251
            FWUPD_ERROR,
252
            FWUPD_ERROR_INVALID_DATA,
253
            "failed to get activation time");
254
    return FALSE;
255
  }
256
  fu_x509_certificate_set_activation_time(self, ts);
257
258
  /* key ID */
259
  rc = gnutls_x509_crt_get_key_id(crt, 0, key_id, &key_idsz);
260
  if (rc < 0) {
261
    g_set_error(error,
262
          FWUPD_ERROR,
263
          FWUPD_ERROR_INVALID_DATA,
264
          "failed to get key ID: %s [%i]",
265
          gnutls_strerror(rc),
266
          rc);
267
    return FALSE;
268
  }
269
  for (guint i = 0; i < MIN(sizeof(key_id), key_idsz); i++)
270
    g_string_append_printf(key_idstr, "%02x", key_id[i]);
271
  fu_firmware_set_id(firmware, key_idstr->str);
272
273
  /* success */
274
  return TRUE;
275
#else
276
0
  g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no GnuTLS support");
277
0
  return FALSE;
278
0
#endif
279
0
}
280
281
static GByteArray *
282
fu_x509_certificate_write(FuFirmware *firmware, GError **error)
283
0
{
284
#ifdef HAVE_GNUTLS
285
  FuX509Certificate *self = FU_X509_CERTIFICATE(firmware);
286
  int rc;
287
  g_auto(gnutls_datum_t) d = {0};
288
  g_auto(gnutls_x509_crt_t) crt = NULL;
289
  g_auto(gnutls_x509_privkey_t) key = NULL;
290
  g_autoptr(GByteArray) buf = NULL;
291
292
  /* generate a small private key */
293
  rc = gnutls_x509_privkey_init(&key);
294
  if (rc < 0) {
295
    g_set_error(error,
296
          FWUPD_ERROR,
297
          FWUPD_ERROR_INTERNAL,
298
          "privkey_init: %s [%i]",
299
          gnutls_strerror(rc),
300
          rc);
301
    return NULL;
302
  }
303
  rc = gnutls_x509_privkey_generate(key,
304
            GNUTLS_PK_ECDSA,
305
            GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1),
306
            0);
307
  if (rc < 0) {
308
    g_set_error(error,
309
          FWUPD_ERROR,
310
          FWUPD_ERROR_INTERNAL,
311
          "privkey_generate: %s [%i]",
312
          gnutls_strerror(rc),
313
          rc);
314
    return NULL;
315
  }
316
317
  /* create a self-signed certificate */
318
  rc = gnutls_x509_crt_init(&crt);
319
  if (rc < 0) {
320
    g_set_error(error,
321
          FWUPD_ERROR,
322
          FWUPD_ERROR_INTERNAL,
323
          "crt_init: %s [%i]",
324
          gnutls_strerror(rc),
325
          rc);
326
    return NULL;
327
  }
328
  rc = gnutls_x509_crt_set_version(crt, 3);
329
  if (rc < 0) {
330
    g_set_error(error,
331
          FWUPD_ERROR,
332
          FWUPD_ERROR_INTERNAL,
333
          "crt_set_version: %s [%i]",
334
          gnutls_strerror(rc),
335
          rc);
336
    return NULL;
337
  }
338
  if (self->subject != NULL) {
339
    rc = gnutls_x509_crt_set_dn(crt, self->subject, NULL);
340
    if (rc < 0) {
341
      g_set_error(error,
342
            FWUPD_ERROR,
343
            FWUPD_ERROR_INTERNAL,
344
            "crt_set_dn: %s [%i]",
345
            gnutls_strerror(rc),
346
            rc);
347
      return NULL;
348
    }
349
  }
350
  rc = gnutls_x509_crt_set_serial(crt, "\x01", 1);
351
  if (rc < 0) {
352
    g_set_error(error,
353
          FWUPD_ERROR,
354
          FWUPD_ERROR_INTERNAL,
355
          "crt_set_serial: %s [%i]",
356
          gnutls_strerror(rc),
357
          rc);
358
    return NULL;
359
  }
360
  rc = gnutls_x509_crt_set_activation_time(
361
      crt,
362
      self->activation_time != NULL ? g_date_time_to_unix(self->activation_time) : 0);
363
  if (rc < 0) {
364
    g_set_error(error,
365
          FWUPD_ERROR,
366
          FWUPD_ERROR_INTERNAL,
367
          "crt_set_activation_time: %s [%i]",
368
          gnutls_strerror(rc),
369
          rc);
370
    return NULL;
371
  }
372
  rc = gnutls_x509_crt_set_expiration_time(crt, (time_t)-1);
373
  if (rc < 0) {
374
    g_set_error(error,
375
          FWUPD_ERROR,
376
          FWUPD_ERROR_INTERNAL,
377
          "crt_set_expiration_time: %s [%i]",
378
          gnutls_strerror(rc),
379
          rc);
380
    return NULL;
381
  }
382
  rc = gnutls_x509_crt_set_key(crt, key);
383
  if (rc < 0) {
384
    g_set_error(error,
385
          FWUPD_ERROR,
386
          FWUPD_ERROR_INTERNAL,
387
          "crt_set_key: %s [%i]",
388
          gnutls_strerror(rc),
389
          rc);
390
    return NULL;
391
  }
392
  rc = gnutls_x509_crt_sign2(crt, crt, key, GNUTLS_DIG_SHA256, 0);
393
  if (rc < 0) {
394
    g_set_error(error,
395
          FWUPD_ERROR,
396
          FWUPD_ERROR_INTERNAL,
397
          "crt_sign2: %s [%i]",
398
          gnutls_strerror(rc),
399
          rc);
400
    return NULL;
401
  }
402
403
  /* export as DER */
404
  rc = gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &d);
405
  if (rc < 0) {
406
    g_set_error(error,
407
          FWUPD_ERROR,
408
          FWUPD_ERROR_INTERNAL,
409
          "crt_export2: %s [%i]",
410
          gnutls_strerror(rc),
411
          rc);
412
    return NULL;
413
  }
414
  buf = g_byte_array_sized_new(d.size);
415
  g_byte_array_append(buf, d.data, d.size);
416
  return g_steal_pointer(&buf);
417
#else
418
0
  g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no GnuTLS support");
419
0
  return NULL;
420
0
#endif
421
0
}
422
423
static void
424
fu_x509_certificate_init(FuX509Certificate *self)
425
0
{
426
0
}
427
428
static void
429
fu_x509_certificate_finalize(GObject *obj)
430
0
{
431
0
  FuX509Certificate *self = FU_X509_CERTIFICATE(obj);
432
0
  g_free(self->issuer);
433
0
  g_free(self->subject);
434
0
  if (self->activation_time != NULL)
435
0
    g_date_time_unref(self->activation_time);
436
0
  G_OBJECT_CLASS(fu_x509_certificate_parent_class)->finalize(obj);
437
0
}
438
439
static void
440
fu_x509_certificate_class_init(FuX509CertificateClass *klass)
441
0
{
442
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
443
0
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
444
0
  object_class->finalize = fu_x509_certificate_finalize;
445
0
  firmware_class->export = fu_x509_certificate_export;
446
0
  firmware_class->parse = fu_x509_certificate_parse;
447
0
  firmware_class->write = fu_x509_certificate_write;
448
0
  fu_firmware_set_size_max(firmware_class, 1 * FU_MB);
449
0
}
450
451
/**
452
 * fu_x509_certificate_new:
453
 *
454
 * Creates a new #FuX509Certificate.
455
 *
456
 * Returns: (transfer full): object
457
 *
458
 * Since: 2.0.9
459
 **/
460
FuX509Certificate *
461
fu_x509_certificate_new(void)
462
0
{
463
0
  return g_object_new(FU_TYPE_X509_CERTIFICATE, NULL);
464
0
}