hotp: Initial commit for HMAC-OTP (RFC4226)
Add host application and a new TA that are capable of generating HMAC
based One Time Passwords according to the RFC4226 specification [1].
[1] https://www.ietf.org/rfc/rfc4226.txt
Signed-off-by: Joakim Bech <joakim.bech@linaro.org>
Tested-by: Joakim Bech <joakim.bech@linaro.org> (QEMU v7)
Acked-by: Jens Wiklander <jens.wiklander@linaro.org>
diff --git a/.gitignore b/.gitignore
index 0a65cf5..e90e7d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,6 @@
*.lds
*.map
*.o
-*.png
*.swp
*.ta
cscope.*
@@ -17,3 +16,4 @@
hello_world/host/optee_example_hello_world
random/host/optee_example_random
aes/host/optee_example_aes
+hotp/host/optee_example_hotp
diff --git a/Makefile b/Makefile
index bc92298..a00dc38 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,7 @@
EXAMPLE_LIST := hello_world
EXAMPLE_LIST += random
EXAMPLE_LIST += aes
+EXAMPLE_LIST += hotp
.PHONY: all
all: examples prepare-for-rootfs
diff --git a/hotp/Makefile b/hotp/Makefile
new file mode 100644
index 0000000..3b9a8dc
--- /dev/null
+++ b/hotp/Makefile
@@ -0,0 +1,15 @@
+export V ?= 0
+
+# If _HOST or _TA specific compilers are not specified, then use CROSS_COMPILE
+HOST_CROSS_COMPILE ?= $(CROSS_COMPILE)
+TA_CROSS_COMPILE ?= $(CROSS_COMPILE)
+
+.PHONY: all
+all:
+ $(MAKE) -C host CROSS_COMPILE="$(HOST_CROSS_COMPILE)"
+ $(MAKE) -C ta CROSS_COMPILE="$(TA_CROSS_COMPILE)"
+
+.PHONY: clean
+clean:
+ $(MAKE) -C host clean
+ $(MAKE) -C ta clean
diff --git a/hotp/host/Makefile b/hotp/host/Makefile
new file mode 100644
index 0000000..ce7065f
--- /dev/null
+++ b/hotp/host/Makefile
@@ -0,0 +1,25 @@
+CC = $(CROSS_COMPILE)gcc
+LD = $(CROSS_COMPILE)ld
+AR = $(CROSS_COMPILE)ar
+NM = $(CROSS_COMPILE)nm
+OBJCOPY = $(CROSS_COMPILE)objcopy
+OBJDUMP = $(CROSS_COMPILE)objdump
+READELF = $(CROSS_COMPILE)readelf
+
+OBJS = main.o
+
+CFLAGS += -Wall -I../ta/include -I./include
+CFLAGS += -I$(TEEC_EXPORT)/include
+LDADD += -lteec -L$(TEEC_EXPORT)/lib
+
+BINARY = optee_example_hotp
+
+.PHONY: all
+all: $(BINARY)
+
+$(BINARY): $(OBJS)
+ $(CC) -o $@ $< $(LDADD)
+
+.PHONY: clean
+clean:
+ rm -f $(OBJS) $(BINARY)
diff --git a/hotp/host/main.c b/hotp/host/main.c
new file mode 100644
index 0000000..c816b40
--- /dev/null
+++ b/hotp/host/main.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2017, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+
+/* OP-TEE TEE client API (built by optee_client) */
+#include <tee_client_api.h>
+
+/* For the UUID (found in the TA's h-file(s)) */
+#include <hotp_ta.h>
+
+struct test_value {
+ size_t count;
+ uint32_t expected;
+};
+
+/*
+ * Test values coming from the RFC4226 specification.
+ */
+struct test_value rfc4226_test_values[] = {
+ { 0, 755224 },
+ { 1, 287082 },
+ { 2, 359152 },
+ { 3, 969429 },
+ { 4, 338314 },
+ { 5, 254676 },
+ { 6, 287922 },
+ { 7, 162583 },
+ { 8, 399871 },
+ { 9, 520489 }
+};
+
+int main(int argc, char *argv[])
+{
+ TEEC_Context ctx;
+ TEEC_Operation op = { 0 };
+ TEEC_Result res;
+ TEEC_Session sess;
+ TEEC_UUID uuid = TA_HOTP_UUID;
+
+ int i;
+ uint32_t err_origin;
+ uint32_t hotp_value;
+
+ /*
+ * Shared key K ("12345678901234567890"), this is the key used in
+ * RFC4226 - Test Vectors.
+ */
+ uint8_t K[] = {
+ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ 0x37, 0x38, 0x39, 0x30
+ };
+
+ /* Initialize a context connecting us to the TEE */
+ res = TEEC_InitializeContext(NULL, &ctx);
+ if (res != TEEC_SUCCESS)
+ errx(1, "TEEC_InitializeContext failed with code 0x%x", res);
+
+ res = TEEC_OpenSession(&ctx, &sess, &uuid,
+ TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin);
+ if (res != TEEC_SUCCESS)
+ errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x",
+ res, err_origin);
+
+ /* 1. Register the shared key */
+ op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT,
+ TEEC_NONE, TEEC_NONE, TEEC_NONE);
+ op.params[0].tmpref.buffer = K;
+ op.params[0].tmpref.size = sizeof(K);
+
+ fprintf(stdout, "Register the shared key: %s\n", K);
+ res = TEEC_InvokeCommand(&sess, TA_HOTP_CMD_REGISTER_SHARED_KEY,
+ &op, &err_origin);
+ if (res != TEEC_SUCCESS) {
+ fprintf(stderr, "TEEC_InvokeCommand failed with code 0x%x "
+ "origin 0x%x\n",
+ res, err_origin);
+ goto exit;
+ }
+
+ /* 2. Get HMAC based One Time Passwords */
+ op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_OUTPUT, TEEC_NONE,
+ TEEC_NONE, TEEC_NONE);
+
+ for (i = 0; i < sizeof(rfc4226_test_values) / sizeof(struct test_value);
+ i++) {
+ res = TEEC_InvokeCommand(&sess, TA_HOTP_CMD_GET_HOTP, &op,
+ &err_origin);
+ if (res != TEEC_SUCCESS) {
+ fprintf(stderr, "TEEC_InvokeCommand failed with code "
+ "0x%x origin 0x%x\n", res, err_origin);
+ goto exit;
+ }
+
+ hotp_value = op.params[0].value.a;
+ fprintf(stdout, "HOTP: %d\n", hotp_value);
+
+ if (hotp_value != rfc4226_test_values[i].expected) {
+ fprintf(stderr, "Got unexpected HOTP from TEE! "
+ "Expected: %d, got: %d\n",
+ rfc4226_test_values[i].expected, hotp_value);
+ }
+ }
+exit:
+ TEEC_CloseSession(&sess);
+ TEEC_FinalizeContext(&ctx);
+
+ return 0;
+}
diff --git a/hotp/ta/Makefile b/hotp/ta/Makefile
new file mode 100644
index 0000000..a719ab1
--- /dev/null
+++ b/hotp/ta/Makefile
@@ -0,0 +1,13 @@
+CFG_TEE_TA_LOG_LEVEL ?= 3
+CPPFLAGS += -DCFG_TEE_TA_LOG_LEVEL=$(CFG_TEE_TA_LOG_LEVEL)
+
+# The UUID for the Trusted Application
+BINARY=484d4143-2d53-4841-3120-4a6f636b6542
+
+-include $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk
+
+ifeq ($(wildcard $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk), )
+clean:
+ @echo 'Note: $$(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk not found, cannot clean TA'
+ @echo 'Note: TA_DEV_KIT_DIR=$(TA_DEV_KIT_DIR)'
+endif
diff --git a/hotp/ta/hotp_ta.c b/hotp/ta/hotp_ta.c
new file mode 100644
index 0000000..18b7ec2
--- /dev/null
+++ b/hotp/ta/hotp_ta.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2017, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+#include <hotp_ta.h>
+#include <string.h>
+#include <tee_internal_api_extensions.h>
+#include <tee_internal_api.h>
+
+/* The size of a SHA1 hash in bytes. */
+#define SHA1_HASH_SIZE 20
+
+/* GP says that for HMAC SHA-1, max is 512 bits and min 80 bits. */
+#define MAX_KEY_SIZE 64 /* In bytes */
+#define MIN_KEY_SIZE 10 /* In bytes */
+
+/* Dynamic Binary Code 2 Modulo, which is 10^6 according to the spec. */
+#define DBC2_MODULO 1000000
+
+/*
+ * Currently this only supports a single key, in the future this could be
+ * updated to support multiple users, all with different unique keys (stored
+ * using secure storage).
+ */
+static uint8_t K[MAX_KEY_SIZE];
+static uint32_t K_len;
+
+/* The counter as defined by RFC4226. */
+static uint8_t counter[] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
+
+/*
+ * HMAC a block of memory to produce the authentication tag
+ * @param key The secret key
+ * @param keylen The length of the secret key (bytes)
+ * @param in The data to HMAC
+ * @param inlen The length of the data to HMAC (bytes)
+ * @param out [out] Destination of the authentication tag
+ * @param outlen [in/out] Max size and resulting size of authentication tag
+ */
+static TEE_Result hmac_sha1(const uint8_t *key, const size_t keylen,
+ const uint8_t *in, const size_t inlen,
+ uint8_t *out, size_t *outlen)
+{
+ TEE_Attribute attr = { 0 };
+ TEE_ObjectHandle key_handle = TEE_HANDLE_NULL;
+ TEE_OperationHandle op_handle = TEE_HANDLE_NULL;
+ TEE_Result res = TEE_SUCCESS;
+
+ if (keylen < MIN_KEY_SIZE || keylen > MAX_KEY_SIZE)
+ return TEE_ERROR_BAD_PARAMETERS;
+
+ if (!in || !out || !outlen)
+ return TEE_ERROR_BAD_PARAMETERS;
+
+ /*
+ * 1. Allocate cryptographic (operation) handle for the HMAC operation.
+ * Note that the expected size here is in bits (and therefore times
+ * 8)!
+ */
+ res = TEE_AllocateOperation(&op_handle, TEE_ALG_HMAC_SHA1, TEE_MODE_MAC,
+ keylen * 8);
+ if (res != TEE_SUCCESS) {
+ EMSG("0x%08x", res);
+ goto exit;
+ }
+
+ /*
+ * 2. Allocate a container (key handle) for the HMAC attributes. Note
+ * that the expected size here is in bits (and therefore times 8)!
+ */
+ res = TEE_AllocateTransientObject(TEE_TYPE_HMAC_SHA1, keylen * 8,
+ &key_handle);
+ if (res != TEE_SUCCESS) {
+ EMSG("0x%08x", res);
+ goto exit;
+ }
+
+ /*
+ * 3. Initialize the attributes, i.e., point to the actual HMAC key.
+ * Here, the expected size is in bytes and not bits as above!
+ */
+ TEE_InitRefAttribute(&attr, TEE_ATTR_SECRET_VALUE, key, keylen);
+
+ /* 4. Populate/assign the attributes with the key object */
+ res = TEE_PopulateTransientObject(key_handle, &attr, 1);
+ if (res != TEE_SUCCESS) {
+ EMSG("0x%08x", res);
+ goto exit;
+ }
+
+ /* 5. Associate the key (object) with the operation */
+ res = TEE_SetOperationKey(op_handle, key_handle);
+ if (res != TEE_SUCCESS) {
+ EMSG("0x%08x", res);
+ goto exit;
+ }
+
+ /* 6. Do the HMAC operations */
+ TEE_MACInit(op_handle, NULL, 0);
+ TEE_MACUpdate(op_handle, in, inlen);
+ res = TEE_MACComputeFinal(op_handle, NULL, 0, out, outlen);
+exit:
+ if (op_handle != TEE_HANDLE_NULL)
+ TEE_FreeOperation(op_handle);
+
+ /* It is OK to call this when key_handle is TEE_HANDLE_NULL */
+ TEE_FreeTransientObject(key_handle);
+
+ return res;
+}
+
+/*
+ * Truncate function working as described in RFC4226.
+ */
+static void truncate(uint8_t *hmac_result, uint32_t *bin_code)
+{
+ int offset = hmac_result[19] & 0xf;
+
+ *bin_code = (hmac_result[offset] & 0x7f) << 24 |
+ (hmac_result[offset+1] & 0xff) << 16 |
+ (hmac_result[offset+2] & 0xff) << 8 |
+ (hmac_result[offset+3] & 0xff);
+
+ *bin_code %= DBC2_MODULO;
+}
+
+static TEE_Result register_shared_key(uint32_t param_types, TEE_Param params[4])
+{
+ TEE_Result res = TEE_SUCCESS;
+
+ uint32_t exp_param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
+ TEE_PARAM_TYPE_NONE,
+ TEE_PARAM_TYPE_NONE,
+ TEE_PARAM_TYPE_NONE);
+
+ if (param_types != exp_param_types) {
+ EMSG("Expected: 0x%x, got: 0x%x", exp_param_types, param_types);
+ return TEE_ERROR_BAD_PARAMETERS;
+ }
+
+ memset(K, 0, sizeof(K));
+ memcpy(K, params[0].memref.buffer, params[0].memref.size);
+
+ K_len = params[0].memref.size;
+ DMSG("Got shared key %s (%u bytes).", K, params[0].memref.size);
+
+ return res;
+}
+
+static TEE_Result get_hotp(uint32_t param_types, TEE_Param params[4])
+{
+ TEE_Result res = TEE_SUCCESS;
+ uint32_t hotp_val;
+ uint8_t mac[SHA1_HASH_SIZE];
+ size_t mac_len = sizeof(mac);
+ int i;
+
+ uint32_t exp_param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT,
+ TEE_PARAM_TYPE_NONE,
+ TEE_PARAM_TYPE_NONE,
+ TEE_PARAM_TYPE_NONE);
+
+ if (param_types != exp_param_types) {
+ EMSG("Expected: 0x%x, got: 0x%x", exp_param_types, param_types);
+ return TEE_ERROR_BAD_PARAMETERS;
+ }
+
+ res = hmac_sha1(K, K_len, counter, sizeof(counter), mac, &mac_len);
+
+ /* Increment the counter. */
+ for (i = sizeof(counter) - 1; i >= 0; i--) {
+ if (++counter[i])
+ break;
+ }
+
+ truncate(mac, &hotp_val);
+ DMSG("HOTP is: %d", hotp_val);
+ params[0].value.a = hotp_val;
+
+ return res;
+}
+
+/*******************************************************************************
+ * Mandatory TA functions.
+ ******************************************************************************/
+TEE_Result TA_CreateEntryPoint(void)
+{
+ return TEE_SUCCESS;
+}
+
+void TA_DestroyEntryPoint(void)
+{
+}
+
+TEE_Result TA_OpenSessionEntryPoint(uint32_t param_types,
+ TEE_Param __unused params[4],
+ void __unused **sess_ctx)
+{
+ uint32_t exp_param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_NONE,
+ TEE_PARAM_TYPE_NONE,
+ TEE_PARAM_TYPE_NONE,
+ TEE_PARAM_TYPE_NONE);
+ if (param_types != exp_param_types)
+ return TEE_ERROR_BAD_PARAMETERS;
+
+ return TEE_SUCCESS;
+}
+
+void TA_CloseSessionEntryPoint(void __unused *sess_ctx)
+{
+}
+
+TEE_Result TA_InvokeCommandEntryPoint(void __unused *sess_ctx,
+ uint32_t cmd_id,
+ uint32_t param_types, TEE_Param params[4])
+{
+ switch (cmd_id) {
+ case TA_HOTP_CMD_REGISTER_SHARED_KEY:
+ return register_shared_key(param_types, params);
+
+ case TA_HOTP_CMD_GET_HOTP:
+ return get_hotp(param_types, params);
+
+ default:
+ return TEE_ERROR_BAD_PARAMETERS;
+ }
+}
diff --git a/hotp/ta/include/hotp_ta.h b/hotp/ta/include/hotp_ta.h
new file mode 100644
index 0000000..b9a787c
--- /dev/null
+++ b/hotp/ta/include/hotp_ta.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2017, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+#ifndef __HOTP_TA_H__
+#define __HOTP_TA_H__
+
+/*
+ * This TA implements HOTP according to:
+ * https://www.ietf.org/rfc/rfc4226.txt
+ */
+
+#define TA_HOTP_UUID \
+ { 0x484d4143, 0x2d53, 0x4841, \
+ { 0x31, 0x20, 0x4a, 0x6f, 0x63, 0x6b, 0x65, 0x42 } }
+
+/* The function ID(s) implemented in this TA */
+#define TA_HOTP_CMD_REGISTER_SHARED_KEY 0
+#define TA_HOTP_CMD_GET_HOTP 1
+
+#endif
diff --git a/hotp/ta/sub.mk b/hotp/ta/sub.mk
new file mode 100644
index 0000000..122c5bb
--- /dev/null
+++ b/hotp/ta/sub.mk
@@ -0,0 +1,2 @@
+global-incdirs-y += include
+srcs-y += hotp_ta.c
diff --git a/hotp/ta/user_ta_header_defines.h b/hotp/ta/user_ta_header_defines.h
new file mode 100644
index 0000000..5018a69
--- /dev/null
+++ b/hotp/ta/user_ta_header_defines.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2017, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* The name of this file must not be modified */
+#ifndef USER_TA_HEADER_DEFINES_H
+#define USER_TA_HEADER_DEFINES_H
+
+ /* To get the TA_HOTP_UUID define */
+#include <hotp_ta.h>
+
+#define TA_UUID TA_HOTP_UUID
+
+#define TA_FLAGS TA_FLAG_EXEC_DDR
+
+/* Provisioned stack size */
+#define TA_STACK_SIZE (2 * 1024)
+
+/* Provisioned heap size for TEE_Malloc() and friends */
+#define TA_DATA_SIZE (32 * 1024)
+
+/* Extra properties (give a version id and a string name) */
+#define TA_CURRENT_TA_EXT_PROPERTIES \
+ { "gp.ta.description", USER_TA_PROP_TYPE_STRING, \
+ "HMAC-Based One-Time Password Algorithm (RFC4226)" }, \
+ { "gp.ta.version", USER_TA_PROP_TYPE_U32, &(const uint32_t){ 0x0010 } }
+
+#endif