1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include <sd-messages.h>
8 #include "efi-loader.h"
10 #include "main-func.h"
11 #include "openssl-util.h"
12 #include "parse-argument.h"
13 #include "parse-util.h"
14 #include "pcrextend-util.h"
15 #include "pretty-print.h"
18 #include "tpm2-util.h"
20 #include "varlink-io.systemd.PCRExtend.h"
22 static bool arg_graceful
= false;
23 static char *arg_tpm2_device
= NULL
;
24 static char **arg_banks
= NULL
;
25 static char *arg_file_system
= NULL
;
26 static bool arg_machine_id
= false;
27 static unsigned arg_pcr_index
= UINT_MAX
;
28 static bool arg_varlink
= false;
30 STATIC_DESTRUCTOR_REGISTER(arg_banks
, strv_freep
);
31 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device
, freep
);
32 STATIC_DESTRUCTOR_REGISTER(arg_file_system
, freep
);
34 #define EXTENSION_STRING_SAFE_LIMIT 1024
36 static int help(int argc
, char *argv
[], void *userdata
) {
37 _cleanup_free_
char *link
= NULL
;
40 r
= terminal_urlify_man("systemd-pcrextend", "8", &link
);
44 printf("%1$s [OPTIONS...] WORD\n"
45 "%1$s [OPTIONS...] --file-system=PATH\n"
46 "%1$s [OPTIONS...] --machine-id\n"
47 "\n%5$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%6$s\n"
48 "\n%3$sOptions:%4$s\n"
49 " -h --help Show this help\n"
50 " --version Print version\n"
51 " --bank=DIGEST Select TPM PCR bank (SHA1, SHA256)\n"
52 " --pcr=INDEX Select TPM PCR index (0…23)\n"
53 " --tpm2-device=PATH Use specified TPM2 device\n"
54 " --graceful Exit gracefully if no TPM2 device is found\n"
55 " --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
56 " --machine-id Measure machine ID into PCR 15\n"
57 "\nSee the %2$s for details.\n",
58 program_invocation_short_name
,
68 static int parse_argv(int argc
, char *argv
[]) {
79 static const struct option options
[] = {
80 { "help", no_argument
, NULL
, 'h' },
81 { "version", no_argument
, NULL
, ARG_VERSION
},
82 { "bank", required_argument
, NULL
, ARG_BANK
},
83 { "pcr", required_argument
, NULL
, ARG_PCR
},
84 { "tpm2-device", required_argument
, NULL
, ARG_TPM2_DEVICE
},
85 { "graceful", no_argument
, NULL
, ARG_GRACEFUL
},
86 { "file-system", required_argument
, NULL
, ARG_FILE_SYSTEM
},
87 { "machine-id", no_argument
, NULL
, ARG_MACHINE_ID
},
96 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
107 const EVP_MD
*implementation
;
109 implementation
= EVP_get_digestbyname(optarg
);
111 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Unknown bank '%s', refusing.", optarg
);
113 if (strv_extend(&arg_banks
, EVP_MD_name(implementation
)) < 0)
120 r
= tpm2_pcr_index_from_string(optarg
);
122 return log_error_errno(r
, "Failed to parse PCR index: %s", optarg
);
127 case ARG_TPM2_DEVICE
: {
128 _cleanup_free_
char *device
= NULL
;
130 if (streq(optarg
, "list"))
131 return tpm2_list_devices();
133 if (!streq(optarg
, "auto")) {
134 device
= strdup(optarg
);
139 free_and_replace(arg_tpm2_device
, device
);
147 case ARG_FILE_SYSTEM
:
148 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_file_system
);
155 arg_machine_id
= true;
162 assert_not_reached();
165 if (arg_file_system
&& arg_machine_id
)
166 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "--file-system= and --machine-id may not be combined.");
168 r
= varlink_invocation(VARLINK_ALLOW_ACCEPT
);
170 return log_error_errno(r
, "Failed to check if invoked in Varlink mode: %m");
173 else if (arg_pcr_index
== UINT_MAX
)
174 arg_pcr_index
= (arg_file_system
|| arg_machine_id
) ?
175 TPM2_PCR_SYSTEM_IDENTITY
: /* → PCR 15 */
176 TPM2_PCR_KERNEL_BOOT
; /* → PCR 11 */
181 static int determine_banks(Tpm2Context
*c
, unsigned target_pcr_nr
) {
182 _cleanup_strv_free_
char **l
= NULL
;
187 if (!strv_isempty(arg_banks
)) /* Explicitly configured? Then use that */
190 r
= tpm2_get_good_pcr_banks_strv(c
, UINT32_C(1) << target_pcr_nr
, &l
);
192 return log_error_errno(r
, "Could not verify pcr banks: %m");
194 strv_free_and_replace(arg_banks
, l
);
198 static int extend_now(unsigned pcr
, const void *data
, size_t size
, Tpm2UserspaceEventType event
) {
199 _cleanup_(tpm2_context_unrefp
) Tpm2Context
*c
= NULL
;
202 r
= tpm2_context_new(arg_tpm2_device
, &c
);
206 r
= determine_banks(c
, pcr
);
209 if (strv_isempty(arg_banks
)) /* Still none? */
210 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Found a TPM2 without enabled PCR banks. Can't operate.");
212 _cleanup_free_
char *joined_banks
= NULL
;
213 joined_banks
= strv_join(arg_banks
, ", ");
217 _cleanup_free_
char *safe
= NULL
;
218 if (size
> EXTENSION_STRING_SAFE_LIMIT
) {
219 safe
= cescape_length(data
, EXTENSION_STRING_SAFE_LIMIT
);
223 if (!strextend(&safe
, "..."))
226 safe
= cescape_length(data
, size
);
231 log_debug("Measuring '%s' into PCR index %u, banks %s.", safe
, pcr
, joined_banks
);
233 r
= tpm2_extend_bytes(c
, arg_banks
, pcr
, data
, size
, /* secret= */ NULL
, /* secret_size= */ 0, event
, safe
);
235 return log_error_errno(r
, "Could not extend PCR: %m");
238 "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR
,
239 LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", pcr
, safe
, joined_banks
),
240 "MEASURING=%s", safe
,
242 "BANKS=%s", joined_banks
);
247 typedef struct MethodExtendParameters
{
251 } MethodExtendParameters
;
253 static void method_extend_parameters_done(MethodExtendParameters
*p
) {
256 iovec_done(&p
->data
);
259 static int vl_method_extend(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
261 static const JsonDispatch dispatch_table
[] = {
262 { "pcr", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_uint
, offsetof(MethodExtendParameters
, pcr
), JSON_MANDATORY
},
263 { "text", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(MethodExtendParameters
, text
), 0 },
264 { "data", JSON_VARIANT_STRING
, json_dispatch_unbase64_iovec
, offsetof(MethodExtendParameters
, data
), 0 },
267 _cleanup_(method_extend_parameters_done
) MethodExtendParameters p
= {
274 r
= varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
278 if (!TPM2_PCR_INDEX_VALID(p
.pcr
))
279 return varlink_error_invalid_parameter_name(link
, "pcr");
282 /* Specifying both the text string and the binary data is not allowed */
284 return varlink_error_invalid_parameter_name(link
, "data");
286 r
= extend_now(p
.pcr
, p
.text
, strlen(p
.text
), _TPM2_USERSPACE_EVENT_TYPE_INVALID
);
287 } else if (p
.data
.iov_base
)
288 r
= extend_now(p
.pcr
, p
.data
.iov_base
, p
.data
.iov_len
, _TPM2_USERSPACE_EVENT_TYPE_INVALID
);
290 return varlink_error_invalid_parameter_name(link
, "text");
294 return varlink_reply(link
, NULL
);
297 static int run(int argc
, char *argv
[]) {
298 _cleanup_free_
char *word
= NULL
;
299 Tpm2UserspaceEventType event
;
304 r
= parse_argv(argc
, argv
);
309 _cleanup_(varlink_server_unrefp
) VarlinkServer
*varlink_server
= NULL
;
311 /* Invocation as Varlink service */
313 r
= varlink_server_new(&varlink_server
, VARLINK_SERVER_ROOT_ONLY
);
315 return log_error_errno(r
, "Failed to allocate Varlink server: %m");
317 r
= varlink_server_add_interface(varlink_server
, &vl_interface_io_systemd_PCRExtend
);
319 return log_error_errno(r
, "Failed to add Varlink interface: %m");
321 r
= varlink_server_bind_method(varlink_server
, "io.systemd.PCRExtend.Extend", vl_method_extend
);
323 return log_error_errno(r
, "Failed to bind Varlink method: %m");
325 r
= varlink_server_loop_auto(varlink_server
);
327 return log_error_errno(r
, "Failed to run Varlink event loop: %m");
332 if (arg_file_system
) {
334 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Expected no argument.");
336 r
= pcrextend_file_system_word(arg_file_system
, &word
, NULL
);
340 event
= TPM2_EVENT_FILESYSTEM
;
342 } else if (arg_machine_id
) {
345 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Expected no argument.");
347 r
= pcrextend_machine_id_word(&word
);
351 event
= TPM2_EVENT_MACHINE_ID
;
354 if (optind
+1 != argc
)
355 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Expected a single argument.");
357 word
= strdup(argv
[optind
]);
361 /* Refuse to measure an empty word. We want to be able to write the series of measured words
362 * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
363 * disallow an empty word to avoid ambiguities. */
365 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "String to measure cannot be empty, refusing.");
367 event
= TPM2_EVENT_PHASE
;
370 if (arg_graceful
&& tpm2_support() != TPM2_SUPPORT_FULL
) {
371 log_notice("No complete TPM2 support detected, exiting gracefully.");
375 /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
376 r
= efi_measured_uki(LOG_ERR
);
380 log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT
);
384 r
= extend_now(arg_pcr_index
, word
, strlen(word
), event
);
386 return log_error_errno(r
, "Failed to create TPM2 context: %m");
391 DEFINE_MAIN_FUNCTION(run
);