blob: feb212c38945721140b3147726080c14de1c6f4f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 Google LLC
*/
#include <crypto/sha.h>
#include <crypto/hash.h>
#include <linux/err.h>
#include <linux/version.h>
#include <crypto/pkcs7.h>
#include "integrity.h"
int incfs_validate_pkcs7_signature(struct mem_range pkcs7_blob,
struct mem_range root_hash, struct mem_range add_data)
{
struct pkcs7_message *pkcs7 = NULL;
const void *data = NULL;
size_t data_len = 0;
const char *p;
int err;
pkcs7 = pkcs7_parse_message(pkcs7_blob.data, pkcs7_blob.len);
if (IS_ERR(pkcs7)) {
pr_debug("PKCS#7 parsing error. ptr=%p size=%ld err=%ld\n",
pkcs7_blob.data, pkcs7_blob.len, -PTR_ERR(pkcs7));
return PTR_ERR(pkcs7);
}
err = pkcs7_get_content_data(pkcs7, &data, &data_len, NULL);
if (err || data_len == 0 || data == NULL) {
pr_debug("PKCS#7 message does not contain data\n");
err = -EBADMSG;
goto out;
}
if (root_hash.len == 0) {
pr_debug("Root hash is empty.\n");
err = -EBADMSG;
goto out;
}
if (data_len != root_hash.len + add_data.len) {
pr_debug("PKCS#7 data size doesn't match arguments.\n");
err = -EKEYREJECTED;
goto out;
}
p = data;
if (memcmp(p, root_hash.data, root_hash.len) != 0) {
pr_debug("Root hash mismatch.\n");
err = -EKEYREJECTED;
goto out;
}
p += root_hash.len;
if (memcmp(p, add_data.data, add_data.len) != 0) {
pr_debug("Additional data mismatch.\n");
err = -EKEYREJECTED;
goto out;
}
err = pkcs7_verify(pkcs7, VERIFYING_UNSPECIFIED_SIGNATURE);
if (err)
pr_debug("PKCS#7 signature verification error: %d\n", -err);
/*
* RSA signature verification sometimes returns unexpected error codes
* when signature doesn't match.
*/
if (err == -ERANGE || err == -EINVAL)
err = -EBADMSG;
out:
pkcs7_free_message(pkcs7);
return err;
}
struct incfs_hash_alg *incfs_get_hash_alg(enum incfs_hash_tree_algorithm id)
{
static struct incfs_hash_alg sha256 = {
.name = "sha256",
.digest_size = SHA256_DIGEST_SIZE,
.id = INCFS_HASH_TREE_SHA256
};
struct incfs_hash_alg *result = NULL;
struct crypto_shash *shash;
if (id == INCFS_HASH_TREE_SHA256) {
BUILD_BUG_ON(INCFS_MAX_HASH_SIZE < SHA256_DIGEST_SIZE);
result = &sha256;
}
if (result == NULL)
return ERR_PTR(-ENOENT);
/* pairs with cmpxchg_release() below */
shash = smp_load_acquire(&result->shash);
if (shash)
return result;
shash = crypto_alloc_shash(result->name, 0, 0);
if (IS_ERR(shash)) {
int err = PTR_ERR(shash);
pr_err("Can't allocate hash alg %s, error code:%d",
result->name, err);
return ERR_PTR(err);
}
/* pairs with smp_load_acquire() above */
if (cmpxchg_release(&result->shash, NULL, shash) != NULL)
crypto_free_shash(shash);
return result;
}
struct mtree *incfs_alloc_mtree(enum incfs_hash_tree_algorithm id,
int data_block_count,
struct mem_range root_hash)
{
struct mtree *result = NULL;
struct incfs_hash_alg *hash_alg = NULL;
int hash_per_block;
int lvl;
int total_blocks = 0;
int blocks_in_level[INCFS_MAX_MTREE_LEVELS];
int blocks = data_block_count;
if (data_block_count <= 0)
return ERR_PTR(-EINVAL);
hash_alg = incfs_get_hash_alg(id);
if (IS_ERR(hash_alg))
return ERR_PTR(PTR_ERR(hash_alg));
if (root_hash.len < hash_alg->digest_size)
return ERR_PTR(-EINVAL);
result = kzalloc(sizeof(*result), GFP_NOFS);
if (!result)
return ERR_PTR(-ENOMEM);
result->alg = hash_alg;
hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / result->alg->digest_size;
/* Calculating tree geometry. */
/* First pass: calculate how many blocks in each tree level. */
for (lvl = 0; blocks > 1; lvl++) {
if (lvl >= INCFS_MAX_MTREE_LEVELS) {
pr_err("incfs: too much data in mtree");
goto err;
}
blocks = (blocks + hash_per_block - 1) / hash_per_block;
blocks_in_level[lvl] = blocks;
total_blocks += blocks;
}
result->depth = lvl;
result->hash_tree_area_size = total_blocks * INCFS_DATA_FILE_BLOCK_SIZE;
if (result->hash_tree_area_size > INCFS_MAX_HASH_AREA_SIZE)
goto err;
blocks = 0;
/* Second pass: calculate offset of each level. 0th level goes last. */
for (lvl = 0; lvl < result->depth; lvl++) {
u32 suboffset;
blocks += blocks_in_level[lvl];
suboffset = (total_blocks - blocks)
* INCFS_DATA_FILE_BLOCK_SIZE;
result->hash_level_suboffset[lvl] = suboffset;
}
/* Root hash is stored separately from the rest of the tree. */
memcpy(result->root_hash, root_hash.data, hash_alg->digest_size);
return result;
err:
kfree(result);
return ERR_PTR(-E2BIG);
}
void incfs_free_mtree(struct mtree *tree)
{
kfree(tree);
}
int incfs_calc_digest(struct incfs_hash_alg *alg, struct mem_range data,
struct mem_range digest)
{
SHASH_DESC_ON_STACK(desc, alg->shash);
if (!alg || !alg->shash || !data.data || !digest.data)
return -EFAULT;
if (alg->digest_size > digest.len)
return -EINVAL;
desc->tfm = alg->shash;
return crypto_shash_digest(desc, data.data, data.len, digest.data);
}
void incfs_free_signature_info(struct signature_info *si)
{
if (!si)
return;
kfree(si->root_hash.data);
kfree(si->additional_data.data);
kfree(si->signature.data);
kfree(si);
}