blob: 1a96743c5bf2b55caa7e54fd1121bbb9015e168c [file] [log] [blame]
#include <debug.h>
#include <fit.h>
#include <malloc.h>
#include <platform.h>
#include <libfdt.h>
#include <blob.h>
#include <trace.h>
#include "image.h"
#include "rsa.h"
#define LOCAL_TRACE 0
int sha256_hash(const void *source, int len, u8 *result);
int sha512_hash(const void *source, int len, u8 *result);
int calculate_hash_multi_region(const struct image_region region[],
int region_count, uint8_t *checksum,
int hash_len);
void mod_exp_65537_mont(uintptr_t *r, const uintptr_t *a,
const struct key_prop *pkey);
#define DUMP_DATA 0
#define debug(fmt, args...)
#define MAX_HASH_NODES 64
#define SHA256_SUM_LEN 32
#define FDT_MAX_DEPTH 32
#define MAX_REGS(x) (20 +x*7)
#define IS_NAME_MATCH(x,y) !strncmp((x), (y),strlen(y))
#define SHA512_SUM_LEN 64
#define PADDING_SIZE 19
#define list_each_subnode(fdt, node, parent) \
for (node = fdt_first_subnode(fdt, parent); \
node >= 0; \
node = fdt_next_subnode(fdt, node))
const uint8_t padding_sha256[PADDING_SIZE] = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20
};
const uint8_t padding_sha512[PADDING_SIZE] = {
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40
};
struct hash_algo hash_algos[] = {
{
"sha256",
SHA256_SUM_LEN,
RSA2048_BYTES,
calculate_hash_multi_region,
padding_sha256,
},
{
"sha512",
SHA512_SUM_LEN,
RSA4096_BYTES,
calculate_hash_multi_region,
padding_sha512,
},
{
"sha256",
SHA256_SUM_LEN,
RSA4096_BYTES,
calculate_hash_multi_region,
padding_sha256,
},
{
"sha512",
SHA512_SUM_LEN,
RSA2048_BYTES,
calculate_hash_multi_region,
padding_sha512,
}
};
struct sig_algo image_sig_algos[] = {
{
"sha256,rsa2048",
&hash_algos[0],
rsa_verify,
},
{
"sha512,rsa4096",
&hash_algos[1],
rsa_verify,
},
{
"sha256,rsa4096",
&hash_algos[2],
rsa_verify,
},
{
"sha512,rsa2048",
&hash_algos[3],
rsa_verify,
}
};
static void dump_data(uint8_t *buff,int len)
{
uint i=0;
for (i=0; i<len; i++) {
if ((i%16)==0)
LTRACEF("\n");
LTRACEF("0x%02x,",buff[i]);
}
}
int rsa_check_enabled(void)
{
return CHECK_RSA;
}
int hash_check_enabled(void)
{
return CHECK_HASH;
}
int calculate_hash_multi_region(const struct image_region region[],
int region_count, uint8_t *checksum,
int hash_len)
{
int ret = 0;
int32_t i,j,k=0;
unsigned long inlen=0;
unsigned char *input_data;
const unsigned char *tmp_data;
for (i = 0; i < region_count; i++)
inlen+=region[i].size;
input_data = malloc(inlen);
if (!input_data) {
LTRACEF("malloc inlen(%lu bytes) failed\n", inlen);
return -1;
}
for (i = 0; i < region_count; i++) {
tmp_data=region[i].data;
for (j = 0; j < region[i].size; j++)
input_data[k++]=tmp_data[j];
}
if (hash_len == SHA256_SUM_LEN) {
ret=sha256_hash(input_data, inlen, checksum );
} else if (hash_len == SHA512_SUM_LEN) {
ret=sha512_hash(input_data, inlen, checksum );
} else {
LTRACEF(" Unsupported hash len\n");
ret = -1;
}
free(input_data);
return ret;
}
int fdt_getval(const void *blob, int node, const char *prop_name,
int default_val)
{
const uint *tmp;
int get_val=0;
int len=0;
tmp = fdt_getprop(blob, node, prop_name, &len);
if ((tmp !=0) && ((unsigned int)len >= sizeof(int))) {
get_val = fdt32_to_cpu(tmp[0]);
return get_val;
}
LTRACEF("get %s fail\n",prop_name);
return default_val;
}
static void rsa_verify_raw(const uint8_t *bn_n, uint8_t *bn_out,
const uint8_t *bn_in)
{
mod_exp_65537_mont((uintptr_t *)bn_out, (const uintptr_t *)bn_in,
(const struct key_prop *)bn_n);
}
void get_pubkey_info(struct key_prop *pubkey, const void *blob,int node,
int *len)
{
pubkey->exp_len = sizeof(uint64_t);
pubkey->num_bits = fdt_getval(blob, node, BLOB_NBITS_NODE, 0);
pubkey->n0inv = *(uint64_t *)fdt_getprop(blob, node, BLOB_N0INV_NODE, NULL);
pubkey->rr = fdt_getprop(blob, node, BLOB_RSQU_NODE, NULL);
pubkey->modulus = fdt_getprop(blob, node, BLOB_MOD_NODE, NULL);
pubkey->public_exponent = fdt_getprop(blob, node, BLOB_EXP_NODE, len);
if (!pubkey->public_exponent || (unsigned int)*len < sizeof(uint64_t))
pubkey->public_exponent = NULL;
}
static void rsa_verify_with_pubkey(struct sig_info *info,
uint8_t *sig, uint sig_len,
int node, uint8_t *buf)
{
int i;
int length;
int rsa_length = info->algo->hash_info->pad_len;
struct key_prop prop;
const uint8_t *pubk ,*sign_tmp;
const void *blob = info->pubkey;
uint8_t __attribute__((aligned(8))) key[rsa_length], sign[rsa_length],
plain[rsa_length] ,rr[rsa_length];
uint64_t n0inv;
if (node < 0) {
LTRACEF("%s: Skipping invalid node", __func__);
return;
}
get_pubkey_info(&prop,blob,node,&length);
pubk=prop.modulus;
sign_tmp=sig;
for (i = 0; i < rsa_length; i++) {
key[rsa_length -i-1] = pubk[i];
sign[rsa_length - i -1] = sign_tmp[i];
rr[rsa_length - i -1] = *((uint8_t *)prop.rr + i);
}
#if DUMP_DATA
LTRACEF("start to dump public key ===>>>\n");
dump_data(key,rsa_length);
LTRACEF("\n end of dump public key <<<===\n");
#endif
n0inv=__builtin_bswap64(prop.n0inv);
prop.n0inv = n0inv;
prop.rr = (const uintptr_t *)rr;
prop.modulus = (const uintptr_t *)key;
mod_exp_65537_mont((uintptr_t *)plain,(const uintptr_t *)sign,&prop);
for (i = 0; i < rsa_length; i++)
buf[rsa_length - i -1] = plain[i];
#if DUMP_DATA
LTRACEF("\nstart to dump rsa_hash ===>>>\n");
dump_data(buf,rsa_length);
LTRACEF("\nend of dump public rsa_hash <<<===\n");
#endif
}
int rsa_verify(struct sig_info *info,
const struct fdt_region region[], int region_count,
uint8_t *sig, uint sig_len)
{
int ret=0,i;
int pad_len;
const uint8_t *p,*t;
uint8_t buf[sig_len];
uint8_t hash[info->algo->hash_info->pad_len];
struct image_region hash_region[region_count];
/* genarte hash region*/
for (i = 0; i < region_count; i++) {
hash_region[i].data = info->fit_image + region[i].offset;
hash_region[i].size = region[i].size;
}
if (info->algo->hash_info->hash_len >
info->algo->hash_info->pad_len) {
LTRACEF("%s: invlaid checksum-algorithm %s for %s\n",
__func__, info->algo->hash_info->hash, info->algo->rsa);
return -EINVAL;
}
/* checksum hash*/
ret = info->algo->hash_info->hash_cal(hash_region, region_count,
hash,info->algo->hash_info->hash_len);
if (ret < 0) {
LTRACEF("%s: Error in checksum calculation\n", __func__);
return -EINVAL;
}
#if DUMP_DATA
LTRACEF("start to dump calculate_hash ===>>\n");
dump_data(hash,info->algo->hash_info->pad_len);
LTRACEF("\n end of dump calculate_hash <<===\n");
#endif
if (info->req_offset != -1) {
/*rsa calculate hash*/
rsa_verify_with_pubkey(info, sig, sig_len,
info->req_offset,&buf[0]);
/* compare rsa padding. */
pad_len = info->algo->hash_info->pad_len -
info->algo->hash_info->hash_len - PADDING_SIZE;
uint8_t padding[pad_len];
memset(padding, 0xff, pad_len);
padding[0] = 0x00;
padding[1] = 0x01;
padding[pad_len -1]=0x00;
if (memcmp(padding, buf, pad_len)) {
LTRACEF("RSA check padding fail !\n");
return -EINVAL;
}
p = buf + pad_len;
t = &(info->algo->hash_info->hash_padding)[0];
if (memcmp(t, p, PADDING_SIZE)) {
LTRACEF("RSA check padding fail !\n");
return -EINVAL;
}
/* compare calculate hash and rsa hash. */
if (memcmp((uint8_t *)buf + pad_len + PADDING_SIZE, hash, sig_len - pad_len - PADDING_SIZE)) {
LTRACEF("RSA check hash fail!\n");
return -EACCES;
}
if (!ret)
return ret;
}
return ret;
}
int calculate_hash_one_region(const void *data, int data_len, const char *algo,
uint8_t *value, int *value_len)
{
if (strcmp(algo, "sha256") == 0) {
sha256_hash((unsigned char *)data, data_len,
(unsigned char *)value);
*value_len = SHA256_SUM_LEN;
} else if (strcmp(algo, "sha512") == 0) {
sha512_hash((unsigned char *)data, data_len,
(unsigned char *)value);
*value_len = SHA512_SUM_LEN;
} else {
LTRACEF(" Unsupported hash alogrithm\n");
return -1;
}
return 0;
}
int fit_get_node_value(const void *fit, int noffset, uint8_t **value,
int *value_len)
{
int len;
*value = (uint8_t *)fdt_getprop(fit, noffset, FDT_VAL_NODE, &len);
if (*value != NULL) {
*value_len = len;
return 0;
} else {
LTRACEF("fit_get_node_value NULL !!\n");
*value_len = 0;
}
return -1;
}
int fit_get_hash_algo(const void *fit, int noffset, char **algo)
{
int len;
*algo = (char *)fdt_getprop(fit, noffset, FDT_ALGO_NODE, &len);
if (*algo != NULL)
return 0;
else
LTRACEF("fdt_getprop algo NULL !!\n");
return -1;
}
static int fit_image_check_hash(const void *fit, int noffset, const void *data,
uint32_t size)
{
uint8_t value[SHA512_SUM_LEN];
int value_len;
char *algo;
uint8_t *image_hash;
int image_hash_len;
if (fit_get_hash_algo(fit, noffset, &algo)) {
LTRACEF("get hash alogrithm fail \n");
return -1;
}
if (fit_get_node_value(fit, noffset, &image_hash,&image_hash_len)) {
LTRACEF("get hash value fail \n");
return -1;
}
#if DUMP_DATA
LTRACEF("\nstart dump img hash %d===>\n",image_hash_len);
dump_data(image_hash,image_hash_len);
#endif
if (calculate_hash_one_region(data, (int)size, algo, value, &value_len)) {
LTRACEF("calsulate new hash fail !! \n");
return -1;
}
#if DUMP_DATA
LTRACEF("\nstart dump cal hash %d===>\n",value_len);
dump_data(value,value_len);
#endif
if (value_len != image_hash_len) {
LTRACEF("hash length error ! \n");
return -1;
}
if (memcmp(value, image_hash, value_len) != 0) {
LTRACEF("compare hash value fail !! \n");
return -1;
}
LTRACEF("check integrity success! \n");
return 0;
}
static int fit_verify_prepare(struct sig_info *sign_info,
const void *fit, int noffset,
int required_keynode)
{
char *algo_name;
if (fit_get_hash_algo(fit, noffset, &algo_name))
return -1;
memset(sign_info, '\0', sizeof(*sign_info));
sign_info->fit_image = (void *)fit;
sign_info->algo = image_get_sig_algo(algo_name);
sign_info->pubkey = &blob[0];
sign_info->req_offset = required_keynode;
LTRACEF("%s\n", algo_name);
if (!sign_info->algo)
return -1;
return 0;
}
int fit_image_integrity_verify(const void *fit, int image_noffset)
{
const void *data;
int noffset;
uint32_t size;
/* Get image data and data length */
if (fit_image_get_data(fit, image_noffset, &data, &size)) {
LTRACEF("fit_image_get_data fail ! \n");
return -EACCES;
}
/* Process all hash subnodes of the component image node */
list_each_subnode(fit, noffset, image_noffset) {
const char *node_name = fit_get_name(fit, noffset, NULL);
if (IS_NAME_MATCH(node_name, FDT_HASH_NODE)) {
if (fit_image_check_hash(fit, noffset, data, size))
return -EACCES;
}
}
return 0;
}
struct sig_algo *image_get_sig_algo(const char *name)
{
uint i;
for (i = 0; i < ARRAY_SIZE(image_sig_algos); i++) {
if (!strcmp(image_sig_algos[i].rsa, name))
return &image_sig_algos[i];
}
return NULL;
}
static int fit_get_hashed_node_name(const void *fit, int noffset,
int conf_noffset, char *nod_name[])
{
int prop_len;
int i;
const char *prop_start, *prop_end, *prop_name;
const char *conf_name;
char *conf_path;
size_t conf_path_len;
bool conf_hashed;
prop_start = fdt_getprop(fit, noffset, FDT_HASHED_NODE, &prop_len);
prop_end = prop_start ? prop_start + prop_len : prop_start;
prop_name = prop_start;
conf_name = fit_get_name(fit, conf_noffset, NULL);
if (conf_name == NULL)
return -1;
conf_path_len = strlen(FIT_CONFIGS_PATH) + 1 + strlen(conf_name) + 1;
conf_path = malloc(conf_path_len);
if (conf_path == NULL)
return -1;
snprintf(conf_path, conf_path_len, "%s/%s", FIT_CONFIGS_PATH, conf_name);
i = 0;
conf_hashed = false;
while (prop_name < prop_end) {
if (!conf_hashed && !strncmp(conf_path, prop_name, conf_path_len - 1))
conf_hashed = true;
nod_name[i] = (char *)prop_name;
prop_name += strlen(prop_name) + 1;
i++;
}
free(conf_path);
return conf_hashed ? i : -1;
}
static int is_recorded_string(const char *string, char *const recorder[],
int rec_num)
{
int i;
for (i = 0; i < rec_num; i++) {
if (!strcmp(recorder[i], string))
return 1;
}
return 0;
}
#define get_base(x) fdt_off_dt_struct(x)
int fdt_prop_check(const void *fit, uint offset, char *const invalid_list[],
int invalid_num, int default_val)
{
int def_val = 0;
const char *string;
const struct fdt_property *property;
if (default_val >= 2)
def_val = 1;
property = fdt_get_property_by_offset(fit, offset, NULL);
string = fdt_string(fit, fdt32_to_cpu(property->nameoff));
if (is_recorded_string(string, invalid_list, invalid_num))
def_val = 0;
return def_val;
}
int fdt_check_range(const void *fit, int reg_num,
int max_reg_num,uint nextoffset)
{
if (nextoffset != fdt_size_dt_struct(fit))
return -FDT_ERR_BADLAYOUT;
if (reg_num >= max_reg_num)
return -FDT_ERR_BADVALUE;
return 0;
}
int fdt_parsing_regions(const void *fit, char *const record_list[],
int record_num, char *const property_list[],
int property_num, struct fdt_region region[],
int max_regions_num)
{
int stack[FDT_MAX_DEPTH];
char path[200] = { '\0' };
int path_len = sizeof(path);
int path_level = -1;
char *ppath;
int nextoffset = 0;
uint previousoffset = 0;
uint32_t tag;
int region_num = 0;
int region_offset = -1;
int record = 0;
ppath = path;
do {
const char *node_name = NULL;
int node_name_len = 0;
int is_region_recorded = 0;
uint stopoffset = 0;
uint startoffset = 0;
startoffset = nextoffset;
tag = fdt_next_tag(fit, startoffset, &nextoffset);
stopoffset = nextoffset;
/* Start of node*/
if (tag == FDT_BEGIN_NODE) {
path_level++;
if (path_level == FDT_MAX_DEPTH)
return -FDT_ERR_BADSTRUCTURE;
node_name = fdt_get_name(fit, startoffset, &node_name_len);
if (ppath - path + 2 + node_name_len >= path_len)
return -FDT_ERR_NOSPACE;
if (ppath != path + 1)
*ppath++ = '/';
strncpy(ppath, node_name, node_name_len + 1);
ppath += node_name_len;
stack[path_level] = record;
if (record == 1)
stopoffset = startoffset;
if (is_recorded_string(path, record_list, record_num))
record = 2;
else if (record)
record--;
else
stopoffset = startoffset;
is_region_recorded = record;
} else if (tag == FDT_END_NODE) {
/* End of node */
is_region_recorded = record;
record = stack[path_level--];
for (; ppath > path;) {
if (*--ppath == '/') {
*ppath = '\0';
break;
}
}
} else if (tag == FDT_PROP) {
/* Property node: contain: name, size, content */
is_region_recorded = fdt_prop_check(fit, startoffset, property_list,
property_num, record);
stopoffset = startoffset;
} else if (tag == FDT_NOP) {
/* nop node*/
stopoffset = startoffset;
is_region_recorded = record >= 2;
} else if (tag == FDT_END) {
/* End of fdt */
is_region_recorded = 1;
}
if (is_region_recorded && region_offset == -1) {
if (region_num > 0) {
previousoffset = region[region_num - 1].offset +
region[region_num - 1].size - get_base(fit);
if ((region_num <= max_regions_num) &&
(startoffset == previousoffset))
region_offset = region[--region_num].offset - get_base(fit);
}
if (region_offset == -1)
region_offset = startoffset;
}
if (!is_region_recorded && region_offset != -1) {
if (region_num < max_regions_num) {
region[region_num].offset = get_base(fit) + region_offset;
region[region_num].size = stopoffset - region_offset;
}
region_num++;
region_offset = -1;
}
} while (tag != FDT_END);
//end handle
if (region_num < max_regions_num) {
region[region_num].offset = get_base(fit) + region_offset;
region[region_num].size = nextoffset - region_offset;
region_num++;
}
if (fdt_check_range(fit, region_num, max_regions_num, nextoffset))
return 0;
return region_num;
}
static int fit_check_sign(const void *fit, int noffset, int conf_noffset,
int required_keynode)
{
int node_num;
int image_sign_len;
uint8_t *image_sign;
const uint32_t *strings;
struct sig_info sign_info;
const char *tmp_data = "data" ;
char *const exc_prop[] = {(char *const)tmp_data};
char *node_name[MAX_HASH_NODES];
/* get sign info and sign image */
if (fit_verify_prepare(&sign_info, fit, noffset, required_keynode))
return -1;
if (fit_get_node_value(fit, noffset, &image_sign, &image_sign_len))
return -1;
#if DUMP_DATA
LTRACEF("\nstart to dump sign (len=%d) ===>>\n", image_sign_len);
dump_data(image_sign, image_sign_len);
LTRACEF("\n end of dump sign <<====\n");
#endif
/* get node name of fdt image */
node_num = fit_get_hashed_node_name(fit, noffset, conf_noffset, node_name);
if (node_num < 0)
return -1;
/* parsing regions */
struct fdt_region fdt_regions[MAX_REGS(node_num)];
node_num = fdt_parsing_regions(fit, node_name, node_num,
exc_prop, ARRAY_SIZE(exc_prop),
fdt_regions, (MAX_REGS(node_num) - 1));
if (node_num < 0)
return -1;
strings = fdt_getprop(fit, noffset, FDT_HASHED_STR, NULL);
if (strings) {
fdt_regions[node_num].offset = fdt_off_dt_strings(fit) +
fdt32_to_cpu(strings[0]);
fdt_regions[node_num].size = fdt32_to_cpu(strings[1]);
node_num++;
}
/* signature verify */
if (sign_info.algo->sig_verify(&sign_info, fdt_regions, node_num,
image_sign, image_sign_len)) {
return -1;
}
return 0;
}
static int fit_verify_configed_sign(const void *fit, int conf_noffset,
const void *sig_blob, int sig_offset)
{
int noffset;
int ret = -1;
list_each_subnode(fit, noffset, conf_noffset) {
const char *node_name = fit_get_name(fit, noffset, NULL);
if (IS_NAME_MATCH(node_name,FDT_SIG_NODE)) {
ret = fit_check_sign(fit, noffset, conf_noffset, sig_offset);
LTRACEF("check sign %s!\n", ret ? "fail" : "pass");
if (ret == 0)
break;
}
}
return ret;
}
int fit_verify_sign(const void *fit, int conf_noffset)
{
const char *req_node = NULL;
int ret;
int noffset;
int sig_node;
const void *sig_blob = &blob[0];
sig_node = fdt_subnode_offset(sig_blob, 0, FDT_SIG_NODE);
if (sig_node < 0) {
LTRACEF(" No sign node (signature): %s\n",fdt_strerror(sig_node));
return -1;
}
list_each_subnode(sig_blob, noffset, sig_node) {
req_node = fdt_getprop(sig_blob, noffset, BLOB_REQ_NODE, NULL);
if ((req_node !=NULL)&&(!strcmp(req_node, "conf"))) {
ret = fit_verify_configed_sign(fit, conf_noffset, sig_blob,noffset);
if (ret) {
LTRACEF("Failed to verify '%s'\n",
fit_get_name(sig_blob, noffset, NULL));
}
return ret;
}
}
return -1;
}