aes-test: Add script to test AES implementations according to AESAVS/GCMVS
authorTobias Brunner <tobias@strongswan.org>
Mon, 5 Aug 2013 16:20:50 +0000 (18:20 +0200)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Sat, 24 Aug 2013 14:22:51 +0000 (16:22 +0200)
scripts/Makefile.am
scripts/aes-test.c [new file with mode: 0644]

index 53c4bcf..ed5147a 100644 (file)
@@ -5,7 +5,7 @@ AM_CPPFLAGS = \
 
 noinst_PROGRAMS = bin2array bin2sql id2sql key2keyid keyid2sql oid2der \
        thread_analysis dh_speed pubkey_speed crypt_burn hash_burn fetch \
-       dnssec malloc_speed
+       dnssec malloc_speed aes-test
 
 if USE_TLS
   noinst_PROGRAMS += tls_test
@@ -39,6 +39,7 @@ hash_burn_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
 malloc_speed_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la $(RTLIB)
 fetch_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
 dnssec_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
+aes_test_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
 
 key2keyid.o :  $(top_builddir)/config.status
 
diff --git a/scripts/aes-test.c b/scripts/aes-test.c
new file mode 100644 (file)
index 0000000..df9e8e7
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2013 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include <library.h>
+
+/** plugins to load */
+#undef PLUGINS
+#define PLUGINS "openssl"
+
+/**
+ * Context
+ */
+static struct {
+       /** input file */
+       FILE *in;
+       /** output file */
+       FILE *out;
+       /** whether to use GCM or CBC */
+       bool use_gcm;
+       /** whether to run the Monte Carlo Test */
+       bool use_mct;
+       /** whether to test encryption or decryption */
+       bool decrypt;
+       /** IV length in bits in case of GCM */
+       int ivlen;
+       /** ICV length in bits in case of GCM */
+       int icvlen;
+} ctx;
+
+/**
+ * Types of parameters of a test vector
+ */
+typedef enum {
+       PARAM_UNKNOWN,
+       PARAM_COUNT,
+       PARAM_KEY,
+       PARAM_IV,
+       PARAM_PLAINTEXT,
+       PARAM_CIPHERTEXT,
+       PARAM_AAD,
+       PARAM_ICV,
+} param_t;
+
+static param_t parse_parameter(char *param)
+{
+       if (strcaseeq(param, "COUNT"))
+       {
+               return PARAM_COUNT;
+       }
+       if (strcaseeq(param, "KEY"))
+       {
+               return PARAM_KEY;
+       }
+       if (strcaseeq(param, "IV"))
+       {
+               return PARAM_IV;
+       }
+       if (strcaseeq(param, "PLAINTEXT") ||
+               strcaseeq(param, "PT"))
+       {
+               return PARAM_PLAINTEXT;
+       }
+       if (strcaseeq(param, "CIPHERTEXT") ||
+               strcaseeq(param, "CT"))
+       {
+               return PARAM_CIPHERTEXT;
+       }
+       if (strcaseeq(param, "AAD"))
+       {
+               return PARAM_AAD;
+       }
+       if (strcaseeq(param, "TAG"))
+       {
+               return PARAM_ICV;
+       }
+       return PARAM_UNKNOWN;
+}
+
+/**
+ * Test vector
+ */
+typedef struct {
+       /** encryption/decryption key */
+       chunk_t key;
+       /** initialization vector */
+       chunk_t iv;
+       /** plain text */
+       chunk_t plain;
+       /** cipher text */
+       chunk_t cipher;
+       /** associated data */
+       chunk_t aad;
+       /** ICV/tag */
+       chunk_t icv;
+       /** whether the IV was provided */
+       bool external_iv;
+       /** whether the decryption/verification in GCM mode was successful */
+       bool success;
+} test_vector_t;
+
+static void test_vector_free(test_vector_t *test)
+{
+       chunk_free(&test->key);
+       chunk_free(&test->iv);
+       chunk_free(&test->plain);
+       chunk_free(&test->cipher);
+       chunk_free(&test->aad);
+       chunk_free(&test->icv);
+}
+
+static void print_result(test_vector_t *test)
+{
+       if (ctx.use_gcm)
+       {
+               if (ctx.decrypt)
+               {
+                       if (test->success)
+                       {
+                               fprintf(ctx.out, "PT = %+B\n", &test->plain);
+                       }
+                       else
+                       {
+                               fprintf(ctx.out, "FAIL\n");
+                       }
+                       return;
+               }
+               if (!test->external_iv)
+               {
+                       fprintf(ctx.out, "IV = %+B\n", &test->iv);
+               }
+               fprintf(ctx.out, "CT = %+B\n", &test->cipher);
+               fprintf(ctx.out, "Tag = %+B\n", &test->icv);
+       }
+       else
+       {
+               fprintf(ctx.out, "%s = %+B\n", ctx.decrypt ? "PLAINTEXT" : "CIPHERTEXT",
+                               ctx.decrypt ? &test->plain : &test->cipher);
+       }
+}
+
+static bool get_next_test_vector(test_vector_t *test)
+{
+       param_t param = PARAM_UNKNOWN;
+       char line[512];
+
+       memset(test, 0, sizeof(test_vector_t));
+
+       while (fgets(line, sizeof(line), ctx.in))
+       {
+               enumerator_t *enumerator;
+               chunk_t value;
+               char *token;
+               int i;
+
+               switch (line[0])
+               {
+                       case '\n':
+                       case '\r':
+                       case '#':
+                       case '\0':
+                               /* copy comments, empty lines etc. directly to the output */
+                               if (param != PARAM_UNKNOWN)
+                               {       /* seems we got a complete test vector */
+                                       return TRUE;
+                               }
+                               fputs(line, ctx.out);
+                               continue;
+                       case '[':
+                               /* control directives */
+                               fputs(line, ctx.out);
+                               if (strpfx(line, "[ENCRYPT]"))
+                               {
+                                       ctx.decrypt = FALSE;
+                               }
+                               else if (strpfx(line, "[DECRYPT]"))
+                               {
+                                       ctx.decrypt = TRUE;
+                               }
+                               else if (strcasepfx(line, "[IVlen = "))
+                               {
+                                       ctx.ivlen = atoi(line + strlen("[IVlen = "));
+                               }
+                               else if (strcasepfx(line, "[Taglen = "))
+                               {
+                                       ctx.icvlen = atoi(line + strlen("[Taglen = "));
+                               }
+                               continue;
+                       default:
+                               /* we assume the rest of the lines are PARAM = VALUE pairs*/
+                               fputs(line, ctx.out);
+                               break;
+               }
+
+               i = 0;
+               enumerator = enumerator_create_token(line, "=", " \n\r");
+               while (enumerator->enumerate(enumerator, &token))
+               {
+                       switch (i++)
+                       {
+                               case 0: /* PARAM */
+                                       param = parse_parameter(token);
+                                       continue;
+                               case 1: /* VALUE */
+                                       if (param != PARAM_UNKNOWN && param != PARAM_COUNT)
+                                       {
+                                               value = chunk_from_hex(chunk_from_str(token), NULL);
+                                       }
+                                       else
+                                       {
+                                               value = chunk_empty;
+                                       }
+                                       continue;
+                               default:
+                                       break;
+                       }
+                       break;
+               }
+               enumerator->destroy(enumerator);
+               if (i < 2)
+               {
+                       value = chunk_empty;
+               }
+               switch (param)
+               {
+                       case PARAM_KEY:
+                               test->key = value;
+                               break;
+                       case PARAM_IV:
+                               test->iv = value;
+                               test->external_iv = TRUE;
+                               break;
+                       case PARAM_PLAINTEXT:
+                               test->plain = value;
+                               break;
+                       case PARAM_CIPHERTEXT:
+                               test->cipher = value;
+                               break;
+                       case PARAM_AAD:
+                               test->aad = value;
+                               break;
+                       case PARAM_ICV:
+                               test->icv = value;
+                               break;
+                       default:
+                               chunk_free(&value);
+                               break;
+               }
+       }
+       return FALSE;
+}
+
+static bool verify_test_vector(test_vector_t *test)
+{
+       if (ctx.use_gcm)
+       {
+               if (ctx.decrypt)
+               {
+                       return test->key.ptr && test->iv.ptr && test->cipher.ptr &&
+                                  test->icv.ptr;
+               }
+               return test->key.ptr && test->plain.ptr;
+       }
+       if (ctx.decrypt)
+       {
+               return test->key.ptr && test->iv.ptr && test->cipher.ptr;
+       }
+       return test->key.ptr && test->iv.ptr && test->plain.ptr;
+}
+
+static bool do_test_gcm(test_vector_t *test)
+{
+       encryption_algorithm_t alg;
+       chunk_t key, iv;
+       aead_t *aead;
+       size_t saltlen, ivlen;
+
+       switch (ctx.icvlen / 8)
+       {
+               case 8:
+                       alg = ENCR_AES_GCM_ICV8;
+                       break;
+               case 12:
+                       alg = ENCR_AES_GCM_ICV12;
+                       break;
+               case 16:
+                       alg = ENCR_AES_GCM_ICV16;
+                       break;
+               default:
+                       DBG1(DBG_APP, "unsupported ICV length: %d", ctx.icvlen);
+                       return FALSE;
+       }
+
+       aead = lib->crypto->create_aead(lib->crypto, alg, test->key.len);
+       if (!aead)
+       {
+               DBG1(DBG_APP, "algorithm %N or key length (%d bits) not supported",
+                        encryption_algorithm_names, alg, test->key.len * 8);
+               return FALSE;
+       }
+       /* our API is quite RFC 4106 specific, that is, part of the IV is provided
+        * at the end of the key. */
+       saltlen = aead->get_key_size(aead) - test->key.len;
+       ivlen = aead->get_iv_size(aead);
+       if (ctx.ivlen / 8 != saltlen + ivlen)
+       {
+               DBG1(DBG_APP, "unsupported IV length: %d", ctx.ivlen);
+               aead->destroy(aead);
+               return FALSE;
+       }
+       if (!test->external_iv)
+       {
+               rng_t *rng;
+
+               /* the IV consists of saltlen random bytes (usually additional keymat)
+                * followed by a counter, zero here */
+               test->iv = chunk_alloc(saltlen + ivlen);
+               memset(test->iv.ptr, 0, test->iv.len);
+               rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
+               if (!rng || !rng->get_bytes(rng, saltlen, test->iv.ptr))
+               {
+                       DBG1(DBG_APP, "failed to generate IV");
+                       DESTROY_IF(rng);
+                       aead->destroy(aead);
+                       return FALSE;
+               }
+               rng->destroy(rng);
+       }
+       key = chunk_alloca(test->key.len + saltlen);
+       memcpy(key.ptr, test->key.ptr, test->key.len);
+       memcpy(key.ptr + test->key.len, test->iv.ptr, saltlen);
+       iv = chunk_alloca(ivlen);
+       memcpy(iv.ptr, test->iv.ptr + saltlen, iv.len);
+       if (!aead->set_key(aead, key))
+       {
+               DBG1(DBG_APP, "failed to set key");
+               aead->destroy(aead);
+               return FALSE;
+       }
+       if (ctx.decrypt)
+       {
+               /* the ICV is expected to follow the cipher text */
+               chunk_t cipher = chunk_cata("cc", test->cipher, test->icv);
+               /* store if the verification of the ICV verification is successful */
+               test->success = aead->decrypt(aead, cipher, test->aad, iv,
+                                                                         &test->plain);
+       }
+       else
+       {
+               if (!aead->encrypt(aead, test->plain, test->aad, iv, &test->cipher))
+               {
+                       DBG1(DBG_APP, "encryption failed");
+                       aead->destroy(aead);
+                       return FALSE;
+               }
+               /* copy ICV from the end of the cipher text */
+               test->icv = chunk_alloc(ctx.icvlen / 8);
+               test->cipher.len -= test->icv.len;
+               memcpy(test->icv.ptr, test->cipher.ptr + test->cipher.len,
+                          test->icv.len);
+       }
+       aead->destroy(aead);
+       return TRUE;
+}
+
+static bool crypt(crypter_t *crypter, test_vector_t *test)
+{
+       if (ctx.decrypt)
+       {
+               if (!crypter->decrypt(crypter, test->cipher, test->iv, &test->plain))
+               {
+                       DBG1(DBG_APP, "decryption failed");
+                       return FALSE;
+               }
+       }
+       else
+       {
+               if (!crypter->encrypt(crypter, test->plain, test->iv, &test->cipher))
+               {
+                       DBG1(DBG_APP, "encryption failed");
+                       return FALSE;
+               }
+       }
+       return TRUE;
+}
+
+static bool do_test_cbc(test_vector_t *test)
+{
+       crypter_t *crypter;
+
+       crypter = lib->crypto->create_crypter(lib->crypto, ENCR_AES_CBC,
+                                                                                 test->key.len);
+       if (!crypter)
+       {
+               DBG1(DBG_APP, "algorithm %N or key length (%d bits) not supported",
+                        encryption_algorithm_names, ENCR_AES_CBC, test->key.len * 8);
+               return FALSE;
+       }
+       if (!crypter->set_key(crypter, test->key))
+       {
+               DBG1(DBG_APP, "failed to set key");
+               crypter->destroy(crypter);
+               return FALSE;
+       }
+       if (!crypt(crypter, test))
+       {
+               crypter->destroy(crypter);
+               return FALSE;
+       }
+       crypter->destroy(crypter);
+       return TRUE;
+}
+
+static bool do_test_mct(test_vector_t *test)
+{
+       crypter_t *crypter;
+       chunk_t prev, *input, *output;
+       int i, j;
+
+       crypter = lib->crypto->create_crypter(lib->crypto, ENCR_AES_CBC,
+                                                                                 test->key.len);
+       if (!crypter)
+       {
+               DBG1(DBG_APP, "algorithm %N or key length (%d bits) not supported",
+                        encryption_algorithm_names, ENCR_AES_CBC, test->key.len * 8);
+               return FALSE;
+       }
+       input = ctx.decrypt ? &test->cipher : &test->plain;
+       output = ctx.decrypt ? &test->plain : &test->cipher;
+       if (crypter->get_block_size(crypter) != input->len)
+       {
+               DBG1(DBG_APP, "MCT only works for input with a length of one block");
+               crypter->destroy(crypter);
+               return FALSE;
+       }
+       prev = chunk_alloca(input->len);
+       /* assume initial IV as previous output */
+       *output = chunk_clone(test->iv);
+       for (i = 0; i < 100; i++)
+       {
+               if (i > 0)
+               {       /* we copied the original lines already */
+                       fprintf(ctx.out, "COUNT = %d\n", i);
+                       fprintf(ctx.out, "KEY = %+B\n", &test->key);
+                       fprintf(ctx.out, "IV = %+B\n", &test->iv);
+                       fprintf(ctx.out, "%s = %+B\n",
+                                       ctx.decrypt ? "CIPHERTEXT" : "PLAINTEXT", input);
+               }
+               if (!crypter->set_key(crypter, test->key))
+               {
+                       DBG1(DBG_APP, "failed to set key");
+                       return FALSE;
+               }
+               for (j = 0; j < 1000; j++)
+               {
+                       /* store previous output as it is used as input after next */
+                       memcpy(prev.ptr, output->ptr, prev.len);
+                       chunk_free(output);
+                       if (!crypt(crypter, test))
+                       {
+                               crypter->destroy(crypter);
+                               return FALSE;
+                       }
+                       /* prepare the next IV (our API does not allow incremental calls) */
+                       if (ctx.decrypt)
+                       {
+                               memcpy(test->iv.ptr, input->ptr, test->iv.len);
+                       }
+                       else
+                       {
+                               memcpy(test->iv.ptr, output->ptr, test->iv.len);
+                       }
+                       /* the previous output is the next input */
+                       memcpy(input->ptr, prev.ptr, input->len);
+               }
+               fprintf(ctx.out, "%s = %+B\n\n",
+                               ctx.decrypt ? "PLAINTEXT" : "CIPHERTEXT", output);
+               /* derive key for next round */
+               switch (test->key.len)
+               {
+                       case 16:
+                               memxor(test->key.ptr, output->ptr, output->len);
+                               break;
+                       case 24:
+                               memxor(test->key.ptr, prev.ptr + 8, 8);
+                               memxor(test->key.ptr + 8, output->ptr, output->len);
+                               break;
+                       case 32:
+                               memxor(test->key.ptr, prev.ptr, prev.len);
+                               memxor(test->key.ptr + prev.len, output->ptr, output->len);
+                               break;
+               }
+               /* the current output is used as IV for the next round */
+               memcpy(test->iv.ptr, output->ptr, test->iv.len);
+       }
+       crypter->destroy(crypter);
+       /* we return FALSE as we print the output ourselves */
+       return FALSE;
+}
+
+static bool do_test(test_vector_t *test)
+{
+       if (ctx.use_gcm)
+       {
+               return do_test_gcm(test);
+       }
+       if (ctx.use_mct)
+       {
+               return do_test_mct(test);
+       }
+       return do_test_cbc(test);
+}
+
+static void usage(FILE *out, char *name)
+{
+       fprintf(out, "Test AES implementation according to the AES Algorithm Validation Suite (AESAVS)\n");
+       fprintf(out, "and the GCM Validation System (GCMVS)\n\n");
+       fprintf(out, "%s [OPTIONS]\n\n", name);
+       fprintf(out, "Options:\n");
+       fprintf(out, "  -h, --help          print this help.\n");
+       fprintf(out, "  -d, --debug=LEVEL   set debug level (default 1).\n");
+       fprintf(out, "  -m, --mode=MODE     mode to test, either CBC or GCM (default CBC).\n");
+       fprintf(out, "  -t, --mct           run Monte Carlo Test (MCT), only for CBC.\n");
+       fprintf(out, "  -x, --decrypt       test decryption (not needed for CBC as files contain control directives).\n");
+       fprintf(out, "  -i, --in=FILE       request file (default STDIN).\n");
+       fprintf(out, "  -o, --out=FILE      response file (default STDOUT).\n");
+       fprintf(out, "\n");
+}
+
+int main(int argc, char *argv[])
+{
+       test_vector_t test;
+
+       ctx.in = stdin;
+       ctx.out = stdout;
+
+       library_init(NULL);
+       atexit(library_deinit);
+
+       while (true)
+       {
+               struct option long_opts[] = {
+                       {"help",                no_argument,            NULL,   'h' },
+                       {"debug",               required_argument,      NULL,   'd' },
+                       {"mode",                required_argument,      NULL,   'm' },
+                       {"mct",                 no_argument,            NULL,   't' },
+                       {"decrypt",             no_argument,            NULL,   'x' },
+                       {"in",                  required_argument,      NULL,   'i' },
+                       {"out",                 required_argument,      NULL,   'o' },
+                       {0,0,0,0 },
+               };
+               switch (getopt_long(argc, argv, "hd:m:txi:o:", long_opts, NULL))
+               {
+                       case EOF:
+                               break;
+                       case 'h':
+                               usage(stdout, argv[0]);
+                               return 0;
+                       case 'd':
+                               dbg_default_set_level(atoi(optarg));
+                               continue;
+                       case 'm':
+                               if (strcaseeq(optarg, "GCM"))
+                               {
+                                       ctx.use_gcm = TRUE;
+                               }
+                               else if (!strcaseeq(optarg, "CBC"))
+                               {
+                                       usage(stderr, argv[0]);
+                                       return 1;
+                               }
+                               continue;
+                       case 't':
+                               ctx.use_mct = TRUE;
+                               continue;
+                       case 'x':
+                               ctx.decrypt = TRUE;
+                               continue;
+                       case 'i':
+                               ctx.in = fopen(optarg, "r");
+                               if (!ctx.in)
+                               {
+                                       fprintf(stderr, "failed to open '%s': %s\n", optarg,
+                                                       strerror(errno));
+                                       usage(stderr, argv[0]);
+                                       return 1;
+                               }
+                               continue;
+                       case 'o':
+                               ctx.out = fopen(optarg, "w");
+                               if (!ctx.out)
+                               {
+                                       fprintf(stderr, "failed to open '%s': %s\n", optarg,
+                                                       strerror(errno));
+                                       usage(stderr, argv[0]);
+                                       return 1;
+                               }
+                               continue;
+                       default:
+                               usage(stderr, argv[0]);
+                               return 1;
+               }
+               break;
+       }
+       /* TODO: maybe make plugins configurable */
+       lib->plugins->load(lib->plugins, PLUGINS);
+       lib->plugins->status(lib->plugins, LEVEL_CTRL);
+
+       while (get_next_test_vector(&test))
+       {
+               if (verify_test_vector(&test))
+               {
+                       if (do_test(&test))
+                       {
+                               print_result(&test);
+                       }
+               }
+               else
+               {
+                       DBG1(DBG_APP, "test vector with missing data encountered");
+               }
+               fprintf(ctx.out, "\n");
+               test_vector_free(&test);
+       }
+
+       if (ctx.in != stdin)
+       {
+               fclose(ctx.in);
+       }
+       if (ctx.out != stdout)
+       {
+               fclose(ctx.out);
+       }
+       return 0;
+}