aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'fs/incfs/integrity.c')
-rw-r--r--fs/incfs/integrity.c213
1 files changed, 213 insertions, 0 deletions
diff --git a/fs/incfs/integrity.c b/fs/incfs/integrity.c
new file mode 100644
index 000000000000..feb212c38945
--- /dev/null
+++ b/fs/incfs/integrity.c
@@ -0,0 +1,213 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright 2019 Google LLC
4 */
5#include <crypto/sha.h>
6#include <crypto/hash.h>
7#include <linux/err.h>
8#include <linux/version.h>
9#include <crypto/pkcs7.h>
10
11#include "integrity.h"
12
13int incfs_validate_pkcs7_signature(struct mem_range pkcs7_blob,
14 struct mem_range root_hash, struct mem_range add_data)
15{
16 struct pkcs7_message *pkcs7 = NULL;
17 const void *data = NULL;
18 size_t data_len = 0;
19 const char *p;
20 int err;
21
22 pkcs7 = pkcs7_parse_message(pkcs7_blob.data, pkcs7_blob.len);
23 if (IS_ERR(pkcs7)) {
24 pr_debug("PKCS#7 parsing error. ptr=%p size=%ld err=%ld\n",
25 pkcs7_blob.data, pkcs7_blob.len, -PTR_ERR(pkcs7));
26 return PTR_ERR(pkcs7);
27 }
28
29 err = pkcs7_get_content_data(pkcs7, &data, &data_len, NULL);
30 if (err || data_len == 0 || data == NULL) {
31 pr_debug("PKCS#7 message does not contain data\n");
32 err = -EBADMSG;
33 goto out;
34 }
35
36 if (root_hash.len == 0) {
37 pr_debug("Root hash is empty.\n");
38 err = -EBADMSG;
39 goto out;
40 }
41
42 if (data_len != root_hash.len + add_data.len) {
43 pr_debug("PKCS#7 data size doesn't match arguments.\n");
44 err = -EKEYREJECTED;
45 goto out;
46 }
47
48 p = data;
49 if (memcmp(p, root_hash.data, root_hash.len) != 0) {
50 pr_debug("Root hash mismatch.\n");
51 err = -EKEYREJECTED;
52 goto out;
53 }
54 p += root_hash.len;
55 if (memcmp(p, add_data.data, add_data.len) != 0) {
56 pr_debug("Additional data mismatch.\n");
57 err = -EKEYREJECTED;
58 goto out;
59 }
60
61 err = pkcs7_verify(pkcs7, VERIFYING_UNSPECIFIED_SIGNATURE);
62 if (err)
63 pr_debug("PKCS#7 signature verification error: %d\n", -err);
64
65 /*
66 * RSA signature verification sometimes returns unexpected error codes
67 * when signature doesn't match.
68 */
69 if (err == -ERANGE || err == -EINVAL)
70 err = -EBADMSG;
71
72out:
73 pkcs7_free_message(pkcs7);
74 return err;
75}
76
77struct incfs_hash_alg *incfs_get_hash_alg(enum incfs_hash_tree_algorithm id)
78{
79 static struct incfs_hash_alg sha256 = {
80 .name = "sha256",
81 .digest_size = SHA256_DIGEST_SIZE,
82 .id = INCFS_HASH_TREE_SHA256
83 };
84 struct incfs_hash_alg *result = NULL;
85 struct crypto_shash *shash;
86
87 if (id == INCFS_HASH_TREE_SHA256) {
88 BUILD_BUG_ON(INCFS_MAX_HASH_SIZE < SHA256_DIGEST_SIZE);
89 result = &sha256;
90 }
91
92 if (result == NULL)
93 return ERR_PTR(-ENOENT);
94
95 /* pairs with cmpxchg_release() below */
96 shash = smp_load_acquire(&result->shash);
97 if (shash)
98 return result;
99
100 shash = crypto_alloc_shash(result->name, 0, 0);
101 if (IS_ERR(shash)) {
102 int err = PTR_ERR(shash);
103
104 pr_err("Can't allocate hash alg %s, error code:%d",
105 result->name, err);
106 return ERR_PTR(err);
107 }
108
109 /* pairs with smp_load_acquire() above */
110 if (cmpxchg_release(&result->shash, NULL, shash) != NULL)
111 crypto_free_shash(shash);
112
113 return result;
114}
115
116
117struct mtree *incfs_alloc_mtree(enum incfs_hash_tree_algorithm id,
118 int data_block_count,
119 struct mem_range root_hash)
120{
121 struct mtree *result = NULL;
122 struct incfs_hash_alg *hash_alg = NULL;
123 int hash_per_block;
124 int lvl;
125 int total_blocks = 0;
126 int blocks_in_level[INCFS_MAX_MTREE_LEVELS];
127 int blocks = data_block_count;
128
129 if (data_block_count <= 0)
130 return ERR_PTR(-EINVAL);
131
132 hash_alg = incfs_get_hash_alg(id);
133 if (IS_ERR(hash_alg))
134 return ERR_PTR(PTR_ERR(hash_alg));
135
136 if (root_hash.len < hash_alg->digest_size)
137 return ERR_PTR(-EINVAL);
138
139 result = kzalloc(sizeof(*result), GFP_NOFS);
140 if (!result)
141 return ERR_PTR(-ENOMEM);
142
143 result->alg = hash_alg;
144 hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / result->alg->digest_size;
145
146 /* Calculating tree geometry. */
147 /* First pass: calculate how many blocks in each tree level. */
148 for (lvl = 0; blocks > 1; lvl++) {
149 if (lvl >= INCFS_MAX_MTREE_LEVELS) {
150 pr_err("incfs: too much data in mtree");
151 goto err;
152 }
153
154 blocks = (blocks + hash_per_block - 1) / hash_per_block;
155 blocks_in_level[lvl] = blocks;
156 total_blocks += blocks;
157 }
158 result->depth = lvl;
159 result->hash_tree_area_size = total_blocks * INCFS_DATA_FILE_BLOCK_SIZE;
160 if (result->hash_tree_area_size > INCFS_MAX_HASH_AREA_SIZE)
161 goto err;
162
163 blocks = 0;
164 /* Second pass: calculate offset of each level. 0th level goes last. */
165 for (lvl = 0; lvl < result->depth; lvl++) {
166 u32 suboffset;
167
168 blocks += blocks_in_level[lvl];
169 suboffset = (total_blocks - blocks)
170 * INCFS_DATA_FILE_BLOCK_SIZE;
171
172 result->hash_level_suboffset[lvl] = suboffset;
173 }
174
175 /* Root hash is stored separately from the rest of the tree. */
176 memcpy(result->root_hash, root_hash.data, hash_alg->digest_size);
177 return result;
178
179err:
180 kfree(result);
181 return ERR_PTR(-E2BIG);
182}
183
184void incfs_free_mtree(struct mtree *tree)
185{
186 kfree(tree);
187}
188
189int incfs_calc_digest(struct incfs_hash_alg *alg, struct mem_range data,
190 struct mem_range digest)
191{
192 SHASH_DESC_ON_STACK(desc, alg->shash);
193
194 if (!alg || !alg->shash || !data.data || !digest.data)
195 return -EFAULT;
196
197 if (alg->digest_size > digest.len)
198 return -EINVAL;
199
200 desc->tfm = alg->shash;
201 return crypto_shash_digest(desc, data.data, data.len, digest.data);
202}
203
204void incfs_free_signature_info(struct signature_info *si)
205{
206 if (!si)
207 return;
208 kfree(si->root_hash.data);
209 kfree(si->additional_data.data);
210 kfree(si->signature.data);
211 kfree(si);
212}
213