From 5313d2d78ca150515f7f5eb39801c100690b6b29 Mon Sep 17 00:00:00 2001 From: Yves-Alexis Perez Date: Fri, 1 Nov 2013 13:32:07 +0100 Subject: Imported Upstream version 5.1.1 --- scripts/aes-test.c | 657 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 scripts/aes-test.c (limited to 'scripts/aes-test.c') diff --git a/scripts/aes-test.c b/scripts/aes-test.c new file mode 100644 index 000000000..ddf4a5ded --- /dev/null +++ b/scripts/aes-test.c @@ -0,0 +1,657 @@ +/* + * 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 . + * + * 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 +#include +#include +#include +#include +#include + +#include + +/** 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; + } + } + if (param != PARAM_UNKNOWN) + { /* could be that the file ended with a complete test vector */ + return TRUE; + } + 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 do_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 (!do_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 (!do_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; +} -- cgit v1.2.3