/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 | } |