blob: d739c456ca773d3e39c5d8f70a5d4727f75c1f66 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <apploader/cbor.h>
#include <apploader/cose.h>
#include <dice/cbor_reader.h>
#include <dice/cbor_writer.h>
#include <endian.h>
#include <fcntl.h>
#include <getopt.h>
#include <interface/apploader/apploader_package.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <array>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "../app_manifest_parser.h"
enum class Mode {
UNKNOWN,
BUILD,
SIGN,
VERIFY,
ENCRYPT,
DECRYPT,
INFO,
};
static Mode mode = Mode::UNKNOWN;
static bool strict = false;
static const char* _sopts = "hm:s";
static const struct option _lopts[] = {
{"help", no_argument, 0, 'h'},
{"mode", required_argument, 0, 'm'},
{"strict", no_argument, 0, 's'},
{0, 0, 0, 0},
};
static void print_usage_and_exit(const char* prog, int code) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, "\t%s --mode <mode> [options] ...\n", prog);
fprintf(stderr, "\t%s --mode build [options] <output> <ELF> <manifest>\n",
prog);
fprintf(stderr,
"\t%s --mode sign [options] <output> <input> <key> <key id>\n",
prog);
fprintf(stderr, "\t%s --mode verify [options] <input> <key>\n", prog);
fprintf(stderr,
"\t%s --mode encrypt [options] <output> <input> <key> <key id>\n",
prog);
fprintf(stderr, "\t%s --mode decrypt [options] <output> <input> <key>\n",
prog);
fprintf(stderr, "\t%s --mode info [options] <input>\n", prog);
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, "\t-h, --help prints this message and exit\n");
fprintf(stderr,
"\t-m, --mode mode; one of: build, sign, verify, encrypt, decrypt, info\n");
fprintf(stderr,
"\t-s, --strict verify signature in strict mode\n");
fprintf(stderr, "\n");
fprintf(stderr, "Exit Code:\n");
fprintf(stderr, " 0 on success, otherwise a non-zero error code.\n");
fprintf(stderr,
"\n"
" For info mode, it is considered an error if a package manifest requires\n"
" app encryption, but the package is not encrypted.\n");
fprintf(stderr, "\n");
fprintf(stderr, "Build:\n");
fprintf(stderr, " Cipher: %s\n", coseGetCipherAlg());
fprintf(stderr, " Signing: %s\n", coseGetSigningDsa());
fprintf(stderr, "\n");
exit(code);
}
static void parse_options(int argc, char** argv) {
int c;
int oidx = 0;
while (1) {
c = getopt_long(argc, argv, _sopts, _lopts, &oidx);
if (c == -1) {
break; /* done */
}
switch (c) {
case 'h':
print_usage_and_exit(argv[0], EXIT_SUCCESS);
break;
case 'm':
if (!strcmp(optarg, "build")) {
mode = Mode::BUILD;
} else if (!strcmp(optarg, "sign")) {
mode = Mode::SIGN;
} else if (!strcmp(optarg, "verify")) {
mode = Mode::VERIFY;
} else if (!strcmp(optarg, "encrypt")) {
mode = Mode::ENCRYPT;
} else if (!strcmp(optarg, "decrypt")) {
mode = Mode::DECRYPT;
} else if (!strcmp(optarg, "info")) {
mode = Mode::INFO;
} else {
fprintf(stderr, "Unrecognized command mode: %s\n", optarg);
/*
* Set the mode to UNKNOWN so main prints the usage and exits
*/
mode = Mode::UNKNOWN;
}
break;
case 's':
strict = true;
break;
default:
print_usage_and_exit(argv[0], EXIT_FAILURE);
}
}
}
static std::string read_entire_file(const char* file_name) {
/*
* Disable synchronization between C++ streams and FILE* functions for a
* performance boost
*/
std::ios::sync_with_stdio(false);
std::ifstream ifs(file_name, std::ios::in | std::ios::binary);
if (!ifs || !ifs.is_open()) {
fprintf(stderr, "Failed to open file '%s'\n", file_name);
exit(EXIT_FAILURE);
}
std::ostringstream ss;
ss << ifs.rdbuf();
if (!ss) {
fprintf(stderr, "Failed to read file '%s'\n", file_name);
exit(EXIT_FAILURE);
}
return ss.str();
}
static void write_entire_file(const char* file_name,
const std::vector<uint8_t>& data) {
/*
* Disable synchronization between C++ streams and FILE* functions for a
* performance boost
*/
std::ios::sync_with_stdio(false);
std::ofstream ofs(file_name,
std::ios::out | std::ios::binary | std::ios::trunc);
if (!ofs || !ofs.is_open()) {
fprintf(stderr, "Failed to create file '%s'\n", file_name);
exit(EXIT_FAILURE);
}
ofs.write(reinterpret_cast<const char*>(data.data()), data.size());
if (!ofs) {
fprintf(stderr, "Failed to write to file '%s'\n", file_name);
exit(EXIT_FAILURE);
}
}
static void build_package(const char* output_path,
const char* elf_path,
const char* manifest_path) {
auto elf = read_entire_file(elf_path);
auto manifest = read_entire_file(manifest_path);
cbor::VectorCborEncoder encoded_package;
encoded_package.encodeTag(APPLOADER_PACKAGE_CBOR_TAG_APP, [&](auto& enc) {
enc.encodeArray([&](auto& enc) {
enc.encodeUint(APPLOADER_PACKAGE_FORMAT_VERSION_CURRENT);
enc.encodeMap([&](auto& enc) { /* no elements */ });
enc.encodeBstr(elf);
enc.encodeBstr(manifest);
});
});
write_entire_file(output_path, encoded_package.intoVec());
}
static std::vector<uint8_t> string_to_vector(std::string s) {
auto* start_ptr = reinterpret_cast<uint8_t*>(s.data());
return {start_ptr, start_ptr + s.size()};
}
static uint8_t parse_key_id(const char* key_id) {
std::string key_id_str{key_id};
size_t key_id_end;
int int_key_id = std::stoi(key_id_str, &key_id_end);
if (key_id_end < key_id_str.size()) {
fprintf(stderr, "Invalid key id: %s\n", key_id);
exit(EXIT_FAILURE);
}
if (int_key_id < std::numeric_limits<uint8_t>::min() ||
int_key_id > std::numeric_limits<uint8_t>::max()) {
fprintf(stderr, "Key id out of range: %d\n", int_key_id);
exit(EXIT_FAILURE);
}
return static_cast<uint8_t>(int_key_id);
}
static void sign_package(const char* output_path,
const char* input_path,
const char* key_path,
uint8_t key_id) {
auto input = string_to_vector(read_entire_file(input_path));
if (coseIsSigned({input.data(), input.size()}, nullptr)) {
fprintf(stderr, "Input file is already signed\n");
exit(EXIT_FAILURE);
}
cbor::VectorCborEncoder enc;
enc.encodeMap([&](auto& enc) {
enc.encodeKeyValue(COSE_LABEL_ALG, COSE_VAL_SIGN_ALG);
enc.encodeKeyValue(COSE_LABEL_TRUSTY, [&](auto& enc) {
enc.encodeArray([&](auto& enc) {
enc.encodeTstr("TrustyApp");
enc.encodeUint(APPLOADER_SIGNATURE_FORMAT_VERSION_CURRENT);
});
});
});
auto key = string_to_vector(read_entire_file(key_path));
CoseByteView protectedHeadersView = enc.view();
std::basic_string_view<uint8_t> unprotectedHeadersView = {};
auto sig = coseSignEcDsa(key, key_id, input, protectedHeadersView,
unprotectedHeadersView, true, true);
if (!sig) {
fprintf(stderr, "Failed to sign package\n");
exit(EXIT_FAILURE);
}
auto full_sig = sig.value();
full_sig.insert(full_sig.end(), input.begin(), input.end());
write_entire_file(output_path, full_sig);
}
static void verify_package(const char* input_path, const char* key_path) {
auto input = string_to_vector(read_entire_file(input_path));
size_t signature_length;
if (!coseIsSigned({input.data(), input.size()}, &signature_length)) {
fprintf(stderr, "Input file is not signed\n");
exit(EXIT_FAILURE);
}
auto key = string_to_vector(read_entire_file(key_path));
bool signature_ok;
if (strict) {
auto get_key = [&key](uint8_t key_id)
-> std::tuple<std::unique_ptr<uint8_t[]>, size_t> {
auto key_data = std::make_unique<uint8_t[]>(key.size());
if (!key_data) {
return {};
}
memcpy(key_data.get(), key.data(), key.size());
return {std::move(key_data), key.size()};
};
signature_ok = strictCheckEcDsaSignature(input.data(), input.size(),
get_key, nullptr, nullptr);
} else {
std::vector<uint8_t> payload(input.begin() + signature_length,
input.end());
input.resize(signature_length);
signature_ok = coseCheckEcDsaSignature(input, payload, key);
}
if (!signature_ok) {
fprintf(stderr, "Signature verification failed\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "Signature verification passed\n");
}
struct ContentIsCoseEncrypt {
struct CborOut cursor;
bool value;
};
static std::optional<ContentIsCoseEncrypt> find_content_is_cose_encrypt(
const CoseByteView& headers) {
struct CborIn in;
size_t num_headers;
uint64_t label;
CborInInit(headers.data(), headers.size(), &in);
CborReadMap(&in, &num_headers);
std::optional<ContentIsCoseEncrypt> res;
for (size_t i = 0; i < num_headers; i++) {
if (CborReadUint(&in, &label) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid COSE header label.\n");
exit(EXIT_FAILURE);
}
if (label == APPLOADER_PACKAGE_HEADER_LABEL_CONTENT_IS_COSE_ENCRYPT) {
if (res.has_value()) {
fprintf(stderr,
"Duplicate content_is_cose_encrypt header fields\n");
exit(EXIT_FAILURE);
}
/*
* CborIn and CborOut may be layout compatible but we should not
* assume that will always be true so copy each field explicitly.
*/
struct CborOut cursor = {.buffer = (uint8_t*)in.buffer,
.buffer_size = in.buffer_size,
.cursor = in.cursor};
auto val = cbor::readCborBoolean(in);
if (!val.has_value()) {
fprintf(stderr,
"Invalid value for content_is_cose_encrypt header\n");
exit(EXIT_FAILURE);
}
res = {cursor, *val};
} else if (CborReadSkip(&in) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Failed to parse COSE headers\n");
exit(EXIT_FAILURE);
}
}
return res;
}
static void update_header_content_is_cose_encrypt(std::vector<uint8_t>& headers,
bool new_value) {
auto content_is_cose_encrypt =
find_content_is_cose_encrypt({headers.data(), headers.size()});
if (content_is_cose_encrypt.has_value()) {
if (content_is_cose_encrypt->value == new_value) {
fprintf(stderr, "Invalid content_is_cose_encrypt value\n");
exit(EXIT_FAILURE);
}
// Update the content flag
if (new_value) {
CborWriteTrue(&content_is_cose_encrypt->cursor);
} else {
CborWriteFalse(&content_is_cose_encrypt->cursor);
}
assert(!CborOutOverflowed(&content_is_cose_encrypt->cursor));
} else if (new_value) {
cbor::VectorCborEncoder enc;
enc.encodeMap([&](auto& enc) {
enc.encodeKeyValue(
APPLOADER_PACKAGE_HEADER_LABEL_CONTENT_IS_COSE_ENCRYPT,
true);
});
const auto newHeaders = enc.view();
auto updatedHeaders =
cbor::mergeMaps({headers.data(), headers.size()}, newHeaders);
assert(updatedHeaders.has_value() && "Failed to update COSE headers");
headers.assign(updatedHeaders->begin(), updatedHeaders->end());
}
}
struct PackageInfo {
// Application package format version
uint64_t version;
// Application metadata as a map of headers
CoseByteView headers;
// ELF image or COSE_Encrypt structure
CoseByteView elf_item;
// Application manifest
CoseByteView manifest;
};
static void parse_cose_recipient(struct CborIn* in,
struct PackageInfo* package) {
size_t num_elements, num_pairs;
if (CborReadArray(in, &num_elements) != CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Failed to read COSE_Recipient "
"from COSE encryption structure\n");
exit(EXIT_FAILURE);
}
if (num_elements != 3) {
fprintf(stderr, "Invalid COSE_Recipient array size, got %zu\n",
num_elements);
exit(EXIT_FAILURE);
}
const uint8_t* enc_protected_headers_data;
size_t enc_protected_headers_size;
if (CborReadBstr(in, &enc_protected_headers_size,
&enc_protected_headers_data) != CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Invalid COSE_Recipient. "
"Encrypted protected headers is not a binary string\n");
exit(EXIT_FAILURE);
}
if (CborReadMap(in, &num_pairs) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid COSE_Recipient. Failed to read map\n");
exit(EXIT_FAILURE);
}
for (size_t i = 0; i < 2 * num_pairs; i++) {
if (CborReadSkip(in) != CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Invalid COSE_Recipient. Failed to skip map element\n");
exit(EXIT_FAILURE);
}
}
const uint8_t* ciphertext_data;
size_t ciphertext_size;
if (CborReadBstr(in, &ciphertext_size, &ciphertext_data) !=
CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Invalid COSE_Recipient. "
"Ciphertext is not a binary string\n");
exit(EXIT_FAILURE);
}
}
static PackageInfo parse_package(std::string_view input, bool check_sign_tag) {
struct CborIn in;
uint64_t tag;
size_t num_elements, num_pairs;
struct PackageInfo package;
CborInInit(reinterpret_cast<const uint8_t*>(input.data()), input.size(),
&in);
if (CborReadTag(&in, &tag) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Failed to parse input file as CBOR\n");
exit(EXIT_FAILURE);
}
if (check_sign_tag && tag == COSE_TAG_SIGN1) {
fprintf(stderr, "Input file is already signed\n");
exit(EXIT_FAILURE);
}
if (tag != APPLOADER_PACKAGE_CBOR_TAG_APP) {
fprintf(stderr, "Input file is not a Trusty application package\n");
exit(EXIT_FAILURE);
}
if (CborReadArray(&in, &num_elements) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid input file format\n");
exit(EXIT_FAILURE);
}
if (num_elements != APPLOADER_PACKAGE_CBOR_ARRAY_SZ) {
fprintf(stderr, "Invalid number of CBOR array elements: %zd\n",
num_elements);
exit(EXIT_FAILURE);
}
if (CborReadUint(&in, &package.version) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid input file format\n");
exit(EXIT_FAILURE);
}
if (package.version != APPLOADER_PACKAGE_FORMAT_VERSION_CURRENT) {
fprintf(stderr,
"Invalid package version, expected %" PRIu64 " got %" PRIu64
"\n",
APPLOADER_PACKAGE_FORMAT_VERSION_CURRENT, package.version);
exit(EXIT_FAILURE);
}
const size_t headers_offset = CborInOffset(&in);
if (CborReadMap(&in, &num_pairs) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid input file format\n");
exit(EXIT_FAILURE);
}
uint64_t label;
bool content_is_cose_encrypt = false;
for (size_t i = 0; i < num_pairs; i++) {
// read key
if (CborReadUint(&in, &label) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid package headers\n");
exit(EXIT_FAILURE);
}
// read value
switch (label) {
case APPLOADER_PACKAGE_HEADER_LABEL_CONTENT_IS_COSE_ENCRYPT: {
auto val = cbor::readCborBoolean(in);
if (!val.has_value()) {
fprintf(stderr, "Invalid value for content_is_cose_encrypt\n");
exit(EXIT_FAILURE);
}
content_is_cose_encrypt = *val;
break;
}
default:
fprintf(stderr,
"Package headers contain invalid label: %" PRIu64 "\n",
label);
exit(EXIT_FAILURE);
}
}
const size_t elf_offset = CborInOffset(&in);
package.headers = {(const uint8_t*)input.data() + headers_offset,
elf_offset - headers_offset};
const uint8_t* elf_data;
size_t elf_size;
if (content_is_cose_encrypt) {
if (CborReadArray(&in, &num_elements) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid COSE encryption array\n");
exit(EXIT_FAILURE);
}
/* content is COSE_Encrypt */
if (num_elements < 3 || num_elements > 4) {
fprintf(stderr, "Invalid COSE encryption array size, got %zu\n",
num_elements);
exit(EXIT_FAILURE);
}
const uint8_t* enc_protected_headers_data;
size_t enc_protected_headers_size;
if (CborReadBstr(&in, &enc_protected_headers_size,
&enc_protected_headers_data) != CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Failed to retrieve protected headers from COSE "
"encryption structure\n");
exit(EXIT_FAILURE);
}
/* TODO: parse and validate protected headers */
if (CborReadMap(&in, &num_pairs) != CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Failed to retrieve unprotected headers from COSE "
"encryption structure\n");
exit(EXIT_FAILURE);
}
/* TODO: parse and validate unprotected headers */
for (size_t i = 0; i < 2 * num_pairs; i++) {
if (CborReadSkip(&in) != CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid input file format\n");
exit(EXIT_FAILURE);
}
}
const uint8_t* ciphertext_data;
size_t ciphertext_size;
if (CborReadBstr(&in, &ciphertext_size, &ciphertext_data) !=
CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Failed to retrieve ciphertext "
"from COSE encryption structure\n");
exit(EXIT_FAILURE);
}
if (num_elements == 4) {
size_t num_recipients;
if (CborReadArray(&in, &num_recipients) != CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Failed to read recipients array "
"from COSE encryption structure\n");
exit(EXIT_FAILURE);
}
while (num_recipients--) {
parse_cose_recipient(&in, &package);
}
}
package.elf_item = {(const uint8_t*)input.data() + elf_offset,
CborInOffset(&in) - elf_offset};
} else { /* content is unencrypted */
if (CborReadBstr(&in, &elf_size, &elf_data) != CBOR_READ_RESULT_OK) {
fprintf(stderr,
"Failed to read ELF content from application package\n");
exit(EXIT_FAILURE);
}
package.elf_item = {elf_data, elf_size};
}
const uint8_t* manifest_data;
size_t manifest_size;
if (CborReadBstr(&in, &manifest_size, &manifest_data) !=
CBOR_READ_RESULT_OK) {
fprintf(stderr, "Invalid CBOR type. Failed to read manifest as Bstr\n");
exit(EXIT_FAILURE);
}
package.manifest = {manifest_data, manifest_size};
assert(CborInAtEnd(&in));
return package;
}
static void encrypt_package(const char* output_path,
const char* input_path,
const char* key_path,
uint8_t key_id) {
auto input = read_entire_file(input_path);
auto pkg_info = parse_package(input, true);
auto key = string_to_vector(read_entire_file(key_path));
if (key.size() != kAesGcmKeySize) {
fprintf(stderr, "Wrong AES-GCM key size: %zu\n", key.size());
exit(EXIT_FAILURE);
}
cbor::VectorCborEncoder enc;
enc.encodeMap([&](auto& enc) {
enc.encodeKeyValue(COSE_LABEL_ALG, COSE_VAL_CIPHER_ALG);
enc.encodeKeyValue(COSE_LABEL_TRUSTY, "TrustyApp");
});
auto encodedProtectedHeaders = enc.intoVec();
auto cose_encrypt =
coseEncryptAesGcmKeyWrap(key, key_id, pkg_info.elf_item, {},
encodedProtectedHeaders, {}, false);
if (!cose_encrypt) {
fprintf(stderr, "Failed to encrypt ELF file\n");
exit(EXIT_FAILURE);
}
std::vector<uint8_t> enc_headers(pkg_info.headers.size());
pkg_info.headers.copy(enc_headers.data(), pkg_info.headers.size());
update_header_content_is_cose_encrypt(enc_headers, true);
// Build a new encrypted array since the original array has a semantic
// tag that we do not want to preserve.
enc = cbor::VectorCborEncoder();
enc.encodeTag(APPLOADER_PACKAGE_CBOR_TAG_APP, [&](auto& enc) {
enc.encodeArray([&](auto& enc) {
enc.encodeInt(pkg_info.version);
enc.copyBytes(enc_headers);
enc.copyBytes(cose_encrypt.value());
enc.encodeBstr(pkg_info.manifest);
});
});
auto encoded_package = enc.intoVec();
write_entire_file(output_path, encoded_package);
}
static void decrypt_package(const char* output_path,
const char* input_path,
const char* key_path) {
auto input = read_entire_file(input_path);
auto pkg_info = parse_package(input, true);
auto key = string_to_vector(read_entire_file(key_path));
if (key.size() != kAesGcmKeySize) {
fprintf(stderr, "Wrong AES-GCM key size: %zu, expected %zu\n",
key.size(), kAesGcmKeySize);
exit(EXIT_FAILURE);
}
auto get_key = [&key](
uint8_t key_id) -> std::tuple<std::unique_ptr<uint8_t[]>, size_t> {
auto key_data = std::make_unique<uint8_t[]>(key.size());
if (!key_data) {
return {};
}
memcpy(key_data.get(), key.data(), key.size());
return {std::move(key_data), key.size()};
};
const uint8_t* package_start;
size_t package_size;
if (!coseDecryptAesGcmKeyWrapInPlace(pkg_info.elf_item, get_key, {}, false,
&package_start, &package_size)) {
fprintf(stderr, "Failed to decrypt ELF file\n");
exit(EXIT_FAILURE);
}
std::vector<uint8_t> dec_headers(pkg_info.headers.size());
pkg_info.headers.copy(dec_headers.data(), pkg_info.headers.size());
update_header_content_is_cose_encrypt(dec_headers, false);
// Build a new decrypted array since the original array has a semantic
// tag that we do not want to preserve.
cbor::VectorCborEncoder enc;
enc.encodeTag(APPLOADER_PACKAGE_CBOR_TAG_APP, [&](auto& enc) {
enc.encodeArray([&](auto& enc) {
enc.encodeInt(pkg_info.version);
enc.copyBytes(dec_headers);
enc.encodeBstr({package_start, package_size});
enc.copyBytes(pkg_info.manifest);
});
});
auto encoded_package = enc.intoVec();
write_entire_file(output_path, encoded_package);
}
static void print_package_info(const char* input_path) {
// We call into some COSE functions to retrieve the
// key ids, and we don't want them to print any errors
// (which they do since we pass them invalid keys)
bool oldSilenceErrors = coseSetSilenceErrors(true);
printf("File: %s\n", input_path);
auto input = read_entire_file(input_path);
size_t signature_length = 0;
if (coseIsSigned({reinterpret_cast<uint8_t*>(input.data()), input.size()},
&signature_length)) {
printf("Signed: YES\n");
// Call into cose.cpp with a callback that prints the key id
auto print_key_id = [
](uint8_t key_id) -> std::tuple<std::unique_ptr<uint8_t[]>, size_t> {
printf("Signature key id: %" PRIu8 "\n", key_id);
return {};
};
strictCheckEcDsaSignature(
reinterpret_cast<const uint8_t*>(input.data()), input.size(),
print_key_id, nullptr, nullptr);
} else {
printf("Signed: NO\n");
}
std::string_view signed_package{input.data() + signature_length,
input.size() - signature_length};
auto pkg_info = parse_package(signed_package, false);
auto content_is_cose_encrypt =
find_content_is_cose_encrypt(pkg_info.headers);
// Get manifest to check encryption requirement
if (pkg_info.manifest.size() == 0) {
fprintf(stderr, "Package did not contain a valid manifest\n");
exit(EXIT_FAILURE);
}
struct manifest_extracts manifest_extracts;
if (!apploader_parse_manifest(
reinterpret_cast<const char*>(pkg_info.manifest.data()),
pkg_info.manifest.size(), &manifest_extracts)) {
fprintf(stderr, "Unable to extract manifest fields\n");
exit(EXIT_FAILURE);
}
if (content_is_cose_encrypt && content_is_cose_encrypt->value) {
if (manifest_extracts.requires_encryption) {
printf("Encrypted: YES, REQUIRED\n");
} else {
printf("Encrypted: YES, OPTIONAL\n");
}
// Call into cose.cpp with a callback that prints the key id
auto print_key_id = [
](uint8_t key_id) -> std::tuple<std::unique_ptr<uint8_t[]>, size_t> {
printf("Encryption key id: %" PRIu8 "\n", key_id);
return {};
};
const uint8_t* package_start;
size_t package_size;
coseDecryptAesGcmKeyWrapInPlace(pkg_info.elf_item, print_key_id, {},
false, &package_start, &package_size);
} else {
if (manifest_extracts.requires_encryption) {
printf("Encrypted: NO, REQUIRED\n");
fprintf(stderr,
"Error: app is not encrypted, contrary to manifest requirement.\n");
fprintf(stderr,
"Either encrypt the app, or remove the manifest requirement.\n");
exit(EXIT_FAILURE);
}
printf("Encrypted: NO, OPTIONAL\n");
}
const uuid_t* uuid = &manifest_extracts.uuid;
printf("UUID: %08" PRIx32 "-%04" PRIx16 "-%04" PRIx16 "-%02" PRIx8
"%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8
"%02" PRIx8 "%02" PRIx8 "\n",
uuid->time_low, uuid->time_mid, uuid->time_hi_and_version,
uuid->clock_seq_and_node[0], uuid->clock_seq_and_node[1],
uuid->clock_seq_and_node[2], uuid->clock_seq_and_node[3],
uuid->clock_seq_and_node[4], uuid->clock_seq_and_node[5],
uuid->clock_seq_and_node[6], uuid->clock_seq_and_node[7]);
printf("Version: %" PRIu32 "\n", manifest_extracts.version);
printf("Min version: %" PRIu32 "\n", manifest_extracts.min_version);
// Restore the old silence flag
coseSetSilenceErrors(oldSilenceErrors);
}
int main(int argc, char** argv) {
parse_options(argc, argv);
switch (mode) {
case Mode::BUILD:
if (optind + 3 != argc) {
print_usage_and_exit(argv[0], EXIT_FAILURE);
}
build_package(argv[optind], argv[optind + 1], argv[optind + 2]);
break;
case Mode::SIGN:
if (optind + 4 != argc) {
print_usage_and_exit(argv[0], EXIT_FAILURE);
}
sign_package(argv[optind], argv[optind + 1], argv[optind + 2],
parse_key_id(argv[optind + 3]));
break;
case Mode::VERIFY:
if (optind + 2 != argc) {
print_usage_and_exit(argv[0], EXIT_FAILURE);
}
verify_package(argv[optind], argv[optind + 1]);
break;
case Mode::ENCRYPT:
if (optind + 4 != argc) {
print_usage_and_exit(argv[0], EXIT_FAILURE);
}
encrypt_package(argv[optind], argv[optind + 1], argv[optind + 2],
parse_key_id(argv[optind + 3]));
break;
case Mode::DECRYPT:
if (optind + 3 != argc) {
print_usage_and_exit(argv[0], EXIT_FAILURE);
}
decrypt_package(argv[optind], argv[optind + 1], argv[optind + 2]);
break;
case Mode::INFO:
if (optind + 1 != argc) {
print_usage_and_exit(argv[0], EXIT_FAILURE);
}
print_package_info(argv[optind]);
break;
default:
print_usage_and_exit(argv[0], EXIT_FAILURE);
break;
}
return 0;
}