1ced56a00Sopenharmony_ci// SPDX-License-Identifier: MIT
2ced56a00Sopenharmony_ci/*
3ced56a00Sopenharmony_ci * fs-verity userspace tool
4ced56a00Sopenharmony_ci *
5ced56a00Sopenharmony_ci * Copyright 2018 Google LLC
6ced56a00Sopenharmony_ci *
7ced56a00Sopenharmony_ci * Use of this source code is governed by an MIT-style
8ced56a00Sopenharmony_ci * license that can be found in the LICENSE file or at
9ced56a00Sopenharmony_ci * https://opensource.org/licenses/MIT.
10ced56a00Sopenharmony_ci */
11ced56a00Sopenharmony_ci
12ced56a00Sopenharmony_ci#include "fsverity.h"
13ced56a00Sopenharmony_ci
14ced56a00Sopenharmony_ci#include <fcntl.h>
15ced56a00Sopenharmony_ci#include <limits.h>
16ced56a00Sopenharmony_ci
17ced56a00Sopenharmony_cistatic const struct fsverity_command {
18ced56a00Sopenharmony_ci	const char *name;
19ced56a00Sopenharmony_ci	int (*func)(const struct fsverity_command *cmd, int argc, char *argv[]);
20ced56a00Sopenharmony_ci	const char *short_desc;
21ced56a00Sopenharmony_ci	const char *usage_str;
22ced56a00Sopenharmony_ci} fsverity_commands[] = {
23ced56a00Sopenharmony_ci	{
24ced56a00Sopenharmony_ci		.name = "digest",
25ced56a00Sopenharmony_ci		.func = fsverity_cmd_digest,
26ced56a00Sopenharmony_ci		.short_desc =
27ced56a00Sopenharmony_ci"Compute the fs-verity digest of the given file(s), for offline signing",
28ced56a00Sopenharmony_ci		.usage_str =
29ced56a00Sopenharmony_ci"    fsverity digest FILE...\n"
30ced56a00Sopenharmony_ci"               [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
31ced56a00Sopenharmony_ci"               [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
32ced56a00Sopenharmony_ci"               [--compact] [--for-builtin-sig]\n"
33ced56a00Sopenharmony_ci#ifndef _WIN32
34ced56a00Sopenharmony_ci	}, {
35ced56a00Sopenharmony_ci		.name = "dump_metadata",
36ced56a00Sopenharmony_ci		.func = fsverity_cmd_dump_metadata,
37ced56a00Sopenharmony_ci		.short_desc = "Dump the fs-verity metadata of the given file",
38ced56a00Sopenharmony_ci		.usage_str =
39ced56a00Sopenharmony_ci"    fsverity dump_metadata TYPE FILE [--offset=OFFSET] [--length=LENGTH]\n"
40ced56a00Sopenharmony_ci	}, {
41ced56a00Sopenharmony_ci		.name = "enable",
42ced56a00Sopenharmony_ci		.func = fsverity_cmd_enable,
43ced56a00Sopenharmony_ci		.short_desc = "Enable fs-verity on a file",
44ced56a00Sopenharmony_ci		.usage_str =
45ced56a00Sopenharmony_ci"    fsverity enable FILE\n"
46ced56a00Sopenharmony_ci"               [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
47ced56a00Sopenharmony_ci"               [--signature=SIGFILE]\n"
48ced56a00Sopenharmony_ci	}, {
49ced56a00Sopenharmony_ci		.name = "measure",
50ced56a00Sopenharmony_ci		.func = fsverity_cmd_measure,
51ced56a00Sopenharmony_ci		.short_desc =
52ced56a00Sopenharmony_ci"Display the fs-verity digest of the given verity file(s)",
53ced56a00Sopenharmony_ci		.usage_str =
54ced56a00Sopenharmony_ci"    fsverity measure FILE...\n"
55ced56a00Sopenharmony_ci#endif /* !_WIN32 */
56ced56a00Sopenharmony_ci	}, {
57ced56a00Sopenharmony_ci		.name = "sign",
58ced56a00Sopenharmony_ci		.func = fsverity_cmd_sign,
59ced56a00Sopenharmony_ci		.short_desc = "Sign a file for fs-verity built-in signature verification",
60ced56a00Sopenharmony_ci		.usage_str =
61ced56a00Sopenharmony_ci"    fsverity sign FILE OUT_SIGFILE\n"
62ced56a00Sopenharmony_ci"               [--key=KEYFILE] [--cert=CERTFILE] [--pkcs11-engine=SOFILE]\n"
63ced56a00Sopenharmony_ci"               [--pkcs11-module=SOFILE] [--pkcs11-keyid=KEYID]\n"
64ced56a00Sopenharmony_ci"               [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
65ced56a00Sopenharmony_ci"               [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
66ced56a00Sopenharmony_ci	}
67ced56a00Sopenharmony_ci};
68ced56a00Sopenharmony_ci
69ced56a00Sopenharmony_cistatic void show_all_hash_algs(FILE *fp)
70ced56a00Sopenharmony_ci{
71ced56a00Sopenharmony_ci	u32 alg_num = 1;
72ced56a00Sopenharmony_ci	const char *name;
73ced56a00Sopenharmony_ci
74ced56a00Sopenharmony_ci	fprintf(fp, "Available hash algorithms:");
75ced56a00Sopenharmony_ci	while ((name = libfsverity_get_hash_name(alg_num++)) != NULL)
76ced56a00Sopenharmony_ci		fprintf(fp, " %s", name);
77ced56a00Sopenharmony_ci	putc('\n', fp);
78ced56a00Sopenharmony_ci}
79ced56a00Sopenharmony_ci
80ced56a00Sopenharmony_cistatic void usage_all(FILE *fp)
81ced56a00Sopenharmony_ci{
82ced56a00Sopenharmony_ci	int i;
83ced56a00Sopenharmony_ci
84ced56a00Sopenharmony_ci	fputs("Usage:\n", fp);
85ced56a00Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(fsverity_commands); i++)
86ced56a00Sopenharmony_ci		fprintf(fp, "  %s:\n%s\n", fsverity_commands[i].short_desc,
87ced56a00Sopenharmony_ci			fsverity_commands[i].usage_str);
88ced56a00Sopenharmony_ci	fputs(
89ced56a00Sopenharmony_ci"  Standard options:\n"
90ced56a00Sopenharmony_ci"    fsverity --help\n"
91ced56a00Sopenharmony_ci"    fsverity --version\n"
92ced56a00Sopenharmony_ci"\n", fp);
93ced56a00Sopenharmony_ci	show_all_hash_algs(fp);
94ced56a00Sopenharmony_ci}
95ced56a00Sopenharmony_ci
96ced56a00Sopenharmony_cistatic void usage_cmd(const struct fsverity_command *cmd, FILE *fp)
97ced56a00Sopenharmony_ci{
98ced56a00Sopenharmony_ci	fprintf(fp, "Usage:\n%s", cmd->usage_str);
99ced56a00Sopenharmony_ci}
100ced56a00Sopenharmony_ci
101ced56a00Sopenharmony_civoid usage(const struct fsverity_command *cmd, FILE *fp)
102ced56a00Sopenharmony_ci{
103ced56a00Sopenharmony_ci	if (cmd)
104ced56a00Sopenharmony_ci		usage_cmd(cmd, fp);
105ced56a00Sopenharmony_ci	else
106ced56a00Sopenharmony_ci		usage_all(fp);
107ced56a00Sopenharmony_ci}
108ced56a00Sopenharmony_ci
109ced56a00Sopenharmony_cistatic void show_version(void)
110ced56a00Sopenharmony_ci{
111ced56a00Sopenharmony_ci	printf("fsverity v%d.%d\n", FSVERITY_UTILS_MAJOR_VERSION,
112ced56a00Sopenharmony_ci	       FSVERITY_UTILS_MINOR_VERSION);
113ced56a00Sopenharmony_ci}
114ced56a00Sopenharmony_ci
115ced56a00Sopenharmony_cistatic void handle_common_options(int argc, char *argv[],
116ced56a00Sopenharmony_ci				  const struct fsverity_command *cmd)
117ced56a00Sopenharmony_ci{
118ced56a00Sopenharmony_ci	int i;
119ced56a00Sopenharmony_ci
120ced56a00Sopenharmony_ci	for (i = 1; i < argc; i++) {
121ced56a00Sopenharmony_ci		const char *arg = argv[i];
122ced56a00Sopenharmony_ci
123ced56a00Sopenharmony_ci		if (*arg++ != '-')
124ced56a00Sopenharmony_ci			continue;
125ced56a00Sopenharmony_ci		if (*arg++ != '-')
126ced56a00Sopenharmony_ci			continue;
127ced56a00Sopenharmony_ci		if (!strcmp(arg, "help")) {
128ced56a00Sopenharmony_ci			usage(cmd, stdout);
129ced56a00Sopenharmony_ci			exit(0);
130ced56a00Sopenharmony_ci		} else if (!strcmp(arg, "version")) {
131ced56a00Sopenharmony_ci			show_version();
132ced56a00Sopenharmony_ci			exit(0);
133ced56a00Sopenharmony_ci		} else if (!*arg) /* reached "--", no more options */
134ced56a00Sopenharmony_ci			return;
135ced56a00Sopenharmony_ci	}
136ced56a00Sopenharmony_ci}
137ced56a00Sopenharmony_ci
138ced56a00Sopenharmony_cistatic const struct fsverity_command *find_command(const char *name)
139ced56a00Sopenharmony_ci{
140ced56a00Sopenharmony_ci	int i;
141ced56a00Sopenharmony_ci
142ced56a00Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(fsverity_commands); i++)
143ced56a00Sopenharmony_ci		if (!strcmp(name, fsverity_commands[i].name))
144ced56a00Sopenharmony_ci			return &fsverity_commands[i];
145ced56a00Sopenharmony_ci	return NULL;
146ced56a00Sopenharmony_ci}
147ced56a00Sopenharmony_ci
148ced56a00Sopenharmony_cistatic bool parse_hash_alg_option(const char *arg, u32 *alg_ptr)
149ced56a00Sopenharmony_ci{
150ced56a00Sopenharmony_ci	char *end;
151ced56a00Sopenharmony_ci	unsigned long n = strtoul(arg, &end, 10);
152ced56a00Sopenharmony_ci
153ced56a00Sopenharmony_ci	if (*alg_ptr != 0) {
154ced56a00Sopenharmony_ci		error_msg("--hash-alg can only be specified once");
155ced56a00Sopenharmony_ci		return false;
156ced56a00Sopenharmony_ci	}
157ced56a00Sopenharmony_ci
158ced56a00Sopenharmony_ci	/* Specified by number? */
159ced56a00Sopenharmony_ci	if (n > 0 && n < INT32_MAX && *end == '\0') {
160ced56a00Sopenharmony_ci		*alg_ptr = n;
161ced56a00Sopenharmony_ci		return true;
162ced56a00Sopenharmony_ci	}
163ced56a00Sopenharmony_ci
164ced56a00Sopenharmony_ci	/* Specified by name? */
165ced56a00Sopenharmony_ci	*alg_ptr = libfsverity_find_hash_alg_by_name(arg);
166ced56a00Sopenharmony_ci	if (*alg_ptr)
167ced56a00Sopenharmony_ci		return true;
168ced56a00Sopenharmony_ci	error_msg("unknown hash algorithm: '%s'", arg);
169ced56a00Sopenharmony_ci	show_all_hash_algs(stderr);
170ced56a00Sopenharmony_ci	return false;
171ced56a00Sopenharmony_ci}
172ced56a00Sopenharmony_ci
173ced56a00Sopenharmony_cistatic bool parse_block_size_option(const char *arg, u32 *size_ptr)
174ced56a00Sopenharmony_ci{
175ced56a00Sopenharmony_ci	char *end;
176ced56a00Sopenharmony_ci	unsigned long n = strtoul(arg, &end, 10);
177ced56a00Sopenharmony_ci
178ced56a00Sopenharmony_ci	if (*size_ptr != 0) {
179ced56a00Sopenharmony_ci		error_msg("--block-size can only be specified once");
180ced56a00Sopenharmony_ci		return false;
181ced56a00Sopenharmony_ci	}
182ced56a00Sopenharmony_ci
183ced56a00Sopenharmony_ci	if (n <= 0 || n >= INT_MAX || !is_power_of_2(n) || *end != '\0') {
184ced56a00Sopenharmony_ci		error_msg("Invalid block size: %s.  Must be power of 2", arg);
185ced56a00Sopenharmony_ci		return false;
186ced56a00Sopenharmony_ci	}
187ced56a00Sopenharmony_ci	*size_ptr = n;
188ced56a00Sopenharmony_ci	return true;
189ced56a00Sopenharmony_ci}
190ced56a00Sopenharmony_ci
191ced56a00Sopenharmony_cistatic bool parse_salt_option(const char *arg, u8 **salt_ptr,
192ced56a00Sopenharmony_ci			      u32 *salt_size_ptr)
193ced56a00Sopenharmony_ci{
194ced56a00Sopenharmony_ci	if (*salt_ptr != NULL) {
195ced56a00Sopenharmony_ci		error_msg("--salt can only be specified once");
196ced56a00Sopenharmony_ci		return false;
197ced56a00Sopenharmony_ci	}
198ced56a00Sopenharmony_ci	*salt_size_ptr = strlen(arg) / 2;
199ced56a00Sopenharmony_ci	*salt_ptr = xmalloc(*salt_size_ptr);
200ced56a00Sopenharmony_ci	if (!hex2bin(arg, *salt_ptr, *salt_size_ptr)) {
201ced56a00Sopenharmony_ci		error_msg("salt is not a valid hex string");
202ced56a00Sopenharmony_ci		return false;
203ced56a00Sopenharmony_ci	}
204ced56a00Sopenharmony_ci	return true;
205ced56a00Sopenharmony_ci}
206ced56a00Sopenharmony_ci
207ced56a00Sopenharmony_cistruct metadata_callback_ctx {
208ced56a00Sopenharmony_ci	struct filedes merkle_tree_file;
209ced56a00Sopenharmony_ci	struct filedes descriptor_file;
210ced56a00Sopenharmony_ci	struct libfsverity_metadata_callbacks callbacks;
211ced56a00Sopenharmony_ci};
212ced56a00Sopenharmony_ci
213ced56a00Sopenharmony_cistatic int handle_merkle_tree_size(void *_ctx, u64 size)
214ced56a00Sopenharmony_ci{
215ced56a00Sopenharmony_ci	struct metadata_callback_ctx *ctx = _ctx;
216ced56a00Sopenharmony_ci
217ced56a00Sopenharmony_ci	if (!preallocate_file(&ctx->merkle_tree_file, size))
218ced56a00Sopenharmony_ci		return -EIO;
219ced56a00Sopenharmony_ci	return 0;
220ced56a00Sopenharmony_ci}
221ced56a00Sopenharmony_ci
222ced56a00Sopenharmony_cistatic int handle_merkle_tree_block(void *_ctx, const void *block, size_t size,
223ced56a00Sopenharmony_ci				    u64 offset)
224ced56a00Sopenharmony_ci{
225ced56a00Sopenharmony_ci	struct metadata_callback_ctx *ctx = _ctx;
226ced56a00Sopenharmony_ci
227ced56a00Sopenharmony_ci	if (!full_pwrite(&ctx->merkle_tree_file, block, size, offset))
228ced56a00Sopenharmony_ci		return -EIO;
229ced56a00Sopenharmony_ci	return 0;
230ced56a00Sopenharmony_ci}
231ced56a00Sopenharmony_ci
232ced56a00Sopenharmony_cistatic int handle_descriptor(void *_ctx, const void *descriptor, size_t size)
233ced56a00Sopenharmony_ci{
234ced56a00Sopenharmony_ci	struct metadata_callback_ctx *ctx = _ctx;
235ced56a00Sopenharmony_ci
236ced56a00Sopenharmony_ci	if (!full_write(&ctx->descriptor_file, descriptor, size))
237ced56a00Sopenharmony_ci		return -EIO;
238ced56a00Sopenharmony_ci	return 0;
239ced56a00Sopenharmony_ci}
240ced56a00Sopenharmony_ci
241ced56a00Sopenharmony_cistatic bool parse_out_metadata_option(int opt_char, const char *arg,
242ced56a00Sopenharmony_ci				      const struct libfsverity_metadata_callbacks **cbs)
243ced56a00Sopenharmony_ci{
244ced56a00Sopenharmony_ci	struct metadata_callback_ctx *ctx;
245ced56a00Sopenharmony_ci	struct filedes *file;
246ced56a00Sopenharmony_ci	const char *opt_name;
247ced56a00Sopenharmony_ci
248ced56a00Sopenharmony_ci	if (*cbs) {
249ced56a00Sopenharmony_ci		ctx = (*cbs)->ctx;
250ced56a00Sopenharmony_ci	} else {
251ced56a00Sopenharmony_ci		ctx = xzalloc(sizeof(*ctx));
252ced56a00Sopenharmony_ci		ctx->merkle_tree_file.fd = -1;
253ced56a00Sopenharmony_ci		ctx->descriptor_file.fd = -1;
254ced56a00Sopenharmony_ci		ctx->callbacks.ctx = ctx;
255ced56a00Sopenharmony_ci		*cbs = &ctx->callbacks;
256ced56a00Sopenharmony_ci	}
257ced56a00Sopenharmony_ci
258ced56a00Sopenharmony_ci	if (opt_char == OPT_OUT_MERKLE_TREE) {
259ced56a00Sopenharmony_ci		file = &ctx->merkle_tree_file;
260ced56a00Sopenharmony_ci		opt_name = "--out-merkle-tree";
261ced56a00Sopenharmony_ci		ctx->callbacks.merkle_tree_size = handle_merkle_tree_size;
262ced56a00Sopenharmony_ci		ctx->callbacks.merkle_tree_block = handle_merkle_tree_block;
263ced56a00Sopenharmony_ci	} else {
264ced56a00Sopenharmony_ci		file = &ctx->descriptor_file;
265ced56a00Sopenharmony_ci		opt_name = "--out-descriptor";
266ced56a00Sopenharmony_ci		ctx->callbacks.descriptor = handle_descriptor;
267ced56a00Sopenharmony_ci	}
268ced56a00Sopenharmony_ci	if (file->fd >= 0) {
269ced56a00Sopenharmony_ci		error_msg("%s can only be specified once", opt_name);
270ced56a00Sopenharmony_ci		return false;
271ced56a00Sopenharmony_ci	}
272ced56a00Sopenharmony_ci	return open_file(file, arg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
273ced56a00Sopenharmony_ci}
274ced56a00Sopenharmony_ci
275ced56a00Sopenharmony_cibool parse_tree_param(int opt_char, const char *arg,
276ced56a00Sopenharmony_ci		      struct libfsverity_merkle_tree_params *params)
277ced56a00Sopenharmony_ci{
278ced56a00Sopenharmony_ci	switch (opt_char) {
279ced56a00Sopenharmony_ci	case OPT_HASH_ALG:
280ced56a00Sopenharmony_ci		return parse_hash_alg_option(arg, &params->hash_algorithm);
281ced56a00Sopenharmony_ci	case OPT_BLOCK_SIZE:
282ced56a00Sopenharmony_ci		return parse_block_size_option(arg, &params->block_size);
283ced56a00Sopenharmony_ci	case OPT_SALT:
284ced56a00Sopenharmony_ci		return parse_salt_option(arg, (u8 **)&params->salt,
285ced56a00Sopenharmony_ci					 &params->salt_size);
286ced56a00Sopenharmony_ci	case OPT_OUT_MERKLE_TREE:
287ced56a00Sopenharmony_ci	case OPT_OUT_DESCRIPTOR:
288ced56a00Sopenharmony_ci		return parse_out_metadata_option(opt_char, arg,
289ced56a00Sopenharmony_ci						 &params->metadata_callbacks);
290ced56a00Sopenharmony_ci	default:
291ced56a00Sopenharmony_ci		ASSERT(0);
292ced56a00Sopenharmony_ci	}
293ced56a00Sopenharmony_ci}
294ced56a00Sopenharmony_ci
295ced56a00Sopenharmony_cibool destroy_tree_params(struct libfsverity_merkle_tree_params *params)
296ced56a00Sopenharmony_ci{
297ced56a00Sopenharmony_ci	bool ok = true;
298ced56a00Sopenharmony_ci
299ced56a00Sopenharmony_ci	free((u8 *)params->salt);
300ced56a00Sopenharmony_ci	if (params->metadata_callbacks) {
301ced56a00Sopenharmony_ci		struct metadata_callback_ctx *ctx =
302ced56a00Sopenharmony_ci			params->metadata_callbacks->ctx;
303ced56a00Sopenharmony_ci
304ced56a00Sopenharmony_ci		ok &= filedes_close(&ctx->merkle_tree_file);
305ced56a00Sopenharmony_ci		ok &= filedes_close(&ctx->descriptor_file);
306ced56a00Sopenharmony_ci		free(ctx);
307ced56a00Sopenharmony_ci	}
308ced56a00Sopenharmony_ci	memset(params, 0, sizeof(*params));
309ced56a00Sopenharmony_ci	return ok;
310ced56a00Sopenharmony_ci}
311ced56a00Sopenharmony_ci
312ced56a00Sopenharmony_ciint main(int argc, char *argv[])
313ced56a00Sopenharmony_ci{
314ced56a00Sopenharmony_ci	const struct fsverity_command *cmd;
315ced56a00Sopenharmony_ci
316ced56a00Sopenharmony_ci	install_libfsverity_error_handler();
317ced56a00Sopenharmony_ci
318ced56a00Sopenharmony_ci	if (argc < 2) {
319ced56a00Sopenharmony_ci		error_msg("no command specified");
320ced56a00Sopenharmony_ci		usage_all(stderr);
321ced56a00Sopenharmony_ci		return 2;
322ced56a00Sopenharmony_ci	}
323ced56a00Sopenharmony_ci
324ced56a00Sopenharmony_ci	cmd = find_command(argv[1]);
325ced56a00Sopenharmony_ci
326ced56a00Sopenharmony_ci	handle_common_options(argc, argv, cmd);
327ced56a00Sopenharmony_ci
328ced56a00Sopenharmony_ci	if (!cmd) {
329ced56a00Sopenharmony_ci		error_msg("unrecognized command: '%s'", argv[1]);
330ced56a00Sopenharmony_ci		usage_all(stderr);
331ced56a00Sopenharmony_ci		return 2;
332ced56a00Sopenharmony_ci	}
333ced56a00Sopenharmony_ci	return cmd->func(cmd, argc - 1, argv + 1);
334ced56a00Sopenharmony_ci}
335