/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <getopt.h>
#include <unistd.h>
#include "sd-json.h"
#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"
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",
+ 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,
- link,
- ansi_underline(),
- ansi_normal(),
- ansi_highlight(),
- ansi_normal(),
- glyph(GLYPH_ARROW_RIGHT));
+ 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;
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;
+ OPTION('c', "current", NULL,
+ "Use current PCR values"):
+ arg_current = true;
+ break;
+
+ OPTION_LONG("phase", "PHASE",
+ "Specify a boot phase to sign for"): {
+ char *n;
- r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section);
+ n = normalize_phase(arg);
+ if (!n)
+ return log_oom();
+
+ r = strv_consume(&arg_phase, TAKE_PTR(n));
if (r < 0)
return r;
- break;
- }
- case 'c':
- arg_current = true;
break;
+ }
- case ARG_BANK: {
+ OPTION_LONG("bank", "DIGEST",
+ "Select TPM bank (SHA1, SHA256, SHA384, SHA512)"): {
const EVP_MD *implementation;
- implementation = EVP_get_digestbyname(optarg);
+ implementation = EVP_get_digestbyname(arg);
if (!implementation)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
+ 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;
}
- case ARG_PRIVATE_KEY:
- r = free_and_strdup_warn(&arg_private_key, optarg);
+ 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 ARG_PRIVATE_KEY_SOURCE:
+ OPTION_COMMON_PRIVATE_KEY_SOURCE:
r = parse_openssl_key_source_argument(
- optarg,
+ arg,
&arg_private_key_source,
&arg_private_key_source_type);
if (r < 0)
break;
- case ARG_PUBLIC_KEY:
- r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key);
+ 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;
- case ARG_CERTIFICATE:
- r = free_and_strdup_warn(&arg_certificate, optarg);
+ OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"):
+ r = free_and_strdup_warn(&arg_certificate, arg);
if (r < 0)
return r;
break;
- case ARG_CERTIFICATE_SOURCE:
+ OPTION_COMMON_CERTIFICATE_SOURCE:
r = parse_openssl_certificate_source_argument(
- optarg,
+ arg,
&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);
+ OPTION_COMMON_JSON:
+ r = parse_json_argument(arg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
- case ARG_PHASE: {
- char *n;
-
- n = normalize_phase(optarg);
- if (!n)
- return log_oom();
+ OPTION_COMMON_LOWERCASE_J:
+ arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
+ break;
- r = strv_consume(&arg_phase, TAKE_PTR(n));
+ 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;
- }
- case ARG_APPEND:
- r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append);
+ 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;
-
- case '?':
- return -EINVAL;
-
- default:
- assert_not_reached();
}
if (arg_public_key && arg_certificate)
return log_oom();
log_debug("Measuring boot phases: %s", j);
+
+ *ret_args = option_parser_get_args(&state);
return 1;
}
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;
}
}
+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;
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);