]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/pcrextend/pcrextend.c
varlink,json: introduce new varlink_dispatch() helper
[thirdparty/systemd.git] / src / pcrextend / pcrextend.c
CommitLineData
708d7524
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
4
5#include <sd-messages.h>
6
d6b4d1c7 7#include "build.h"
6c51b49c 8#include "efi-loader.h"
17984c55 9#include "escape.h"
708d7524
LP
10#include "main-func.h"
11#include "openssl-util.h"
17984c55 12#include "parse-argument.h"
b0d00ec6 13#include "parse-util.h"
4cdef9f0 14#include "pcrextend-util.h"
708d7524 15#include "pretty-print.h"
4cdef9f0 16#include "strv.h"
e0e1f4f7 17#include "tpm2-pcr.h"
708d7524 18#include "tpm2-util.h"
4e16d5c6
LP
19#include "varlink.h"
20#include "varlink-io.systemd.PCRExtend.h"
708d7524 21
0318d545 22static bool arg_graceful = false;
708d7524
LP
23static char *arg_tpm2_device = NULL;
24static char **arg_banks = NULL;
17984c55
LP
25static char *arg_file_system = NULL;
26static bool arg_machine_id = false;
b0d00ec6 27static unsigned arg_pcr_index = UINT_MAX;
4e16d5c6 28static bool arg_varlink = false;
708d7524
LP
29
30STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
31STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
17984c55 32STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
708d7524 33
4e16d5c6
LP
34#define EXTENSION_STRING_SAFE_LIMIT 1024
35
708d7524
LP
36static int help(int argc, char *argv[], void *userdata) {
37 _cleanup_free_ char *link = NULL;
38 int r;
39
32295fa0 40 r = terminal_urlify_man("systemd-pcrextend", "8", &link);
708d7524
LP
41 if (r < 0)
42 return log_oom();
43
17984c55
LP
44 printf("%1$s [OPTIONS...] WORD\n"
45 "%1$s [OPTIONS...] --file-system=PATH\n"
46 "%1$s [OPTIONS...] --machine-id\n"
32295fa0 47 "\n%5$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%6$s\n"
708d7524
LP
48 "\n%3$sOptions:%4$s\n"
49 " -h --help Show this help\n"
50 " --version Print version\n"
b0d00ec6
LP
51 " --bank=DIGEST Select TPM PCR bank (SHA1, SHA256)\n"
52 " --pcr=INDEX Select TPM PCR index (0…23)\n"
708d7524 53 " --tpm2-device=PATH Use specified TPM2 device\n"
0318d545 54 " --graceful Exit gracefully if no TPM2 device is found\n"
17984c55
LP
55 " --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
56 " --machine-id Measure machine ID into PCR 15\n"
708d7524
LP
57 "\nSee the %2$s for details.\n",
58 program_invocation_short_name,
59 link,
60 ansi_underline(),
61 ansi_normal(),
62 ansi_highlight(),
63 ansi_normal());
64
65 return 0;
66}
67
68static int parse_argv(int argc, char *argv[]) {
69 enum {
70 ARG_VERSION = 0x100,
71 ARG_BANK,
b0d00ec6 72 ARG_PCR,
708d7524 73 ARG_TPM2_DEVICE,
0318d545 74 ARG_GRACEFUL,
17984c55
LP
75 ARG_FILE_SYSTEM,
76 ARG_MACHINE_ID,
708d7524
LP
77 };
78
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 },
b0d00ec6 83 { "pcr", required_argument, NULL, ARG_PCR },
708d7524 84 { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
0318d545 85 { "graceful", no_argument, NULL, ARG_GRACEFUL },
17984c55
LP
86 { "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
87 { "machine-id", no_argument, NULL, ARG_MACHINE_ID },
708d7524
LP
88 {}
89 };
90
17984c55 91 int c, r;
708d7524
LP
92
93 assert(argc >= 0);
94 assert(argv);
95
96 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
97 switch (c) {
98
99 case 'h':
100 help(0, NULL, NULL);
101 return 0;
102
103 case ARG_VERSION:
104 return version();
105
106 case ARG_BANK: {
107 const EVP_MD *implementation;
108
109 implementation = EVP_get_digestbyname(optarg);
110 if (!implementation)
111 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
112
113 if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0)
114 return log_oom();
115
116 break;
117 }
118
b0d00ec6
LP
119 case ARG_PCR:
120 r = tpm2_pcr_index_from_string(optarg);
121 if (r < 0)
122 return log_error_errno(r, "Failed to parse PCR index: %s", optarg);
123
124 arg_pcr_index = r;
125 break;
126
708d7524
LP
127 case ARG_TPM2_DEVICE: {
128 _cleanup_free_ char *device = NULL;
129
130 if (streq(optarg, "list"))
131 return tpm2_list_devices();
132
133 if (!streq(optarg, "auto")) {
134 device = strdup(optarg);
135 if (!device)
136 return log_oom();
137 }
138
139 free_and_replace(arg_tpm2_device, device);
140 break;
141 }
142
0318d545
LP
143 case ARG_GRACEFUL:
144 arg_graceful = true;
145 break;
146
17984c55
LP
147 case ARG_FILE_SYSTEM:
148 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system);
149 if (r < 0)
150 return r;
151
152 break;
153
154 case ARG_MACHINE_ID:
155 arg_machine_id = true;
156 break;
157
708d7524
LP
158 case '?':
159 return -EINVAL;
160
161 default:
162 assert_not_reached();
163 }
164
17984c55
LP
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.");
167
4e16d5c6
LP
168 r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
169 if (r < 0)
170 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
171 if (r > 0)
172 arg_varlink = true;
173 else if (arg_pcr_index == UINT_MAX)
b0d00ec6
LP
174 arg_pcr_index = (arg_file_system || arg_machine_id) ?
175 TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
176 TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
177
708d7524
LP
178 return 1;
179}
180
bd860983 181static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) {
e4481cc5
LP
182 _cleanup_strv_free_ char **l = NULL;
183 int r;
708d7524
LP
184
185 assert(c);
186
187 if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
188 return 0;
189
23e9ccc2 190 r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l);
e4481cc5 191 if (r < 0)
f9a0ee75 192 return log_error_errno(r, "Could not verify pcr banks: %m");
708d7524 193
e4481cc5 194 strv_free_and_replace(arg_banks, l);
708d7524
LP
195 return 0;
196}
197
4e16d5c6
LP
198static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2UserspaceEventType event) {
199 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
200 int r;
201
202 r = tpm2_context_new(arg_tpm2_device, &c);
203 if (r < 0)
204 return r;
205
206 r = determine_banks(c, pcr);
207 if (r < 0)
208 return r;
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.");
211
212 _cleanup_free_ char *joined_banks = NULL;
213 joined_banks = strv_join(arg_banks, ", ");
214 if (!joined_banks)
215 return log_oom();
216
217 _cleanup_free_ char *safe = NULL;
218 if (size > EXTENSION_STRING_SAFE_LIMIT) {
219 safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
220 if (!safe)
221 return log_oom();
222
223 if (!strextend(&safe, "..."))
224 return log_oom();
225 } else {
226 safe = cescape_length(data, size);
227 if (!safe)
228 return log_oom();
229 }
230
231 log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks);
232
233 r = tpm2_extend_bytes(c, arg_banks, pcr, data, size, /* secret= */ NULL, /* secret_size= */ 0, event, safe);
234 if (r < 0)
235 return log_error_errno(r, "Could not extend PCR: %m");
236
237 log_struct(LOG_INFO,
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,
241 "PCR=%u", pcr,
242 "BANKS=%s", joined_banks);
243
244 return 0;
245}
246
247typedef struct MethodExtendParameters {
248 unsigned pcr;
249 const char *text;
250 void *data;
251 size_t data_size;
252} MethodExtendParameters;
253
254static int json_dispatch_binary_data(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
255 MethodExtendParameters *p = ASSERT_PTR(userdata);
256 _cleanup_free_ void *d = NULL;
257 size_t l;
258 int r;
259
260 r = json_variant_unbase64(variant, &d, &l);
261 if (r < 0)
262 return json_log(variant, flags, r, "JSON variant is not a base64 string.");
263
264 free_and_replace(p->data, d);
265 p->data_size = l;
266
267 return 0;
268}
269
270static int vl_method_extend(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
271
272 static const JsonDispatch dispatch_table[] = {
273 { "pcr", JSON_VARIANT_UNSIGNED, json_dispatch_uint, offsetof(MethodExtendParameters, pcr), JSON_MANDATORY },
274 { "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 },
275 { "data", JSON_VARIANT_STRING, json_dispatch_binary_data, 0, 0 },
276 {}
277 };
278 MethodExtendParameters p = {
279 .pcr = UINT_MAX,
280 };
281 int r;
282
283 assert(link);
284
f1b622a0
LP
285 r = varlink_dispatch(link, parameters, dispatch_table, &p);
286 if (r != 0)
4e16d5c6
LP
287 return r;
288
289 if (!TPM2_PCR_INDEX_VALID(p.pcr))
290 return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "pcr")));
291
292 if (p.text) {
293 /* Specifying both the text string and the binary data is not allowed */
294 if (p.data)
295 return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "data")));
296
297 r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID);
298 } else if (p.data)
299 r = extend_now(p.pcr, p.data, p.data_size, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
300 else
301 return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "text")));
302 if (r < 0)
303 return r;
304
305 return varlink_reply(link, NULL);
306}
307
708d7524 308static int run(int argc, char *argv[]) {
4e16d5c6 309 _cleanup_free_ char *word = NULL;
cb19bdae 310 Tpm2UserspaceEventType event;
708d7524
LP
311 int r;
312
313 log_setup();
314
315 r = parse_argv(argc, argv);
316 if (r <= 0)
317 return r;
318
4e16d5c6
LP
319 if (arg_varlink) {
320 _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
321
322 /* Invocation as Varlink service */
323
324 r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY);
325 if (r < 0)
326 return log_error_errno(r, "Failed to allocate Varlink server: %m");
327
328 r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRExtend);
329 if (r < 0)
330 return log_error_errno(r, "Failed to add Varlink interface: %m");
331
332 r = varlink_server_bind_method(varlink_server, "io.systemd.PCRExtend.Extend", vl_method_extend);
333 if (r < 0)
334 return log_error_errno(r, "Failed to bind Varlink method: %m");
335
336 r = varlink_server_loop_auto(varlink_server);
337 if (r < 0)
338 return log_error_errno(r, "Failed to run Varlink event loop: %m");
339
340 return EXIT_SUCCESS;
341 }
342
17984c55 343 if (arg_file_system) {
17984c55
LP
344 if (optind != argc)
345 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
708d7524 346
4cdef9f0 347 r = pcrextend_file_system_word(arg_file_system, &word, NULL);
17984c55 348 if (r < 0)
4cdef9f0 349 return r;
17984c55 350
cb19bdae 351 event = TPM2_EVENT_FILESYSTEM;
17984c55
LP
352
353 } else if (arg_machine_id) {
17984c55
LP
354
355 if (optind != argc)
356 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
357
4cdef9f0 358 r = pcrextend_machine_id_word(&word);
17984c55 359 if (r < 0)
4cdef9f0 360 return r;
17984c55 361
cb19bdae 362 event = TPM2_EVENT_MACHINE_ID;
17984c55
LP
363
364 } else {
365 if (optind+1 != argc)
366 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
367
368 word = strdup(argv[optind]);
369 if (!word)
370 return log_oom();
371
372 /* Refuse to measure an empty word. We want to be able to write the series of measured words
373 * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
374 * disallow an empty word to avoid ambiguities. */
375 if (isempty(word))
376 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
377
cb19bdae 378 event = TPM2_EVENT_PHASE;
17984c55 379 }
708d7524 380
0318d545
LP
381 if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
382 log_notice("No complete TPM2 support detected, exiting gracefully.");
383 return EXIT_SUCCESS;
384 }
385
708d7524 386 /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
be8f478c 387 r = efi_measured_uki(LOG_ERR);
6c51b49c 388 if (r < 0)
2f6c52b9 389 return r;
6c51b49c 390 if (r == 0) {
2099cd62 391 log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT);
6c51b49c 392 return EXIT_SUCCESS;
6337be0a 393 }
708d7524 394
4e16d5c6 395 r = extend_now(arg_pcr_index, word, strlen(word), event);
708d7524 396 if (r < 0)
f9a0ee75 397 return log_error_errno(r, "Failed to create TPM2 context: %m");
708d7524 398
708d7524
LP
399 return EXIT_SUCCESS;
400}
401
402DEFINE_MAIN_FUNCTION(run);