diff options
-rw-r--r-- | futility/file_type.inc | 7 | ||||
-rw-r--r-- | futility/file_type_usbpd1.c | 322 | ||||
-rw-r--r-- | tests/futility/data/zinger_mp_image.bin | bin | 0 -> 32768 bytes | |||
-rwxr-xr-x | tests/futility/run_test_scripts.sh | 1 | ||||
-rw-r--r-- | tests/futility/test_file_types.c | 9 | ||||
-rwxr-xr-x | tests/futility/test_show_usbpd1.sh | 46 |
6 files changed, 346 insertions, 39 deletions
diff --git a/futility/file_type.inc b/futility/file_type.inc index 4014c0b4..5433d03c 100644 --- a/futility/file_type.inc +++ b/futility/file_type.inc @@ -71,9 +71,8 @@ FILE_TYPE(CHROMIUMOS_DISK, "disk_img", "chromiumos disk image", NONE, NONE, NONE) - -/* Firmware for Samus' USB Type-C power adapters */ +/* Firmware for USB Type-C power adapters */ FILE_TYPE(USBPD1, "usbpd1", "USB-PD charger image (v1.0)", - NONE, - NONE, + R_(ft_recognize_usbpd1), + S_(ft_show_usbpd1), S_(ft_sign_usbpd1)) diff --git a/futility/file_type_usbpd1.c b/futility/file_type_usbpd1.c index 7230b0ca..acf3de06 100644 --- a/futility/file_type_usbpd1.c +++ b/futility/file_type_usbpd1.c @@ -31,41 +31,22 @@ #include "host_signature2.h" #include "util_misc.h" -int ft_sign_usbpd1(const char *name, uint8_t *buf, uint32_t len, void *data) +/* Return 1 if okay, 0 if not */ +static int parse_size_opts(uint32_t len, + uint32_t *ro_size_ptr, uint32_t *rw_size_ptr, + uint32_t *ro_offset_ptr, uint32_t * rw_offset_ptr) { - struct vb2_private_key *key_ptr = 0; - struct vb2_signature *sig_ptr = 0; - uint8_t *keyb_data = 0; - uint32_t keyb_size; - int retval = 1; - uint32_t sig_size; - uint32_t sig_offset; - uint32_t pub_size; - uint32_t pub_offset; - uint32_t ro_size; - uint32_t rw_size; - uint32_t ro_offset; - uint32_t rw_offset; - uint32_t r; - - Debug("%s(): name %s\n", __func__, name); - Debug("%s(): len 0x%08x (%d)\n", __func__, len, len); + uint32_t ro_size, rw_size, ro_offset, rw_offset; - /* - * Check for size args. Note that we're NOT worrying about rollover, - * overlapping regions, out of bounds, etc. - */ + /* Assume the image has both RO and RW, evenly split. */ ro_offset = 0; ro_size = rw_size = rw_offset = len / 2; - /* Override some stuff? */ + /* Unless told otherwise... */ if (sign_option.ro_size != 0xffffffff) ro_size = sign_option.ro_size; - if (sign_option.rw_size != 0xffffffff) - rw_size = sign_option.rw_size; - - Debug("ro_size 0x%08x\n", ro_size); - Debug("ro_offset 0x%08x\n", ro_offset); + if (sign_option.ro_offset != 0xffffffff) + ro_offset = sign_option.ro_offset; /* If RO is missing, the whole thing must be RW */ if (!ro_size) { @@ -74,14 +55,55 @@ int ft_sign_usbpd1(const char *name, uint8_t *buf, uint32_t len, void *data) } /* Unless that's overridden too */ - if (sign_option.ro_offset != 0xffffffff) - ro_offset = sign_option.ro_offset; + if (sign_option.rw_size != 0xffffffff) + rw_size = sign_option.rw_size; if (sign_option.rw_offset != 0xffffffff) rw_offset = sign_option.rw_offset; + Debug("ro_size 0x%08x\n", ro_size); + Debug("ro_offset 0x%08x\n", ro_offset); Debug("rw_size 0x%08x\n", rw_size); Debug("rw_offset 0x%08x\n", rw_offset); + /* Now let's do some sanity checks. */ + if (ro_size > len || ro_offset > len - ro_size || + rw_size > len || rw_offset > len - rw_size) { + printf("size/offset values are bogus\n"); + return 0; + } + + *ro_size_ptr = ro_size; + *rw_size_ptr = rw_size; + *ro_offset_ptr = ro_offset; + *rw_offset_ptr = rw_offset; + + return 1; +} + +int ft_sign_usbpd1(const char *name, uint8_t *buf, uint32_t len, void *data) +{ + struct vb2_private_key *key_ptr = 0; + struct vb2_signature *sig_ptr = 0; + uint8_t *keyb_data = 0; + uint32_t keyb_size; + int retval = 1; + uint32_t sig_size; + uint32_t sig_offset; + uint32_t pub_size; + uint32_t pub_offset; + uint32_t ro_size; + uint32_t rw_size; + uint32_t ro_offset; + uint32_t rw_offset; + uint32_t r; + + Debug("%s(): name %s\n", __func__, name); + Debug("%s(): len 0x%08x (%d)\n", __func__, len, len); + + /* Get image locations */ + if (!parse_size_opts(len, &ro_size, &rw_size, &ro_offset, &rw_offset)) + goto done; + /* Read the signing keypair file */ if (vb2_private_key_read_pem(&key_ptr, sign_option.pem_signpriv)) { fprintf(stderr, "Unable to read keypair from %s\n", @@ -225,3 +247,243 @@ done: return retval; } + + +/* + * Algorithms that we want to try, in order. We've only ever shipped with + * RSA2048 / SHA256, but the others should work in tests. + */ +static enum vb2_signature_algorithm sigs[] = { + VB2_SIG_RSA2048, + VB2_SIG_RSA1024, + VB2_SIG_RSA4096, + VB2_SIG_RSA8192, +}; +enum vb2_hash_algorithm hashes[] = { + VB2_HASH_SHA256, + VB2_HASH_SHA1, + VB2_HASH_SHA512, +}; + +/* + * The size of the public key structure used by usbpd1 is + * 2 x RSANUMBYTES for n and rr fields + * plus 4 for n0inv, aligned on a multiple of 16 + */ +static uint32_t usbpd1_packed_key_size(enum vb2_signature_algorithm sig_alg) +{ + switch (sig_alg) { + case VB2_SIG_RSA1024: + return 272; + case VB2_SIG_RSA2048: + return 528; + case VB2_SIG_RSA4096: + return 1040; + case VB2_SIG_RSA8192: + return 2064; + default: + return 0; + } +} +static void vb2_pubkey_from_usbpd1(struct vb2_public_key *key, + enum vb2_signature_algorithm sig_alg, + enum vb2_hash_algorithm hash_alg, + const uint8_t *o_pubkey, + uint32_t o_pubkey_size) +{ + key->arrsize = vb2_rsa_sig_size(sig_alg) / sizeof(uint32_t); + key->n0inv = *((uint32_t *)o_pubkey + 2 * key->arrsize); + key->n = (uint32_t *)o_pubkey; + key->rr = (uint32_t *)o_pubkey + key->arrsize; + key->sig_alg = sig_alg; + key->hash_alg = hash_alg; + key->desc = 0; + key->version = 0; + key->id = vb2_hash_id(hash_alg); +} + +static int vb2_sig_from_usbpd1(struct vb2_signature **sig, + enum vb2_signature_algorithm sig_alg, + enum vb2_hash_algorithm hash_alg, + const uint8_t *o_sig, + uint32_t o_sig_size, + uint32_t data_size) +{ + struct vb2_signature s = { + .c.magic = VB2_MAGIC_SIGNATURE, + .c.struct_version_major = VB2_SIGNATURE_VERSION_MAJOR, + .c.struct_version_minor = VB2_SIGNATURE_VERSION_MINOR, + .c.fixed_size = sizeof(s), + .sig_alg = sig_alg, + .hash_alg = hash_alg, + .data_size = data_size, + .sig_size = vb2_rsa_sig_size(sig_alg), + .sig_offset = sizeof(s), + }; + uint32_t total_size = sizeof(s) + o_sig_size; + uint8_t *buf = calloc(1, total_size); + if (!buf) + return VB2_ERROR_UNKNOWN; + + memcpy(buf, &s, sizeof(s)); + memcpy(buf + sizeof(s), o_sig, o_sig_size); + + *sig = (struct vb2_signature *)buf; + return VB2_SUCCESS; +} + +static void show_usbpd1_stuff(const char *name, + enum vb2_signature_algorithm sig_alg, + enum vb2_hash_algorithm hash_alg, + const uint8_t *o_pubkey, uint32_t o_pubkey_size) +{ + struct vb2_public_key key; + struct vb2_packed_key *pkey; + uint8_t *sha1sum; + int i; + + vb2_pubkey_from_usbpd1(&key, sig_alg, hash_alg, + o_pubkey, o_pubkey_size); + + if (vb2_public_key_pack(&pkey, &key)) + return; + + sha1sum = DigestBuf((uint8_t *)pkey + pkey->key_offset, + pkey->key_size, SHA1_DIGEST_ALGORITHM); + + printf("USB-PD v1 image: %s\n", name); + printf(" Algorithm: %s %s\n", + vb2_lookup_by_num(vb2_text_vs_sig, sig_alg)->name, + vb2_lookup_by_num(vb2_text_vs_hash, hash_alg)->name); + printf(" Key sha1sum: "); + for (i = 0; i < SHA1_DIGEST_SIZE; i++) + printf("%02x", sha1sum[i]); + printf("\n"); + + free(sha1sum); + free(pkey); +} + + +/* Returns VB2_SUCCESS or random error code */ +static int try_our_own(enum vb2_signature_algorithm sig_alg, + enum vb2_hash_algorithm hash_alg, + const uint8_t *o_pubkey, uint32_t o_pubkey_size, + const uint8_t *o_sig, uint32_t o_sig_size, + const uint8_t *data, uint32_t data_size) +{ + struct vb2_public_key pubkey; + struct vb2_signature *sig; + uint8_t buf[VB2_WORKBUF_RECOMMENDED_SIZE] + __attribute__ ((aligned (VB2_WORKBUF_ALIGN))); + struct vb2_workbuf wb = { + .buf = buf, + .size = sizeof(buf), + }; + int rv = VB2_ERROR_UNKNOWN; + + vb2_pubkey_from_usbpd1(&pubkey, sig_alg, hash_alg, + o_pubkey, o_pubkey_size); + + if ((rv = vb2_sig_from_usbpd1(&sig, sig_alg, hash_alg, + o_sig, o_sig_size, data_size))) + return rv; + + rv = vb2_verify_data(data, data_size, sig, &pubkey, &wb); + + free(sig); + + return rv; +} + +/* Returns VB2_SUCCESS if the image validates itself */ +static int check_self_consistency(const uint8_t *buf, + const char *name, + uint32_t ro_size, uint32_t rw_size, + uint32_t ro_offset, uint32_t rw_offset, + enum vb2_signature_algorithm sig_alg, + enum vb2_hash_algorithm hash_alg) +{ + /* Where are the important bits? */ + uint32_t sig_size = vb2_rsa_sig_size(sig_alg); + uint32_t sig_offset = rw_offset + rw_size - sig_size; + uint32_t pubkey_size = usbpd1_packed_key_size(sig_alg); + uint32_t pubkey_offset = ro_offset + ro_size - pubkey_size; + int rv; + + /* Skip stuff that obviously doesn't work */ + if (sig_size > rw_size || pubkey_size > ro_size) + return VB2_ERROR_UNKNOWN; + + rv = try_our_own(sig_alg, hash_alg, /* algs */ + buf + pubkey_offset, pubkey_size, /* pubkey blob */ + buf + sig_offset, sig_size, /* sig blob */ + buf + rw_offset, rw_size - sig_size); /* RW image */ + + if (rv == VB2_SUCCESS && name) + show_usbpd1_stuff(name, sig_alg, hash_alg, + buf + pubkey_offset, pubkey_size); + + return rv; +} + + +int ft_show_usbpd1(const char *name, uint8_t *buf, uint32_t len, void *data) +{ + uint32_t ro_size, rw_size, ro_offset, rw_offset; + int s, h; + + Debug("%s(): name %s\n", __func__, name); + Debug("%s(): len 0x%08x (%d)\n", __func__, len, len); + + /* Get image locations */ + if (!parse_size_opts(len, &ro_size, &rw_size, &ro_offset, &rw_offset)) + return 1; + + /* TODO: If we don't have a RO image, ask for a public key + * TODO: If we're given an external public key, use it (and its alg) */ + if (!ro_size) { + printf("Can't find the public key\n"); + return 1; + } + + /* TODO: Only loop through the numbers we haven't been given */ + for (s = 0; s < ARRAY_SIZE(sigs); s++) + for (h = 0; h < ARRAY_SIZE(hashes); h++) + if (!check_self_consistency(buf, name, + ro_size, rw_size, + ro_offset, rw_offset, + sigs[s], hashes[h])) + return 0; + + printf("This doesn't appear to be a complete usbpd1 image\n"); + return 1; +} + +enum futil_file_type ft_recognize_usbpd1(uint8_t *buf, uint32_t len) +{ + uint32_t ro_size, rw_size, ro_offset, rw_offset; + int s, h; + + Debug("%s(): len 0x%08x (%d)\n", __func__, len, len); + + /* + * Since we don't use any headers to identify or locate the pubkey and + * signature, in order to identify blob as the right type we have to + * just assume that the RO & RW are 1) both present, and 2) evenly + * split. Then we just try to use what we think might be the pubkey to + * validate what we think might be the signature. + */ + ro_offset = 0; + ro_size = rw_size = rw_offset = len / 2; + + for (s = 0; s < ARRAY_SIZE(sigs); s++) + for (h = 0; h < ARRAY_SIZE(hashes); h++) + if (!check_self_consistency(buf, 0, + ro_size, rw_size, + ro_offset, rw_offset, + sigs[s], hashes[h])) + return FILE_TYPE_USBPD1; + + return FILE_TYPE_UNKNOWN; +} diff --git a/tests/futility/data/zinger_mp_image.bin b/tests/futility/data/zinger_mp_image.bin Binary files differnew file mode 100644 index 00000000..68152c02 --- /dev/null +++ b/tests/futility/data/zinger_mp_image.bin diff --git a/tests/futility/run_test_scripts.sh b/tests/futility/run_test_scripts.sh index 8e6281a1..a0d9e471 100755 --- a/tests/futility/run_test_scripts.sh +++ b/tests/futility/run_test_scripts.sh @@ -47,6 +47,7 @@ ${SCRIPTDIR}/test_load_fmap.sh ${SCRIPTDIR}/test_main.sh ${SCRIPTDIR}/test_show_kernel.sh ${SCRIPTDIR}/test_show_vs_verify.sh +${SCRIPTDIR}/test_show_usbpd1.sh ${SCRIPTDIR}/test_sign_firmware.sh ${SCRIPTDIR}/test_sign_fw_main.sh ${SCRIPTDIR}/test_sign_kernel.sh diff --git a/tests/futility/test_file_types.c b/tests/futility/test_file_types.c index 3fb21cc6..9f90a0f3 100644 --- a/tests/futility/test_file_types.c +++ b/tests/futility/test_file_types.c @@ -29,15 +29,14 @@ static struct { {FILE_TYPE_BIOS_IMAGE, "tests/futility/data/bios_zgb_mp.bin"}, {FILE_TYPE_OLD_BIOS_IMAGE, "tests/futility/data/bios_mario_mp.bin"}, {FILE_TYPE_KERN_PREAMBLE, "tests/futility/data/kern_preamble.bin"}, - /* We don't have a way to identify these (yet?) */ - {FILE_TYPE_RAW_FIRMWARE, }, - {FILE_TYPE_RAW_KERNEL, }, - {FILE_TYPE_CHROMIUMOS_DISK, }, + {FILE_TYPE_RAW_FIRMWARE, }, /* need a test for this */ + {FILE_TYPE_RAW_KERNEL, }, /* need a test for this */ + {FILE_TYPE_CHROMIUMOS_DISK, }, /* need a test for this */ {FILE_TYPE_PRIVKEY, "tests/devkeys/root_key.vbprivk"}, {FILE_TYPE_VB2_PUBKEY, "tests/futility/data/sample.vbpubk2"}, {FILE_TYPE_VB2_PRIVKEY, "tests/futility/data/sample.vbprik2"}, {FILE_TYPE_PEM, "tests/testkeys/key_rsa2048.pem"}, - {FILE_TYPE_USBPD1, }, + {FILE_TYPE_USBPD1, "tests/futility/data/zinger_mp_image.bin"}, }; BUILD_ASSERT(ARRAY_SIZE(test_case) == NUM_FILE_TYPES); diff --git a/tests/futility/test_show_usbpd1.sh b/tests/futility/test_show_usbpd1.sh new file mode 100755 index 00000000..5fa5b93c --- /dev/null +++ b/tests/futility/test_show_usbpd1.sh @@ -0,0 +1,46 @@ +#!/bin/bash -eux +# Copyright 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. + +me=${0##*/} +TMP="$me.tmp" + +# Work in scratch directory +cd "$OUTDIR" + +DATADIR="${SCRIPTDIR}/data" +TESTS="dingdong hoho minimuffin zinger" +TESTKEYS=${SRCDIR}/tests/testkeys + +SIGS="1024 2048 4096 8192" +HASHES="SHA1 SHA256 SHA512" + +set -o pipefail + +for s in $SIGS; do + + echo -n "$s " 1>&3 + + for test in $TESTS; do + + infile=${DATADIR}/${test}.unsigned + + for h in $HASHES; do + + pemfile=${TESTKEYS}/key_rsa${s}.pem + outfile=${TMP}.${test}_${s}_${h}.new + + # sign it + ${FUTILITY} sign --type usbpd1 --pem ${pemfile} ${infile} ${outfile} + + # make sure it identifies correctly + ${FUTILITY} verify ${outfile} + + done + done +done + +# cleanup +rm -rf ${TMP}* +exit 0 |