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