123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- /*
- * Copyright (C) 2013 Tobias Brunner
- * HSR 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 = chunk_empty;
- 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, 4);
- 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, "aes-test");
- 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;
- }
|