Files
OpenCellular/util/signer/codesigner.cc
Vadim Bendebury 857ab8ffa2 cr50: generate self signed images with proper header values
The self signed images generated when running 'make BOARD=cr50' use
constant default values for the epoch, major and minor image header
fields. For the purposes of continuous testing we need the generated
images have sensible values in those header fields.

Since adding a full blown C++ based parser to the signer image is too
much trouble, let's just have a very basic Python based parser, which
pays attention only to the required fields from the current manifest.

BRANCH=cr50
BUG=none
TEST=built the new image and checked its version:
  $ make BOARD=cr50
  ...
  $ ./extra/usb_updater/usb_updater -b build/cr50/ec.bin
  read 524288(0x80000) bytes from build/cr50/ec.bin
  RO_A:0.0.23 RW_A:0.0.23[00000000:00000000:00000000] RO_B:-1.-1.-1 ...

Change-Id: I822475ed0a3c481b08e9268f9c13663b0b132d4a
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/651132
Reviewed-by: Marius Schilder <mschilder@chromium.org>
Reviewed-by: Mary Ruthven <mruthven@chromium.org>
2017-09-13 21:47:07 -07:00

602 lines
17 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.
*/
#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <common/image.h>
#include <common/publickey.h>
#include <common/signed_header.h>
#ifdef HAVE_JSON
#include <rapidjson/document.h>
#else
#include <pmjp.h>
#endif
#include <map>
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>
#include <libxml/parser.h>
#include <libxml/tree.h>
using namespace std;
#define VERBOSE(...) \
do { \
if (FLAGS_verbose) fprintf(stderr, __VA_ARGS__); \
} while (0)
#define FATAL(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
abort(); \
} while (0)
bool FLAGS_verbose = false;
bool FLAGS_cros = false;
int last_logical_offset = -1;
int fuse_index = 0;
// Brute xml parsing.
// Find HashItem w/ key == name, return val field, recursively.
static xmlChar* get_val(xmlNodePtr node, const char* key) {
xmlNode* cur_node = NULL;
xmlChar* val = NULL;
for (cur_node = node->children; cur_node; cur_node = cur_node->next) {
if (!strcmp("HashItem", (const char*)(cur_node->name))) {
// Hardcode parse <HashItem><Key>key</Key><Val>val</Val></HashItem>
xmlNodePtr key_node = cur_node->children->next;
xmlNodePtr val_node = cur_node->children->next->next->next;
xmlChar* keyName = xmlNodeGetContent(key_node);
xmlChar* valData = xmlNodeGetContent(val_node);
if (!strcmp(key, (const char*)keyName)) {
// Found our key, save val and done.
xmlFree(keyName);
val = valData;
break;
}
xmlFree(valData);
xmlFree(keyName);
}
val = get_val(cur_node, key);
if (val) {
// Found our key somewhere deeper down; done.
break;
}
}
return val;
}
static bool get_fuse(xmlNodePtr a_node, map<string, uint32_t>* ids,
map<string, uint32_t>* bits) {
bool result = false;
// Interested in <HashType>
if (strcmp("HashType", (const char*)(a_node->name))) {
return result;
}
// Values we are interested in.
xmlChar* RegName = get_val(a_node, "RegName");
xmlChar* Width = get_val(a_node, "Width");
xmlChar* FuseLogicalOffset = get_val(a_node, "FuseLogicalOffset");
// Track 1024 fuses at most.
int fuseLogicalOffset = atoi((const char*)FuseLogicalOffset);
if (fuseLogicalOffset >= last_logical_offset) {
last_logical_offset = fuseLogicalOffset;
ids->insert(make_pair((const char*)RegName, fuse_index++));
bits->insert(make_pair((const char*)RegName, atoi((const char*)Width)));
} else {
// Logical offset is regressing; assume we saw all the fuses.
// There are multiple sections that list all the fuses in the xml;
// we only care about parsing them once.
result = true;
}
xmlFree(FuseLogicalOffset);
xmlFree(Width);
xmlFree(RegName);
return result;
}
static bool find_fuses(xmlNodePtr a_node, map<string, uint32_t>* ids,
map<string, uint32_t>* bits) {
xmlNode* cur_node = NULL;
bool done = false;
for (cur_node = a_node; !done && cur_node; cur_node = cur_node->next) {
xmlChar* content = NULL;
if (cur_node->type == XML_TEXT_NODE &&
(content = xmlNodeGetContent(cur_node)) != NULL) {
if (!strcmp("FuseLogicalOffset", (const char*)content)) {
// Found a likely fuse definition section; collect it.
done = get_fuse(a_node->parent->parent->parent, ids, bits);
}
}
if (content) xmlFree(content);
if (!done && cur_node->children) {
done = find_fuses(cur_node->children, ids, bits);
}
}
return done;
}
static bool find_default_reg_value(xmlNodePtr a_node, const string& regname,
string* result) {
xmlNode* cur_node = NULL;
bool done = false;
for (cur_node = a_node; !done && cur_node; cur_node = cur_node->next) {
xmlChar* content = NULL;
if (cur_node->type == XML_TEXT_NODE &&
(content = xmlNodeGetContent(cur_node)) != NULL) {
if (!strcmp(regname.c_str(), (const char*)content)) {
xmlChar* val = get_val(cur_node->parent->parent->parent, "Default");
if (val) {
result->assign((const char*)val);
xmlFree(val);
done = true;
}
}
}
if (content) xmlFree(content);
if (!done && cur_node->children) {
done = find_default_reg_value(cur_node->children, regname, result);
}
}
return done;
}
// Read XML, populate two maps, name -> val
bool readXML(const string& filename, map<string, uint32_t>* ids,
map<string, uint32_t>* bits, uint32_t* p4cl) {
bool result = false;
LIBXML_TEST_VERSION
xmlDocPtr doc = xmlReadFile(filename.c_str(), NULL, 0);
if (doc) {
result = find_fuses(xmlDocGetRootElement(doc), ids, bits);
string p4clStr;
result &= find_default_reg_value(xmlDocGetRootElement(doc),
"SWDP_P4_LAST_SYNC", &p4clStr);
if (result) {
*p4cl = atoi(p4clStr.c_str());
}
xmlFreeDoc(doc);
}
xmlCleanupParser();
xmlMemoryDump();
return result;
}
// Read JSON, populate map, name -> val
bool readJSON(const string& filename, string* tag,
map<string, uint32_t>* values, map<string, uint32_t>* fusemap,
map<string, uint32_t>* infomap) {
bool result = false;
#ifdef HAVE_JSON
ifstream ifs(filename.c_str());
if (ifs) {
// Touch up a bit to allow for comments.
// Beware: we drop everything past and including '//' from any line.
// Thus '//' cannot be substring of any value..
string s;
while (ifs) {
string line;
getline(ifs, line);
size_t slash = line.find("//");
if (slash != string::npos) {
line.erase(slash);
}
s.append(line);
}
// Try parse.
rapidjson::Document d;
if (d.Parse(s.c_str()).HasParseError()) {
FATAL("JSON %s[%lu]: parse error\n", filename.c_str(),
d.GetErrorOffset());
} else {
#define CHECKVALUE(x) \
do { \
if (!d.HasMember(x)) { \
FATAL("manifest is lacking field '%s'\n", x); \
}; \
} while (0)
#define GETVALUE(x) \
do { \
if (!d.HasMember(x)) { \
FATAL("manifest is lacking field '%s'\n", x); \
}; \
(*values)[x] = d[x].GetInt(); \
} while (0)
CHECKVALUE("fuses");
const rapidjson::Document::ValueType& fuses = d["fuses"];
for (rapidjson::Value::ConstMemberIterator it = fuses.MemberBegin();
it != fuses.MemberEnd(); ++it) {
(*fusemap)[it->name.GetString()] = it->value.GetInt();
}
CHECKVALUE("info");
const rapidjson::Document::ValueType& infos = d["info"];
for (rapidjson::Value::ConstMemberIterator it = infos.MemberBegin();
it != infos.MemberEnd(); ++it) {
(*infomap)[it->name.GetString()] = it->value.GetInt();
}
GETVALUE("keyid");
GETVALUE("p4cl");
GETVALUE("epoch");
GETVALUE("major");
GETVALUE("minor");
GETVALUE("applysec");
GETVALUE("config1");
GETVALUE("err_response");
GETVALUE("expect_response");
GETVALUE("timestamp");
CHECKVALUE("tag");
const rapidjson::Document::ValueType& Tag = d["tag"];
tag->assign(Tag.GetString());
result = true;
#undef GETVALUE
#undef CHECKVALUE
}
}
#endif // HAVE_JSON
return result;
}
string inputFilename;
string outputFilename;
string keyFilename;
string xmlFilename;
string jsonFilename;
string outputFormat;
string signatureFilename;
string hashesFilename;
bool fillPattern = false;
uint32_t pattern = -1;
bool fillRandom = false;
void usage(int argc, char* argv[]) {
fprintf(stderr,
"Usage: %s options\n"
"--input=$elf-filename\n"
"--output=output-filename\n"
"--key=$pem-filename\n"
"[--b] ignored option, could be included for forward compatibility\n"
"[--cros] to sign for the ChromeOS realm w/o manifest\n"
"[--xml=$xml-filename] typically 'havenTop.xml'\n"
"[--json=$json-filename] the signing manifest\n"
"[--format=bin|hex] output file format, hex is default\n"
"[--signature=$sig-filename] replace signature with file content\n"
"[--hashes=$hashes-filename] destination file for intermediary "
"hashes to be signed\n"
"[--randomfill] to pad image to 512K with random bits\n"
"[--patternfill=N] to pad image to 512K with pattern N\n"
"[--verbose]\n",
argv[0]);
}
int getOptions(int argc, char* argv[]) {
static struct option long_options[] = {
// name, has_arg
{"b", no_argument, NULL, 'b'},
{"cros", no_argument, NULL, 'c'},
{"format", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"input", required_argument, NULL, 'i'},
{"json", required_argument, NULL, 'j'},
{"key", required_argument, NULL, 'k'},
{"output", required_argument, NULL, 'o'},
{"verbose", no_argument, NULL, 'v'},
{"xml", required_argument, NULL, 'x'},
{"signature", required_argument, NULL, 's'},
{"hashes", required_argument, NULL, 'H'},
{"randomfill", no_argument, NULL, 'r'},
{"patternfill", required_argument, NULL, 'p'},
{"writefuses", required_argument, NULL, 'w'},
{0, 0, 0, 0}};
int c, option_index = 0;
outputFormat.assign("hex");
while ((c = getopt_long(argc, argv, "i:o:p:k:x:j:f:s:H:bchvr", long_options,
&option_index)) != -1) {
switch (c) {
case 0:
fprintf(stderr, "option %s", long_options[option_index].name);
if (optarg) fprintf(stderr, " with arg %s", optarg);
fprintf(stderr, "\n");
break;
case 'b':
break;
case 'c':
FLAGS_cros = true;
break;
case 'i':
inputFilename.assign(optarg);
break;
case 'o':
outputFilename.assign(optarg);
break;
case 'k':
keyFilename.assign(optarg);
break;
case 'x':
xmlFilename.assign(optarg);
break;
case 's':
signatureFilename.assign(optarg);
break;
case 'j':
jsonFilename.assign(optarg);
break;
case 'f':
outputFormat.assign(optarg);
break;
case 'H':
hashesFilename.assign(optarg);
break;
case 'r':
fillRandom = true;
break;
case 'p':
fillPattern = true;
pattern = strtoul(optarg, NULL, 0);
break;
case 'h':
usage(argc, argv);
return 1;
case 'v':
FLAGS_verbose = true;
break;
case '?':
// getopt_long printed error
return 1;
}
}
if (inputFilename.empty() || outputFilename.empty() || keyFilename.empty() ||
((outputFormat != "bin") && (outputFormat != "hex"))) {
usage(argc, argv);
return 1;
}
return 0;
}
int main(int argc, char* argv[]) {
if (getOptions(argc, argv)) {
exit(1);
}
PublicKey key(keyFilename);
if (!key.ok()) return -1;
// Load elf.
Image image;
if (!image.fromElf(inputFilename)) return -2;
if (fillPattern) image.fillPattern(pattern);
if (fillRandom) image.fillRandom();
SignedHeader hdr;
hdr.keyid = key.n0inv();
hdr.ro_base = image.ro_base();
hdr.ro_max = image.ro_max();
hdr.rx_base = image.rx_base();
hdr.rx_max =
image.rx_max() +
12; // TODO: m3 instruction prefetch sets off GLOBALSEC when too tight
// make sure these are nops or such?
hdr.timestamp_ = time(NULL);
// Parse signing manifest.
map<string, uint32_t> values;
map<string, uint32_t> fuses;
map<string, uint32_t> infos;
string tag;
if (jsonFilename.empty()) {
// Defaults, in case no JSON
values.insert(make_pair("keyid", key.n0inv()));
values.insert(make_pair("epoch", MANIFEST_EPOCH));
values.insert(make_pair("major", MANIFEST_MAJOR));
values.insert(make_pair("minor", MANIFEST_MINOR));
}
// Hardcoded expectation. Can be overwritten in JSON w/ new explicit value.
fuses["FW_DEFINED_DATA_EXTRA_BLK6"] = 0;
if (!jsonFilename.empty() &&
!readJSON(jsonFilename, &tag, &values, &fuses, &infos)) {
FATAL("Failed to read JSON from '%s'\n", jsonFilename.c_str());
}
// Fill in more of hdr, per manifest values
for (map<string, uint32_t>::const_iterator it = values.begin();
it != values.end(); ++it) {
VERBOSE("%s : %u\n", it->first.c_str(), it->second);
}
hdr.p4cl_ = values["p4cl"];
hdr.epoch_ = values["epoch"];
hdr.major_ = values["major"];
hdr.minor_ = values["minor"];
hdr.applysec_ = values["applysec"];
hdr.config1_ = values["config1"];
hdr.err_response_ = values["err_response"];
hdr.expect_response_ = values["expect_response"];
if (values["timestamp"]) hdr.timestamp_ = values["timestamp"];
VERBOSE("timestamp: %ld\n", hdr.timestamp_);
// Check keyId.
if (values["keyid"] != hdr.keyid) {
FATAL("mismatched keyid JSON %d vs. key %d\n", values["keyid"], hdr.keyid);
}
if (FLAGS_cros) {
if (!tag.empty()) {
FATAL("--cros whilst also specifying tag per manifest is a no go");
}
tag = "\x01\x00\x00\x00"; // cros realm identifier in rwr[0]
}
// Fill in tag.
VERBOSE("tag: \"%s\"\n", tag.c_str());
strncpy((char*)(&hdr.tag), tag.c_str(), sizeof(hdr.tag));
// List the specific fuses and values.
VERBOSE("care about %lu fuses:\n", fuses.size());
for (map<string, uint32_t>::const_iterator it = fuses.begin();
it != fuses.end(); ++it) {
VERBOSE("fuse '%s' should have value %u\n", it->first.c_str(), it->second);
}
// Parse xml.
map<string, uint32_t> fuse_ids;
map<string, uint32_t> fuse_bits;
uint32_t xml_p4cl = 0;
if (!xmlFilename.empty() &&
!readXML(xmlFilename, &fuse_ids, &fuse_bits, &xml_p4cl)) {
FATAL("Failed to read XML from '%s'\n", xmlFilename.c_str());
}
if (values["p4cl"] != xml_p4cl) {
FATAL("mismatching p4cl: xml %u vs. json %u\n", xml_p4cl, values["p4cl"]);
}
VERBOSE("found %lu fuse definitions\n", fuse_ids.size());
assert(fuse_ids.size() < FUSE_MAX);
if (fuse_ids.size() != 0) {
// Make sure FW_DEFINED_DATA_EXTRA_BLK6 is still at 125, width 3.
assert(fuse_ids["FW_DEFINED_DATA_EXTRA_BLK6"] == 125);
assert(fuse_bits["FW_DEFINED_DATA_EXTRA_BLK6"] == 5);
}
// Whether we loaded xml or not, hardcode FW_DEFINED_DATA_EXTRA_BLK6
fuse_ids["FW_DEFINED_DATA_EXTRA_BLK6"] = 125;
fuse_bits["FW_DEFINED_DATA_EXTRA_BLK6"] = 5;
for (map<string, uint32_t>::const_iterator it = fuse_ids.begin();
it != fuse_ids.end(); ++it) {
VERBOSE("fuse '%s' at %u, width %u\n", it->first.c_str(), it->second,
fuse_bits[it->first]);
}
// Compute fuse_values array, according to manifest and xml.
uint32_t fuse_values[FUSE_MAX];
for (size_t i = 0; i < FUSE_MAX; ++i) fuse_values[i] = FUSE_IGNORE;
for (map<string, uint32_t>::const_iterator x = fuses.begin();
x != fuses.end(); ++x) {
map<string, uint32_t>::const_iterator it = fuse_ids.find(x->first);
if (it == fuse_ids.end()) {
FATAL("cannot find definition for fuse '%s'\n", x->first.c_str());
}
uint32_t idx = it->second;
assert(idx < FUSE_MAX);
uint32_t mask = (1ul << fuse_bits[x->first]) - 1;
if ((x->second & mask) != x->second) {
FATAL("specified fuse value too large\n");
}
uint32_t val = FUSE_PADDING & ~mask;
val |= x->second;
fuse_values[idx] = val;
hdr.markFuse(idx);
}
// Print out fuse hash input.
VERBOSE("expected fuse state:\n");
for (size_t i = 0; i < FUSE_MAX; ++i) {
VERBOSE("%08x ", fuse_values[i]);
}
VERBOSE("\n");
// Compute info_values array, according to manifest.
uint32_t info_values[INFO_MAX];
for (size_t i = 0; i < INFO_MAX; ++i) info_values[i] = INFO_IGNORE;
for (map<string, uint32_t>::const_iterator x = infos.begin();
x != infos.end(); ++x) {
uint32_t index = atoi(x->first.c_str());
assert(index < INFO_MAX);
info_values[index] ^= x->second;
hdr.markInfo(index);
}
// TODO: read values from JSON or implement version logic here.
// Print out info hash input.
VERBOSE("expected info state:\n");
for (size_t i = 0; i < INFO_MAX; ++i) {
VERBOSE("%08x ", info_values[i]);
}
VERBOSE("\n");
if (!signatureFilename.empty()) {
int fd = ::open(signatureFilename.c_str(), O_RDONLY);
if (fd > 0) {
int n = ::read(fd, hdr.signature, sizeof(hdr.signature));
::close(fd);
if (n != sizeof(hdr.signature))
FATAL("cannot read from '%s'\n", signatureFilename.c_str());
VERBOSE("provided signature\n");
} else {
FATAL("cannot open '%s'\n", signatureFilename.c_str());
}
}
// Sign image.
if (image.sign(key, &hdr, fuse_values, info_values, hashesFilename)) {
image.generate(outputFilename, outputFormat == "hex");
} else {
FATAL("failed to sign\n");
}
return 0;
}