diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index 8d67b13b8b..627538a0eb 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -15,7 +15,7 @@ typedef enum UnifiedSection { UNIFIED_SECTION_DTB, UNIFIED_SECTION_UNAME, UNIFIED_SECTION_SBAT, - UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRSIG, /* This is is not measured actually */ UNIFIED_SECTION_PCRPKEY, UNIFIED_SECTION_PROFILE, UNIFIED_SECTION_DTBAUTO, diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index bd1339ddfa..af52e5aeb8 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-json.h" @@ -12,10 +11,12 @@ #include "efivars.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "openssl-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -65,71 +66,55 @@ STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL, *options2 = NULL; int r; r = terminal_urlify_man("systemd-measure", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Show current PCR values\n" - " calculate Calculate expected PCR values\n" - " sign Calculate and sign expected PCR values\n" - " policy-digest Calculate expected TPM2 policy digests\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " -c --current Use current PCR values\n" - " --phase=PHASE Specify a boot phase to sign for\n" - " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" - " --tpm2-device=PATH Use specified TPM2 device\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --public-key=KEY Public key (PEM) to validate against\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " --append=PATH Load specified JSON signature, and append new signature to it\n" - "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n" - " --linux=PATH Path to Linux kernel image file %7$s .linux\n" - " --osrel=PATH Path to os-release file %7$s .osrel\n" - " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" - " --initrd=PATH Path to initrd image file %7$s .initrd\n" - " --ucode=PATH Path to microcode image file %7$s .ucode\n" - " --splash=PATH Path to splash bitmap file %7$s .splash\n" - " --dtb=PATH Path to DeviceTree file %7$s .dtb\n" - " --dtbauto=PATH Path to DeviceTree file for auto selection %7$s .dtbauto\n" - " --uname=PATH Path to 'uname -r' file %7$s .uname\n" - " --sbat=PATH Path to SBAT file %7$s .sbat\n" - " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" - " --profile=PATH Path to profile file %7$s .profile\n" - " --hwids=PATH Path to HWIDs file %7$s .hwids\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - glyph(GLYPH_ARROW_RIGHT)); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("UKI PE Section Options", &options2); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options, options2); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sPre-calculate and sign PCR hash for a unified kernel image (UKI).%s\n" + "\n%sCommands:%s\n", + program_invocation_short_name, + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sUKI PE Section Options:%s\n", ansi_underline(), ansi_normal()); + + r = table_print_or_warn(options2); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); static char *normalize_phase(const char *s) { _cleanup_strv_free_ char **l = NULL; @@ -146,187 +131,39 @@ static char *normalize_phase(const char *s) { return strv_join(strv_remove(l, ""), ":"); } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - _ARG_SECTION_FIRST, - ARG_LINUX = _ARG_SECTION_FIRST, - ARG_OSREL, - ARG_CMDLINE, - ARG_INITRD, - ARG_UCODE, - ARG_SPLASH, - ARG_DTB, - ARG_UNAME, - ARG_SBAT, - _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ - ARG_PCRPKEY, - ARG_PROFILE, - ARG_DTBAUTO, - ARG_HWIDS, - _ARG_SECTION_LAST, - ARG_EFIFW = _ARG_SECTION_LAST, - ARG_BANK, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PUBLIC_KEY, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_TPM2_DEVICE, - ARG_JSON, - ARG_PHASE, - ARG_APPEND, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "ucode", required_argument, NULL, ARG_UCODE }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "dtbauto", required_argument, NULL, ARG_DTBAUTO }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "profile", required_argument, NULL, ARG_PROFILE }, - { "hwids", required_argument, NULL, ARG_HWIDS }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - /* Make sure the arguments list and the section list, stays in sync */ - assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1); + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + int r; - while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0) + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: { - UnifiedSection section = c - _ARG_SECTION_FIRST; - - r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section); - if (r < 0) - return r; - break; - } - - case 'c': + OPTION('c', "current", NULL, + "Use current PCR values"): arg_current = true; break; - case ARG_BANK: { - const EVP_MD *implementation; - - implementation = EVP_get_digestbyname(optarg); - if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); - - if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) - return log_oom(); - - break; - } - - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); - if (r < 0) - return r; - - break; - - case ARG_PRIVATE_KEY_SOURCE: - r = parse_openssl_key_source_argument( - optarg, - &arg_private_key_source, - &arg_private_key_source_type); - if (r < 0) - return r; - - break; - - case ARG_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key); - if (r < 0) - return r; - - break; - - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); - if (r < 0) - return r; - break; - - case ARG_CERTIFICATE_SOURCE: - r = parse_openssl_certificate_source_argument( - optarg, - &arg_certificate_source, - &arg_certificate_source_type); - if (r < 0) - return r; - break; - - case ARG_TPM2_DEVICE: { - _cleanup_free_ char *device = NULL; - - if (streq(optarg, "list")) - return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - - if (!streq(optarg, "auto")) { - device = strdup(optarg); - if (!device) - return log_oom(); - } - - free_and_replace(arg_tpm2_device, device); - break; - } - - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - - break; - - case ARG_PHASE: { + OPTION_LONG("phase", "PHASE", + "Specify a boot phase to sign for"): { char *n; - n = normalize_phase(optarg); + n = normalize_phase(arg); if (!n) return log_oom(); @@ -337,18 +174,133 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_APPEND: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append); + OPTION_LONG("bank", "DIGEST", + "Select TPM bank (SHA1, SHA256, SHA384, SHA512)"): { + const EVP_MD *implementation; + + implementation = EVP_get_digestbyname(arg); + if (!implementation) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); + + if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + return log_oom(); + + break; + } + + OPTION_LONG("tpm2-device", "PATH", + "Use specified TPM2 device"): { + _cleanup_free_ char *device = NULL; + + if (streq(arg, "list")) + return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); + + if (!streq(arg, "auto")) { + device = strdup(arg); + if (!device) + return log_oom(); + } + + free_and_replace(arg_tpm2_device, device); + break; + } + + OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case '?': - return -EINVAL; + OPTION_COMMON_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument( + arg, + &arg_private_key_source, + &arg_private_key_source_type); + if (r < 0) + return r; - default: - assert_not_reached(); + break; + + OPTION_LONG("public-key", "KEY", + "Public key (PEM) to validate against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_public_key); + if (r < 0) + return r; + + break; + + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); + if (r < 0) + return r; + break; + + OPTION_COMMON_CERTIFICATE_SOURCE: + r = parse_openssl_certificate_source_argument( + arg, + &arg_certificate_source, + &arg_certificate_source_type); + if (r < 0) + return r; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; + + OPTION_LONG("append", "PATH", + "Load specified JSON signature, and append new signature to it"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_append); + if (r < 0) + return r; + + break; + + OPTION_GROUP("UKI PE Section Options"): {} + + OPTION_LONG_DATA("linux", "PATH", UNIFIED_SECTION_LINUX, + "Path to Linux kernel image file (→ .linux)"): {} + OPTION_LONG_DATA("osrel", "PATH", UNIFIED_SECTION_OSREL, + "Path to os-release file (→ .osrel)"): {} + OPTION_LONG_DATA("cmdline", "PATH", UNIFIED_SECTION_CMDLINE, + "Path to file with kernel command line (→ .cmdline)"): {} + OPTION_LONG_DATA("initrd", "PATH", UNIFIED_SECTION_INITRD, + "Path to initrd image file (→ .initrd)"): {} + OPTION_LONG_DATA("ucode", "PATH", UNIFIED_SECTION_UCODE, + "Path to microcode image file (→ .ucode)"): {} + OPTION_LONG_DATA("splash", "PATH", UNIFIED_SECTION_SPLASH, + "Path to splash bitmap file (→ .splash)"): {} + OPTION_LONG_DATA("dtb", "PATH", UNIFIED_SECTION_DTB, + "Path to DeviceTree file (→ .dtb)"): {} + OPTION_LONG_DATA("dtbauto", "PATH", UNIFIED_SECTION_DTBAUTO, + "Path to DeviceTree file for auto selection (→ .dtbauto)"): {} + OPTION_LONG_DATA("uname", "PATH", UNIFIED_SECTION_UNAME, + "Path to 'uname -r' file (→ .uname)"): {} + OPTION_LONG_DATA("sbat", "PATH", UNIFIED_SECTION_SBAT, + "Path to SBAT file (→ .sbat)"): {} + /* The .pcrsig section is not input for signing, hence not actually an argument here */ + OPTION_LONG_DATA("pcrpkey", "PATH", UNIFIED_SECTION_PCRPKEY, + "Path to public key for PCR signatures (→ .pcrpkey)"): {} + OPTION_LONG_DATA("profile", "PATH", UNIFIED_SECTION_PROFILE, + "Path to profile file (→ .profile)"): {} + OPTION_LONG_DATA("hwids", "PATH", UNIFIED_SECTION_HWIDS, + "Path to HWIDs file (→ .hwids)"): + /* Make sure that if new sections are added, the list here is updated. */ + assert_cc(UNIFIED_SECTION_HWIDS + 1 + 1 /* FIXME */ == _UNIFIED_SECTION_MAX); + assert(opt->data < _UNIFIED_SECTION_MAX); + + r = parse_path_argument(arg, /* suppress_root= */ false, arg_sections + opt->data); + if (r < 0) + return r; + break; } if (arg_public_key && arg_certificate) @@ -390,6 +342,8 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); log_debug("Measuring boot phases: %s", j); + + *ret_args = option_parser_get_args(&state); return 1; } @@ -458,6 +412,8 @@ static int validate_stub(void) { return 0; } +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show current PCR values"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; @@ -858,6 +814,8 @@ static void pcr_states_restore(PcrState *pcr_states, size_t n) { } } +VERB_NOARG(verb_calculate, "calculate", + "Calculate expected PCR values"); static int verb_calculate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; @@ -1139,37 +1097,29 @@ static int build_policy_digest(bool sign) { return 0; } +VERB_NOARG(verb_sign, "sign", + "Calculate and sign expected PCR values"); static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ true); } +VERB_NOARG(verb_policy_digest, "policy-digest", + "Calculate expected TPM2 policy digests"); static int verb_policy_digest(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ false); } -static int measure_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "calculate", VERB_ANY, 1, 0, verb_calculate }, - { "policy-digest", VERB_ANY, 1, 0, verb_policy_digest }, - { "sign", VERB_ANY, 1, 0, verb_sign }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return measure_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run);