diff --git a/Makefile b/Makefile index f7ae5ea..8f50d7c 100644 --- a/Makefile +++ b/Makefile @@ -716,6 +716,10 @@ CRTTOOLPATH ?= tools/cert_create CRTTOOL ?= ${CRTTOOLPATH}/cert_create${BIN_EXT} +# Variables for use with Firmware Encryption Tool +ENCTOOLPATH ?= tools/encrypt_fw +ENCTOOL ?= ${ENCTOOLPATH}/encrypt_fw${BIN_EXT} + # Variables for use with Firmware Image Package FIPTOOLPATH ?= tools/fiptool FIPTOOL ?= ${FIPTOOLPATH}/fiptool${BIN_EXT} @@ -935,7 +939,7 @@ # Build targets ################################################################################ -.PHONY: all msg_start clean realclean distclean cscope locate-checkpatch checkcodebase checkpatch fiptool sptool fip sp fwu_fip certtool dtbs memmap doc +.PHONY: all msg_start clean realclean distclean cscope locate-checkpatch checkcodebase checkpatch fiptool sptool fip sp fwu_fip certtool dtbs memmap doc enctool .SUFFIXES: all: msg_start @@ -1038,6 +1042,7 @@ $(call SHELL_REMOVE_DIR,${BUILD_PLAT}) ${Q}${MAKE} --no-print-directory -C ${FIPTOOLPATH} clean ${Q}${MAKE} PLAT=${PLAT} --no-print-directory -C ${CRTTOOLPATH} clean + ${Q}${MAKE} PLAT=${PLAT} --no-print-directory -C ${ENCTOOLPATH} clean ${Q}${MAKE} --no-print-directory -C ${ROMLIBPATH} clean realclean distclean: @@ -1047,6 +1052,7 @@ ${Q}${MAKE} --no-print-directory -C ${FIPTOOLPATH} clean ${Q}${MAKE} --no-print-directory -C ${SPTOOLPATH} clean ${Q}${MAKE} PLAT=${PLAT} --no-print-directory -C ${CRTTOOLPATH} clean + ${Q}${MAKE} PLAT=${PLAT} --no-print-directory -C ${ENCTOOLPATH} realclean ${Q}${MAKE} --no-print-directory -C ${ROMLIBPATH} clean checkcodebase: locate-checkpatch @@ -1148,6 +1154,15 @@ @echo " BUILD DOCUMENTATION" ${Q}${MAKE} --no-print-directory -C ${DOCS_PATH} html +enctool: ${ENCTOOL} + +.PHONY: ${ENCTOOL} +${ENCTOOL}: + ${Q}${MAKE} PLAT=${PLAT} BUILD_INFO=0 --no-print-directory -C ${ENCTOOLPATH} + @${ECHO_BLANK_LINE} + @echo "Built $@ successfully" + @${ECHO_BLANK_LINE} + cscope: @echo " CSCOPE" ${Q}find ${CURDIR} -name "*.[chsS]" > cscope.files @@ -1184,6 +1199,7 @@ @echo " cscope Generate cscope index" @echo " distclean Remove all build artifacts for all platforms" @echo " certtool Build the Certificate generation tool" + @echo " enctool Build the Firmware encryption tool" @echo " fiptool Build the Firmware Image Package (FIP) creation tool" @echo " sp Build the Secure Partition Packages" @echo " sptool Build the Secure Partition Package creation tool" diff --git a/tools/encrypt_fw/Makefile b/tools/encrypt_fw/Makefile new file mode 100644 index 0000000..cb81d0b --- /dev/null +++ b/tools/encrypt_fw/Makefile @@ -0,0 +1,65 @@ +# +# Copyright (c) 2019, Linaro Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +PROJECT := encrypt_fw +V ?= 0 +BUILD_INFO ?= 1 +DEBUG := 0 +BINARY := ${PROJECT}${BIN_EXT} +OPENSSL_DIR := /usr + +OBJECTS := src/encrypt.o \ + src/cmd_opt.o \ + src/main.o + +HOSTCCFLAGS := -Wall -std=c99 + +MAKE_HELPERS_DIRECTORY := ../../make_helpers/ +include ${MAKE_HELPERS_DIRECTORY}build_macros.mk +include ${MAKE_HELPERS_DIRECTORY}build_env.mk + +ifeq (${DEBUG},1) + HOSTCCFLAGS += -g -O0 -DDEBUG -DLOG_LEVEL=40 +else +ifeq (${BUILD_INFO},1) + HOSTCCFLAGS += -O2 -DLOG_LEVEL=20 +else + HOSTCCFLAGS += -O2 -DLOG_LEVEL=10 +endif +endif +ifeq (${V},0) + Q := @ +else + Q := +endif + +# Make soft links and include from local directory otherwise wrong headers +# could get pulled in from firmware tree. +INC_DIR := -I ./include -I ../../include/tools_share -I ${OPENSSL_DIR}/include +LIB_DIR := -L ${OPENSSL_DIR}/lib +LIB := -lssl -lcrypto + +HOSTCC ?= gcc + +.PHONY: all clean realclean + +all: clean ${BINARY} + +${BINARY}: ${OBJECTS} Makefile + @echo " HOSTLD $@" + @echo 'const char build_msg[] = "Built : "__TIME__", "__DATE__;' | \ + ${HOSTCC} -c ${HOSTCCFLAGS} -xc - -o src/build_msg.o + ${Q}${HOSTCC} src/build_msg.o ${OBJECTS} ${LIB_DIR} ${LIB} -o $@ + +%.o: %.c + @echo " HOSTCC $<" + ${Q}${HOSTCC} -c ${HOSTCCFLAGS} ${INC_DIR} $< -o $@ + +clean: + $(call SHELL_DELETE_ALL, src/build_msg.o ${OBJECTS}) + +realclean: clean + $(call SHELL_DELETE,${BINARY}) diff --git a/tools/encrypt_fw/include/cmd_opt.h b/tools/encrypt_fw/include/cmd_opt.h new file mode 100644 index 0000000..bd7d31f --- /dev/null +++ b/tools/encrypt_fw/include/cmd_opt.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015, ARM Limited and Contributors. All rights reserved. + * Copyright (c) 2019, Linaro Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef CMD_OPT_H +#define CMD_OPT_H + +#include + +#define CMD_OPT_MAX_NUM 64 + +/* Supported long command line option types */ +enum { + CMD_OPT_FW +}; + +/* Structure to define a command line option */ +typedef struct cmd_opt_s { + struct option long_opt; + const char *help_msg; +} cmd_opt_t; + +/* Exported API*/ +void cmd_opt_add(const cmd_opt_t *cmd_opt); +const struct option *cmd_opt_get_array(void); +const char *cmd_opt_get_name(int idx); +const char *cmd_opt_get_help_msg(int idx); + +#endif /* CMD_OPT_H */ diff --git a/tools/encrypt_fw/include/debug.h b/tools/encrypt_fw/include/debug.h new file mode 100644 index 0000000..ee8f1f5 --- /dev/null +++ b/tools/encrypt_fw/include/debug.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef DEBUG_H +#define DEBUG_H + +#include + +/* The log output macros print output to the console. These macros produce + * compiled log output only if the LOG_LEVEL defined in the makefile (or the + * make command line) is greater or equal than the level required for that + * type of log output. + * The format expected is the same as for printf(). For example: + * INFO("Info %s.\n", "message") -> INFO: Info message. + * WARN("Warning %s.\n", "message") -> WARNING: Warning message. + */ + +#define LOG_LEVEL_NONE 0 +#define LOG_LEVEL_ERROR 10 +#define LOG_LEVEL_NOTICE 20 +#define LOG_LEVEL_WARNING 30 +#define LOG_LEVEL_INFO 40 +#define LOG_LEVEL_VERBOSE 50 + + +#if LOG_LEVEL >= LOG_LEVEL_NOTICE +# define NOTICE(...) printf("NOTICE: " __VA_ARGS__) +#else +# define NOTICE(...) +#endif + +#if LOG_LEVEL >= LOG_LEVEL_ERROR +# define ERROR(...) printf("ERROR: " __VA_ARGS__) +#else +# define ERROR(...) +#endif + +#if LOG_LEVEL >= LOG_LEVEL_WARNING +# define WARN(...) printf("WARNING: " __VA_ARGS__) +#else +# define WARN(...) +#endif + +#if LOG_LEVEL >= LOG_LEVEL_INFO +# define INFO(...) printf("INFO: " __VA_ARGS__) +#else +# define INFO(...) +#endif + +#if LOG_LEVEL >= LOG_LEVEL_VERBOSE +# define VERBOSE(...) printf("VERBOSE: " __VA_ARGS__) +#else +# define VERBOSE(...) +#endif + +#endif /* DEBUG_H */ diff --git a/tools/encrypt_fw/include/encrypt.h b/tools/encrypt_fw/include/encrypt.h new file mode 100644 index 0000000..25d3011 --- /dev/null +++ b/tools/encrypt_fw/include/encrypt.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, Linaro Limited. All rights reserved. + * Author: Sumit Garg + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef ENCRYPT_H +#define ENCRYPT_H + +/* Supported key algorithms */ +enum { + KEY_ALG_GCM /* AES-GCM (default) */ +}; + +int encrypt_file(unsigned short fw_enc_status, int enc_alg, char *key_string, + char *nonce_string, const char *ip_name, const char *op_name); + +#endif /* ENCRYPT_H */ diff --git a/tools/encrypt_fw/src/cmd_opt.c b/tools/encrypt_fw/src/cmd_opt.c new file mode 100644 index 0000000..64180d1 --- /dev/null +++ b/tools/encrypt_fw/src/cmd_opt.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include "debug.h" + +/* Command line options */ +static struct option long_opt[CMD_OPT_MAX_NUM+1]; +static const char *help_msg[CMD_OPT_MAX_NUM+1]; +static int num_reg_opt; + +void cmd_opt_add(const cmd_opt_t *cmd_opt) +{ + assert(cmd_opt != NULL); + + if (num_reg_opt >= CMD_OPT_MAX_NUM) { + ERROR("Out of memory. Please increase CMD_OPT_MAX_NUM\n"); + exit(1); + } + + long_opt[num_reg_opt].name = cmd_opt->long_opt.name; + long_opt[num_reg_opt].has_arg = cmd_opt->long_opt.has_arg; + long_opt[num_reg_opt].flag = 0; + long_opt[num_reg_opt].val = cmd_opt->long_opt.val; + + help_msg[num_reg_opt] = cmd_opt->help_msg; + + num_reg_opt++; +} + +const struct option *cmd_opt_get_array(void) +{ + return long_opt; +} + +const char *cmd_opt_get_name(int idx) +{ + if (idx >= num_reg_opt) { + return NULL; + } + + return long_opt[idx].name; +} + +const char *cmd_opt_get_help_msg(int idx) +{ + if (idx >= num_reg_opt) { + return NULL; + } + + return help_msg[idx]; +} diff --git a/tools/encrypt_fw/src/encrypt.c b/tools/encrypt_fw/src/encrypt.c new file mode 100644 index 0000000..18a514c --- /dev/null +++ b/tools/encrypt_fw/src/encrypt.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019, Linaro Limited. All rights reserved. + * Author: Sumit Garg + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include "debug.h" +#include "encrypt.h" + +#define BUFFER_SIZE 256 +#define IV_SIZE 12 +#define IV_STRING_SIZE 24 +#define TAG_SIZE 16 +#define KEY_SIZE 32 +#define KEY_STRING_SIZE 64 + +static int gcm_encrypt(unsigned short fw_enc_status, char *key_string, + char *nonce_string, const char *ip_name, + const char *op_name) +{ + FILE *ip_file; + FILE *op_file; + EVP_CIPHER_CTX *ctx; + unsigned char data[BUFFER_SIZE], enc_data[BUFFER_SIZE]; + unsigned char key[KEY_SIZE], iv[IV_SIZE], tag[TAG_SIZE]; + int bytes, enc_len = 0, i, j, ret = 0; + struct fw_enc_hdr header; + + memset(&header, 0, sizeof(struct fw_enc_hdr)); + + if (strlen(key_string) != KEY_STRING_SIZE) { + ERROR("Unsupported key size: %lu\n", strlen(key_string)); + return -1; + } + + for (i = 0, j = 0; i < KEY_SIZE; i++, j += 2) { + if (sscanf(&key_string[j], "%02hhx", &key[i]) != 1) { + ERROR("Incorrect key format\n"); + return -1; + } + } + + if (strlen(nonce_string) != IV_STRING_SIZE) { + ERROR("Unsupported IV size: %lu\n", strlen(nonce_string)); + return -1; + } + + for (i = 0, j = 0; i < IV_SIZE; i++, j += 2) { + if (sscanf(&nonce_string[j], "%02hhx", &iv[i]) != 1) { + ERROR("Incorrect IV format\n"); + return -1; + } + } + + ip_file = fopen(ip_name, "rb"); + if (ip_file == NULL) { + ERROR("Cannot read %s\n", ip_name); + return -1; + } + + op_file = fopen(op_name, "wb"); + if (op_file == NULL) { + ERROR("Cannot write %s\n", op_name); + fclose(ip_file); + return -1; + } + + ret = fseek(op_file, sizeof(struct fw_enc_hdr), SEEK_SET); + if (ret) { + ERROR("fseek failed\n"); + goto out_file; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ERROR("EVP_CIPHER_CTX_new failed\n"); + ret = -1; + goto out_file; + } + + ret = EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL); + if (ret != 1) { + ERROR("EVP_EncryptInit_ex failed\n"); + ret = -1; + goto out; + } + + ret = EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv); + if (ret != 1) { + ERROR("EVP_EncryptInit_ex failed\n"); + goto out; + } + + while ((bytes = fread(data, 1, BUFFER_SIZE, ip_file)) != 0) { + ret = EVP_EncryptUpdate(ctx, enc_data, &enc_len, data, bytes); + if (ret != 1) { + ERROR("EVP_EncryptUpdate failed\n"); + ret = -1; + goto out; + } + + fwrite(enc_data, 1, enc_len, op_file); + } + + ret = EVP_EncryptFinal_ex(ctx, enc_data, &enc_len); + if (ret != 1) { + ERROR("EVP_EncryptFinal_ex failed\n"); + ret = -1; + goto out; + } + + ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, TAG_SIZE, tag); + if (ret != 1) { + ERROR("EVP_CIPHER_CTX_ctrl failed\n"); + ret = -1; + goto out; + } + + header.magic = ENC_HEADER_MAGIC; + header.flags |= fw_enc_status & FW_ENC_STATUS_FLAG_MASK; + header.dec_algo = KEY_ALG_GCM; + header.iv_len = IV_SIZE; + header.tag_len = TAG_SIZE; + memcpy(header.iv, iv, IV_SIZE); + memcpy(header.tag, tag, TAG_SIZE); + + ret = fseek(op_file, 0, SEEK_SET); + if (ret) { + ERROR("fseek failed\n"); + goto out; + } + + fwrite(&header, 1, sizeof(struct fw_enc_hdr), op_file); + +out: + EVP_CIPHER_CTX_free(ctx); + +out_file: + fclose(ip_file); + fclose(op_file); + + /* + * EVP_* APIs returns 1 as success but enctool considers + * 0 as success. + */ + if (ret == 1) + ret = 0; + + return ret; +} + +int encrypt_file(unsigned short fw_enc_status, int enc_alg, char *key_string, + char *nonce_string, const char *ip_name, const char *op_name) +{ + switch (enc_alg) { + case KEY_ALG_GCM: + return gcm_encrypt(fw_enc_status, key_string, nonce_string, + ip_name, op_name); + default: + return -1; + } +} diff --git a/tools/encrypt_fw/src/main.c b/tools/encrypt_fw/src/main.c new file mode 100644 index 0000000..39b7af7 --- /dev/null +++ b/tools/encrypt_fw/src/main.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2019, Linaro Limited. All rights reserved. + * Author: Sumit Garg + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cmd_opt.h" +#include "debug.h" +#include "encrypt.h" +#include "firmware_encrypted.h" + +#define NUM_ELEM(x) ((sizeof(x)) / (sizeof(x[0]))) +#define HELP_OPT_MAX_LEN 128 + +/* Global options */ + +/* Info messages created in the Makefile */ +extern const char build_msg[]; + +static char *key_algs_str[] = { + [KEY_ALG_GCM] = "gcm", +}; + +static void print_help(const char *cmd, const struct option *long_opt) +{ + int rem, i = 0; + const struct option *opt; + char line[HELP_OPT_MAX_LEN]; + char *p; + + assert(cmd != NULL); + assert(long_opt != NULL); + + printf("\n\n"); + printf("The firmware encryption tool loads the binary image and\n" + "outputs encrypted binary image using an encryption key\n" + "provided as an input hex string.\n"); + printf("\n"); + printf("Usage:\n"); + printf("\t%s [OPTIONS]\n\n", cmd); + + printf("Available options:\n"); + opt = long_opt; + while (opt->name) { + p = line; + rem = HELP_OPT_MAX_LEN; + if (isalpha(opt->val)) { + /* Short format */ + sprintf(p, "-%c,", (char)opt->val); + p += 3; + rem -= 3; + } + snprintf(p, rem, "--%s %s", opt->name, + (opt->has_arg == required_argument) ? "" : ""); + printf("\t%-32s %s\n", line, cmd_opt_get_help_msg(i)); + opt++; + i++; + } + printf("\n"); +} + +static int get_key_alg(const char *key_alg_str) +{ + int i; + + for (i = 0 ; i < NUM_ELEM(key_algs_str) ; i++) { + if (strcmp(key_alg_str, key_algs_str[i]) == 0) { + return i; + } + } + + return -1; +} + +static void parse_fw_enc_status_flag(const char *arg, + unsigned short *fw_enc_status) +{ + unsigned long flag; + char *endptr; + + flag = strtoul(arg, &endptr, 16); + if (*endptr != '\0' || flag > FW_ENC_WITH_BSSK) { + ERROR("Invalid fw_enc_status flag '%s'\n", arg); + exit(1); + } + + *fw_enc_status = flag & FW_ENC_STATUS_FLAG_MASK; +} + +/* Common command line options */ +static const cmd_opt_t common_cmd_opt[] = { + { + { "help", no_argument, NULL, 'h' }, + "Print this message and exit" + }, + { + { "fw-enc-status", required_argument, NULL, 'f' }, + "Firmware encryption status flag (with SSK=0 or BSSK=1)." + }, + { + { "key-alg", required_argument, NULL, 'a' }, + "Encryption key algorithm: 'gcm' (default)" + }, + { + { "key", required_argument, NULL, 'k' }, + "Encryption key (for supported algorithm)." + }, + { + { "nonce", required_argument, NULL, 'n' }, + "Nonce or Initialization Vector (for supported algorithm)." + }, + { + { "in", required_argument, NULL, 'i' }, + "Input filename to be encrypted." + }, + { + { "out", required_argument, NULL, 'o' }, + "Encrypted output filename." + }, +}; + +int main(int argc, char *argv[]) +{ + int i, key_alg, ret; + int c, opt_idx = 0; + const struct option *cmd_opt; + char *key = NULL; + char *nonce = NULL; + char *in_fn = NULL; + char *out_fn = NULL; + unsigned short fw_enc_status = 0; + + NOTICE("Firmware Encryption Tool: %s\n", build_msg); + + /* Set default options */ + key_alg = KEY_ALG_GCM; + + /* Add common command line options */ + for (i = 0; i < NUM_ELEM(common_cmd_opt); i++) { + cmd_opt_add(&common_cmd_opt[i]); + } + + /* Get the command line options populated during the initialization */ + cmd_opt = cmd_opt_get_array(); + + while (1) { + /* getopt_long stores the option index here. */ + c = getopt_long(argc, argv, "a:f:hi:k:n:o:", cmd_opt, &opt_idx); + + /* Detect the end of the options. */ + if (c == -1) { + break; + } + + switch (c) { + case 'a': + key_alg = get_key_alg(optarg); + if (key_alg < 0) { + ERROR("Invalid key algorithm '%s'\n", optarg); + exit(1); + } + break; + case 'f': + parse_fw_enc_status_flag(optarg, &fw_enc_status); + break; + case 'k': + key = optarg; + break; + case 'i': + in_fn = optarg; + break; + case 'o': + out_fn = optarg; + break; + case 'n': + nonce = optarg; + break; + case 'h': + print_help(argv[0], cmd_opt); + exit(0); + case '?': + default: + print_help(argv[0], cmd_opt); + exit(1); + } + } + + if (!key) { + ERROR("Key must not be NULL\n"); + exit(1); + } + + if (!nonce) { + ERROR("Nonce must not be NULL\n"); + exit(1); + } + + if (!in_fn) { + ERROR("Input filename must not be NULL\n"); + exit(1); + } + + if (!out_fn) { + ERROR("Output filename must not be NULL\n"); + exit(1); + } + + ret = encrypt_file(fw_enc_status, key_alg, key, nonce, in_fn, out_fn); + + CRYPTO_cleanup_all_ex_data(); + + return ret; +}