diff options
Diffstat (limited to 'board/cr50/dcrypto/app_cipher.c')
-rw-r--r-- | board/cr50/dcrypto/app_cipher.c | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/board/cr50/dcrypto/app_cipher.c b/board/cr50/dcrypto/app_cipher.c new file mode 100644 index 0000000000..125b443ee6 --- /dev/null +++ b/board/cr50/dcrypto/app_cipher.c @@ -0,0 +1,451 @@ +/* + * Copyright 2017 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 "dcrypto.h" +#include "registers.h" + +/* The default build options compile for size (-Os); instruct the + * compiler to optimize for speed here. Incidentally -O produces + * faster code than -O2! + */ +static int __attribute__((optimize("O"))) +inner_loop(uint32_t **out, const uint32_t **in, size_t len) +{ + uint32_t *outw = *out; + const uint32_t *inw = *in; + + while (len >= 16) { + uint32_t w0, w1, w2, w3; + + w0 = inw[0]; + w1 = inw[1]; + w2 = inw[2]; + w3 = inw[3]; + GREG32(KEYMGR, AES_WFIFO_DATA) = w0; + GREG32(KEYMGR, AES_WFIFO_DATA) = w1; + GREG32(KEYMGR, AES_WFIFO_DATA) = w2; + GREG32(KEYMGR, AES_WFIFO_DATA) = w3; + + while (GREG32(KEYMGR, AES_RFIFO_EMPTY)) + ; + + w0 = GREG32(KEYMGR, AES_RFIFO_DATA); + w1 = GREG32(KEYMGR, AES_RFIFO_DATA); + w2 = GREG32(KEYMGR, AES_RFIFO_DATA); + w3 = GREG32(KEYMGR, AES_RFIFO_DATA); + outw[0] = w0; + outw[1] = w1; + outw[2] = w2; + outw[3] = w3; + + inw += 4; + outw += 4; + len -= 16; + } + + *in = inw; + *out = outw; + return len; +} + +static int outer_loop(uint32_t **out, const uint32_t **in, size_t len) +{ + uint32_t *outw = *out; + const uint32_t *inw = *in; + + if (len >= 16) { + GREG32(KEYMGR, AES_WFIFO_DATA) = inw[0]; + GREG32(KEYMGR, AES_WFIFO_DATA) = inw[1]; + GREG32(KEYMGR, AES_WFIFO_DATA) = inw[2]; + GREG32(KEYMGR, AES_WFIFO_DATA) = inw[3]; + inw += 4; + len -= 16; + + len = inner_loop(&outw, &inw, len); + + while (GREG32(KEYMGR, AES_RFIFO_EMPTY)) + ; + + outw[0] = GREG32(KEYMGR, AES_RFIFO_DATA); + outw[1] = GREG32(KEYMGR, AES_RFIFO_DATA); + outw[2] = GREG32(KEYMGR, AES_RFIFO_DATA); + outw[3] = GREG32(KEYMGR, AES_RFIFO_DATA); + outw += 4; + } + + *in = inw; + *out = outw; + return len; +} + +static int aes_init(struct APPKEY_CTX *ctx, enum dcrypto_appid appid, + const uint32_t iv[4]) +{ + /* Setup USR-based application key. */ + if (!DCRYPTO_appkey_init(appid, ctx)) + return 0; + + /* Configure AES engine. */ + GWRITE_FIELD(KEYMGR, AES_CTRL, RESET, CTRL_NO_SOFT_RESET); + GWRITE_FIELD(KEYMGR, AES_CTRL, KEYSIZE, 2 /* AES-256 */); + GWRITE_FIELD(KEYMGR, AES_CTRL, CIPHER_MODE, CIPHER_MODE_CTR); + GWRITE_FIELD(KEYMGR, AES_CTRL, ENC_MODE, ENCRYPT_MODE); + GWRITE_FIELD(KEYMGR, AES_CTRL, CTR_ENDIAN, CTRL_CTR_BIG_ENDIAN); + + /* + * For fixed-key, bulk ciphering, turn off random nops (which + * are enabled by default). + */ + GWRITE_FIELD(KEYMGR, AES_RAND_STALL_CTL, STALL_EN, 0); + + /* Enable hidden key usage, each appid gets its own + * USR, with USR0 starting at 0x2a0. + */ + GWRITE_FIELD(KEYMGR, AES_USE_HIDDEN_KEY, INDEX, + 0x2a0 + (appid * 2)); + GWRITE_FIELD(KEYMGR, AES_USE_HIDDEN_KEY, ENABLE, 1); + GWRITE_FIELD(KEYMGR, AES_CTRL, ENABLE, CTRL_ENABLE); + + /* Wait for key-expansion. */ + GREG32(KEYMGR, AES_KEY_START) = 1; + while (GREG32(KEYMGR, AES_KEY_START)) + ; + + /* Check for errors (e.g. USR not correctly setup. */ + if (GREG32(KEYMGR, HKEY_ERR_FLAGS)) + return 0; + + /* Set IV. */ + GR_KEYMGR_AES_CTR(0) = iv[0]; + GR_KEYMGR_AES_CTR(1) = iv[1]; + GR_KEYMGR_AES_CTR(2) = iv[2]; + GR_KEYMGR_AES_CTR(3) = iv[3]; + + return 1; +} + +int DCRYPTO_app_cipher(enum dcrypto_appid appid, const void *salt, + void *out, const void *in, size_t len) +{ + struct APPKEY_CTX ctx; + const uint32_t *inw = in; + uint32_t *outw = out; + + /* Test pointers for word alignment. */ + if (((uintptr_t) in & 0x03) || ((uintptr_t) out & 0x03)) + return 0; + + { + /* Initialize key, and AES engine. */ + uint32_t iv[4]; + + memcpy(iv, salt, sizeof(iv)); + if (!aes_init(&ctx, appid, iv)) + return 0; + } + + len = outer_loop(&outw, &inw, len); + + if (len) { + /* Cipher the final partial block */ + uint32_t tmpin[4]; + uint32_t tmpout[4]; + const uint32_t *tmpinw; + uint32_t *tmpoutw; + + tmpinw = tmpin; + tmpoutw = tmpout; + + memcpy(tmpin, inw, len); + outer_loop(&tmpoutw, &tmpinw, 16); + memcpy(outw, tmpout, len); + } + + DCRYPTO_appkey_finish(&ctx); + return 1; +} + +#ifdef CRYPTO_TEST_SETUP + +#include "common.h" +#include "console.h" +#include "hooks.h" +#include "shared_mem.h" +#include "task.h" +#include "timer.h" +#include "watchdog.h" + +#define HEAP_HEAD_ROOM 0x400 +static uint32_t number_of_iterations; +static uint8_t result; + +/* Staticstics for ecrypt and decryp passes. */ +struct ciph_stats { + uint16_t min_time; + uint16_t max_time; + uint32_t total_time; +} __packed; /* Just in case. */ + +/* A common structure to contain information about the test run. */ +struct test_info { + size_t test_blob_size; + struct ciph_stats enc_stats; + struct ciph_stats dec_stats; + char *p; /* Pointer to an allcoated buffer of test_blob_size bytes. */ +}; + +static void init_stats(struct ciph_stats *stats) +{ + stats->min_time = ~0; + stats->max_time = 0; + stats->total_time = 0; +} + +static void update_stats(struct ciph_stats *stats, uint32_t time) +{ + if (time < stats->min_time) + stats->min_time = time; + + if (time > stats->max_time) + stats->max_time = time; + + stats->total_time += time; +} + +static void report_stats(const char *direction, struct ciph_stats *stats) +{ + ccprintf("%s results: min %d us, max %d us, average %d us\n", + direction, stats->min_time, stats->max_time, + stats->total_time / number_of_iterations); +} + +/* + * Prepare to run the test: allocate memory, initialize stats structures. + * + * Returns EC_SUCCESS if everything is fine, EC_ERROR_OVERFLOW on malloc + * failures. + */ +static int prepare_running(struct test_info *pinfo) +{ + memset(pinfo, 0, sizeof(*pinfo)); + + + pinfo->test_blob_size = shared_mem_size(); + /* + * Leave some room for crypto functions if they need to allocate + * something, just in case. 0x20 extra bytes are needed to be able to + * modify size alignment of the allocated buffer. + */ + if (pinfo->test_blob_size < (HEAP_HEAD_ROOM + 0x20)) { + ccprintf("Not enough memory to run the test\n"); + return EC_ERROR_OVERFLOW; + } + pinfo->test_blob_size = (pinfo->test_blob_size - HEAP_HEAD_ROOM); + + if (shared_mem_acquire(pinfo->test_blob_size, + (char **)&(pinfo->p)) != EC_SUCCESS) { + ccprintf("Failed to allocate %d bytes\n", + pinfo->test_blob_size); + return EC_ERROR_OVERFLOW; + } + + /* + * Use odd block size to make sure unaligned length blocks are handled + * properly. This leaves room in the end of the buffer to check if the + * decryption routine scratches it. + */ + pinfo->test_blob_size &= ~0x1f; + pinfo->test_blob_size |= 7; + + ccprintf("running %d iterations\n", number_of_iterations); + ccprintf("blob size %d at %pP\n", pinfo->test_blob_size, pinfo->p); + + init_stats(&(pinfo->enc_stats)); + init_stats(&(pinfo->dec_stats)); + + return EC_SUCCESS; +} + +/* + * Let's split the buffer in two equal halves, encrypt the lower half into the + * upper half and compare them word by word. There should be no repetitions. + * + * The side effect of this is starting the test with random clear text data. + * + * The first 16 bytes of the allocated buffer are used as the encryption IV. + */ +static int basic_check(struct test_info *pinfo) +{ + size_t half; + int i; + uint32_t *p; + + ccprintf("original data %ph\n", HEX_BUF(pinfo->p, 16)); + + half = (pinfo->test_blob_size/2) & ~3; + if (!DCRYPTO_app_cipher(NVMEM, pinfo->p, pinfo->p, + pinfo->p + half, half)) { + ccprintf("first ecnryption run failed\n"); + return EC_ERROR_UNKNOWN; + } + + p = (uint32_t *)pinfo->p; + half /= sizeof(*p); + + for (i = 0; i < half; i++) + if (p[i] == p[i + half]) { + ccprintf("repeating 32 bit word detected" + " at offset 0x%x!\n", i * 4); + return EC_ERROR_UNKNOWN; + } + + ccprintf("hashed data %ph\n", HEX_BUF(pinfo->p, 16)); + + return EC_SUCCESS; +} + +/* + * Main iteration of the console command, runs ecnryption/decryption cycles, + * vefifying that decrypted text's hash matches the original, and accumulating + * timing statistics. + */ +static int command_loop(struct test_info *pinfo) +{ + uint8_t sha[SHA_DIGEST_SIZE]; + uint8_t sha_after[SHA_DIGEST_SIZE]; + uint32_t iteration; + uint8_t *p_last_byte; + int rv; + + /* + * Prepare the hash of the original data to be able to verify + * results. + */ + DCRYPTO_SHA1_hash(pinfo->p, pinfo->test_blob_size, sha); + + /* Use the hash as an IV for the cipher. */ + memcpy(sha_after, sha, sizeof(sha_after)); + + iteration = number_of_iterations; + p_last_byte = pinfo->p + pinfo->test_blob_size; + + while (iteration--) { + char last_byte = (char) iteration; + uint32_t tstamp; + + *p_last_byte = last_byte; + + if (!(iteration % 500)) + watchdog_reload(); + + tstamp = get_time().val; + rv = DCRYPTO_app_cipher(NVMEM, sha_after, pinfo->p, + pinfo->p, pinfo->test_blob_size); + tstamp = get_time().val - tstamp; + + if (!rv) { + ccprintf("encryption failed\n"); + return EC_ERROR_UNKNOWN; + } + if (*p_last_byte != last_byte) { + ccprintf("encryption overflowed\n"); + return EC_ERROR_UNKNOWN; + } + update_stats(&pinfo->enc_stats, tstamp); + + tstamp = get_time().val; + rv = DCRYPTO_app_cipher(NVMEM, sha_after, pinfo->p, + pinfo->p, pinfo->test_blob_size); + tstamp = get_time().val - tstamp; + + if (!rv) { + ccprintf("decryption failed\n"); + return EC_ERROR_UNKNOWN; + } + if (*p_last_byte != last_byte) { + ccprintf("decryption overflowed\n"); + return EC_ERROR_UNKNOWN; + } + + DCRYPTO_SHA1_hash(pinfo->p, pinfo->test_blob_size, sha_after); + if (memcmp(sha, sha_after, sizeof(sha))) { + ccprintf("\n" + "sha1 before and after mismatch, %d to go!\n", + iteration); + return EC_ERROR_UNKNOWN; + } + + update_stats(&pinfo->dec_stats, tstamp); + + /* get a new IV */ + DCRYPTO_SHA1_hash(sha_after, sizeof(sha), sha_after); + } + + return EC_SUCCESS; +} + +/* + * Run cipher command on the hooks task context, as dcrypto's stack + * requirements exceed console tasks' allowance. + */ +static void run_cipher_cmd(void) +{ + struct test_info info; + + result = prepare_running(&info); + + if (result == EC_SUCCESS) + result = basic_check(&info); + + if (result == EC_SUCCESS) + result = command_loop(&info); + + if (result == EC_SUCCESS) { + report_stats("Encryption", &info.enc_stats); + report_stats("Decryption", &info.dec_stats); + } else if (info.p) { + ccprintf("current data %ph\n", HEX_BUF(info.p, 16)); + } + + if (info.p) + shared_mem_release(info.p); + + task_set_event(TASK_ID_CONSOLE, TASK_EVENT_CUSTOM_BIT(0), 0); +} +DECLARE_DEFERRED(run_cipher_cmd); + +static int cmd_cipher(int argc, char **argv) +{ + uint32_t events; + uint32_t max_time; + + /* Ignore potential input errors, let the user handle them. */ + if (argc > 1) + number_of_iterations = strtoi(argv[1], NULL, 0); + else + number_of_iterations = 1000; + + if (!number_of_iterations) { + ccprintf("not running zero iterations\n"); + return EC_ERROR_PARAM1; + } + + hook_call_deferred(&run_cipher_cmd_data, 0); + + /* Roughly, .5 us per byte should be more than enough. */ + max_time = number_of_iterations * shared_mem_size() / 2; + + ccprintf("Will wait up to %d ms\n", (max_time + 500)/1000); + + events = task_wait_event_mask(TASK_EVENT_CUSTOM_BIT(0), max_time); + if (!(events & TASK_EVENT_CUSTOM_BIT(0))) { + ccprintf("Timed out, you might want to reboot...\n"); + return EC_ERROR_TIMEOUT; + } + + return result; +} +DECLARE_SAFE_CONSOLE_COMMAND(cipher, cmd_cipher, NULL, NULL); +#endif |