blob: 19dff63b5e679ac734713adca5e8ae0c3c22092e [file] [log] [blame]
Eric Biggersc67b06a2019-05-20 17:03:46 -07001// 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 */
28struct 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 */
47struct fsverity_signed_digest {
48 char magic[8]; /* must be "FSVerity" */
49 __le16 digest_algorithm;
50 __le16 digest_size;
51 __u8 digest[];
52};
53
54static void __printf(1, 2) __cold
55error_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 */
71static 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 */
93static 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
115static 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;
207out:
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
219static 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
235static 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;
299out:
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 */
313static 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);
343out:
344 EVP_PKEY_free(pkey);
345 X509_free(cert);
346 return ok;
347}
348
349static 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
363struct 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 */
372static 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 */
395static 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;
452out:
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 */
465static 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 Biggersc67b06a2019-05-20 17:03:46 -0700482 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 Biggersb09ba7e2019-06-18 12:26:59 -0700501 /* 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 Biggersc67b06a2019-05-20 17:03:46 -0700504 salt_size, desc.root_hash))
505 goto out;
506
507 hash_full(hash, &desc, sizeof(desc), measurement);
508 ok = true;
509out:
510 filedes_close(&file);
511 hash_free(hash);
512 return ok;
513}
514
515enum {
516 OPT_HASH_ALG,
517 OPT_BLOCK_SIZE,
518 OPT_SALT,
519 OPT_KEY,
520 OPT_CERT,
521};
522
523static 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. */
533int 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 Biggers6033f552019-06-26 17:25:52 -0700543 char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + 1];
Eric Biggersc67b06a2019-05-20 17:03:46 -0700544 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 Biggers6033f552019-06-26 17:25:52 -0700622 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 Biggersc67b06a2019-05-20 17:03:46 -0700625 status = 0;
626out:
627 free(salt);
628 free(digest);
629 free(sig);
630 return status;
631
632out_err:
633 status = 1;
634 goto out;
635
636out_usage:
637 usage(cmd, stderr);
638 status = 2;
639 goto out;
640}