From patchwork Fri Feb 28 21:28:13 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jes Sorensen X-Patchwork-Id: 11413295 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 9B2A3930 for ; Fri, 28 Feb 2020 21:28:43 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6853A246AF for ; Fri, 28 Feb 2020 21:28:43 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="aaLh2+b+" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726843AbgB1V2n (ORCPT ); Fri, 28 Feb 2020 16:28:43 -0500 Received: from mail-qt1-f195.google.com ([209.85.160.195]:41053 "EHLO mail-qt1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725805AbgB1V2n (ORCPT ); Fri, 28 Feb 2020 16:28:43 -0500 Received: by mail-qt1-f195.google.com with SMTP id l21so3151183qtr.8 for ; Fri, 28 Feb 2020 13:28:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=OghQt65i9+PYAuXQHnddaF+pNnvUXk1biFeFIC0Xp5k=; b=aaLh2+b+pcaQYdHGx+2Ffjv7rVkOOk13Kxr+PYVyIptjFpuanYXskEwIbJ51eQGUJS KSzF4za0mVuzTohjVzGzYRXOI+2Z91KWcqpbHWhIGZ/bpZGXj9ncqLArUrX3YU2FMtG2 Lk4+FnTwrptR2FU7k2ytOayodzvDXOJP+8sP9+UYQe624wO13OTvNLQ0412lHjiuyfzx NTx/tuRYTADI2ehLrUmtSUwU97Tuk45c5aaZ5t1Cr3DU4RKLEXLVEq08Bi/T1dyh7GEf ns5ta0JIHh15t+2mFcy2sFI3yPLp+wo9VAK67L9koAoI/u5uFcyV2h+eTyii30BY49mv 1WbQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=OghQt65i9+PYAuXQHnddaF+pNnvUXk1biFeFIC0Xp5k=; b=GxGavrhIz74w75cBOBOofshrQ42ZbolCyAKoplScSV3NoMFyKpBZHXpvRZv0ZflBCs iFVTH4lsT7HcR8ZBoqLKoEsvOu4jgqjE4B6Am4cyhaLsX0eGUsygK1vhg1rhygIKDbjS jtRcoQP6ULUzfH33SsJguD0nK07RYe0YXuqG7/CVeXbbHSRfqvXxnHDPcbnHR9sHQIei pc3fKmX1NKP6tElmK58ef2Yf059y2Co0sA3C3g1tOtmP3m6qopOLBxsogiVpHDFyWIrK HXA0n2E2sP3GxzLfAAHZD6KtmB9Pr5SfLbdL0qdTT6yFGX/H6uRCDDW0TWXOKRaaIHxu pW5Q== X-Gm-Message-State: APjAAAWmyUWpLiA9X6FJ2FWIfCTuwz/IzLSTQdYbnTy9RDApdc9tgiCY EbfzaUGL4UmoGIs20LfrN+E85l3f X-Google-Smtp-Source: APXvYqz9saL7nA5sTLcA8nqhXE69zE8Gqp2pTb8A8s7/LTfbtoDeXQNEO790htc66v9jT4w98SUM9Q== X-Received: by 2002:ac8:7751:: with SMTP id g17mr5972802qtu.77.1582925319675; Fri, 28 Feb 2020 13:28:39 -0800 (PST) Received: from localhost ([2620:10d:c091:500::1:bc9d]) by smtp.gmail.com with ESMTPSA id l4sm5767977qkl.22.2020.02.28.13.28.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 28 Feb 2020 13:28:39 -0800 (PST) From: Jes Sorensen X-Google-Original-From: Jes Sorensen To: linux-fscrypt@vger.kernel.org Cc: kernel-team@fb.com, ebiggers@kernel.org, Jes Sorensen Subject: [PATCH 5/6] Create libfsverity_compute_digest() and adapt cmd_sign to use it Date: Fri, 28 Feb 2020 16:28:13 -0500 Message-Id: <20200228212814.105897-6-Jes.Sorensen@gmail.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200228212814.105897-1-Jes.Sorensen@gmail.com> References: <20200228212814.105897-1-Jes.Sorensen@gmail.com> MIME-Version: 1.0 Sender: linux-fscrypt-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fscrypt@vger.kernel.org From: Jes Sorensen This reorganizes the digest signing code and moves it to the shared library. Signed-off-by: Jes Sorensen --- cmd_sign.c | 194 +++--------------------------------------------- libverity.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 184 deletions(-) diff --git a/cmd_sign.c b/cmd_sign.c index 5ad4eda..6a5d185 100644 --- a/cmd_sign.c +++ b/cmd_sign.c @@ -16,12 +16,9 @@ #include #include #include -#include -#include #include "commands.h" #include "libfsverity.h" -#include "hash_algs.h" /* * Format in which verity file measurements are signed. This is the same as @@ -337,179 +334,6 @@ static bool write_signature(const char *filename, const u8 *sig, u32 sig_size) return ok; } -#define FS_VERITY_MAX_LEVELS 64 - -struct block_buffer { - u32 filled; - u8 *data; -}; - -/* - * Hash a block, writing the result to the next level's pending block buffer. - * Returns true if the next level's block became full, else false. - */ -static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur, - u32 block_size, const u8 *salt, u32 salt_size) -{ - struct block_buffer *next = cur + 1; - - /* Zero-pad the block if it's shorter than block_size. */ - memset(&cur->data[cur->filled], 0, block_size - cur->filled); - - hash_init(hash); - hash_update(hash, salt, salt_size); - hash_update(hash, cur->data, block_size); - hash_final(hash, &next->data[next->filled]); - - next->filled += hash->alg->digest_size; - cur->filled = 0; - - return next->filled + hash->alg->digest_size > block_size; -} - -static int full_read_fd(int fd, void *buf, size_t count) -{ - while (count) { - int n = read(fd, buf, min(count, INT_MAX)); - - if (n < 0) { - error_msg_errno("reading from file"); - return n; - } - if (n == 0) { - error_msg("unexpected end-of-file"); - return -ENODATA; - } - buf += n; - count -= n; - } - return 0; -} - -/* - * Compute the file's Merkle tree root hash using the given hash algorithm, - * block size, and salt. - */ -static bool compute_root_hash(int fd, u64 file_size, - struct hash_ctx *hash, u32 block_size, - const u8 *salt, u32 salt_size, u8 *root_hash) -{ - const u32 hashes_per_block = block_size / hash->alg->digest_size; - const u32 padded_salt_size = roundup(salt_size, hash->alg->block_size); - u8 *padded_salt = xzalloc(padded_salt_size); - u64 blocks; - int num_levels = 0; - int level; - struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {}; - struct block_buffer *buffers = &_buffers[1]; - u64 offset; - bool ok = false; - - if (salt_size != 0) - memcpy(padded_salt, salt, salt_size); - - /* Compute number of levels */ - for (blocks = DIV_ROUND_UP(file_size, block_size); blocks > 1; - blocks = DIV_ROUND_UP(blocks, hashes_per_block)) { - ASSERT(num_levels < FS_VERITY_MAX_LEVELS); - num_levels++; - } - - /* - * Allocate the block buffers. Buffer "-1" is for data blocks. - * Buffers 0 <= level < num_levels are for the actual tree levels. - * Buffer 'num_levels' is for the root hash. - */ - for (level = -1; level < num_levels; level++) - buffers[level].data = xmalloc(block_size); - buffers[num_levels].data = root_hash; - - /* Hash each data block, also hashing the tree blocks as they fill up */ - for (offset = 0; offset < file_size; offset += block_size) { - buffers[-1].filled = min(block_size, file_size - offset); - - if (full_read_fd(fd, buffers[-1].data, buffers[-1].filled)) - goto out; - - level = -1; - while (hash_one_block(hash, &buffers[level], block_size, - padded_salt, padded_salt_size)) { - level++; - ASSERT(level < num_levels); - } - } - /* Finish all nonempty pending tree blocks */ - for (level = 0; level < num_levels; level++) { - if (buffers[level].filled != 0) - hash_one_block(hash, &buffers[level], block_size, - padded_salt, padded_salt_size); - } - - /* Root hash was filled by the last call to hash_one_block() */ - ASSERT(buffers[num_levels].filled == hash->alg->digest_size); - ok = true; -out: - for (level = -1; level < num_levels; level++) - free(buffers[level].data); - free(padded_salt); - return ok; -} - -/* - * Compute the fs-verity measurement of the given file. - * - * The fs-verity measurement is the hash of the fsverity_descriptor, which - * contains the Merkle tree properties including the root hash. - */ -static bool compute_file_measurement(int fd, - const struct fsverity_hash_alg *hash_alg, - u32 block_size, const u8 *salt, - u32 salt_size, u8 *measurement) -{ - struct hash_ctx *hash = hash_alg->create_ctx(hash_alg); - u64 file_size; - struct fsverity_descriptor desc; - struct stat stbuf; - bool ok = false; - - if (fstat(fd, &stbuf) != 0) { - error_msg_errno("can't stat input file"); - goto out; - } - file_size = stbuf.st_size; - - memset(&desc, 0, sizeof(desc)); - desc.version = 1; - desc.hash_algorithm = hash_alg->hash_num; - - ASSERT(is_power_of_2(block_size)); - desc.log_blocksize = ilog2(block_size); - - if (salt_size != 0) { - if (salt_size > sizeof(desc.salt)) { - error_msg("Salt too long (got %u bytes; max is %zu bytes)", - salt_size, sizeof(desc.salt)); - goto out; - } - memcpy(desc.salt, salt, salt_size); - desc.salt_size = salt_size; - } - - desc.data_size = cpu_to_le64(file_size); - - /* Root hash of empty file is all 0's */ - if (file_size != 0 && - !compute_root_hash(fd, file_size, hash, block_size, salt, - salt_size, desc.root_hash)) - goto out; - - hash_full(hash, &desc, sizeof(desc), measurement); - ok = true; -out: - hash_free(hash); - return ok; -} - enum { OPT_HASH_ALG, OPT_BLOCK_SIZE, @@ -538,7 +362,8 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd, u32 salt_size = 0; const char *keyfile = NULL; const char *certfile = NULL; - struct fsverity_signed_digest *digest = NULL; + struct libfsverity_digest *digest = NULL; + struct libfsverity_merkle_tree_params params; char digest_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + 1]; u8 *sig = NULL; u32 sig_size; @@ -608,16 +433,17 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd, if (certfile == NULL) certfile = keyfile; - digest = xzalloc(sizeof(*digest) + hash_alg->digest_size); - memcpy(digest->magic, "FSVerity", 8); - digest->digest_algorithm = cpu_to_le16(hash_alg->hash_num); - digest->digest_size = cpu_to_le16(hash_alg->digest_size); - if (!open_file(&file, argv[0], O_RDONLY, 0)) goto out_err; - if (!compute_file_measurement(file.fd, hash_alg, block_size, - salt, salt_size, digest->digest)) + memset(¶ms, 0, sizeof(struct libfsverity_merkle_tree_params)); + params.version = 1; + params.hash_algorithm = hash_alg->hash_num; + params.block_size = block_size; + params.salt_size = salt_size; + params.salt = salt; + + if (libfsverity_compute_digest(file.fd, ¶ms, &digest)) goto out_err; filedes_close(&file); diff --git a/libverity.c b/libverity.c index 6821aa2..4f01992 100644 --- a/libverity.c +++ b/libverity.c @@ -8,3 +8,210 @@ * Written by Eric Biggers and Jes Sorensen. */ +#include +#include +#include +#include +#include +#include +#include + +#include "commands.h" +#include "libfsverity.h" +#include "hash_algs.h" + +#define FS_VERITY_MAX_LEVELS 64 + +struct block_buffer { + u32 filled; + u8 *data; +}; + +/* + * Hash a block, writing the result to the next level's pending block buffer. + * Returns true if the next level's block became full, else false. + */ +static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur, + u32 block_size, const u8 *salt, u32 salt_size) +{ + struct block_buffer *next = cur + 1; + + /* Zero-pad the block if it's shorter than block_size. */ + memset(&cur->data[cur->filled], 0, block_size - cur->filled); + + hash_init(hash); + hash_update(hash, salt, salt_size); + hash_update(hash, cur->data, block_size); + hash_final(hash, &next->data[next->filled]); + + next->filled += hash->alg->digest_size; + cur->filled = 0; + + return next->filled + hash->alg->digest_size > block_size; +} + +static int full_read_fd(int fd, void *buf, size_t count) +{ + while (count) { + int n = read(fd, buf, min(count, INT_MAX)); + + if (n < 0) { + error_msg_errno("reading from file"); + return n; + } + if (n == 0) { + error_msg("unexpected end-of-file"); + return -ENODATA; + } + buf += n; + count -= n; + } + return 0; +} + +/* + * Compute the file's Merkle tree root hash using the given hash algorithm, + * block size, and salt. + */ +static bool compute_root_hash(int fd, u64 file_size, + struct hash_ctx *hash, u32 block_size, + const u8 *salt, u32 salt_size, u8 *root_hash) +{ + const u32 hashes_per_block = block_size / hash->alg->digest_size; + const u32 padded_salt_size = roundup(salt_size, hash->alg->block_size); + u8 *padded_salt = xzalloc(padded_salt_size); + u64 blocks; + int num_levels = 0; + int level; + struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {}; + struct block_buffer *buffers = &_buffers[1]; + u64 offset; + bool ok = false; + + if (salt_size != 0) + memcpy(padded_salt, salt, salt_size); + + /* Compute number of levels */ + for (blocks = DIV_ROUND_UP(file_size, block_size); blocks > 1; + blocks = DIV_ROUND_UP(blocks, hashes_per_block)) { + ASSERT(num_levels < FS_VERITY_MAX_LEVELS); + num_levels++; + } + + /* + * Allocate the block buffers. Buffer "-1" is for data blocks. + * Buffers 0 <= level < num_levels are for the actual tree levels. + * Buffer 'num_levels' is for the root hash. + */ + for (level = -1; level < num_levels; level++) + buffers[level].data = xmalloc(block_size); + buffers[num_levels].data = root_hash; + + /* Hash each data block, also hashing the tree blocks as they fill up */ + for (offset = 0; offset < file_size; offset += block_size) { + buffers[-1].filled = min(block_size, file_size - offset); + + if (full_read_fd(fd, buffers[-1].data, buffers[-1].filled)) + goto out; + + level = -1; + while (hash_one_block(hash, &buffers[level], block_size, + padded_salt, padded_salt_size)) { + level++; + ASSERT(level < num_levels); + } + } + /* Finish all nonempty pending tree blocks */ + for (level = 0; level < num_levels; level++) { + if (buffers[level].filled != 0) + hash_one_block(hash, &buffers[level], block_size, + padded_salt, padded_salt_size); + } + + /* Root hash was filled by the last call to hash_one_block() */ + ASSERT(buffers[num_levels].filled == hash->alg->digest_size); + ok = true; +out: + for (level = -1; level < num_levels; level++) + free(buffers[level].data); + free(padded_salt); + return ok; +} + +/* + * Compute the fs-verity measurement of the given file. + * + * The fs-verity measurement is the hash of the fsverity_descriptor, which + * contains the Merkle tree properties including the root hash. + */ +int +libfsverity_compute_digest(int fd, + const struct libfsverity_merkle_tree_params *params, + struct libfsverity_digest **digest_ret) +{ + const struct fsverity_hash_alg *hash_alg; + struct libfsverity_digest *digest; + struct hash_ctx *hash; + struct fsverity_descriptor desc; + struct stat stbuf; + u64 file_size; + int retval = -EINVAL; + + hash_alg = libfsverity_find_hash_alg_by_num(params->hash_algorithm); + hash = hash_alg->create_ctx(hash_alg); + + digest = malloc(sizeof(struct libfsverity_digest) + + hash_alg->digest_size); + if (!digest_ret) + return -ENOMEM; + memcpy(digest->magic, "FSVerity", 8); + digest->digest_algorithm = cpu_to_le16(hash_alg->hash_num); + digest->digest_size = cpu_to_le16(hash_alg->digest_size); + memset(digest->digest, 0, hash_alg->digest_size); + + if (fstat(fd, &stbuf) != 0) { + error_msg_errno("can't stat input file"); + retval = -EBADF; + goto error_out; + } + file_size = stbuf.st_size; + + memset(&desc, 0, sizeof(desc)); + desc.version = 1; + desc.hash_algorithm = params->hash_algorithm; + + ASSERT(is_power_of_2(params->block_size)); + desc.log_blocksize = ilog2(params->block_size); + + if (params->salt_size != 0) { + if (params->salt_size > sizeof(desc.salt)) { + error_msg("Salt too long (got %u bytes; max is %zu bytes)", + params->salt_size, sizeof(desc.salt)); + retval = EINVAL; + goto error_out; + } + memcpy(desc.salt, params->salt, params->salt_size); + desc.salt_size = params->salt_size; + } + + desc.data_size = cpu_to_le64(file_size); + + /* Root hash of empty file is all 0's */ + if (file_size != 0 && + !compute_root_hash(fd, file_size, hash, params->block_size, + params->salt, params->salt_size, + desc.root_hash)) { + retval = -EAGAIN; + goto error_out; + } + + hash_full(hash, &desc, sizeof(desc), digest->digest); + hash_free(hash); + *digest_ret = digest; + + return 0; + + error_out: + free(digest); + return retval; +}