]>
Commit | Line | Data |
---|---|---|
c3b5af63 MK |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Menu-driven UEFI Secure Boot Key Maintenance | |
4 | * | |
5 | * Copyright (c) 2022 Masahisa Kojima, Linaro Limited | |
6 | */ | |
7 | ||
8 | #include <ansi.h> | |
9 | #include <common.h> | |
10 | #include <charset.h> | |
11 | #include <hexdump.h> | |
12 | #include <log.h> | |
13 | #include <malloc.h> | |
14 | #include <menu.h> | |
15 | #include <efi_loader.h> | |
16 | #include <efi_config.h> | |
17 | #include <efi_variable.h> | |
18 | #include <crypto/pkcs7_parser.h> | |
19 | ||
d0f9ae35 MK |
20 | struct eficonfig_sig_data { |
21 | struct efi_signature_list *esl; | |
22 | struct efi_signature_data *esd; | |
23 | struct list_head list; | |
24 | u16 *varname; | |
25 | }; | |
26 | ||
c3b5af63 MK |
27 | enum efi_sbkey_signature_type { |
28 | SIG_TYPE_X509 = 0, | |
29 | SIG_TYPE_HASH, | |
30 | SIG_TYPE_CRL, | |
31 | SIG_TYPE_RSA2048, | |
32 | }; | |
33 | ||
34 | struct eficonfig_sigtype_to_str { | |
35 | efi_guid_t sig_type; | |
36 | char *str; | |
37 | enum efi_sbkey_signature_type type; | |
38 | }; | |
39 | ||
40 | static const struct eficonfig_sigtype_to_str sigtype_to_str[] = { | |
41 | {EFI_CERT_X509_GUID, "X509", SIG_TYPE_X509}, | |
42 | {EFI_CERT_SHA256_GUID, "SHA256", SIG_TYPE_HASH}, | |
43 | {EFI_CERT_X509_SHA256_GUID, "X509_SHA256 CRL", SIG_TYPE_CRL}, | |
44 | {EFI_CERT_X509_SHA384_GUID, "X509_SHA384 CRL", SIG_TYPE_CRL}, | |
45 | {EFI_CERT_X509_SHA512_GUID, "X509_SHA512 CRL", SIG_TYPE_CRL}, | |
46 | /* U-Boot does not support the following signature types */ | |
47 | /* {EFI_CERT_RSA2048_GUID, "RSA2048", SIG_TYPE_RSA2048}, */ | |
48 | /* {EFI_CERT_RSA2048_SHA256_GUID, "RSA2048_SHA256", SIG_TYPE_RSA2048}, */ | |
49 | /* {EFI_CERT_SHA1_GUID, "SHA1", SIG_TYPE_HASH}, */ | |
50 | /* {EFI_CERT_RSA2048_SHA_GUID, "RSA2048_SHA", SIG_TYPE_RSA2048 }, */ | |
51 | /* {EFI_CERT_SHA224_GUID, "SHA224", SIG_TYPE_HASH}, */ | |
52 | /* {EFI_CERT_SHA384_GUID, "SHA384", SIG_TYPE_HASH}, */ | |
53 | /* {EFI_CERT_SHA512_GUID, "SHA512", SIG_TYPE_HASH}, */ | |
54 | }; | |
55 | ||
56 | /** | |
57 | * file_have_auth_header() - check file has EFI_VARIABLE_AUTHENTICATION_2 header | |
58 | * @buf: pointer to file | |
59 | * @size: file size | |
60 | * Return: true if file has auth header, false otherwise | |
61 | */ | |
62 | static bool file_have_auth_header(void *buf, efi_uintn_t size) | |
63 | { | |
64 | struct efi_variable_authentication_2 *auth = buf; | |
65 | ||
66 | if (auth->auth_info.hdr.wCertificateType != WIN_CERT_TYPE_EFI_GUID) | |
67 | return false; | |
68 | ||
69 | if (guidcmp(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7)) | |
70 | return false; | |
71 | ||
72 | return true; | |
73 | } | |
74 | ||
ad50ca50 MK |
75 | /** |
76 | * file_is_null_key() - check the file is an authenticated and signed null key | |
77 | * | |
78 | * @auth: pointer to the file | |
79 | * @size: file size | |
80 | * @null_key: pointer to store the result | |
81 | * Return: status code | |
82 | */ | |
83 | static efi_status_t file_is_null_key(struct efi_variable_authentication_2 *auth, | |
84 | efi_uintn_t size, bool *null_key) | |
85 | { | |
86 | efi_uintn_t auth_size = | |
87 | sizeof(auth->time_stamp) + auth->auth_info.hdr.dwLength; | |
88 | ||
89 | if (size < auth_size) | |
90 | return EFI_INVALID_PARAMETER; | |
91 | ||
92 | *null_key = (size == auth_size); | |
93 | ||
94 | return EFI_SUCCESS; | |
95 | } | |
96 | ||
c3b5af63 MK |
97 | /** |
98 | * eficonfig_process_enroll_key() - enroll key into signature database | |
99 | * | |
100 | * @data: pointer to the data for each entry | |
101 | * Return: status code | |
102 | */ | |
103 | static efi_status_t eficonfig_process_enroll_key(void *data) | |
104 | { | |
105 | u32 attr; | |
106 | char *buf = NULL; | |
107 | efi_uintn_t size; | |
108 | efi_status_t ret; | |
ad50ca50 | 109 | bool null_key = false; |
c3b5af63 MK |
110 | struct efi_file_handle *f = NULL; |
111 | struct efi_device_path *full_dp = NULL; | |
112 | struct eficonfig_select_file_info file_info; | |
113 | ||
114 | file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE); | |
115 | if (!file_info.current_path) { | |
116 | ret = EFI_OUT_OF_RESOURCES; | |
117 | goto out; | |
118 | } | |
119 | ||
120 | ret = eficonfig_process_select_file(&file_info); | |
121 | if (ret != EFI_SUCCESS) | |
122 | goto out; | |
123 | ||
124 | full_dp = eficonfig_create_device_path(file_info.dp_volume, file_info.current_path); | |
125 | if (!full_dp) { | |
126 | ret = EFI_OUT_OF_RESOURCES; | |
127 | goto out; | |
128 | } | |
129 | f = efi_file_from_path(full_dp); | |
130 | if (!f) { | |
131 | ret = EFI_NOT_FOUND; | |
132 | goto out; | |
133 | } | |
134 | ||
135 | size = 0; | |
136 | ret = EFI_CALL(f->getinfo(f, &efi_file_info_guid, &size, NULL)); | |
137 | if (ret != EFI_BUFFER_TOO_SMALL) | |
138 | goto out; | |
139 | ||
140 | buf = malloc(size); | |
141 | if (!buf) { | |
142 | ret = EFI_OUT_OF_RESOURCES; | |
143 | goto out; | |
144 | } | |
145 | ret = EFI_CALL(f->getinfo(f, &efi_file_info_guid, &size, buf)); | |
146 | if (ret != EFI_SUCCESS) | |
147 | goto out; | |
148 | ||
149 | size = ((struct efi_file_info *)buf)->file_size; | |
150 | free(buf); | |
151 | ||
152 | if (!size) { | |
153 | eficonfig_print_msg("ERROR! File is empty."); | |
154 | ret = EFI_INVALID_PARAMETER; | |
155 | goto out; | |
156 | } | |
157 | ||
158 | buf = malloc(size); | |
159 | if (!buf) { | |
160 | ret = EFI_OUT_OF_RESOURCES; | |
161 | goto out; | |
162 | } | |
163 | ||
164 | ret = EFI_CALL(f->read(f, &size, buf)); | |
165 | if (ret != EFI_SUCCESS) { | |
166 | eficonfig_print_msg("ERROR! Failed to read file."); | |
167 | goto out; | |
168 | } | |
169 | if (!file_have_auth_header(buf, size)) { | |
170 | eficonfig_print_msg("ERROR! Invalid file format. Only .auth variables is allowed."); | |
171 | ret = EFI_INVALID_PARAMETER; | |
172 | goto out; | |
173 | } | |
174 | ||
ad50ca50 MK |
175 | ret = file_is_null_key((struct efi_variable_authentication_2 *)buf, |
176 | size, &null_key); | |
177 | if (ret != EFI_SUCCESS) { | |
178 | eficonfig_print_msg("ERROR! Invalid file format."); | |
179 | goto out; | |
180 | } | |
181 | ||
c3b5af63 MK |
182 | attr = EFI_VARIABLE_NON_VOLATILE | |
183 | EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
184 | EFI_VARIABLE_RUNTIME_ACCESS | | |
185 | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; | |
186 | ||
ad50ca50 MK |
187 | /* |
188 | * PK can enroll only one certificate. | |
189 | * The signed null key is used to clear KEK, db and dbx. | |
190 | * EFI_VARIABLE_APPEND_WRITE attribute must not be set in these cases. | |
191 | */ | |
192 | if (u16_strcmp(data, u"PK") && !null_key) { | |
c3b5af63 MK |
193 | efi_uintn_t db_size = 0; |
194 | ||
195 | /* check the variable exists. If exists, add APPEND_WRITE attribute */ | |
196 | ret = efi_get_variable_int(data, efi_auth_var_get_guid(data), NULL, | |
197 | &db_size, NULL, NULL); | |
198 | if (ret == EFI_BUFFER_TOO_SMALL) | |
199 | attr |= EFI_VARIABLE_APPEND_WRITE; | |
200 | } | |
201 | ||
202 | ret = efi_set_variable_int((u16 *)data, efi_auth_var_get_guid((u16 *)data), | |
203 | attr, size, buf, false); | |
204 | if (ret != EFI_SUCCESS) | |
205 | eficonfig_print_msg("ERROR! Failed to update signature database"); | |
206 | ||
207 | out: | |
208 | free(file_info.current_path); | |
209 | free(buf); | |
210 | efi_free_pool(full_dp); | |
211 | if (f) | |
212 | EFI_CALL(f->close(f)); | |
213 | ||
214 | /* return to the parent menu */ | |
215 | ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
216 | ||
217 | return ret; | |
218 | } | |
219 | ||
d0f9ae35 MK |
220 | /** |
221 | * eficonfig_process_show_siglist() - show signature list content | |
222 | * | |
223 | * @data: pointer to the data for each entry | |
224 | * Return: status code | |
225 | */ | |
226 | static efi_status_t eficonfig_process_show_siglist(void *data) | |
227 | { | |
228 | u32 i; | |
229 | struct eficonfig_sig_data *sg = data; | |
230 | ||
231 | puts(ANSI_CURSOR_HIDE); | |
232 | puts(ANSI_CLEAR_CONSOLE); | |
233 | printf(ANSI_CURSOR_POSITION, 1, 1); | |
234 | ||
235 | printf("\n ** Show Signature Database (%ls) **\n\n" | |
236 | " Owner GUID:\n" | |
237 | " %pUL\n", | |
238 | sg->varname, sg->esd->signature_owner.b); | |
239 | ||
240 | for (i = 0; i < ARRAY_SIZE(sigtype_to_str); i++) { | |
241 | if (!guidcmp(&sg->esl->signature_type, &sigtype_to_str[i].sig_type)) { | |
242 | printf(" Signature Type:\n" | |
243 | " %s\n", sigtype_to_str[i].str); | |
244 | ||
245 | switch (sigtype_to_str[i].type) { | |
246 | case SIG_TYPE_X509: | |
247 | { | |
248 | struct x509_certificate *cert_tmp; | |
249 | ||
250 | cert_tmp = x509_cert_parse(sg->esd->signature_data, | |
251 | sg->esl->signature_size); | |
252 | printf(" Subject:\n" | |
253 | " %s\n" | |
254 | " Issuer:\n" | |
255 | " %s\n", | |
256 | cert_tmp->subject, cert_tmp->issuer); | |
257 | break; | |
258 | } | |
259 | case SIG_TYPE_CRL: | |
260 | { | |
261 | u32 hash_size = sg->esl->signature_size - sizeof(efi_guid_t) - | |
262 | sizeof(struct efi_time); | |
263 | struct efi_time *time = | |
264 | (struct efi_time *)((u8 *)sg->esd->signature_data + | |
265 | hash_size); | |
266 | ||
267 | printf(" ToBeSignedHash:\n"); | |
268 | print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, | |
269 | sg->esd->signature_data, hash_size, false); | |
270 | printf(" TimeOfRevocation:\n" | |
271 | " %d-%d-%d %02d:%02d:%02d\n", | |
272 | time->year, time->month, time->day, | |
273 | time->hour, time->minute, time->second); | |
274 | break; | |
275 | } | |
276 | case SIG_TYPE_HASH: | |
277 | { | |
278 | u32 hash_size = sg->esl->signature_size - sizeof(efi_guid_t); | |
279 | ||
280 | printf(" Hash:\n"); | |
281 | print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, | |
282 | sg->esd->signature_data, hash_size, false); | |
283 | break; | |
284 | } | |
285 | default: | |
286 | eficonfig_print_msg("ERROR! Unsupported format."); | |
287 | return EFI_INVALID_PARAMETER; | |
288 | } | |
289 | } | |
290 | } | |
291 | ||
292 | while (tstc()) | |
293 | getchar(); | |
294 | ||
295 | printf("\n\n Press any key to continue"); | |
296 | getchar(); | |
297 | ||
298 | return EFI_SUCCESS; | |
299 | } | |
300 | ||
301 | /** | |
302 | * prepare_signature_list_menu() - create the signature list menu entry | |
303 | * | |
304 | * @efimenu: pointer to the efimenu structure | |
305 | * @varname: pointer to the variable name | |
306 | * @db: pointer to the variable raw data | |
307 | * @db_size: variable data size | |
308 | * @func: callback of each entry | |
309 | * Return: status code | |
310 | */ | |
311 | static efi_status_t prepare_signature_list_menu(struct efimenu *efi_menu, void *varname, | |
312 | void *db, efi_uintn_t db_size, | |
313 | eficonfig_entry_func func) | |
314 | { | |
315 | u32 num = 0; | |
316 | efi_uintn_t size; | |
317 | struct eficonfig_sig_data *sg; | |
318 | struct efi_signature_list *esl; | |
319 | struct efi_signature_data *esd; | |
320 | efi_status_t ret = EFI_SUCCESS; | |
321 | ||
322 | INIT_LIST_HEAD(&efi_menu->list); | |
323 | ||
324 | esl = db; | |
325 | size = db_size; | |
326 | while (size > 0) { | |
327 | u32 remain; | |
328 | ||
329 | esd = (struct efi_signature_data *)((u8 *)esl + | |
330 | (sizeof(struct efi_signature_list) + | |
331 | esl->signature_header_size)); | |
332 | remain = esl->signature_list_size - sizeof(struct efi_signature_list) - | |
333 | esl->signature_header_size; | |
334 | for (; remain > 0; remain -= esl->signature_size) { | |
335 | char buf[37]; | |
336 | char *title; | |
337 | ||
338 | if (num >= EFICONFIG_ENTRY_NUM_MAX - 1) { | |
339 | ret = EFI_OUT_OF_RESOURCES; | |
340 | goto out; | |
341 | } | |
342 | ||
343 | sg = calloc(1, sizeof(struct eficonfig_sig_data)); | |
344 | if (!sg) { | |
345 | ret = EFI_OUT_OF_RESOURCES; | |
346 | goto err; | |
347 | } | |
348 | ||
349 | snprintf(buf, sizeof(buf), "%pUL", &esd->signature_owner); | |
350 | title = strdup(buf); | |
351 | if (!title) { | |
352 | free(sg); | |
353 | ret = EFI_OUT_OF_RESOURCES; | |
354 | goto err; | |
355 | } | |
356 | ||
357 | sg->esl = esl; | |
358 | sg->esd = esd; | |
359 | sg->varname = varname; | |
360 | ret = eficonfig_append_menu_entry(efi_menu, title, func, sg); | |
361 | if (ret != EFI_SUCCESS) { | |
362 | free(sg); | |
363 | free(title); | |
364 | goto err; | |
365 | } | |
366 | esd = (struct efi_signature_data *)((u8 *)esd + esl->signature_size); | |
367 | num++; | |
368 | } | |
369 | ||
370 | size -= esl->signature_list_size; | |
371 | esl = (struct efi_signature_list *)((u8 *)esl + esl->signature_list_size); | |
372 | } | |
373 | out: | |
374 | ret = eficonfig_append_quit_entry(efi_menu); | |
375 | err: | |
376 | return ret; | |
377 | } | |
378 | ||
379 | /** | |
380 | * enumerate_and_show_signature_database() - enumerate and show the signature database | |
381 | * | |
382 | * @data: pointer to the data for each entry | |
383 | * Return: status code | |
384 | */ | |
385 | static efi_status_t enumerate_and_show_signature_database(void *varname) | |
386 | { | |
387 | void *db; | |
388 | char buf[50]; | |
389 | efi_status_t ret; | |
390 | efi_uintn_t db_size; | |
391 | struct efimenu *efi_menu; | |
392 | struct list_head *pos, *n; | |
393 | struct eficonfig_entry *entry; | |
394 | ||
395 | db = efi_get_var(varname, efi_auth_var_get_guid(varname), &db_size); | |
396 | if (!db) { | |
397 | eficonfig_print_msg("There is no entry in the signature database."); | |
398 | return EFI_NOT_FOUND; | |
399 | } | |
400 | ||
401 | efi_menu = calloc(1, sizeof(struct efimenu)); | |
402 | if (!efi_menu) { | |
403 | free(db); | |
404 | return EFI_OUT_OF_RESOURCES; | |
405 | } | |
406 | ||
407 | ret = prepare_signature_list_menu(efi_menu, varname, db, db_size, | |
408 | eficonfig_process_show_siglist); | |
409 | if (ret != EFI_SUCCESS) | |
410 | goto out; | |
411 | ||
412 | snprintf(buf, sizeof(buf), " ** Show Signature Database (%ls) **", (u16 *)varname); | |
cd160b27 MK |
413 | ret = eficonfig_process_common(efi_menu, buf, eficonfig_menu_desc, |
414 | eficonfig_display_statusline, | |
415 | eficonfig_print_entry, | |
416 | eficonfig_choice_entry); | |
d0f9ae35 MK |
417 | out: |
418 | list_for_each_safe(pos, n, &efi_menu->list) { | |
419 | entry = list_entry(pos, struct eficonfig_entry, list); | |
420 | free(entry->data); | |
421 | } | |
422 | eficonfig_destroy(efi_menu); | |
423 | free(db); | |
424 | ||
425 | return ret; | |
426 | } | |
427 | ||
428 | /** | |
429 | * eficonfig_process_show_signature_database() - process show signature database | |
430 | * | |
431 | * @data: pointer to the data for each entry | |
432 | * Return: status code | |
433 | */ | |
434 | static efi_status_t eficonfig_process_show_signature_database(void *data) | |
435 | { | |
436 | efi_status_t ret; | |
437 | ||
438 | while (1) { | |
439 | ret = enumerate_and_show_signature_database(data); | |
440 | if (ret != EFI_SUCCESS && ret != EFI_NOT_READY) | |
441 | break; | |
442 | } | |
443 | ||
444 | /* return to the parent menu */ | |
445 | ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
446 | ||
447 | return ret; | |
448 | } | |
449 | ||
c3b5af63 MK |
450 | static struct eficonfig_item key_config_menu_items[] = { |
451 | {"Enroll New Key", eficonfig_process_enroll_key}, | |
d0f9ae35 | 452 | {"Show Signature Database", eficonfig_process_show_signature_database}, |
c3b5af63 MK |
453 | {"Quit", eficonfig_process_quit}, |
454 | }; | |
455 | ||
456 | /** | |
457 | * eficonfig_process_set_secure_boot_key() - display the key configuration menu | |
458 | * | |
459 | * @data: pointer to the data for each entry | |
460 | * Return: status code | |
461 | */ | |
462 | static efi_status_t eficonfig_process_set_secure_boot_key(void *data) | |
463 | { | |
464 | u32 i; | |
465 | efi_status_t ret; | |
466 | char header_str[32]; | |
467 | struct efimenu *efi_menu; | |
468 | ||
469 | for (i = 0; i < ARRAY_SIZE(key_config_menu_items); i++) | |
470 | key_config_menu_items[i].data = data; | |
471 | ||
472 | snprintf(header_str, sizeof(header_str), " ** Configure %ls **", (u16 *)data); | |
473 | ||
474 | while (1) { | |
475 | efi_menu = eficonfig_create_fixed_menu(key_config_menu_items, | |
476 | ARRAY_SIZE(key_config_menu_items)); | |
477 | ||
cd160b27 MK |
478 | ret = eficonfig_process_common(efi_menu, header_str, |
479 | eficonfig_menu_desc, | |
480 | eficonfig_display_statusline, | |
481 | eficonfig_print_entry, | |
482 | eficonfig_choice_entry); | |
c3b5af63 MK |
483 | eficonfig_destroy(efi_menu); |
484 | ||
485 | if (ret == EFI_ABORTED) | |
486 | break; | |
487 | } | |
488 | ||
489 | /* return to the parent menu */ | |
490 | ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
491 | ||
492 | return ret; | |
493 | } | |
494 | ||
495 | static const struct eficonfig_item secure_boot_menu_items[] = { | |
496 | {"PK", eficonfig_process_set_secure_boot_key, u"PK"}, | |
497 | {"KEK", eficonfig_process_set_secure_boot_key, u"KEK"}, | |
498 | {"db", eficonfig_process_set_secure_boot_key, u"db"}, | |
499 | {"dbx", eficonfig_process_set_secure_boot_key, u"dbx"}, | |
500 | {"Quit", eficonfig_process_quit}, | |
501 | }; | |
502 | ||
503 | /** | |
504 | * eficonfig_process_secure_boot_config() - display the key list menu | |
505 | * | |
506 | * @data: pointer to the data for each entry | |
507 | * Return: status code | |
508 | */ | |
509 | efi_status_t eficonfig_process_secure_boot_config(void *data) | |
510 | { | |
511 | efi_status_t ret; | |
512 | struct efimenu *efi_menu; | |
513 | ||
514 | while (1) { | |
515 | char header_str[64]; | |
516 | ||
517 | snprintf(header_str, sizeof(header_str), | |
518 | " ** UEFI Secure Boot Key Configuration (SecureBoot : %s) **", | |
519 | (efi_secure_boot_enabled() ? "ON" : "OFF")); | |
520 | ||
521 | efi_menu = eficonfig_create_fixed_menu(secure_boot_menu_items, | |
522 | ARRAY_SIZE(secure_boot_menu_items)); | |
523 | if (!efi_menu) { | |
524 | ret = EFI_OUT_OF_RESOURCES; | |
525 | break; | |
526 | } | |
527 | ||
cd160b27 MK |
528 | ret = eficonfig_process_common(efi_menu, header_str, |
529 | eficonfig_menu_desc, | |
530 | eficonfig_display_statusline, | |
531 | eficonfig_print_entry, | |
532 | eficonfig_choice_entry); | |
533 | ||
c3b5af63 MK |
534 | eficonfig_destroy(efi_menu); |
535 | ||
536 | if (ret == EFI_ABORTED) | |
537 | break; | |
538 | } | |
539 | ||
540 | /* return to the parent menu */ | |
541 | ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
542 | ||
543 | return ret; | |
544 | } |