tools: Add firmware authenticated encryption tool

Add firmware authenticated encryption tool which utilizes OpenSSL
library to encrypt firmwares using a key provided via cmdline. Currently
this tool supports AES-GCM as an authenticated encryption algorithm.

Signed-off-by: Sumit Garg <sumit.garg@linaro.org>
Change-Id: I60e296af1b98f1912a19d5f91066be7ea85836e4
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 <sumit.garg@linaro.org>
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <firmware_encrypted.h>
+#include <openssl/evp.h>
+#include <stdio.h>
+#include <string.h>
+#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;
+	}
+}