Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * The 'fsverity setup' command |
| 4 | * |
Eric Biggers | 8387ad3 | 2018-08-21 12:37:56 -0700 | [diff] [blame] | 5 | * Copyright (C) 2018 Google LLC |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 6 | * |
Eric Biggers | 8387ad3 | 2018-08-21 12:37:56 -0700 | [diff] [blame] | 7 | * Written by Eric Biggers. |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 8 | */ |
| 9 | |
| 10 | #include <fcntl.h> |
| 11 | #include <getopt.h> |
| 12 | #include <stdlib.h> |
| 13 | #include <string.h> |
| 14 | #include <unistd.h> |
| 15 | |
| 16 | #include "commands.h" |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 17 | #include "fsverity_uapi.h" |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 18 | #include "fsveritysetup.h" |
| 19 | #include "hash_algs.h" |
| 20 | |
| 21 | enum { |
| 22 | OPT_HASH, |
| 23 | OPT_SALT, |
| 24 | OPT_BLOCKSIZE, |
| 25 | OPT_SIGNING_KEY, |
| 26 | OPT_SIGNING_CERT, |
| 27 | OPT_SIGNATURE, |
| 28 | OPT_ELIDE, |
| 29 | OPT_PATCH, |
| 30 | }; |
| 31 | |
| 32 | static const struct option longopts[] = { |
| 33 | {"hash", required_argument, NULL, OPT_HASH}, |
| 34 | {"salt", required_argument, NULL, OPT_SALT}, |
| 35 | {"blocksize", required_argument, NULL, OPT_BLOCKSIZE}, |
| 36 | {"signing-key", required_argument, NULL, OPT_SIGNING_KEY}, |
| 37 | {"signing-cert", required_argument, NULL, OPT_SIGNING_CERT}, |
| 38 | {"signature", required_argument, NULL, OPT_SIGNATURE}, |
| 39 | {"elide", required_argument, NULL, OPT_ELIDE}, |
| 40 | {"patch", required_argument, NULL, OPT_PATCH}, |
| 41 | {NULL, 0, NULL, 0} |
| 42 | }; |
| 43 | |
| 44 | /* Parse the --blocksize=BLOCKSIZE option */ |
| 45 | static bool parse_blocksize_option(const char *opt, int *blocksize_ret) |
| 46 | { |
| 47 | char *end; |
| 48 | unsigned long n = strtoul(opt, &end, 10); |
| 49 | |
| 50 | if (n <= 0 || n >= INT32_MAX || *end || !is_power_of_2(n)) { |
| 51 | error_msg("Invalid block size: %s. Must be power of 2", opt); |
| 52 | return false; |
| 53 | } |
| 54 | *blocksize_ret = n; |
| 55 | return true; |
| 56 | } |
| 57 | |
| 58 | #define FS_VERITY_MAX_LEVELS 64 |
| 59 | |
| 60 | /* |
| 61 | * Calculate the depth of the Merkle tree, then create a map from level to the |
| 62 | * block offset at which that level's hash blocks start. Level 'depth - 1' is |
| 63 | * the root and is stored first in the file, in the first block following the |
| 64 | * original data. Level 0 is the "leaf" level: it's directly "above" the data |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 65 | * blocks and is stored last in the file. |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 66 | */ |
| 67 | static void compute_tree_layout(u64 data_size, u64 tree_offset, int blockbits, |
| 68 | unsigned int hashes_per_block, |
| 69 | u64 hash_lvl_region_idx[FS_VERITY_MAX_LEVELS], |
| 70 | int *depth_ret, u64 *tree_end_ret) |
| 71 | { |
| 72 | u64 blocks = data_size >> blockbits; |
| 73 | u64 offset = tree_offset >> blockbits; |
| 74 | int depth = 0; |
| 75 | int i; |
| 76 | |
| 77 | ASSERT(data_size > 0); |
| 78 | ASSERT(data_size % (1 << blockbits) == 0); |
| 79 | ASSERT(tree_offset % (1 << blockbits) == 0); |
| 80 | ASSERT(hashes_per_block >= 2); |
| 81 | |
| 82 | while (blocks > 1) { |
| 83 | ASSERT(depth < FS_VERITY_MAX_LEVELS); |
| 84 | blocks = DIV_ROUND_UP(blocks, hashes_per_block); |
| 85 | hash_lvl_region_idx[depth++] = blocks; |
| 86 | } |
| 87 | for (i = depth - 1; i >= 0; i--) { |
| 88 | u64 next_count = hash_lvl_region_idx[i]; |
| 89 | |
| 90 | hash_lvl_region_idx[i] = offset; |
| 91 | offset += next_count; |
| 92 | } |
| 93 | *depth_ret = depth; |
| 94 | *tree_end_ret = offset << blockbits; |
| 95 | } |
| 96 | |
| 97 | /* |
| 98 | * Build a Merkle tree (hash tree) over the data of a file. |
| 99 | * |
| 100 | * @params: Block size, hashes per block, and salt |
| 101 | * @hash: Handle for the hash algorithm |
| 102 | * @data_file: input data file |
| 103 | * @data_size: size of data file in bytes; must be aligned to ->blocksize |
| 104 | * @tree_file: output tree file |
| 105 | * @tree_offset: byte offset in tree file at which to write the tree; |
| 106 | * must be aligned to ->blocksize |
| 107 | * @tree_end_ret: On success, the byte offset in the tree file of the end of the |
| 108 | * tree is written here |
| 109 | * @root_hash_ret: On success, the Merkle tree root hash is written here |
| 110 | * |
| 111 | * Return: exit status code (0 on success, nonzero on failure) |
| 112 | */ |
| 113 | static int build_merkle_tree(const struct fsveritysetup_params *params, |
| 114 | struct hash_ctx *hash, |
| 115 | struct filedes *data_file, u64 data_size, |
| 116 | struct filedes *tree_file, u64 tree_offset, |
| 117 | u64 *tree_end_ret, u8 *root_hash_ret) |
| 118 | { |
| 119 | const unsigned int digest_size = hash->alg->digest_size; |
| 120 | int depth; |
| 121 | u64 hash_lvl_region_idx[FS_VERITY_MAX_LEVELS]; |
| 122 | u8 *data_to_hash = NULL; |
| 123 | u8 *pending_hashes = NULL; |
| 124 | unsigned int pending_hash_bytes; |
| 125 | u64 nr_hashes_at_this_lvl; |
| 126 | int lvl; |
| 127 | int status; |
| 128 | |
| 129 | compute_tree_layout(data_size, tree_offset, params->blockbits, |
| 130 | params->hashes_per_block, hash_lvl_region_idx, |
| 131 | &depth, tree_end_ret); |
| 132 | |
| 133 | /* Allocate block buffers */ |
| 134 | data_to_hash = xmalloc(params->blocksize); |
| 135 | pending_hashes = xmalloc(params->blocksize); |
| 136 | pending_hash_bytes = 0; |
| 137 | nr_hashes_at_this_lvl = data_size >> params->blockbits; |
| 138 | |
| 139 | /* |
| 140 | * Generate each level of the Merkle tree, starting at the leaf level |
| 141 | * ('lvl == 0') and ascending to the root node ('lvl == depth - 1'). |
| 142 | * Then at the end ('lvl == depth'), calculate the root node's hash. |
| 143 | */ |
| 144 | for (lvl = 0; lvl <= depth; lvl++) { |
| 145 | u64 i; |
| 146 | |
| 147 | for (i = 0; i < nr_hashes_at_this_lvl; i++) { |
| 148 | struct filedes *file; |
| 149 | u64 blk_idx; |
| 150 | |
| 151 | hash_init(hash); |
| 152 | hash_update(hash, params->salt, params->saltlen); |
| 153 | |
| 154 | if (lvl == 0) { |
| 155 | /* Leaf: hashing a data block */ |
| 156 | file = data_file; |
| 157 | blk_idx = i; |
| 158 | } else { |
| 159 | /* Non-leaf: hashing a hash block */ |
| 160 | file = tree_file; |
| 161 | blk_idx = hash_lvl_region_idx[lvl - 1] + i; |
| 162 | } |
| 163 | if (!full_pread(file, data_to_hash, params->blocksize, |
| 164 | blk_idx << params->blockbits)) |
| 165 | goto out_err; |
| 166 | hash_update(hash, data_to_hash, params->blocksize); |
| 167 | |
| 168 | hash_final(hash, &pending_hashes[pending_hash_bytes]); |
| 169 | pending_hash_bytes += digest_size; |
| 170 | |
| 171 | if (lvl == depth) { |
| 172 | /* Root hash */ |
| 173 | ASSERT(nr_hashes_at_this_lvl == 1); |
| 174 | ASSERT(pending_hash_bytes == digest_size); |
| 175 | memcpy(root_hash_ret, pending_hashes, |
| 176 | digest_size); |
| 177 | status = 0; |
| 178 | goto out; |
| 179 | } |
| 180 | |
| 181 | if (pending_hash_bytes + digest_size > params->blocksize |
| 182 | || i + 1 == nr_hashes_at_this_lvl) { |
| 183 | /* Flush the pending hash block */ |
| 184 | memset(&pending_hashes[pending_hash_bytes], 0, |
| 185 | params->blocksize - pending_hash_bytes); |
| 186 | blk_idx = hash_lvl_region_idx[lvl] + |
| 187 | (i / params->hashes_per_block); |
| 188 | if (!full_pwrite(tree_file, |
| 189 | pending_hashes, |
| 190 | params->blocksize, |
| 191 | blk_idx << params->blockbits)) |
| 192 | goto out_err; |
| 193 | pending_hash_bytes = 0; |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | nr_hashes_at_this_lvl = DIV_ROUND_UP(nr_hashes_at_this_lvl, |
| 198 | params->hashes_per_block); |
| 199 | } |
| 200 | ASSERT(0); /* unreachable; should exit via "Root hash" case above */ |
| 201 | out_err: |
| 202 | status = 1; |
| 203 | out: |
| 204 | free(data_to_hash); |
| 205 | free(pending_hashes); |
| 206 | return status; |
| 207 | } |
| 208 | |
| 209 | /* |
| 210 | * Append to the buffer @*buf_p an extension (variable-length metadata) item of |
| 211 | * type @type, containing the data @ext of length @extlen bytes. |
| 212 | */ |
| 213 | void fsverity_append_extension(void **buf_p, int type, |
| 214 | const void *ext, size_t extlen) |
| 215 | { |
| 216 | void *buf = *buf_p; |
| 217 | struct fsverity_extension *hdr = buf; |
| 218 | |
| 219 | hdr->type = cpu_to_le16(type); |
| 220 | hdr->length = cpu_to_le32(sizeof(*hdr) + extlen); |
| 221 | hdr->reserved = 0; |
| 222 | buf += sizeof(*hdr); |
| 223 | memcpy(buf, ext, extlen); |
| 224 | buf += extlen; |
| 225 | memset(buf, 0, -extlen & 7); |
| 226 | buf += -extlen & 7; |
| 227 | ASSERT(buf - *buf_p == FSVERITY_EXTLEN(extlen)); |
| 228 | *buf_p = buf; |
| 229 | } |
| 230 | |
| 231 | /* |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 232 | * Append the authenticated portion of the fs-verity descriptor to 'out', in the |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 233 | * process updating 'hash' with the data written. |
| 234 | */ |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 235 | static int append_fsverity_descriptor(const struct fsveritysetup_params *params, |
| 236 | u64 filesize, const u8 *root_hash, |
| 237 | struct filedes *out, |
| 238 | struct hash_ctx *hash) |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 239 | { |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 240 | size_t desc_auth_len; |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 241 | void *buf; |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 242 | struct fsverity_descriptor *desc; |
| 243 | u16 auth_ext_count; |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 244 | int status; |
| 245 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 246 | desc_auth_len = sizeof(*desc); |
| 247 | desc_auth_len += FSVERITY_EXTLEN(params->hash_alg->digest_size); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 248 | if (params->saltlen) |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 249 | desc_auth_len += FSVERITY_EXTLEN(params->saltlen); |
| 250 | desc_auth_len += total_elide_patch_ext_length(params); |
| 251 | desc = buf = xzalloc(desc_auth_len); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 252 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 253 | memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic)); |
| 254 | desc->major_version = 1; |
| 255 | desc->minor_version = 0; |
| 256 | desc->log_data_blocksize = params->blockbits; |
| 257 | desc->log_tree_blocksize = params->blockbits; |
| 258 | desc->data_algorithm = cpu_to_le16(params->hash_alg - |
| 259 | fsverity_hash_algs); |
| 260 | desc->tree_algorithm = desc->data_algorithm; |
| 261 | desc->orig_file_size = cpu_to_le64(filesize); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 262 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 263 | auth_ext_count = 1; /* root hash */ |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 264 | if (params->saltlen) |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 265 | auth_ext_count++; |
| 266 | auth_ext_count += params->num_elisions_and_patches; |
| 267 | desc->auth_ext_count = cpu_to_le16(auth_ext_count); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 268 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 269 | buf += sizeof(*desc); |
| 270 | fsverity_append_extension(&buf, FS_VERITY_EXT_ROOT_HASH, |
| 271 | root_hash, params->hash_alg->digest_size); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 272 | if (params->saltlen) |
| 273 | fsverity_append_extension(&buf, FS_VERITY_EXT_SALT, |
| 274 | params->salt, params->saltlen); |
| 275 | append_elide_patch_exts(&buf, params); |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 276 | ASSERT(buf - (void *)desc == desc_auth_len); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 277 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 278 | hash_update(hash, desc, desc_auth_len); |
| 279 | if (!full_write(out, desc, desc_auth_len)) |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 280 | goto out_err; |
| 281 | status = 0; |
| 282 | out: |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 283 | free(desc); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 284 | return status; |
| 285 | |
| 286 | out_err: |
| 287 | status = 1; |
| 288 | goto out; |
| 289 | } |
| 290 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 291 | /* |
| 292 | * Append any needed unauthenticated extension items: currently, just possibly a |
| 293 | * PKCS7_SIGNATURE item containing the signed file measurement. |
| 294 | */ |
| 295 | static int |
| 296 | append_unauthenticated_extensions(struct filedes *out, |
| 297 | const struct fsveritysetup_params *params, |
| 298 | const u8 *measurement) |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 299 | { |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 300 | u16 unauth_ext_count = 0; |
| 301 | struct { |
| 302 | __le16 unauth_ext_count; |
| 303 | __le16 pad[3]; |
| 304 | } hdr; |
| 305 | bool have_sig = params->signing_key_file || params->signature_file; |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 306 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 307 | if (have_sig) |
| 308 | unauth_ext_count++; |
| 309 | |
| 310 | ASSERT(sizeof(hdr) % 8 == 0); |
| 311 | memset(&hdr, 0, sizeof(hdr)); |
| 312 | hdr.unauth_ext_count = cpu_to_le16(unauth_ext_count); |
| 313 | |
| 314 | if (!full_write(out, &hdr, sizeof(hdr))) |
| 315 | return 1; |
| 316 | |
| 317 | if (have_sig) |
| 318 | return append_signed_measurement(out, params, measurement); |
| 319 | |
| 320 | return 0; |
| 321 | } |
| 322 | |
| 323 | static int append_footer(struct filedes *out, u64 desc_offset) |
| 324 | { |
| 325 | struct fsverity_footer ftr; |
| 326 | u32 offset = (out->pos + sizeof(ftr)) - desc_offset; |
| 327 | |
| 328 | ftr.desc_reverse_offset = cpu_to_le32(offset); |
| 329 | memcpy(ftr.magic, FS_VERITY_MAGIC, sizeof(ftr.magic)); |
| 330 | |
| 331 | if (!full_write(out, &ftr, sizeof(ftr))) |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 332 | return 1; |
| 333 | return 0; |
| 334 | } |
| 335 | |
| 336 | static int fsveritysetup(const char *infile, const char *outfile, |
| 337 | const struct fsveritysetup_params *params) |
| 338 | { |
| 339 | struct filedes _in = { .fd = -1 }; |
| 340 | struct filedes _out = { .fd = -1 }; |
| 341 | struct filedes _tmp = { .fd = -1 }; |
| 342 | struct hash_ctx *hash = NULL; |
Eric Biggers | 00c0ca7 | 2018-06-28 18:10:43 -0700 | [diff] [blame] | 343 | struct filedes *in = &_in, *out = &_out, *src; |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 344 | u64 filesize; |
| 345 | u64 aligned_filesize; |
| 346 | u64 src_filesize; |
| 347 | u64 tree_end_offset; |
| 348 | u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; |
| 349 | u8 measurement[FS_VERITY_MAX_DIGEST_SIZE]; |
| 350 | char hash_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + 1]; |
| 351 | int status; |
| 352 | |
| 353 | if (!open_file(in, infile, (infile == outfile ? O_RDWR : O_RDONLY), 0)) |
| 354 | goto out_err; |
| 355 | |
| 356 | if (!get_file_size(in, &filesize)) |
| 357 | goto out_err; |
| 358 | |
| 359 | if (filesize <= 0) { |
| 360 | error_msg("input file is empty: '%s'", infile); |
| 361 | goto out_err; |
| 362 | } |
| 363 | |
| 364 | if (infile == outfile) { |
| 365 | /* |
| 366 | * Invoked with one file argument: we're appending verity |
| 367 | * metadata to an existing file. |
| 368 | */ |
| 369 | out = in; |
| 370 | if (!filedes_seek(out, filesize, SEEK_SET)) |
| 371 | goto out_err; |
| 372 | } else { |
| 373 | /* |
| 374 | * Invoked with two file arguments: we're copying the first file |
| 375 | * to the second file, then appending verity metadata to it. |
| 376 | */ |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 377 | if (!open_file(out, outfile, O_RDWR|O_CREAT|O_TRUNC, 0644)) |
| 378 | goto out_err; |
| 379 | if (!copy_file_data(in, out, filesize)) |
| 380 | goto out_err; |
| 381 | } |
| 382 | |
| 383 | /* Zero-pad the output file to the next block boundary */ |
| 384 | aligned_filesize = ALIGN(filesize, params->blocksize); |
| 385 | if (!write_zeroes(out, aligned_filesize - filesize)) |
| 386 | goto out_err; |
| 387 | |
| 388 | if (params->num_elisions_and_patches) { |
| 389 | /* Merkle tree is built over temporary elided/patched file */ |
| 390 | src = &_tmp; |
| 391 | if (!apply_elisions_and_patches(params, in, filesize, |
| 392 | src, &src_filesize)) |
| 393 | goto out_err; |
| 394 | } else { |
| 395 | /* Merkle tree is built over original file */ |
| 396 | src = out; |
| 397 | src_filesize = aligned_filesize; |
| 398 | } |
| 399 | |
| 400 | hash = hash_create(params->hash_alg); |
| 401 | |
| 402 | /* Build the file's Merkle tree and calculate its root hash */ |
| 403 | status = build_merkle_tree(params, hash, src, src_filesize, |
| 404 | out, aligned_filesize, |
| 405 | &tree_end_offset, root_hash); |
| 406 | if (status) |
| 407 | goto out; |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 408 | if (!filedes_seek(out, tree_end_offset, SEEK_SET)) |
| 409 | goto out_err; |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 410 | |
| 411 | /* Append the additional needed metadata */ |
| 412 | |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 413 | hash_init(hash); |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 414 | status = append_fsverity_descriptor(params, filesize, root_hash, |
| 415 | out, hash); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 416 | if (status) |
| 417 | goto out; |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 418 | hash_final(hash, measurement); |
| 419 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 420 | status = append_unauthenticated_extensions(out, params, measurement); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 421 | if (status) |
| 422 | goto out; |
| 423 | |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 424 | status = append_footer(out, tree_end_offset); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 425 | if (status) |
| 426 | goto out; |
| 427 | |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 428 | bin2hex(measurement, params->hash_alg->digest_size, hash_hex); |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 429 | printf("File measurement: %s:%s\n", params->hash_alg->name, hash_hex); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 430 | status = 0; |
| 431 | out: |
| 432 | hash_free(hash); |
Eric Biggers | 00c0ca7 | 2018-06-28 18:10:43 -0700 | [diff] [blame] | 433 | if (status != 0 && out->fd >= 0) { |
| 434 | /* Error occurred; undo what we wrote */ |
| 435 | if (in == out) |
| 436 | (void)ftruncate(out->fd, filesize); |
| 437 | else |
| 438 | out->autodelete = true; |
| 439 | } |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 440 | filedes_close(&_in); |
| 441 | filedes_close(&_tmp); |
| 442 | if (!filedes_close(&_out) && status == 0) |
| 443 | status = 1; |
| 444 | return status; |
| 445 | |
| 446 | out_err: |
| 447 | status = 1; |
| 448 | goto out; |
| 449 | } |
| 450 | |
| 451 | int fsverity_cmd_setup(const struct fsverity_command *cmd, |
| 452 | int argc, char *argv[]) |
| 453 | { |
| 454 | struct fsveritysetup_params params = { |
| 455 | .hash_alg = DEFAULT_HASH_ALG, |
| 456 | }; |
| 457 | STRING_LIST(elide_opts); |
| 458 | STRING_LIST(patch_opts); |
| 459 | int c; |
| 460 | int status; |
| 461 | |
| 462 | while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { |
| 463 | switch (c) { |
| 464 | case OPT_HASH: |
Eric Biggers | 25b5945 | 2018-07-27 10:47:02 -0700 | [diff] [blame] | 465 | params.hash_alg = find_hash_alg_by_name(optarg); |
Eric Biggers | 431c67b | 2018-06-27 15:01:06 -0700 | [diff] [blame] | 466 | if (!params.hash_alg) |
| 467 | goto out_usage; |
| 468 | break; |
| 469 | case OPT_SALT: |
| 470 | if (params.salt) { |
| 471 | error_msg("--salt can only be specified once"); |
| 472 | goto out_usage; |
| 473 | } |
| 474 | params.saltlen = strlen(optarg) / 2; |
| 475 | params.salt = xmalloc(params.saltlen); |
| 476 | if (!hex2bin(optarg, params.salt, params.saltlen)) { |
| 477 | error_msg("salt is not a valid hex string"); |
| 478 | goto out_usage; |
| 479 | } |
| 480 | break; |
| 481 | case OPT_BLOCKSIZE: |
| 482 | if (!parse_blocksize_option(optarg, ¶ms.blocksize)) |
| 483 | goto out_usage; |
| 484 | break; |
| 485 | case OPT_SIGNING_KEY: |
| 486 | params.signing_key_file = optarg; |
| 487 | break; |
| 488 | case OPT_SIGNING_CERT: |
| 489 | params.signing_cert_file = optarg; |
| 490 | break; |
| 491 | case OPT_SIGNATURE: |
| 492 | params.signature_file = optarg; |
| 493 | break; |
| 494 | case OPT_ELIDE: |
| 495 | string_list_append(&elide_opts, optarg); |
| 496 | break; |
| 497 | case OPT_PATCH: |
| 498 | string_list_append(&patch_opts, optarg); |
| 499 | break; |
| 500 | default: |
| 501 | goto out_usage; |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | argv += optind; |
| 506 | argc -= optind; |
| 507 | |
| 508 | if (argc != 1 && argc != 2) |
| 509 | goto out_usage; |
| 510 | |
| 511 | ASSERT(params.hash_alg->digest_size <= FS_VERITY_MAX_DIGEST_SIZE); |
| 512 | |
| 513 | if (params.blocksize == 0) { |
| 514 | params.blocksize = sysconf(_SC_PAGESIZE); |
| 515 | if (params.blocksize <= 0 || !is_power_of_2(params.blocksize)) { |
| 516 | fprintf(stderr, |
| 517 | "Warning: invalid _SC_PAGESIZE (%d). Assuming 4K blocks.\n", |
| 518 | params.blocksize); |
| 519 | params.blocksize = 4096; |
| 520 | } |
| 521 | } |
| 522 | params.blockbits = ilog2(params.blocksize); |
| 523 | |
| 524 | params.hashes_per_block = params.blocksize / |
| 525 | params.hash_alg->digest_size; |
| 526 | if (params.hashes_per_block < 2) { |
| 527 | error_msg("block size of %d bytes is too small for %s hash", |
| 528 | params.blocksize, params.hash_alg->name); |
| 529 | goto out_err; |
| 530 | } |
| 531 | |
| 532 | if (params.signing_cert_file && !params.signing_key_file) { |
| 533 | error_msg("--signing-cert was given, but --signing-key was not.\n" |
| 534 | " You must provide the certificate's private key file using --signing-key."); |
| 535 | goto out_err; |
| 536 | } |
| 537 | |
| 538 | if ((params.signing_key_file || params.signature_file) && |
| 539 | !params.hash_alg->cryptographic) { |
| 540 | error_msg("Signing a file using '%s' checksums does not make sense\n" |
| 541 | " because '%s' is not a cryptographically secure hash algorithm.", |
| 542 | params.hash_alg->name, params.hash_alg->name); |
| 543 | goto out_err; |
| 544 | } |
| 545 | |
| 546 | if (!load_elisions_and_patches(&elide_opts, &patch_opts, ¶ms)) |
| 547 | goto out_err; |
| 548 | |
| 549 | status = fsveritysetup(argv[0], argv[argc - 1], ¶ms); |
| 550 | out: |
| 551 | free(params.salt); |
| 552 | free_elisions_and_patches(¶ms); |
| 553 | string_list_destroy(&elide_opts); |
| 554 | string_list_destroy(&patch_opts); |
| 555 | return status; |
| 556 | |
| 557 | out_err: |
| 558 | status = 1; |
| 559 | goto out; |
| 560 | |
| 561 | out_usage: |
| 562 | usage(cmd, stderr); |
| 563 | status = 2; |
| 564 | goto out; |
| 565 | } |