]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
60083261 DE |
2 | /* |
3 | * (C) Copyright 2013 | |
4 | * Reinhard Pfau, Guntermann & Drunck GmbH, [email protected] | |
60083261 DE |
5 | */ |
6 | ||
7 | #include <common.h> | |
8 | #include <malloc.h> | |
9 | #include <fs.h> | |
10 | #include <i2c.h> | |
11 | #include <mmc.h> | |
d677bfe2 | 12 | #include <tpm-v1.h> |
60083261 DE |
13 | #include <u-boot/sha1.h> |
14 | #include <asm/byteorder.h> | |
15 | #include <asm/unaligned.h> | |
16 | #include <pca9698.h> | |
17 | ||
18 | #include "hre.h" | |
19 | ||
20 | /* other constants */ | |
21 | enum { | |
22 | ESDHC_BOOT_IMAGE_SIG_OFS = 0x40, | |
23 | ESDHC_BOOT_IMAGE_SIZE_OFS = 0x48, | |
24 | ESDHC_BOOT_IMAGE_ADDR_OFS = 0x50, | |
25 | ESDHC_BOOT_IMAGE_TARGET_OFS = 0x58, | |
26 | ESDHC_BOOT_IMAGE_ENTRY_OFS = 0x60, | |
27 | }; | |
28 | ||
29 | enum { | |
30 | I2C_SOC_0 = 0, | |
31 | I2C_SOC_1 = 1, | |
32 | }; | |
33 | ||
34 | enum access_mode { | |
35 | HREG_NONE = 0, | |
36 | HREG_RD = 1, | |
37 | HREG_WR = 2, | |
38 | HREG_RDWR = 3, | |
39 | }; | |
40 | ||
41 | /* register constants */ | |
42 | enum { | |
43 | FIX_HREG_DEVICE_ID_HASH = 0, | |
44 | FIX_HREG_UNUSED1 = 1, | |
45 | FIX_HREG_UNUSED2 = 2, | |
46 | FIX_HREG_VENDOR = 3, | |
47 | COUNT_FIX_HREGS | |
48 | }; | |
49 | ||
50 | static struct h_reg pcr_hregs[24]; | |
51 | static struct h_reg fix_hregs[COUNT_FIX_HREGS]; | |
52 | static struct h_reg var_hregs[8]; | |
53 | ||
54 | /* hre opcodes */ | |
55 | enum { | |
56 | /* opcodes w/o data */ | |
57 | HRE_NOP = 0x00, | |
58 | HRE_SYNC = HRE_NOP, | |
59 | HRE_CHECK0 = 0x01, | |
60 | /* opcodes w/o data, w/ sync dst */ | |
61 | /* opcodes w/ data */ | |
62 | HRE_LOAD = 0x81, | |
63 | /* opcodes w/data, w/sync dst */ | |
64 | HRE_XOR = 0xC1, | |
65 | HRE_AND = 0xC2, | |
66 | HRE_OR = 0xC3, | |
67 | HRE_EXTEND = 0xC4, | |
68 | HRE_LOADKEY = 0xC5, | |
69 | }; | |
70 | ||
71 | /* hre errors */ | |
72 | enum { | |
73 | HRE_E_OK = 0, | |
74 | HRE_E_TPM_FAILURE, | |
75 | HRE_E_INVALID_HREG, | |
76 | }; | |
77 | ||
78 | static uint64_t device_id; | |
79 | static uint64_t device_cl; | |
80 | static uint64_t device_type; | |
81 | ||
82 | static uint32_t platform_key_handle; | |
83 | ||
84 | static uint32_t hre_tpm_err; | |
85 | static int hre_err = HRE_E_OK; | |
86 | ||
87 | #define IS_PCR_HREG(spec) ((spec) & 0x20) | |
88 | #define IS_FIX_HREG(spec) (((spec) & 0x38) == 0x08) | |
89 | #define IS_VAR_HREG(spec) (((spec) & 0x38) == 0x10) | |
90 | #define HREG_IDX(spec) ((spec) & (IS_PCR_HREG(spec) ? 0x1f : 0x7)) | |
91 | ||
92 | static const uint8_t vendor[] = "Guntermann & Drunck"; | |
93 | ||
94 | /** | |
95 | * @brief get the size of a given (TPM) NV area | |
96 | * @param index NV index of the area to get size for | |
97 | * @param size pointer to the size | |
98 | * @return 0 on success, != 0 on error | |
99 | */ | |
100 | static int get_tpm_nv_size(uint32_t index, uint32_t *size) | |
101 | { | |
102 | uint32_t err; | |
103 | uint8_t info[72]; | |
104 | uint8_t *ptr; | |
105 | uint16_t v16; | |
106 | ||
107 | err = tpm_get_capability(TPM_CAP_NV_INDEX, index, | |
108 | info, sizeof(info)); | |
109 | if (err) { | |
110 | printf("tpm_get_capability(CAP_NV_INDEX, %08x) failed: %u\n", | |
111 | index, err); | |
112 | return 1; | |
113 | } | |
114 | ||
115 | /* skip tag and nvIndex */ | |
116 | ptr = info + 6; | |
117 | /* skip 2 pcr info fields */ | |
118 | v16 = get_unaligned_be16(ptr); | |
119 | ptr += 2 + v16 + 1 + 20; | |
120 | v16 = get_unaligned_be16(ptr); | |
121 | ptr += 2 + v16 + 1 + 20; | |
122 | /* skip permission and flags */ | |
123 | ptr += 6 + 3; | |
124 | ||
125 | *size = get_unaligned_be32(ptr); | |
126 | return 0; | |
127 | } | |
128 | ||
129 | /** | |
130 | * @brief search for a key by usage auth and pub key hash. | |
131 | * @param auth usage auth of the key to search for | |
132 | * @param pubkey_digest (SHA1) hash of the pub key structure of the key | |
133 | * @param[out] handle the handle of the key iff found | |
134 | * @return 0 if key was found in TPM; != 0 if not. | |
135 | */ | |
136 | static int find_key(const uint8_t auth[20], const uint8_t pubkey_digest[20], | |
137 | uint32_t *handle) | |
138 | { | |
139 | uint16_t key_count; | |
140 | uint32_t key_handles[10]; | |
141 | uint8_t buf[288]; | |
142 | uint8_t *ptr; | |
143 | uint32_t err; | |
144 | uint8_t digest[20]; | |
145 | size_t buf_len; | |
146 | unsigned int i; | |
147 | ||
148 | /* fetch list of already loaded keys in the TPM */ | |
149 | err = tpm_get_capability(TPM_CAP_HANDLE, TPM_RT_KEY, buf, sizeof(buf)); | |
150 | if (err) | |
151 | return -1; | |
152 | key_count = get_unaligned_be16(buf); | |
153 | ptr = buf + 2; | |
154 | for (i = 0; i < key_count; ++i, ptr += 4) | |
155 | key_handles[i] = get_unaligned_be32(ptr); | |
156 | ||
157 | /* now search a(/ the) key which we can access with the given auth */ | |
158 | for (i = 0; i < key_count; ++i) { | |
159 | buf_len = sizeof(buf); | |
160 | err = tpm_get_pub_key_oiap(key_handles[i], auth, buf, &buf_len); | |
161 | if (err && err != TPM_AUTHFAIL) | |
162 | return -1; | |
163 | if (err) | |
164 | continue; | |
165 | sha1_csum(buf, buf_len, digest); | |
166 | if (!memcmp(digest, pubkey_digest, 20)) { | |
167 | *handle = key_handles[i]; | |
168 | return 0; | |
169 | } | |
170 | } | |
171 | return 1; | |
172 | } | |
173 | ||
174 | /** | |
175 | * @brief read CCDM common data from TPM NV | |
176 | * @return 0 if CCDM common data was found and read, !=0 if something failed. | |
177 | */ | |
178 | static int read_common_data(void) | |
179 | { | |
180 | uint32_t size = 0; | |
181 | uint32_t err; | |
182 | uint8_t buf[256]; | |
183 | sha1_context ctx; | |
184 | ||
185 | if (get_tpm_nv_size(NV_COMMON_DATA_INDEX, &size) || | |
186 | size < NV_COMMON_DATA_MIN_SIZE) | |
187 | return 1; | |
188 | err = tpm_nv_read_value(NV_COMMON_DATA_INDEX, | |
189 | buf, min(sizeof(buf), size)); | |
190 | if (err) { | |
191 | printf("tpm_nv_read_value() failed: %u\n", err); | |
192 | return 1; | |
193 | } | |
194 | ||
195 | device_id = get_unaligned_be64(buf); | |
196 | device_cl = get_unaligned_be64(buf + 8); | |
197 | device_type = get_unaligned_be64(buf + 16); | |
198 | ||
199 | sha1_starts(&ctx); | |
200 | sha1_update(&ctx, buf, 24); | |
201 | sha1_finish(&ctx, fix_hregs[FIX_HREG_DEVICE_ID_HASH].digest); | |
202 | fix_hregs[FIX_HREG_DEVICE_ID_HASH].valid = true; | |
203 | ||
204 | platform_key_handle = get_unaligned_be32(buf + 24); | |
205 | ||
206 | return 0; | |
207 | } | |
208 | ||
209 | /** | |
210 | * @brief get pointer to hash register by specification | |
211 | * @param spec specification of a hash register | |
212 | * @return pointer to hash register or NULL if @a spec does not qualify a | |
213 | * valid hash register; NULL else. | |
214 | */ | |
215 | static struct h_reg *get_hreg(uint8_t spec) | |
216 | { | |
217 | uint8_t idx; | |
218 | ||
219 | idx = HREG_IDX(spec); | |
220 | if (IS_FIX_HREG(spec)) { | |
221 | if (idx < ARRAY_SIZE(fix_hregs)) | |
222 | return fix_hregs + idx; | |
223 | hre_err = HRE_E_INVALID_HREG; | |
224 | } else if (IS_PCR_HREG(spec)) { | |
225 | if (idx < ARRAY_SIZE(pcr_hregs)) | |
226 | return pcr_hregs + idx; | |
227 | hre_err = HRE_E_INVALID_HREG; | |
228 | } else if (IS_VAR_HREG(spec)) { | |
229 | if (idx < ARRAY_SIZE(var_hregs)) | |
230 | return var_hregs + idx; | |
231 | hre_err = HRE_E_INVALID_HREG; | |
232 | } | |
233 | return NULL; | |
234 | } | |
235 | ||
236 | /** | |
237 | * @brief get pointer of a hash register by specification and usage. | |
238 | * @param spec specification of a hash register | |
239 | * @param mode access mode (read or write or read/write) | |
240 | * @return pointer to hash register if found and valid; NULL else. | |
241 | * | |
242 | * This func uses @a get_reg() to determine the hash register for a given spec. | |
243 | * If a register is found it is validated according to the desired access mode. | |
244 | * The value of automatic registers (PCR register and fixed registers) is | |
245 | * loaded or computed on read access. | |
246 | */ | |
247 | static struct h_reg *access_hreg(uint8_t spec, enum access_mode mode) | |
248 | { | |
249 | struct h_reg *result; | |
250 | ||
251 | result = get_hreg(spec); | |
252 | if (!result) | |
253 | return NULL; | |
254 | ||
255 | if (mode & HREG_WR) { | |
256 | if (IS_FIX_HREG(spec)) { | |
257 | hre_err = HRE_E_INVALID_HREG; | |
258 | return NULL; | |
259 | } | |
260 | } | |
261 | if (mode & HREG_RD) { | |
262 | if (!result->valid) { | |
263 | if (IS_PCR_HREG(spec)) { | |
264 | hre_tpm_err = tpm_pcr_read(HREG_IDX(spec), | |
265 | result->digest, 20); | |
266 | result->valid = (hre_tpm_err == TPM_SUCCESS); | |
267 | } else if (IS_FIX_HREG(spec)) { | |
268 | switch (HREG_IDX(spec)) { | |
269 | case FIX_HREG_DEVICE_ID_HASH: | |
270 | read_common_data(); | |
271 | break; | |
272 | case FIX_HREG_VENDOR: | |
273 | memcpy(result->digest, vendor, 20); | |
274 | result->valid = true; | |
275 | break; | |
276 | } | |
277 | } else { | |
278 | result->valid = true; | |
279 | } | |
280 | } | |
281 | if (!result->valid) { | |
282 | hre_err = HRE_E_INVALID_HREG; | |
283 | return NULL; | |
284 | } | |
285 | } | |
286 | ||
287 | return result; | |
288 | } | |
289 | ||
290 | static void *compute_and(void *_dst, const void *_src, size_t n) | |
291 | { | |
292 | uint8_t *dst = _dst; | |
293 | const uint8_t *src = _src; | |
294 | size_t i; | |
295 | ||
296 | for (i = n; i-- > 0; ) | |
297 | *dst++ &= *src++; | |
298 | ||
299 | return _dst; | |
300 | } | |
301 | ||
302 | static void *compute_or(void *_dst, const void *_src, size_t n) | |
303 | { | |
304 | uint8_t *dst = _dst; | |
305 | const uint8_t *src = _src; | |
306 | size_t i; | |
307 | ||
308 | for (i = n; i-- > 0; ) | |
309 | *dst++ |= *src++; | |
310 | ||
311 | return _dst; | |
312 | } | |
313 | ||
314 | static void *compute_xor(void *_dst, const void *_src, size_t n) | |
315 | { | |
316 | uint8_t *dst = _dst; | |
317 | const uint8_t *src = _src; | |
318 | size_t i; | |
319 | ||
320 | for (i = n; i-- > 0; ) | |
321 | *dst++ ^= *src++; | |
322 | ||
323 | return _dst; | |
324 | } | |
325 | ||
326 | static void *compute_extend(void *_dst, const void *_src, size_t n) | |
327 | { | |
328 | uint8_t digest[20]; | |
329 | sha1_context ctx; | |
330 | ||
331 | sha1_starts(&ctx); | |
332 | sha1_update(&ctx, _dst, n); | |
333 | sha1_update(&ctx, _src, n); | |
334 | sha1_finish(&ctx, digest); | |
335 | memcpy(_dst, digest, min(n, sizeof(digest))); | |
336 | ||
337 | return _dst; | |
338 | } | |
339 | ||
340 | static int hre_op_loadkey(struct h_reg *src_reg, struct h_reg *dst_reg, | |
341 | const void *key, size_t key_size) | |
342 | { | |
343 | uint32_t parent_handle; | |
344 | uint32_t key_handle; | |
345 | ||
346 | if (!src_reg || !dst_reg || !src_reg->valid || !dst_reg->valid) | |
347 | return -1; | |
348 | if (find_key(src_reg->digest, dst_reg->digest, &parent_handle)) | |
349 | return -1; | |
350 | hre_tpm_err = tpm_load_key2_oiap(parent_handle, key, key_size, | |
351 | src_reg->digest, &key_handle); | |
352 | if (hre_tpm_err) { | |
353 | hre_err = HRE_E_TPM_FAILURE; | |
354 | return -1; | |
355 | } | |
356 | ||
357 | return 0; | |
358 | } | |
359 | ||
360 | /** | |
361 | * @brief executes the next opcode on the hash register engine. | |
362 | * @param[in,out] ip pointer to the opcode (instruction pointer) | |
363 | * @param[in,out] code_size (remaining) size of the code | |
364 | * @return new instruction pointer on success, NULL on error. | |
365 | */ | |
366 | static const uint8_t *hre_execute_op(const uint8_t **ip, size_t *code_size) | |
367 | { | |
368 | bool dst_modified = false; | |
369 | uint32_t ins; | |
370 | uint8_t opcode; | |
371 | uint8_t src_spec; | |
372 | uint8_t dst_spec; | |
373 | uint16_t data_size; | |
374 | struct h_reg *src_reg, *dst_reg; | |
375 | uint8_t buf[20]; | |
376 | const uint8_t *src_buf, *data; | |
377 | uint8_t *ptr; | |
378 | int i; | |
379 | void * (*bin_func)(void *, const void *, size_t); | |
380 | ||
381 | if (*code_size < 4) | |
382 | return NULL; | |
383 | ||
384 | ins = get_unaligned_be32(*ip); | |
385 | opcode = **ip; | |
386 | data = *ip + 4; | |
387 | src_spec = (ins >> 18) & 0x3f; | |
388 | dst_spec = (ins >> 12) & 0x3f; | |
389 | data_size = (ins & 0x7ff); | |
390 | ||
391 | debug("HRE: ins=%08x (op=%02x, s=%02x, d=%02x, L=%d)\n", ins, | |
392 | opcode, src_spec, dst_spec, data_size); | |
393 | ||
394 | if ((opcode & 0x80) && (data_size + 4) > *code_size) | |
395 | return NULL; | |
396 | ||
397 | src_reg = access_hreg(src_spec, HREG_RD); | |
398 | if (hre_err || hre_tpm_err) | |
399 | return NULL; | |
400 | dst_reg = access_hreg(dst_spec, (opcode & 0x40) ? HREG_RDWR : HREG_WR); | |
401 | if (hre_err || hre_tpm_err) | |
402 | return NULL; | |
403 | ||
404 | switch (opcode) { | |
405 | case HRE_NOP: | |
406 | goto end; | |
407 | case HRE_CHECK0: | |
408 | if (src_reg) { | |
409 | for (i = 0; i < 20; ++i) { | |
410 | if (src_reg->digest[i]) | |
411 | return NULL; | |
412 | } | |
413 | } | |
414 | break; | |
415 | case HRE_LOAD: | |
416 | bin_func = memcpy; | |
417 | goto do_bin_func; | |
418 | case HRE_XOR: | |
419 | bin_func = compute_xor; | |
420 | goto do_bin_func; | |
421 | case HRE_AND: | |
422 | bin_func = compute_and; | |
423 | goto do_bin_func; | |
424 | case HRE_OR: | |
425 | bin_func = compute_or; | |
426 | goto do_bin_func; | |
427 | case HRE_EXTEND: | |
428 | bin_func = compute_extend; | |
429 | do_bin_func: | |
430 | if (!dst_reg) | |
431 | return NULL; | |
432 | if (src_reg) { | |
433 | src_buf = src_reg->digest; | |
434 | } else { | |
435 | if (!data_size) { | |
436 | memset(buf, 0, 20); | |
437 | src_buf = buf; | |
438 | } else if (data_size == 1) { | |
439 | memset(buf, *data, 20); | |
440 | src_buf = buf; | |
441 | } else if (data_size >= 20) { | |
442 | src_buf = data; | |
443 | } else { | |
444 | src_buf = buf; | |
445 | for (ptr = (uint8_t *)src_buf, i = 20; i > 0; | |
446 | i -= data_size, ptr += data_size) | |
447 | memcpy(ptr, data, | |
448 | min_t(size_t, i, data_size)); | |
449 | } | |
450 | } | |
451 | bin_func(dst_reg->digest, src_buf, 20); | |
452 | dst_reg->valid = true; | |
453 | dst_modified = true; | |
454 | break; | |
455 | case HRE_LOADKEY: | |
456 | if (hre_op_loadkey(src_reg, dst_reg, data, data_size)) | |
457 | return NULL; | |
458 | break; | |
459 | default: | |
460 | return NULL; | |
461 | } | |
462 | ||
463 | if (dst_reg && dst_modified && IS_PCR_HREG(dst_spec)) { | |
464 | hre_tpm_err = tpm_extend(HREG_IDX(dst_spec), dst_reg->digest, | |
465 | dst_reg->digest); | |
466 | if (hre_tpm_err) { | |
467 | hre_err = HRE_E_TPM_FAILURE; | |
468 | return NULL; | |
469 | } | |
470 | } | |
471 | end: | |
472 | *ip += 4; | |
473 | *code_size -= 4; | |
474 | if (opcode & 0x80) { | |
475 | *ip += data_size; | |
476 | *code_size -= data_size; | |
477 | } | |
478 | ||
479 | return *ip; | |
480 | } | |
481 | ||
482 | /** | |
483 | * @brief runs a program on the hash register engine. | |
484 | * @param code pointer to the (HRE) code. | |
485 | * @param code_size size of the code (in bytes). | |
486 | * @return 0 on success, != 0 on failure. | |
487 | */ | |
488 | int hre_run_program(const uint8_t *code, size_t code_size) | |
489 | { | |
490 | size_t code_left; | |
491 | const uint8_t *ip = code; | |
492 | ||
493 | code_left = code_size; | |
494 | hre_tpm_err = 0; | |
495 | hre_err = HRE_E_OK; | |
496 | while (code_left > 0) | |
497 | if (!hre_execute_op(&ip, &code_left)) | |
498 | return -1; | |
499 | ||
500 | return hre_err; | |
501 | } | |
502 | ||
503 | int hre_verify_program(struct key_program *prg) | |
504 | { | |
505 | uint32_t crc; | |
506 | ||
507 | crc = crc32(0, prg->code, prg->code_size); | |
508 | ||
509 | if (crc != prg->code_crc) { | |
510 | printf("HRC crc mismatch: %08x != %08x\n", | |
511 | crc, prg->code_crc); | |
512 | return 1; | |
513 | } | |
514 | return 0; | |
515 | } |