/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 | | #endif |
13 | | |
14 | | #include "fu-common.h" |
15 | | #include "fu-input-stream.h" |
16 | | #include "fu-string.h" |
17 | | #include "fu-x509-certificate.h" |
18 | | |
19 | | #ifdef HAVE_GNUTLS |
20 | | static void |
21 | | fu_x509_certificate_gnutls_datum_deinit(gnutls_datum_t *d) |
22 | | { |
23 | | gnutls_free(d->data); |
24 | | gnutls_free(d); |
25 | | } |
26 | | |
27 | | #pragma clang diagnostic push |
28 | | #pragma clang diagnostic ignored "-Wunused-function" |
29 | | G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_datum_t, fu_x509_certificate_gnutls_datum_deinit) |
30 | | G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_crt_t, gnutls_x509_crt_deinit, NULL) |
31 | | #pragma clang diagnostic pop |
32 | | #endif |
33 | | |
34 | | /** |
35 | | * FuX509Certificate: |
36 | | * |
37 | | * An X.509 certificate. |
38 | | * |
39 | | * See also: [class@FuFirmware] |
40 | | */ |
41 | | |
42 | | struct _FuX509Certificate { |
43 | | FuFirmware parent_instance; |
44 | | gchar *issuer; |
45 | | gchar *subject; |
46 | | GDateTime *activation_time; |
47 | | }; |
48 | | |
49 | 0 | G_DEFINE_TYPE(FuX509Certificate, fu_x509_certificate, FU_TYPE_FIRMWARE) |
50 | 0 |
|
51 | 0 | static void |
52 | 0 | fu_x509_certificate_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
53 | 0 | { |
54 | 0 | FuX509Certificate *self = FU_X509_CERTIFICATE(firmware); |
55 | 0 | fu_xmlb_builder_insert_kv(bn, "issuer", self->issuer); |
56 | 0 | fu_xmlb_builder_insert_kv(bn, "subject", self->subject); |
57 | 0 | } |
58 | | |
59 | | /** |
60 | | * fu_x509_certificate_get_issuer: |
61 | | * @self: A #FuX509Certificate |
62 | | * |
63 | | * Returns the certificate issuer. |
64 | | * |
65 | | * Returns: string, or %NULL for unset |
66 | | * |
67 | | * Since: 2.0.9 |
68 | | **/ |
69 | | const gchar * |
70 | | fu_x509_certificate_get_issuer(FuX509Certificate *self) |
71 | 0 | { |
72 | 0 | g_return_val_if_fail(FU_IS_X509_CERTIFICATE(self), NULL); |
73 | 0 | return self->issuer; |
74 | 0 | } |
75 | | |
76 | | #ifdef HAVE_GNUTLS |
77 | | static void |
78 | | fu_x509_certificate_set_issuer(FuX509Certificate *self, const gchar *issuer) |
79 | | { |
80 | | g_return_if_fail(FU_IS_X509_CERTIFICATE(self)); |
81 | | if (g_strcmp0(issuer, self->issuer) == 0) |
82 | | return; |
83 | | g_free(self->issuer); |
84 | | self->issuer = g_strdup(issuer); |
85 | | } |
86 | | |
87 | | static void |
88 | | fu_x509_certificate_set_subject(FuX509Certificate *self, const gchar *subject) |
89 | | { |
90 | | g_return_if_fail(FU_IS_X509_CERTIFICATE(self)); |
91 | | if (g_strcmp0(subject, self->subject) == 0) |
92 | | return; |
93 | | g_free(self->subject); |
94 | | self->subject = g_strdup(subject); |
95 | | } |
96 | | |
97 | | static void |
98 | | fu_x509_certificate_set_activation_time(FuX509Certificate *self, gint64 activation_time) |
99 | | { |
100 | | g_return_if_fail(FU_IS_X509_CERTIFICATE(self)); |
101 | | if (self->activation_time != NULL) |
102 | | g_date_time_unref(self->activation_time); |
103 | | self->activation_time = g_date_time_new_from_unix_utc(activation_time); |
104 | | } |
105 | | #endif |
106 | | |
107 | | /** |
108 | | * fu_x509_certificate_get_subject: |
109 | | * @self: A #FuX509Certificate |
110 | | * |
111 | | * Returns the certificate subject. |
112 | | * |
113 | | * Returns: string, or %NULL for unset |
114 | | * |
115 | | * Since: 2.0.9 |
116 | | **/ |
117 | | const gchar * |
118 | | fu_x509_certificate_get_subject(FuX509Certificate *self) |
119 | 0 | { |
120 | 0 | g_return_val_if_fail(FU_IS_X509_CERTIFICATE(self), NULL); |
121 | 0 | return self->subject; |
122 | 0 | } |
123 | | |
124 | | /** |
125 | | * fu_x509_certificate_get_activation_time: |
126 | | * @self: A #FuX509Certificate |
127 | | * |
128 | | * Returns the certificate activation time. |
129 | | * |
130 | | * Returns: (transfer full): a #GDateTime, or %NULL for unset |
131 | | * |
132 | | * Since: 2.0.11 |
133 | | **/ |
134 | | GDateTime * |
135 | | fu_x509_certificate_get_activation_time(FuX509Certificate *self) |
136 | 0 | { |
137 | 0 | g_return_val_if_fail(FU_IS_X509_CERTIFICATE(self), NULL); |
138 | 0 | if (self->activation_time == NULL) |
139 | 0 | return NULL; |
140 | 0 | return g_date_time_ref(self->activation_time); |
141 | 0 | } |
142 | | |
143 | | static gboolean |
144 | | fu_x509_certificate_parse(FuFirmware *firmware, |
145 | | GInputStream *stream, |
146 | | FuFirmwareParseFlags flags, |
147 | | GError **error) |
148 | 0 | { |
149 | | #ifdef HAVE_GNUTLS |
150 | | FuX509Certificate *self = FU_X509_CERTIFICATE(firmware); |
151 | | gchar buf[1024] = {'\0'}; |
152 | | guchar key_id[32] = {'\0'}; |
153 | | gsize key_idsz = sizeof(key_id); |
154 | | gnutls_datum_t d = {0}; |
155 | | gnutls_x509_dn_t dn = {0x0}; |
156 | | gint64 ts; |
157 | | gsize bufsz = sizeof(buf); |
158 | | int rc; |
159 | | g_auto(gnutls_x509_crt_t) crt = NULL; |
160 | | g_autoptr(gnutls_datum_t) subject = NULL; |
161 | | g_autoptr(GBytes) blob = NULL; |
162 | | g_autoptr(GString) key_idstr = g_string_new(NULL); |
163 | | |
164 | | /* parse certificate */ |
165 | | blob = fu_input_stream_read_bytes(stream, 0x0, G_MAXSIZE, NULL, error); |
166 | | if (blob == NULL) |
167 | | return FALSE; |
168 | | d.size = g_bytes_get_size(blob); |
169 | | d.data = (unsigned char *)g_bytes_get_data(blob, NULL); |
170 | | |
171 | | rc = gnutls_x509_crt_init(&crt); |
172 | | if (rc < 0) { |
173 | | g_set_error(error, |
174 | | FWUPD_ERROR, |
175 | | FWUPD_ERROR_INVALID_DATA, |
176 | | "crt_init: %s [%i]", |
177 | | gnutls_strerror(rc), |
178 | | rc); |
179 | | return FALSE; |
180 | | } |
181 | | if (flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) |
182 | | gnutls_x509_crt_set_flags(crt, GNUTLS_X509_CRT_FLAG_IGNORE_SANITY); |
183 | | rc = gnutls_x509_crt_import(crt, &d, GNUTLS_X509_FMT_DER); |
184 | | if (rc < 0) { |
185 | | g_set_error(error, |
186 | | FWUPD_ERROR, |
187 | | FWUPD_ERROR_INVALID_DATA, |
188 | | "crt_import: %s [%i]", |
189 | | gnutls_strerror(rc), |
190 | | rc); |
191 | | return FALSE; |
192 | | } |
193 | | |
194 | | /* issuer */ |
195 | | if (gnutls_x509_crt_get_issuer_dn(crt, buf, &bufsz) == GNUTLS_E_SUCCESS) { |
196 | | g_autofree gchar *str = fu_strsafe((const gchar *)buf, bufsz); |
197 | | fu_x509_certificate_set_issuer(self, str); |
198 | | } |
199 | | |
200 | | /* subject */ |
201 | | subject = (gnutls_datum_t *)gnutls_malloc(sizeof(gnutls_datum_t)); |
202 | | if (gnutls_x509_crt_get_subject(crt, &dn) == GNUTLS_E_SUCCESS) { |
203 | | g_autofree gchar *str = NULL; |
204 | | gnutls_x509_dn_get_str(dn, subject); |
205 | | str = fu_strsafe((const gchar *)subject->data, subject->size); |
206 | | fu_x509_certificate_set_subject(self, str); |
207 | | } |
208 | | |
209 | | /* activation_time */ |
210 | | ts = (gint64)gnutls_x509_crt_get_activation_time(crt); |
211 | | if (ts == -1) { |
212 | | g_set_error_literal(error, |
213 | | FWUPD_ERROR, |
214 | | FWUPD_ERROR_INVALID_DATA, |
215 | | "failed to get activation time"); |
216 | | return FALSE; |
217 | | } |
218 | | fu_x509_certificate_set_activation_time(self, ts); |
219 | | |
220 | | /* key ID */ |
221 | | rc = gnutls_x509_crt_get_key_id(crt, 0, key_id, &key_idsz); |
222 | | if (rc < 0) { |
223 | | g_set_error(error, |
224 | | FWUPD_ERROR, |
225 | | FWUPD_ERROR_INVALID_DATA, |
226 | | "failed to get key ID: %s [%i]", |
227 | | gnutls_strerror(rc), |
228 | | rc); |
229 | | return FALSE; |
230 | | } |
231 | | for (guint i = 0; i < key_idsz; i++) |
232 | | g_string_append_printf(key_idstr, "%02x", key_id[i]); |
233 | | fu_firmware_set_id(firmware, key_idstr->str); |
234 | | |
235 | | /* success */ |
236 | | return TRUE; |
237 | | #else |
238 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no GnuTLS support"); |
239 | 0 | return FALSE; |
240 | 0 | #endif |
241 | 0 | } |
242 | | |
243 | | static void |
244 | | fu_x509_certificate_init(FuX509Certificate *self) |
245 | 0 | { |
246 | 0 | } |
247 | | |
248 | | static void |
249 | | fu_x509_certificate_finalize(GObject *obj) |
250 | 0 | { |
251 | 0 | FuX509Certificate *self = FU_X509_CERTIFICATE(obj); |
252 | 0 | g_free(self->issuer); |
253 | 0 | g_free(self->subject); |
254 | 0 | if (self->activation_time != NULL) |
255 | 0 | g_date_time_unref(self->activation_time); |
256 | 0 | G_OBJECT_CLASS(fu_x509_certificate_parent_class)->finalize(obj); |
257 | 0 | } |
258 | | |
259 | | static void |
260 | | fu_x509_certificate_class_init(FuX509CertificateClass *klass) |
261 | 0 | { |
262 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
263 | 0 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
264 | 0 | object_class->finalize = fu_x509_certificate_finalize; |
265 | 0 | firmware_class->export = fu_x509_certificate_export; |
266 | 0 | firmware_class->parse = fu_x509_certificate_parse; |
267 | 0 | } |
268 | | |
269 | | /** |
270 | | * fu_x509_certificate_new: |
271 | | * |
272 | | * Creates a new #FuX509Certificate. |
273 | | * |
274 | | * Returns: (transfer full): object |
275 | | * |
276 | | * Since: 2.0.9 |
277 | | **/ |
278 | | FuX509Certificate * |
279 | | fu_x509_certificate_new(void) |
280 | 0 | { |
281 | 0 | return g_object_new(FU_TYPE_X509_CERTIFICATE, NULL); |
282 | 0 | } |