diff options
Diffstat (limited to 'fs/incfs/integrity.c')
-rw-r--r-- | fs/incfs/integrity.c | 213 |
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 | |||
13 | int 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 | |||
72 | out: | ||
73 | pkcs7_free_message(pkcs7); | ||
74 | return err; | ||
75 | } | ||
76 | |||
77 | struct 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 | |||
117 | struct 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 | |||
179 | err: | ||
180 | kfree(result); | ||
181 | return ERR_PTR(-E2BIG); | ||
182 | } | ||
183 | |||
184 | void incfs_free_mtree(struct mtree *tree) | ||
185 | { | ||
186 | kfree(tree); | ||
187 | } | ||
188 | |||
189 | int 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 | |||
204 | void 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 | |||