Eric Biggers | c67b06a | 2019-05-20 17:03:46 -0700 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * The 'fsverity sign' command |
| 4 | * |
| 5 | * Copyright (C) 2018 Google LLC |
| 6 | * |
| 7 | * Written by Eric Biggers. |
| 8 | */ |
| 9 | |
| 10 | #include <fcntl.h> |
| 11 | #include <getopt.h> |
| 12 | #include <limits.h> |
| 13 | #include <openssl/bio.h> |
| 14 | #include <openssl/err.h> |
| 15 | #include <openssl/pem.h> |
| 16 | #include <openssl/pkcs7.h> |
| 17 | #include <stdlib.h> |
| 18 | #include <string.h> |
| 19 | |
| 20 | #include "commands.h" |
| 21 | #include "fsverity_uapi.h" |
| 22 | #include "hash_algs.h" |
| 23 | |
| 24 | /* |
| 25 | * Merkle tree properties. The file measurement is the hash of this structure |
| 26 | * excluding the signature and with the sig_size field set to 0. |
| 27 | */ |
| 28 | struct fsverity_descriptor { |
| 29 | __u8 version; /* must be 1 */ |
| 30 | __u8 hash_algorithm; /* Merkle tree hash algorithm */ |
| 31 | __u8 log_blocksize; /* log2 of size of data and tree blocks */ |
| 32 | __u8 salt_size; /* size of salt in bytes; 0 if none */ |
| 33 | __le32 sig_size; /* size of signature in bytes; 0 if none */ |
| 34 | __le64 data_size; /* size of file the Merkle tree is built over */ |
| 35 | __u8 root_hash[64]; /* Merkle tree root hash */ |
| 36 | __u8 salt[32]; /* salt prepended to each hashed block */ |
| 37 | __u8 __reserved[144]; /* must be 0's */ |
| 38 | __u8 signature[]; /* optional PKCS#7 signature */ |
| 39 | }; |
| 40 | |
| 41 | /* |
| 42 | * Format in which verity file measurements are signed. This is the same as |
| 43 | * 'struct fsverity_digest', except here some magic bytes are prepended to |
| 44 | * provide some context about what is being signed in case the same key is used |
| 45 | * for non-fsverity purposes, and here the fields have fixed endianness. |
| 46 | */ |
| 47 | struct fsverity_signed_digest { |
| 48 | char magic[8]; /* must be "FSVerity" */ |
| 49 | __le16 digest_algorithm; |
| 50 | __le16 digest_size; |
| 51 | __u8 digest[]; |
| 52 | }; |
| 53 | |
| 54 | static void __printf(1, 2) __cold |
| 55 | error_msg_openssl(const char *format, ...) |
| 56 | { |
| 57 | va_list va; |
| 58 | |
| 59 | va_start(va, format); |
| 60 | do_error_msg(format, va, 0); |
| 61 | va_end(va); |
| 62 | |
| 63 | if (ERR_peek_error() == 0) |
| 64 | return; |
| 65 | |
| 66 | fprintf(stderr, "OpenSSL library errors:\n"); |
| 67 | ERR_print_errors_fp(stderr); |
| 68 | } |
| 69 | |
| 70 | /* Read a PEM PKCS#8 formatted private key */ |
| 71 | static EVP_PKEY *read_private_key(const char *keyfile) |
| 72 | { |
| 73 | BIO *bio; |
| 74 | EVP_PKEY *pkey; |
| 75 | |
| 76 | bio = BIO_new_file(keyfile, "r"); |
| 77 | if (!bio) { |
| 78 | error_msg_openssl("can't open '%s' for reading", keyfile); |
| 79 | return NULL; |
| 80 | } |
| 81 | |
| 82 | pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); |
| 83 | if (!pkey) { |
| 84 | error_msg_openssl("Failed to parse private key file '%s'.\n" |
| 85 | " Note: it must be in PEM PKCS#8 format.", |
| 86 | keyfile); |
| 87 | } |
| 88 | BIO_free(bio); |
| 89 | return pkey; |
| 90 | } |
| 91 | |
| 92 | /* Read a PEM X.509 formatted certificate */ |
| 93 | static X509 *read_certificate(const char *certfile) |
| 94 | { |
| 95 | BIO *bio; |
| 96 | X509 *cert; |
| 97 | |
| 98 | bio = BIO_new_file(certfile, "r"); |
| 99 | if (!bio) { |
| 100 | error_msg_openssl("can't open '%s' for reading", certfile); |
| 101 | return NULL; |
| 102 | } |
| 103 | cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| 104 | if (!cert) { |
| 105 | error_msg_openssl("Failed to parse X.509 certificate file '%s'.\n" |
| 106 | " Note: it must be in PEM format.", |
| 107 | certfile); |
| 108 | } |
| 109 | BIO_free(bio); |
| 110 | return cert; |
| 111 | } |
| 112 | |
| 113 | #ifdef OPENSSL_IS_BORINGSSL |
| 114 | |
| 115 | static bool sign_pkcs7(const void *data_to_sign, size_t data_size, |
| 116 | EVP_PKEY *pkey, X509 *cert, const EVP_MD *md, |
| 117 | u8 **sig_ret, u32 *sig_size_ret) |
| 118 | { |
| 119 | CBB out, outer_seq, wrapped_seq, seq, digest_algos_set, digest_algo, |
| 120 | null, content_info, issuer_and_serial, signed_data, |
| 121 | wrapped_signed_data, signer_infos, signer_info, sign_algo, |
| 122 | signature; |
| 123 | EVP_MD_CTX md_ctx; |
| 124 | u8 *name_der = NULL, *sig = NULL, *pkcs7_data = NULL; |
| 125 | size_t pkcs7_data_len, sig_len; |
| 126 | int name_der_len, sig_nid; |
| 127 | bool ok = false; |
| 128 | |
| 129 | EVP_MD_CTX_init(&md_ctx); |
| 130 | BIGNUM *serial = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), NULL); |
| 131 | |
| 132 | if (!CBB_init(&out, 1024)) { |
| 133 | error_msg("out of memory"); |
| 134 | goto out; |
| 135 | } |
| 136 | |
| 137 | name_der_len = i2d_X509_NAME(X509_get_subject_name(cert), &name_der); |
| 138 | if (name_der_len < 0) { |
| 139 | error_msg_openssl("i2d_X509_NAME failed"); |
| 140 | goto out; |
| 141 | } |
| 142 | |
| 143 | if (!EVP_DigestSignInit(&md_ctx, NULL, md, NULL, pkey)) { |
| 144 | error_msg_openssl("EVP_DigestSignInit failed"); |
| 145 | goto out; |
| 146 | } |
| 147 | |
| 148 | sig_len = EVP_PKEY_size(pkey); |
| 149 | sig = xmalloc(sig_len); |
| 150 | if (!EVP_DigestSign(&md_ctx, sig, &sig_len, data_to_sign, data_size)) { |
| 151 | error_msg_openssl("EVP_DigestSign failed"); |
| 152 | goto out; |
| 153 | } |
| 154 | |
| 155 | sig_nid = EVP_PKEY_id(pkey); |
| 156 | /* To mirror OpenSSL behaviour, always use |NID_rsaEncryption| with RSA |
| 157 | * rather than the combined hash+pkey NID. */ |
| 158 | if (sig_nid != NID_rsaEncryption) { |
| 159 | OBJ_find_sigid_by_algs(&sig_nid, EVP_MD_type(md), |
| 160 | EVP_PKEY_id(pkey)); |
| 161 | } |
| 162 | |
| 163 | // See https://tools.ietf.org/html/rfc2315#section-7 |
| 164 | if (!CBB_add_asn1(&out, &outer_seq, CBS_ASN1_SEQUENCE) || |
| 165 | !OBJ_nid2cbb(&outer_seq, NID_pkcs7_signed) || |
| 166 | !CBB_add_asn1(&outer_seq, &wrapped_seq, CBS_ASN1_CONTEXT_SPECIFIC | |
| 167 | CBS_ASN1_CONSTRUCTED | 0) || |
| 168 | // See https://tools.ietf.org/html/rfc2315#section-9.1 |
| 169 | !CBB_add_asn1(&wrapped_seq, &seq, CBS_ASN1_SEQUENCE) || |
| 170 | !CBB_add_asn1_uint64(&seq, 1 /* version */) || |
| 171 | !CBB_add_asn1(&seq, &digest_algos_set, CBS_ASN1_SET) || |
| 172 | !CBB_add_asn1(&digest_algos_set, &digest_algo, CBS_ASN1_SEQUENCE) || |
| 173 | !OBJ_nid2cbb(&digest_algo, EVP_MD_type(md)) || |
| 174 | !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) || |
| 175 | !CBB_add_asn1(&seq, &content_info, CBS_ASN1_SEQUENCE) || |
| 176 | !OBJ_nid2cbb(&content_info, NID_pkcs7_data) || |
| 177 | !CBB_add_asn1( |
| 178 | &content_info, &signed_data, |
| 179 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || |
| 180 | !CBB_add_asn1(&signed_data, &wrapped_signed_data, |
| 181 | CBS_ASN1_OCTETSTRING) || |
| 182 | !CBB_add_bytes(&wrapped_signed_data, (const u8 *)data_to_sign, |
| 183 | data_size) || |
| 184 | !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET) || |
| 185 | !CBB_add_asn1(&signer_infos, &signer_info, CBS_ASN1_SEQUENCE) || |
| 186 | !CBB_add_asn1_uint64(&signer_info, 1 /* version */) || |
| 187 | !CBB_add_asn1(&signer_info, &issuer_and_serial, |
| 188 | CBS_ASN1_SEQUENCE) || |
| 189 | !CBB_add_bytes(&issuer_and_serial, name_der, name_der_len) || |
| 190 | !BN_marshal_asn1(&issuer_and_serial, serial) || |
| 191 | !CBB_add_asn1(&signer_info, &digest_algo, CBS_ASN1_SEQUENCE) || |
| 192 | !OBJ_nid2cbb(&digest_algo, EVP_MD_type(md)) || |
| 193 | !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) || |
| 194 | !CBB_add_asn1(&signer_info, &sign_algo, CBS_ASN1_SEQUENCE) || |
| 195 | !OBJ_nid2cbb(&sign_algo, sig_nid) || |
| 196 | !CBB_add_asn1(&sign_algo, &null, CBS_ASN1_NULL) || |
| 197 | !CBB_add_asn1(&signer_info, &signature, CBS_ASN1_OCTETSTRING) || |
| 198 | !CBB_add_bytes(&signature, sig, sig_len) || |
| 199 | !CBB_finish(&out, &pkcs7_data, &pkcs7_data_len)) { |
| 200 | error_msg_openssl("failed to construct PKCS#7 data"); |
| 201 | goto out; |
| 202 | } |
| 203 | |
| 204 | *sig_ret = xmemdup(pkcs7_data, pkcs7_data_len); |
| 205 | *sig_size_ret = pkcs7_data_len; |
| 206 | ok = true; |
| 207 | out: |
| 208 | BN_free(serial); |
| 209 | EVP_MD_CTX_cleanup(&md_ctx); |
| 210 | CBB_cleanup(&out); |
| 211 | free(sig); |
| 212 | OPENSSL_free(name_der); |
| 213 | OPENSSL_free(pkcs7_data); |
| 214 | return ok; |
| 215 | } |
| 216 | |
| 217 | #else /* OPENSSL_IS_BORINGSSL */ |
| 218 | |
| 219 | static BIO *new_mem_buf(const void *buf, size_t size) |
| 220 | { |
| 221 | BIO *bio; |
| 222 | |
| 223 | ASSERT(size <= INT_MAX); |
| 224 | /* |
| 225 | * Prior to OpenSSL 1.1.0, BIO_new_mem_buf() took a non-const pointer, |
| 226 | * despite still marking the resulting bio as read-only. So cast away |
| 227 | * the const to avoid a compiler warning with older OpenSSL versions. |
| 228 | */ |
| 229 | bio = BIO_new_mem_buf((void *)buf, size); |
| 230 | if (!bio) |
| 231 | error_msg_openssl("out of memory"); |
| 232 | return bio; |
| 233 | } |
| 234 | |
| 235 | static bool sign_pkcs7(const void *data_to_sign, size_t data_size, |
| 236 | EVP_PKEY *pkey, X509 *cert, const EVP_MD *md, |
| 237 | u8 **sig_ret, u32 *sig_size_ret) |
| 238 | { |
| 239 | /* |
| 240 | * PKCS#7 signing flags: |
| 241 | * |
| 242 | * - PKCS7_BINARY signing binary data, so skip MIME translation |
| 243 | * |
| 244 | * - PKCS7_NOATTR omit extra authenticated attributes, such as |
| 245 | * SMIMECapabilities |
| 246 | * |
| 247 | * - PKCS7_NOCERTS omit the signer's certificate |
| 248 | * |
| 249 | * - PKCS7_PARTIAL PKCS7_sign() creates a handle only, then |
| 250 | * PKCS7_sign_add_signer() can add a signer later. |
| 251 | * This is necessary to change the message digest |
| 252 | * algorithm from the default of SHA-1. Requires |
| 253 | * OpenSSL 1.0.0 or later. |
| 254 | */ |
| 255 | int pkcs7_flags = PKCS7_BINARY | PKCS7_NOATTR | PKCS7_NOCERTS | |
| 256 | PKCS7_PARTIAL; |
| 257 | u8 *sig; |
| 258 | u32 sig_size; |
| 259 | BIO *bio = NULL; |
| 260 | PKCS7 *p7 = NULL; |
| 261 | bool ok = false; |
| 262 | |
| 263 | bio = new_mem_buf(data_to_sign, data_size); |
| 264 | if (!bio) |
| 265 | goto out; |
| 266 | |
| 267 | p7 = PKCS7_sign(NULL, NULL, NULL, bio, pkcs7_flags); |
| 268 | if (!p7) { |
| 269 | error_msg_openssl("failed to initialize PKCS#7 signature object"); |
| 270 | goto out; |
| 271 | } |
| 272 | |
| 273 | if (!PKCS7_sign_add_signer(p7, cert, pkey, md, pkcs7_flags)) { |
| 274 | error_msg_openssl("failed to add signer to PKCS#7 signature object"); |
| 275 | goto out; |
| 276 | } |
| 277 | |
| 278 | if (PKCS7_final(p7, bio, pkcs7_flags) != 1) { |
| 279 | error_msg_openssl("failed to finalize PKCS#7 signature"); |
| 280 | goto out; |
| 281 | } |
| 282 | |
| 283 | BIO_free(bio); |
| 284 | bio = BIO_new(BIO_s_mem()); |
| 285 | if (!bio) { |
| 286 | error_msg_openssl("out of memory"); |
| 287 | goto out; |
| 288 | } |
| 289 | |
| 290 | if (i2d_PKCS7_bio(bio, p7) != 1) { |
| 291 | error_msg_openssl("failed to DER-encode PKCS#7 signature object"); |
| 292 | goto out; |
| 293 | } |
| 294 | |
| 295 | sig_size = BIO_get_mem_data(bio, &sig); |
| 296 | *sig_ret = xmemdup(sig, sig_size); |
| 297 | *sig_size_ret = sig_size; |
| 298 | ok = true; |
| 299 | out: |
| 300 | PKCS7_free(p7); |
| 301 | BIO_free(bio); |
| 302 | return ok; |
| 303 | } |
| 304 | |
| 305 | #endif /* !OPENSSL_IS_BORINGSSL */ |
| 306 | |
| 307 | /* |
| 308 | * Sign the specified @data_to_sign of length @data_size bytes using the private |
| 309 | * key in @keyfile, the certificate in @certfile, and the hash algorithm |
| 310 | * @hash_alg. Returns the DER-formatted PKCS#7 signature, with the signed data |
| 311 | * included (not detached), in @sig_ret and @sig_size_ret. |
| 312 | */ |
| 313 | static bool sign_data(const void *data_to_sign, size_t data_size, |
| 314 | const char *keyfile, const char *certfile, |
| 315 | const struct fsverity_hash_alg *hash_alg, |
| 316 | u8 **sig_ret, u32 *sig_size_ret) |
| 317 | { |
| 318 | EVP_PKEY *pkey = NULL; |
| 319 | X509 *cert = NULL; |
| 320 | const EVP_MD *md; |
| 321 | bool ok = false; |
| 322 | |
| 323 | pkey = read_private_key(keyfile); |
| 324 | if (!pkey) |
| 325 | goto out; |
| 326 | |
| 327 | cert = read_certificate(certfile); |
| 328 | if (!cert) |
| 329 | goto out; |
| 330 | |
| 331 | OpenSSL_add_all_digests(); |
| 332 | md = EVP_get_digestbyname(hash_alg->name); |
| 333 | if (!md) { |
| 334 | fprintf(stderr, |
| 335 | "Warning: '%s' algorithm not found in OpenSSL library.\n" |
| 336 | " Falling back to SHA-256 signature.\n", |
| 337 | hash_alg->name); |
| 338 | md = EVP_sha256(); |
| 339 | } |
| 340 | |
| 341 | ok = sign_pkcs7(data_to_sign, data_size, pkey, cert, md, |
| 342 | sig_ret, sig_size_ret); |
| 343 | out: |
| 344 | EVP_PKEY_free(pkey); |
| 345 | X509_free(cert); |
| 346 | return ok; |
| 347 | } |
| 348 | |
| 349 | static bool write_signature(const char *filename, const u8 *sig, u32 sig_size) |
| 350 | { |
| 351 | struct filedes file; |
| 352 | bool ok; |
| 353 | |
| 354 | if (!open_file(&file, filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) |
| 355 | return false; |
| 356 | ok = full_write(&file, sig, sig_size); |
| 357 | ok &= filedes_close(&file); |
| 358 | return ok; |
| 359 | } |
| 360 | |
| 361 | #define FS_VERITY_MAX_LEVELS 64 |
| 362 | |
| 363 | struct block_buffer { |
| 364 | u32 filled; |
| 365 | u8 *data; |
| 366 | }; |
| 367 | |
| 368 | /* |
| 369 | * Hash a block, writing the result to the next level's pending block buffer. |
| 370 | * Returns true if the next level's block became full, else false. |
| 371 | */ |
| 372 | static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur, |
| 373 | u32 block_size, const u8 *salt, u32 salt_size) |
| 374 | { |
| 375 | struct block_buffer *next = cur + 1; |
| 376 | |
| 377 | /* Zero-pad the block if it's shorter than block_size. */ |
| 378 | memset(&cur->data[cur->filled], 0, block_size - cur->filled); |
| 379 | |
| 380 | hash_init(hash); |
| 381 | hash_update(hash, salt, salt_size); |
| 382 | hash_update(hash, cur->data, block_size); |
| 383 | hash_final(hash, &next->data[next->filled]); |
| 384 | |
| 385 | next->filled += hash->alg->digest_size; |
| 386 | cur->filled = 0; |
| 387 | |
| 388 | return next->filled + hash->alg->digest_size > block_size; |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | * Compute the file's Merkle tree root hash using the given hash algorithm, |
| 393 | * block size, and salt. |
| 394 | */ |
| 395 | static bool compute_root_hash(struct filedes *file, u64 file_size, |
| 396 | struct hash_ctx *hash, u32 block_size, |
| 397 | const u8 *salt, u32 salt_size, u8 *root_hash) |
| 398 | { |
| 399 | const u32 hashes_per_block = block_size / hash->alg->digest_size; |
| 400 | const u32 padded_salt_size = roundup(salt_size, hash->alg->block_size); |
| 401 | u8 *padded_salt = xzalloc(padded_salt_size); |
| 402 | u64 blocks; |
| 403 | int num_levels = 0; |
| 404 | int level; |
| 405 | struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {}; |
| 406 | struct block_buffer *buffers = &_buffers[1]; |
| 407 | u64 offset; |
| 408 | bool ok = false; |
| 409 | |
| 410 | memcpy(padded_salt, salt, salt_size); |
| 411 | |
| 412 | /* Compute number of levels */ |
| 413 | for (blocks = DIV_ROUND_UP(file_size, block_size); blocks > 1; |
| 414 | blocks = DIV_ROUND_UP(blocks, hashes_per_block)) { |
| 415 | ASSERT(num_levels < FS_VERITY_MAX_LEVELS); |
| 416 | num_levels++; |
| 417 | } |
| 418 | |
| 419 | /* |
| 420 | * Allocate the block buffers. Buffer "-1" is for data blocks. |
| 421 | * Buffers 0 <= level < num_levels are for the actual tree levels. |
| 422 | * Buffer 'num_levels' is for the root hash. |
| 423 | */ |
| 424 | for (level = -1; level < num_levels; level++) |
| 425 | buffers[level].data = xmalloc(block_size); |
| 426 | buffers[num_levels].data = root_hash; |
| 427 | |
| 428 | /* Hash each data block, also hashing the tree blocks as they fill up */ |
| 429 | for (offset = 0; offset < file_size; offset += block_size) { |
| 430 | buffers[-1].filled = min(block_size, file_size - offset); |
| 431 | |
| 432 | if (!full_read(file, buffers[-1].data, buffers[-1].filled)) |
| 433 | goto out; |
| 434 | |
| 435 | level = -1; |
| 436 | while (hash_one_block(hash, &buffers[level], block_size, |
| 437 | padded_salt, padded_salt_size)) { |
| 438 | level++; |
| 439 | ASSERT(level < num_levels); |
| 440 | } |
| 441 | } |
| 442 | /* Finish all nonempty pending tree blocks */ |
| 443 | for (level = 0; level < num_levels; level++) { |
| 444 | if (buffers[level].filled != 0) |
| 445 | hash_one_block(hash, &buffers[level], block_size, |
| 446 | padded_salt, padded_salt_size); |
| 447 | } |
| 448 | |
| 449 | /* Root hash was filled by the last call to hash_one_block() */ |
| 450 | ASSERT(buffers[num_levels].filled == hash->alg->digest_size); |
| 451 | ok = true; |
| 452 | out: |
| 453 | for (level = -1; level < num_levels; level++) |
| 454 | free(buffers[level].data); |
| 455 | free(padded_salt); |
| 456 | return ok; |
| 457 | } |
| 458 | |
| 459 | /* |
| 460 | * Compute the fs-verity measurement of the given file. |
| 461 | * |
| 462 | * The fs-verity measurement is the hash of the fsverity_descriptor, which |
| 463 | * contains the Merkle tree properties including the root hash. |
| 464 | */ |
| 465 | static bool compute_file_measurement(const char *filename, |
| 466 | const struct fsverity_hash_alg *hash_alg, |
| 467 | u32 block_size, const u8 *salt, |
| 468 | u32 salt_size, u8 *measurement) |
| 469 | { |
| 470 | struct filedes file = { .fd = -1 }; |
| 471 | struct hash_ctx *hash = hash_create(hash_alg); |
| 472 | u64 file_size; |
| 473 | struct fsverity_descriptor desc; |
| 474 | bool ok = false; |
| 475 | |
| 476 | if (!open_file(&file, filename, O_RDONLY, 0)) |
| 477 | goto out; |
| 478 | |
| 479 | if (!get_file_size(&file, &file_size)) |
| 480 | goto out; |
| 481 | |
Eric Biggers | c67b06a | 2019-05-20 17:03:46 -0700 | [diff] [blame] | 482 | memset(&desc, 0, sizeof(desc)); |
| 483 | desc.version = 1; |
| 484 | desc.hash_algorithm = hash_alg - fsverity_hash_algs; |
| 485 | |
| 486 | ASSERT(is_power_of_2(block_size)); |
| 487 | desc.log_blocksize = ilog2(block_size); |
| 488 | |
| 489 | if (salt_size != 0) { |
| 490 | if (salt_size > sizeof(desc.salt)) { |
| 491 | error_msg("Salt too long (got %u bytes; max is %zu bytes)", |
| 492 | salt_size, sizeof(desc.salt)); |
| 493 | goto out; |
| 494 | } |
| 495 | memcpy(desc.salt, salt, salt_size); |
| 496 | desc.salt_size = salt_size; |
| 497 | } |
| 498 | |
| 499 | desc.data_size = cpu_to_le64(file_size); |
| 500 | |
Eric Biggers | b09ba7e | 2019-06-18 12:26:59 -0700 | [diff] [blame] | 501 | /* Root hash of empty file is all 0's */ |
| 502 | if (file_size != 0 && |
| 503 | !compute_root_hash(&file, file_size, hash, block_size, salt, |
Eric Biggers | c67b06a | 2019-05-20 17:03:46 -0700 | [diff] [blame] | 504 | salt_size, desc.root_hash)) |
| 505 | goto out; |
| 506 | |
| 507 | hash_full(hash, &desc, sizeof(desc), measurement); |
| 508 | ok = true; |
| 509 | out: |
| 510 | filedes_close(&file); |
| 511 | hash_free(hash); |
| 512 | return ok; |
| 513 | } |
| 514 | |
| 515 | enum { |
| 516 | OPT_HASH_ALG, |
| 517 | OPT_BLOCK_SIZE, |
| 518 | OPT_SALT, |
| 519 | OPT_KEY, |
| 520 | OPT_CERT, |
| 521 | }; |
| 522 | |
| 523 | static const struct option longopts[] = { |
| 524 | {"hash-alg", required_argument, NULL, OPT_HASH_ALG}, |
| 525 | {"block-size", required_argument, NULL, OPT_BLOCK_SIZE}, |
| 526 | {"salt", required_argument, NULL, OPT_SALT}, |
| 527 | {"key", required_argument, NULL, OPT_KEY}, |
| 528 | {"cert", required_argument, NULL, OPT_CERT}, |
| 529 | {NULL, 0, NULL, 0} |
| 530 | }; |
| 531 | |
| 532 | /* Sign a file for fs-verity by computing its measurement, then signing it. */ |
| 533 | int fsverity_cmd_sign(const struct fsverity_command *cmd, |
| 534 | int argc, char *argv[]) |
| 535 | { |
| 536 | const struct fsverity_hash_alg *hash_alg = NULL; |
| 537 | u32 block_size = 0; |
| 538 | u8 *salt = NULL; |
| 539 | u32 salt_size = 0; |
| 540 | const char *keyfile = NULL; |
| 541 | const char *certfile = NULL; |
| 542 | struct fsverity_signed_digest *digest = NULL; |
Eric Biggers | 6033f55 | 2019-06-26 17:25:52 -0700 | [diff] [blame^] | 543 | char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + 1]; |
Eric Biggers | c67b06a | 2019-05-20 17:03:46 -0700 | [diff] [blame] | 544 | u8 *sig = NULL; |
| 545 | u32 sig_size; |
| 546 | int status; |
| 547 | int c; |
| 548 | |
| 549 | while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { |
| 550 | switch (c) { |
| 551 | case OPT_HASH_ALG: |
| 552 | if (hash_alg != NULL) { |
| 553 | error_msg("--hash-alg can only be specified once"); |
| 554 | goto out_usage; |
| 555 | } |
| 556 | hash_alg = find_hash_alg_by_name(optarg); |
| 557 | if (hash_alg == NULL) |
| 558 | goto out_usage; |
| 559 | break; |
| 560 | case OPT_BLOCK_SIZE: |
| 561 | if (!parse_block_size_option(optarg, &block_size)) |
| 562 | goto out_usage; |
| 563 | break; |
| 564 | case OPT_SALT: |
| 565 | if (!parse_salt_option(optarg, &salt, &salt_size)) |
| 566 | goto out_usage; |
| 567 | break; |
| 568 | case OPT_KEY: |
| 569 | if (keyfile != NULL) { |
| 570 | error_msg("--key can only be specified once"); |
| 571 | goto out_usage; |
| 572 | } |
| 573 | keyfile = optarg; |
| 574 | break; |
| 575 | case OPT_CERT: |
| 576 | if (certfile != NULL) { |
| 577 | error_msg("--cert can only be specified once"); |
| 578 | goto out_usage; |
| 579 | } |
| 580 | certfile = optarg; |
| 581 | break; |
| 582 | default: |
| 583 | goto out_usage; |
| 584 | } |
| 585 | } |
| 586 | |
| 587 | argv += optind; |
| 588 | argc -= optind; |
| 589 | |
| 590 | if (argc != 2) |
| 591 | goto out_usage; |
| 592 | |
| 593 | if (hash_alg == NULL) |
| 594 | hash_alg = &fsverity_hash_algs[FS_VERITY_HASH_ALG_DEFAULT]; |
| 595 | |
| 596 | if (block_size == 0) |
| 597 | block_size = get_default_block_size(); |
| 598 | |
| 599 | if (keyfile == NULL) { |
| 600 | error_msg("Missing --key argument"); |
| 601 | goto out_usage; |
| 602 | } |
| 603 | if (certfile == NULL) |
| 604 | certfile = keyfile; |
| 605 | |
| 606 | digest = xzalloc(sizeof(*digest) + hash_alg->digest_size); |
| 607 | memcpy(digest->magic, "FSVerity", 8); |
| 608 | digest->digest_algorithm = cpu_to_le16(hash_alg - fsverity_hash_algs); |
| 609 | digest->digest_size = cpu_to_le16(hash_alg->digest_size); |
| 610 | |
| 611 | if (!compute_file_measurement(argv[0], hash_alg, block_size, |
| 612 | salt, salt_size, digest->digest)) |
| 613 | goto out_err; |
| 614 | |
| 615 | if (!sign_data(digest, sizeof(*digest) + hash_alg->digest_size, |
| 616 | keyfile, certfile, hash_alg, &sig, &sig_size)) |
| 617 | goto out_err; |
| 618 | |
| 619 | if (!write_signature(argv[1], sig, sig_size)) |
| 620 | goto out_err; |
| 621 | |
Eric Biggers | 6033f55 | 2019-06-26 17:25:52 -0700 | [diff] [blame^] | 622 | bin2hex(digest->digest, hash_alg->digest_size, digest_hex); |
| 623 | printf("Signed file \"%s\" (%s:%s)\n", argv[0], hash_alg->name, |
| 624 | digest_hex); |
Eric Biggers | c67b06a | 2019-05-20 17:03:46 -0700 | [diff] [blame] | 625 | status = 0; |
| 626 | out: |
| 627 | free(salt); |
| 628 | free(digest); |
| 629 | free(sig); |
| 630 | return status; |
| 631 | |
| 632 | out_err: |
| 633 | status = 1; |
| 634 | goto out; |
| 635 | |
| 636 | out_usage: |
| 637 | usage(cmd, stderr); |
| 638 | status = 2; |
| 639 | goto out; |
| 640 | } |