| /* Copyright (c) 2015 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Boot descriptor block firmware functions |
| */ |
| |
| #include <string.h> |
| #include "bdb.h" |
| |
| /*****************************************************************************/ |
| |
| /** |
| * Check if string contains a null terminator. |
| * |
| * Bytes after the null terminator do not need to be null. |
| * |
| * @param s String to check |
| * @param size Size of string buffer in characters |
| * @return 1 if string has a null terminator, 0 if not |
| */ |
| int string_has_null(const char *s, size_t size) |
| { |
| for (; size; size--) { |
| if (*s++ == 0) |
| return 1; |
| } |
| return 0; |
| } |
| |
| int bdb_check_header(const struct bdb_header *p, size_t size) |
| { |
| if (size < sizeof(*p) || size < p->struct_size) |
| return BDB_ERROR_BUF_SIZE; |
| |
| if (p->struct_magic != BDB_HEADER_MAGIC) |
| return BDB_ERROR_STRUCT_MAGIC; |
| |
| if (p->struct_major_version != BDB_HEADER_VERSION_MAJOR) |
| return BDB_ERROR_STRUCT_VERSION; |
| |
| /* Note that minor version doesn't matter yet */ |
| |
| if (p->struct_size < sizeof(*p)) |
| return BDB_ERROR_STRUCT_SIZE; |
| |
| if (p->oem_area_0_size & 3) |
| return BDB_ERROR_OEM_AREA_SIZE; /* Not 32-bit aligned */ |
| |
| /* |
| * Make sure the BDB is at least big enough for us. At this point, all |
| * the caller may have loaded is this header We'll check if there's |
| * space for everything else after we load it. |
| */ |
| if (p->bdb_size < sizeof(*p)) |
| return BDB_ERROR_BDB_SIZE; |
| |
| /* |
| * The rest of the fields don't matter yet; we'll check them when we |
| * check the BDB itself. |
| */ |
| return BDB_SUCCESS; |
| } |
| |
| int bdb_check_key(const struct bdb_key *p, size_t size) |
| { |
| size_t expect_key_size = 0; |
| |
| if (size < sizeof(*p) || size < p->struct_size) |
| return BDB_ERROR_BUF_SIZE; |
| |
| if (p->struct_magic != BDB_KEY_MAGIC) |
| return BDB_ERROR_STRUCT_MAGIC; |
| |
| if (p->struct_major_version != BDB_KEY_VERSION_MAJOR) |
| return BDB_ERROR_STRUCT_VERSION; |
| |
| /* Note that minor version doesn't matter yet */ |
| |
| if (!string_has_null(p->description, sizeof(p->description))) |
| return BDB_ERROR_DESCRIPTION; |
| |
| /* We currently only support SHA-256 */ |
| if (p->hash_alg != BDB_HASH_ALG_SHA256) |
| return BDB_ERROR_HASH_ALG; |
| |
| /* Make sure signature algorithm and size are correct */ |
| switch (p->sig_alg) { |
| case BDB_SIG_ALG_RSA4096: |
| expect_key_size = BDB_RSA4096_KEY_DATA_SIZE; |
| break; |
| case BDB_SIG_ALG_ECSDSA521: |
| expect_key_size = BDB_ECDSA521_KEY_DATA_SIZE; |
| break; |
| case BDB_SIG_ALG_RSA3072B: |
| expect_key_size = BDB_RSA3072B_KEY_DATA_SIZE; |
| break; |
| default: |
| return BDB_ERROR_SIG_ALG; |
| } |
| |
| if (p->struct_size < sizeof(*p) + expect_key_size) |
| return BDB_ERROR_STRUCT_SIZE; |
| |
| return BDB_SUCCESS; |
| } |
| |
| int bdb_check_sig(const struct bdb_sig *p, size_t size) |
| { |
| size_t expect_sig_size = 0; |
| |
| if (size < sizeof(*p) || size < p->struct_size) |
| return BDB_ERROR_BUF_SIZE; |
| |
| if (p->struct_magic != BDB_SIG_MAGIC) |
| return BDB_ERROR_STRUCT_MAGIC; |
| |
| if (p->struct_major_version != BDB_SIG_VERSION_MAJOR) |
| return BDB_ERROR_STRUCT_VERSION; |
| |
| /* Note that minor version doesn't matter yet */ |
| |
| if (!string_has_null(p->description, sizeof(p->description))) |
| return BDB_ERROR_DESCRIPTION; |
| |
| /* We currently only support SHA-256 */ |
| if (p->hash_alg != BDB_HASH_ALG_SHA256) |
| return BDB_ERROR_HASH_ALG; |
| |
| /* Make sure signature algorithm and size are correct */ |
| switch (p->sig_alg) { |
| case BDB_SIG_ALG_RSA4096: |
| expect_sig_size = BDB_RSA4096_SIG_SIZE; |
| break; |
| case BDB_SIG_ALG_ECSDSA521: |
| expect_sig_size = BDB_ECDSA521_SIG_SIZE; |
| break; |
| case BDB_SIG_ALG_RSA3072B: |
| expect_sig_size = BDB_RSA3072B_SIG_SIZE; |
| break; |
| default: |
| return BDB_ERROR_SIG_ALG; |
| } |
| |
| if (p->struct_size < sizeof(*p) + expect_sig_size) |
| return BDB_ERROR_STRUCT_SIZE; |
| |
| return BDB_SUCCESS; |
| } |
| |
| int bdb_check_data(const struct bdb_data *p, size_t size) |
| { |
| size_t need_size; |
| |
| if (size < sizeof(*p) || size < p->signed_size) |
| return BDB_ERROR_BUF_SIZE; |
| |
| if (p->struct_magic != BDB_DATA_MAGIC) |
| return BDB_ERROR_STRUCT_MAGIC; |
| |
| if (p->struct_major_version != BDB_DATA_VERSION_MAJOR) |
| return BDB_ERROR_STRUCT_VERSION; |
| |
| /* Note that minor version doesn't matter yet */ |
| |
| if (!string_has_null(p->description, sizeof(p->description))) |
| return BDB_ERROR_DESCRIPTION; |
| |
| if (p->struct_size < sizeof(*p)) |
| return BDB_ERROR_STRUCT_SIZE; |
| |
| if (p->hash_entry_size < sizeof(struct bdb_hash)) |
| return BDB_ERROR_HASH_ENTRY_SIZE; |
| |
| /* Calculate expected size */ |
| need_size = p->struct_size + p->num_hashes * p->hash_entry_size; |
| |
| /* Make sure OEM area size doesn't cause wraparound */ |
| if (need_size + p->oem_area_1_size < need_size) |
| return BDB_ERROR_OEM_AREA_SIZE; |
| if (p->oem_area_1_size & 3) |
| return BDB_ERROR_OEM_AREA_SIZE; /* Not 32-bit aligned */ |
| need_size += p->oem_area_1_size; |
| |
| if (p->signed_size < need_size) |
| return BDB_ERROR_SIGNED_SIZE; |
| |
| return BDB_SUCCESS; |
| } |
| |
| /*****************************************************************************/ |
| |
| const struct bdb_header *bdb_get_header(const void *buf) |
| { |
| return buf; |
| } |
| |
| const struct bdb_key *bdb_get_bdbkey(const void *buf) |
| { |
| const struct bdb_header *h = bdb_get_header(buf); |
| const uint8_t *b8 = buf; |
| |
| /* BDB key follows header */ |
| return (const struct bdb_key *)(b8 + h->struct_size); |
| } |
| |
| const void *bdb_get_oem_area_0(const void *buf) |
| { |
| const struct bdb_key *k = bdb_get_bdbkey(buf); |
| const uint8_t *b8 = (const uint8_t *)k; |
| |
| /* OEM area 0 follows BDB key */ |
| return b8 + k->struct_size; |
| } |
| |
| const struct bdb_key *bdb_get_subkey(const void *buf) |
| { |
| const struct bdb_header *h = bdb_get_header(buf); |
| const uint8_t *b8 = bdb_get_oem_area_0(buf); |
| |
| /* Subkey follows OEM area 0 */ |
| return (const struct bdb_key *)(b8 + h->oem_area_0_size); |
| } |
| |
| const struct bdb_sig *bdb_get_header_sig(const void *buf) |
| { |
| const struct bdb_header *h = bdb_get_header(buf); |
| const uint8_t *b8 = bdb_get_oem_area_0(buf); |
| |
| /* Header signature starts after signed data */ |
| return (const struct bdb_sig *)(b8 + h->signed_size); |
| } |
| |
| const struct bdb_data *bdb_get_data(const void *buf) |
| { |
| const struct bdb_sig *s = bdb_get_header_sig(buf); |
| const uint8_t *b8 = (const uint8_t *)s; |
| |
| /* Data follows header signature */ |
| return (const struct bdb_data *)(b8 + s->struct_size); |
| } |
| |
| const void *bdb_get_oem_area_1(const void *buf) |
| { |
| const struct bdb_data *p = bdb_get_data(buf); |
| const uint8_t *b8 = (const uint8_t *)p; |
| |
| /* OEM area 1 follows BDB data */ |
| return b8 + p->struct_size; |
| } |
| |
| const struct bdb_hash *bdb_get_hash(const void *buf, enum bdb_data_type type) |
| { |
| const struct bdb_data *data = bdb_get_data(buf); |
| const uint8_t *b8 = bdb_get_oem_area_1(buf); |
| int i; |
| |
| /* Hashes follow OEM area 0 */ |
| b8 += data->oem_area_1_size; |
| |
| /* Search for a matching hash */ |
| for (i = 0; i < data->num_hashes; i++, b8 += data->hash_entry_size) { |
| const struct bdb_hash *h = (const struct bdb_hash *)b8; |
| |
| if (h->type == type) |
| return h; |
| } |
| |
| return NULL; |
| } |
| |
| const struct bdb_sig *bdb_get_data_sig(const void *buf) |
| { |
| const struct bdb_data *data = bdb_get_data(buf); |
| const uint8_t *b8 = (const uint8_t *)data; |
| |
| /* Data signature starts after signed data */ |
| return (const struct bdb_sig *)(b8 + data->signed_size); |
| } |
| |
| /*****************************************************************************/ |
| |
| int bdb_verify_sig(const struct bdb_key *key, |
| const struct bdb_sig *sig, |
| const uint8_t *digest) |
| { |
| /* Key and signature algorithms must match */ |
| if (key->sig_alg != sig->sig_alg) |
| return BDB_ERROR_SIG_ALG; |
| |
| switch (key->sig_alg) { |
| case BDB_SIG_ALG_RSA4096: |
| if (bdb_rsa4096_verify(key->key_data, sig->sig_data, digest)) |
| return BDB_ERROR_VERIFY_SIG; |
| break; |
| case BDB_SIG_ALG_ECSDSA521: |
| if (bdb_ecdsa521_verify(key->key_data, sig->sig_data, digest)) |
| return BDB_ERROR_VERIFY_SIG; |
| break; |
| case BDB_SIG_ALG_RSA3072B: |
| if (bdb_rsa3072b_verify(key->key_data, sig->sig_data, digest)) |
| return BDB_ERROR_VERIFY_SIG; |
| break; |
| default: |
| return BDB_ERROR_VERIFY_SIG; |
| } |
| |
| return BDB_SUCCESS; |
| } |
| |
| int bdb_verify(const void *buf, size_t size, const uint8_t *bdb_key_digest) |
| { |
| const uint8_t *end = (const uint8_t *)buf + size; |
| const struct bdb_header *h; |
| const struct bdb_key *bdbkey, *subkey; |
| const struct bdb_sig *sig; |
| const struct bdb_data *data; |
| const void *oem; |
| uint8_t digest[BDB_SHA256_DIGEST_SIZE]; |
| int bdb_digest_mismatch; |
| |
| /* Make sure buffer doesn't wrap around address space */ |
| if (end < (const uint8_t *)buf) |
| return BDB_ERROR_BUF_SIZE; |
| |
| /* |
| * Check header now that we've actually loaded it. We can't guarantee |
| * this is the same header which was checked before. |
| */ |
| h = bdb_get_header(buf); |
| if (bdb_check_header(h, size)) |
| return BDB_ERROR_HEADER; |
| |
| /* Sanity-check BDB key */ |
| bdbkey = bdb_get_bdbkey(buf); |
| if (bdb_check_key(bdbkey, end - (const uint8_t *)bdbkey)) |
| return BDB_ERROR_BDBKEY; |
| |
| /* Calculate BDB key digest and compare with expected */ |
| if (bdb_sha256(digest, bdbkey, bdbkey->struct_size)) |
| return BDB_ERROR_DIGEST; |
| |
| bdb_digest_mismatch = memcmp(digest, bdb_key_digest, sizeof(digest)); |
| |
| /* Make sure OEM area 0 fits */ |
| oem = bdb_get_oem_area_0(buf); |
| if (h->oem_area_0_size > end - (const uint8_t *)oem) |
| return BDB_ERROR_OEM_AREA_0; |
| |
| /* Sanity-check subkey */ |
| subkey = bdb_get_subkey(buf); |
| if (bdb_check_key(subkey, end - (const uint8_t *)subkey)) |
| return BDB_ERROR_SUBKEY; |
| |
| /* Make sure enough data was signed, and the signed data fits */ |
| if (h->oem_area_0_size + subkey->struct_size > h->signed_size || |
| h->signed_size > end - (const uint8_t *)oem) |
| return BDB_ERROR_BDB_SIGNED_SIZE; |
| |
| /* Sanity-check header signature */ |
| sig = bdb_get_header_sig(buf); |
| if (bdb_check_sig(sig, end - (const uint8_t *)sig)) |
| return BDB_ERROR_HEADER_SIG; |
| |
| /* Make sure it signed the right amount of data */ |
| if (sig->signed_size != h->signed_size) |
| return BDB_ERROR_HEADER_SIG; |
| |
| /* Calculate header digest and compare with expected signature */ |
| if (bdb_sha256(digest, oem, h->signed_size)) |
| return BDB_ERROR_DIGEST; |
| if (bdb_verify_sig(bdbkey, sig, digest)) |
| return BDB_ERROR_HEADER_SIG; |
| |
| /* |
| * Sanity-check data struct. This also checks that OEM area 1 and the |
| * hashes fit in the remaining buffer. |
| */ |
| data = bdb_get_data(buf); |
| if (bdb_check_data(data, end - (const uint8_t *)data)) |
| return BDB_ERROR_DATA; |
| |
| /* Sanity-check data signature */ |
| sig = bdb_get_data_sig(buf); |
| if (bdb_check_sig(sig, end - (const uint8_t *)sig)) |
| return BDB_ERROR_DATA_SIG; |
| if (sig->signed_size != data->signed_size) |
| return BDB_ERROR_DATA_SIG; |
| |
| /* Calculate data digest and compare with expected signature */ |
| if (bdb_sha256(digest, data, data->signed_size)) |
| return BDB_ERROR_DIGEST; |
| if (bdb_verify_sig(subkey, sig, digest)) |
| return BDB_ERROR_DATA_SIG; |
| |
| /* Return success or success-other-than-BDB-key-mismatch */ |
| return bdb_digest_mismatch ? BDB_GOOD_OTHER_THAN_KEY : BDB_SUCCESS; |
| } |