484 lines
14 KiB
C
484 lines
14 KiB
C
/* 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.
|
|
*
|
|
* The USB Type-C chargers released with Samus ("Pixel (2015)") have upgradable
|
|
* firmware. Due to space considerations, we don't have room for handy things
|
|
* like an FMAP or headers for the signatures. Accordingly, all the normally
|
|
* variable factors (image size, signature algorithms, etc.) are hard coded
|
|
* and the image itself just looks like a bunch of random numbers.
|
|
*
|
|
* This file handles those images, but PLEASE don't use it as a template for
|
|
* new devices. Look at file_type_rwsig.c instead.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include "2common.h"
|
|
#include "2rsa.h"
|
|
#include "2sha.h"
|
|
#include "2sysincludes.h"
|
|
#include "file_type.h"
|
|
#include "futility.h"
|
|
#include "futility_options.h"
|
|
#include "host_common.h"
|
|
#include "host_common21.h"
|
|
#include "host_key21.h"
|
|
#include "host_signature21.h"
|
|
#include "util_misc.h"
|
|
|
|
/* 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)
|
|
{
|
|
uint32_t ro_size, rw_size, ro_offset, rw_offset;
|
|
|
|
/* Assume the image has both RO and RW, evenly split. */
|
|
ro_offset = 0;
|
|
ro_size = rw_size = rw_offset = len / 2;
|
|
|
|
/* Unless told otherwise... */
|
|
if (sign_option.ro_size != 0xffffffff)
|
|
ro_size = sign_option.ro_size;
|
|
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) {
|
|
rw_size = len;
|
|
rw_offset = 0;
|
|
}
|
|
|
|
/* Unless that's overridden too */
|
|
if (sign_option.rw_size != 0xffffffff)
|
|
rw_size = sign_option.rw_size;
|
|
if (sign_option.rw_offset != 0xffffffff)
|
|
rw_offset = sign_option.rw_offset;
|
|
|
|
VB2_DEBUG("ro_size 0x%08x\n", ro_size);
|
|
VB2_DEBUG("ro_offset 0x%08x\n", ro_offset);
|
|
VB2_DEBUG("rw_size 0x%08x\n", rw_size);
|
|
VB2_DEBUG("rw_offset 0x%08x\n", rw_offset);
|
|
|
|
/* Now let's do some validity 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 vb21_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;
|
|
|
|
VB2_DEBUG("name %s len %#.8x (%d)\n", name, 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",
|
|
sign_option.pem_signpriv);
|
|
goto done;
|
|
}
|
|
|
|
/* Set the algs */
|
|
key_ptr->hash_alg = sign_option.hash_alg;
|
|
key_ptr->sig_alg = vb2_rsa_sig_alg(key_ptr->rsa_private_key);
|
|
if (key_ptr->sig_alg == VB2_SIG_INVALID) {
|
|
fprintf(stderr, "Unsupported sig algorithm in RSA key\n");
|
|
goto done;
|
|
}
|
|
|
|
/* Figure out what needs signing */
|
|
sig_size = vb2_rsa_sig_size(key_ptr->sig_alg);
|
|
if (rw_size < sig_size) {
|
|
fprintf(stderr,
|
|
"The RW image is too small to hold the signature"
|
|
" (0x%08x < %08x)\n", rw_size, sig_size);
|
|
goto done;
|
|
}
|
|
rw_size -= sig_size;
|
|
sig_offset = rw_offset + rw_size;
|
|
|
|
VB2_DEBUG("rw_size => 0x%08x\n", rw_size);
|
|
VB2_DEBUG("rw_offset => 0x%08x\n", rw_offset);
|
|
VB2_DEBUG("sig_size 0x%08x\n", sig_size);
|
|
VB2_DEBUG("sig_offset 0x%08x\n", sig_offset);
|
|
|
|
/* Sign the blob */
|
|
r = vb21_sign_data(&sig_ptr, buf + rw_offset, rw_size, key_ptr, "Bah");
|
|
if (r) {
|
|
fprintf(stderr,
|
|
"Unable to sign data (error 0x%08x, if that helps)\n",
|
|
r);
|
|
goto done;
|
|
}
|
|
|
|
/* Double-check the size */
|
|
if (sig_ptr->sig_size != sig_size) {
|
|
fprintf(stderr,
|
|
"ERROR: sig size is %d bytes, not %d as expected.\n",
|
|
sig_ptr->sig_size, sig_size);
|
|
goto done;
|
|
}
|
|
|
|
/* Okay, looking good. Update the signature. */
|
|
memcpy(buf + sig_offset,
|
|
(uint8_t *)sig_ptr + sig_ptr->sig_offset,
|
|
sig_ptr->sig_size);
|
|
|
|
|
|
/* If there's no RO section, we're done. */
|
|
if (!ro_size) {
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/* Otherwise, now update the public key */
|
|
if (vb_keyb_from_rsa(key_ptr->rsa_private_key,
|
|
&keyb_data, &keyb_size)) {
|
|
fprintf(stderr, "Couldn't extract the public key\n");
|
|
goto done;
|
|
}
|
|
VB2_DEBUG("keyb_size is %#x (%d):\n", keyb_size, keyb_size);
|
|
|
|
/*
|
|
* Of course the packed public key format is different. Why would you
|
|
* think otherwise? Since the dawn of time, vboot has used this:
|
|
*
|
|
* uint32_t nwords size of RSA key in 32-bit words
|
|
* uint32_t n0inv magic RSA n0inv
|
|
* uint32_t n[nwords] magic RSA modulus little endian array
|
|
* uint32_t rr[nwords] magic RSA R^2 little endian array
|
|
*
|
|
* But for no discernable reason, the usbpd1 format uses this:
|
|
*
|
|
* uint32_t n[nwords] magic RSA modulus little endian array
|
|
* uint32_t rr[nwords] magic RSA R^2 little endian array
|
|
* uint32_t n0inv magic RSA n0inv
|
|
*
|
|
* There's no nwords field, and n0inv is last insted of first. Sigh.
|
|
*/
|
|
pub_size = keyb_size - 4;
|
|
|
|
/* align pubkey size to 16-byte boundary */
|
|
uint32_t pub_pad = pub_size;
|
|
pub_size = (pub_size + 16) / 16 * 16;
|
|
pub_pad = pub_size - pub_pad;
|
|
|
|
pub_offset = ro_offset + ro_size - pub_size;
|
|
|
|
if (ro_size < pub_size) {
|
|
fprintf(stderr,
|
|
"The RO image is too small to hold the public key"
|
|
" (0x%08x < %08x)\n", ro_size, pub_size);
|
|
goto done;
|
|
}
|
|
|
|
/* How many bytes in the arrays? */
|
|
uint32_t nbytes = 4 * (*(uint32_t *)keyb_data);
|
|
/* Source offsets from keyb_data */
|
|
uint32_t src_ofs_n0inv = 4;
|
|
uint32_t src_ofs_n = src_ofs_n0inv + 4;
|
|
uint32_t src_ofs_rr = src_ofs_n + nbytes;
|
|
/* Dest offsets from buf */
|
|
uint32_t dst_ofs_n = pub_offset + 0;
|
|
uint32_t dst_ofs_rr = dst_ofs_n + nbytes;
|
|
uint32_t dst_ofs_n0inv = dst_ofs_rr + nbytes;
|
|
|
|
VB2_DEBUG("len 0x%08x ro_size 0x%08x ro_offset 0x%08x\n",
|
|
len, ro_size, ro_offset);
|
|
VB2_DEBUG("pub_size 0x%08x pub_offset 0x%08x nbytes 0x%08x\n",
|
|
pub_size, pub_offset, nbytes);
|
|
VB2_DEBUG("pub_pad 0x%08x\n", pub_pad);
|
|
|
|
/* Copy n[nwords] */
|
|
memcpy(buf + dst_ofs_n,
|
|
keyb_data + src_ofs_n,
|
|
nbytes);
|
|
/* Copy rr[nwords] */
|
|
memcpy(buf + dst_ofs_rr,
|
|
keyb_data + src_ofs_rr,
|
|
nbytes);
|
|
/* Copy n0inv */
|
|
memcpy(buf + dst_ofs_n0inv,
|
|
keyb_data + src_ofs_n0inv,
|
|
4);
|
|
/* Pad with 0xff */
|
|
memset(buf + dst_ofs_n0inv + 4, 0xff, pub_pad);
|
|
|
|
/* Finally */
|
|
retval = 0;
|
|
done:
|
|
if (key_ptr)
|
|
vb2_private_key_free(key_ptr);
|
|
if (keyb_data)
|
|
free(keyb_data);
|
|
|
|
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_RSA2048_EXP3,
|
|
VB2_SIG_RSA1024,
|
|
VB2_SIG_RSA4096,
|
|
VB2_SIG_RSA8192,
|
|
};
|
|
static 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:
|
|
case VB2_SIG_RSA2048_EXP3:
|
|
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 vb2_error_t vb21_sig_from_usbpd1(struct vb21_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 vb21_signature s = {
|
|
.c.magic = VB21_MAGIC_SIGNATURE,
|
|
.c.struct_version_major = VB21_SIGNATURE_VERSION_MAJOR,
|
|
.c.struct_version_minor = VB21_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 vb21_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 vb21_packed_key *pkey;
|
|
uint8_t sha1sum[VB2_SHA1_DIGEST_SIZE];
|
|
int i;
|
|
|
|
vb2_pubkey_from_usbpd1(&key, sig_alg, hash_alg,
|
|
o_pubkey, o_pubkey_size);
|
|
|
|
if (vb21_public_key_pack(&pkey, &key))
|
|
return;
|
|
|
|
vb2_digest_buffer((uint8_t *)pkey + pkey->key_offset, pkey->key_size,
|
|
VB2_HASH_SHA1, sha1sum, sizeof(sha1sum));
|
|
|
|
printf("USB-PD v1 image: %s\n", name);
|
|
printf(" Algorithm: %s %s\n",
|
|
vb2_get_sig_algorithm_name(sig_alg),
|
|
vb2_get_hash_algorithm_name(hash_alg));
|
|
printf(" Key sha1sum: ");
|
|
for (i = 0; i < VB2_SHA1_DIGEST_SIZE; i++)
|
|
printf("%02x", sha1sum[i]);
|
|
printf("\n");
|
|
|
|
free(pkey);
|
|
}
|
|
|
|
|
|
/* Returns VB2_SUCCESS or random error code */
|
|
static vb2_error_t 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 vb21_signature *sig;
|
|
uint8_t buf[VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE]
|
|
__attribute__((aligned(VB2_WORKBUF_ALIGN)));
|
|
struct vb2_workbuf wb = {
|
|
.buf = buf,
|
|
.size = sizeof(buf),
|
|
};
|
|
vb2_error_t rv = VB2_ERROR_UNKNOWN;
|
|
|
|
vb2_pubkey_from_usbpd1(&pubkey, sig_alg, hash_alg,
|
|
o_pubkey, o_pubkey_size);
|
|
|
|
if ((rv = vb21_sig_from_usbpd1(&sig, sig_alg, hash_alg,
|
|
o_sig, o_sig_size, data_size)))
|
|
return rv;
|
|
|
|
rv = vb21_verify_data(data, data_size, sig, &pubkey, &wb);
|
|
|
|
free(sig);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Returns VB2_SUCCESS if the image validates itself */
|
|
static vb2_error_t 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;
|
|
vb2_error_t 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;
|
|
|
|
VB2_DEBUG("name %s len 0x%08x (%d)\n", name, 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;
|
|
|
|
/*
|
|
* 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;
|
|
}
|