summaryrefslogtreecommitdiff
path: root/chip/g/dcrypto/app_cipher.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/g/dcrypto/app_cipher.c')
-rw-r--r--chip/g/dcrypto/app_cipher.c451
1 files changed, 451 insertions, 0 deletions
diff --git a/chip/g/dcrypto/app_cipher.c b/chip/g/dcrypto/app_cipher.c
new file mode 100644
index 0000000000..125b443ee6
--- /dev/null
+++ b/chip/g/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