/* * Copyright (C) 2009-2010 Martin Willi * Copyright (C) 2016-2019 Andreas Steffen * * Copyright (C) secunet Security Networks AG * * 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. */ #ifdef HAVE_DLADDR # define _GNU_SOURCE # include #endif #include #include "crypto_tester.h" #include #include #include typedef struct private_crypto_tester_t private_crypto_tester_t; /** * Private data of an crypto_tester_t object. */ struct private_crypto_tester_t { /** * Public crypto_tester_t interface. */ crypto_tester_t public; /** * List of crypter test vectors */ linked_list_t *crypter; /** * List of aead test vectors */ linked_list_t *aead; /** * List of signer test vectors */ linked_list_t *signer; /** * List of hasher test vectors */ linked_list_t *hasher; /** * List of PRF test vectors */ linked_list_t *prf; /** * List of XOF test vectors */ linked_list_t *xof; /** * List of KDF test vectors */ linked_list_t *kdf; /** * List of DRBG test vectors */ linked_list_t *drbg; /** * List of RNG test vectors */ linked_list_t *rng; /** * List of key exchange method test vectors */ linked_list_t *ke; /** * Is a test vector required to pass a test? */ bool required; /** * should we run RNG_TRUE tests? Enough entropy? */ bool rng_true; /** * time we test each algorithm */ int bench_time; /** * size of buffer we use for benchmarking */ int bench_size; }; #if DEBUG_LEVEL >= 1 /** * Get the name of a test vector, if available */ static const char* get_name(void *sym) { #ifdef HAVE_DLADDR Dl_info dli; if (dladdr(sym, &dli)) { return dli.dli_sname; } #endif return "unknown"; } #endif #if defined(CLOCK_THREAD_CPUTIME_ID) && defined(HAVE_CLOCK_GETTIME) /** * Start a benchmark timer */ static void start_timing(struct timespec *start) { clock_gettime(CLOCK_THREAD_CPUTIME_ID, start); } /** * End a benchmark timer, return ms */ static u_int end_timing(struct timespec *start) { struct timespec end; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end); return (end.tv_nsec - start->tv_nsec) / 1000000 + (end.tv_sec - start->tv_sec) * 1000; } #else /* CLOCK_THREAD_CPUTIME_ID */ /* Make benchmarking a no-op if CLOCK_THREAD_CPUTIME_ID is not available */ #define start_timing(start) ((start)->tv_sec = 0, (start)->tv_nsec = 0) #define end_timing(...) (this->bench_time) #endif /* CLOCK_THREAD_CPUTIME_ID */ /** * Benchmark a crypter */ static u_int bench_crypter(private_crypto_tester_t *this, encryption_algorithm_t alg, crypter_constructor_t create, size_t key_size) { crypter_t *crypter; crypter = create(alg, key_size); if (crypter) { char iv[crypter->get_iv_size(crypter)]; char key[crypter->get_key_size(crypter)]; chunk_t buf; struct timespec start; u_int runs; memset(iv, 0x56, sizeof(iv)); memset(key, 0x12, sizeof(key)); if (!crypter->set_key(crypter, chunk_from_thing(key))) { return 0; } buf = chunk_alloc(this->bench_size); memset(buf.ptr, 0x34, buf.len); runs = 0; start_timing(&start); while (end_timing(&start) < this->bench_time) { if (crypter->encrypt(crypter, buf, chunk_from_thing(iv), NULL)) { runs++; } if (crypter->decrypt(crypter, buf, chunk_from_thing(iv), NULL)) { runs++; } } free(buf.ptr); crypter->destroy(crypter); return runs; } return 0; } METHOD(crypto_tester_t, test_crypter, bool, private_crypto_tester_t *this, encryption_algorithm_t alg, size_t key_size, crypter_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; crypter_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; enumerator = this->crypter->create_enumerator(this->crypter); while (enumerator->enumerate(enumerator, &vector)) { crypter_t *crypter; chunk_t key, iv, plain = chunk_empty, cipher = chunk_empty; if (vector->alg != alg) { continue; } if (key_size && key_size != vector->key_size) { /* test only vectors with a specific key size, if key size given */ continue; } crypter = create(alg, vector->key_size); if (!crypter) { /* key size not supported */ continue; } tested++; failed = TRUE; key = chunk_create(vector->key, crypter->get_key_size(crypter)); if (!crypter->set_key(crypter, key)) { goto failure; } iv = chunk_create(vector->iv, crypter->get_iv_size(crypter)); /* allocated encryption */ plain = chunk_create(vector->plain, vector->len); if (!crypter->encrypt(crypter, plain, iv, &cipher)) { goto failure; } if (!memeq(vector->cipher, cipher.ptr, cipher.len)) { goto failure; } /* inline decryption */ if (!crypter->decrypt(crypter, cipher, iv, NULL)) { goto failure; } if (!memeq(vector->plain, cipher.ptr, cipher.len)) { goto failure; } /* allocated decryption */ if (!crypter->decrypt(crypter, chunk_create(vector->cipher, vector->len), iv, &plain)) { goto failure; } if (!memeq(vector->plain, plain.ptr, plain.len)) { goto failure; } /* inline encryption */ if (!crypter->encrypt(crypter, plain, iv, NULL)) { goto failure; } if (!memeq(vector->cipher, plain.ptr, plain.len)) { goto failure; } failed = FALSE; failure: crypter->destroy(crypter); chunk_free(&cipher); if (plain.ptr != vector->plain) { chunk_free(&plain); } if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", encryption_algorithm_names, alg, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { if (failed) { DBG1(DBG_LIB,"disable %N[%s]: %zd byte key size not supported", encryption_algorithm_names, alg, plugin_name, key_size); return FALSE; } else { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", encryption_algorithm_names, alg, plugin_name); return !this->required; } } if (!failed) { if (speed) { *speed = bench_crypter(this, alg, create, key_size); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points " "(%zd bit key)", encryption_algorithm_names, alg, plugin_name, tested, *speed, key_size * 8); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", encryption_algorithm_names, alg, plugin_name, tested); } } return !failed; } /** * Benchmark an aead transform */ static u_int bench_aead(private_crypto_tester_t *this, encryption_algorithm_t alg, aead_constructor_t create, size_t key_size) { aead_t *aead; aead = create(alg, key_size, 0); if (aead) { char iv[aead->get_iv_size(aead)]; char key[aead->get_key_size(aead)]; char assoc[4]; chunk_t buf; struct timespec start; u_int runs; size_t icv; memset(iv, 0x56, sizeof(iv)); memset(key, 0x12, sizeof(key)); memset(assoc, 0x78, sizeof(assoc)); if (!aead->set_key(aead, chunk_from_thing(key))) { return 0; } icv = aead->get_icv_size(aead); buf = chunk_alloc(this->bench_size + icv); memset(buf.ptr, 0x34, buf.len); buf.len -= icv; runs = 0; start_timing(&start); while (end_timing(&start) < this->bench_time) { if (aead->encrypt(aead, buf, chunk_from_thing(assoc), chunk_from_thing(iv), NULL)) { runs += 2; } if (aead->decrypt(aead, chunk_create(buf.ptr, buf.len + icv), chunk_from_thing(assoc), chunk_from_thing(iv), NULL)) { runs += 2; } } free(buf.ptr); aead->destroy(aead); return runs; } return 0; } METHOD(crypto_tester_t, test_aead, bool, private_crypto_tester_t *this, encryption_algorithm_t alg, size_t key_size, size_t salt_size, aead_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; aead_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; enumerator = this->aead->create_enumerator(this->aead); while (enumerator->enumerate(enumerator, &vector)) { aead_t *aead; chunk_t key, iv, assoc, plain = chunk_empty, cipher = chunk_empty; size_t icv; if (vector->alg != alg) { continue; } if (key_size && key_size != vector->key_size) { /* test only vectors with a specific key size, if key size given */ continue; } if (salt_size && salt_size != vector->salt_size) { continue; } tested++; failed = TRUE; aead = create(alg, vector->key_size, vector->salt_size); if (!aead) { DBG1(DBG_LIB, "%N[%s]: %u bit key size not supported", encryption_algorithm_names, alg, plugin_name, BITS_PER_BYTE * vector->key_size); continue; } key = chunk_create(vector->key, aead->get_key_size(aead)); if (!aead->set_key(aead, key)) { goto failure; } iv = chunk_create(vector->iv, aead->get_iv_size(aead)); assoc = chunk_create(vector->adata, vector->alen); icv = aead->get_icv_size(aead); /* allocated encryption */ plain = chunk_create(vector->plain, vector->len); if (!aead->encrypt(aead, plain, assoc, iv, &cipher)) { goto failure; } if (!memeq(vector->cipher, cipher.ptr, cipher.len)) { goto failure; } /* inline decryption */ if (!aead->decrypt(aead, cipher, assoc, iv, NULL)) { goto failure; } if (!memeq(vector->plain, cipher.ptr, cipher.len - icv)) { goto failure; } /* allocated decryption */ if (!aead->decrypt(aead, chunk_create(vector->cipher, vector->len + icv), assoc, iv, &plain)) { goto failure; } if (!memeq(vector->plain, plain.ptr, plain.len)) { goto failure; } plain.ptr = realloc(plain.ptr, plain.len + icv); /* inline encryption */ if (!aead->encrypt(aead, plain, assoc, iv, NULL)) { goto failure; } if (!memeq(vector->cipher, plain.ptr, plain.len + icv)) { goto failure; } failed = FALSE; failure: aead->destroy(aead); chunk_free(&cipher); if (plain.ptr != vector->plain) { chunk_free(&plain); } if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", encryption_algorithm_names, alg, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { if (failed) { DBG1(DBG_LIB,"disable %N[%s]: %zd byte key size not supported", encryption_algorithm_names, alg, plugin_name, key_size); return FALSE; } else { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", encryption_algorithm_names, alg, plugin_name); return !this->required; } } if (!failed) { if (speed) { *speed = bench_aead(this, alg, create, key_size); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points " "(%zd bit key)", encryption_algorithm_names, alg, plugin_name, tested, *speed, key_size * 8); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", encryption_algorithm_names, alg, plugin_name, tested); } } return !failed; } /** * Benchmark a signer */ static u_int bench_signer(private_crypto_tester_t *this, integrity_algorithm_t alg, signer_constructor_t create) { signer_t *signer; signer = create(alg); if (signer) { char key[signer->get_key_size(signer)]; char mac[signer->get_block_size(signer)]; chunk_t buf; struct timespec start; u_int runs; memset(key, 0x12, sizeof(key)); if (!signer->set_key(signer, chunk_from_thing(key))) { return 0; } buf = chunk_alloc(this->bench_size); memset(buf.ptr, 0x34, buf.len); runs = 0; start_timing(&start); while (end_timing(&start) < this->bench_time) { if (signer->get_signature(signer, buf, mac)) { runs++; } if (signer->verify_signature(signer, buf, chunk_from_thing(mac))) { runs++; } } free(buf.ptr); signer->destroy(signer); return runs; } return 0; } METHOD(crypto_tester_t, test_signer, bool, private_crypto_tester_t *this, integrity_algorithm_t alg, signer_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; signer_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; enumerator = this->signer->create_enumerator(this->signer); while (enumerator->enumerate(enumerator, &vector)) { signer_t *signer; chunk_t key, data, mac = chunk_empty; if (vector->alg != alg) { continue; } tested++; failed = TRUE; signer = create(alg); if (!signer) { DBG1(DBG_LIB, "disabled %N[%s]: creating instance failed", integrity_algorithm_names, alg, plugin_name); break; } data = chunk_create(vector->data, vector->len); key = chunk_create(vector->key, signer->get_key_size(signer)); if (!signer->set_key(signer, key)) { goto failure; } /* do partial append mode and check if key gets set correctly */ if (!signer->get_signature(signer, data, NULL)) { goto failure; } if (!signer->set_key(signer, key)) { goto failure; } /* allocated signature */ if (!signer->allocate_signature(signer, data, &mac)) { goto failure; } if (mac.len != signer->get_block_size(signer)) { goto failure; } if (!memeq(vector->mac, mac.ptr, mac.len)) { goto failure; } /* signature to existing buffer */ memset(mac.ptr, 0, mac.len); if (!signer->get_signature(signer, data, mac.ptr)) { goto failure; } if (!memeq(vector->mac, mac.ptr, mac.len)) { goto failure; } /* signature verification, good case */ if (!signer->verify_signature(signer, data, mac)) { goto failure; } /* signature verification, bad case */ *(mac.ptr + mac.len - 1) += 1; if (signer->verify_signature(signer, data, mac)) { goto failure; } /* signature to existing buffer, using append mode */ if (data.len > 2) { if (!signer->allocate_signature(signer, chunk_create(data.ptr, 1), NULL)) { goto failure; } if (!signer->get_signature(signer, chunk_create(data.ptr + 1, 1), NULL)) { goto failure; } if (!signer->verify_signature(signer, chunk_skip(data, 2), chunk_create(vector->mac, mac.len))) { goto failure; } } failed = FALSE; failure: signer->destroy(signer); chunk_free(&mac); if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", integrity_algorithm_names, alg, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", integrity_algorithm_names, alg, plugin_name); return !this->required; } if (!failed) { if (speed) { *speed = bench_signer(this, alg, create); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points", integrity_algorithm_names, alg, plugin_name, tested, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", integrity_algorithm_names, alg, plugin_name, tested); } } return !failed; } /** * Benchmark a hasher */ static u_int bench_hasher(private_crypto_tester_t *this, hash_algorithm_t alg, hasher_constructor_t create) { hasher_t *hasher; hasher = create(alg); if (hasher) { char hash[hasher->get_hash_size(hasher)]; chunk_t buf; struct timespec start; u_int runs; buf = chunk_alloc(this->bench_size); memset(buf.ptr, 0x34, buf.len); runs = 0; start_timing(&start); while (end_timing(&start) < this->bench_time) { if (hasher->get_hash(hasher, buf, hash)) { runs++; } } free(buf.ptr); hasher->destroy(hasher); return runs; } return 0; } METHOD(crypto_tester_t, test_hasher, bool, private_crypto_tester_t *this, hash_algorithm_t alg, hasher_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; hasher_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; enumerator = this->hasher->create_enumerator(this->hasher); while (enumerator->enumerate(enumerator, &vector)) { hasher_t *hasher; chunk_t data, hash; if (vector->alg != alg) { continue; } tested++; failed = TRUE; hasher = create(alg); if (!hasher) { DBG1(DBG_LIB, "disabled %N[%s]: creating instance failed", hash_algorithm_names, alg, plugin_name); break; } /* allocated hash */ data = chunk_create(vector->data, vector->len); if (!hasher->allocate_hash(hasher, data, &hash)) { goto failure; } if (hash.len != hasher->get_hash_size(hasher)) { goto failure; } if (!memeq(vector->hash, hash.ptr, hash.len)) { goto failure; } /* hash to existing buffer, with a reset */ memset(hash.ptr, 0, hash.len); if (!hasher->get_hash(hasher, data, NULL)) { goto failure; } if (!hasher->reset(hasher)) { goto failure; } if (!hasher->get_hash(hasher, data, hash.ptr)) { goto failure; } if (!memeq(vector->hash, hash.ptr, hash.len)) { goto failure; } /* hasher to existing buffer, using append mode */ if (data.len > 2) { memset(hash.ptr, 0, hash.len); if (!hasher->allocate_hash(hasher, chunk_create(data.ptr, 1), NULL)) { goto failure; } if (!hasher->get_hash(hasher, chunk_create(data.ptr + 1, 1), NULL)) { goto failure; } if (!hasher->get_hash(hasher, chunk_skip(data, 2), hash.ptr)) { goto failure; } if (!memeq(vector->hash, hash.ptr, hash.len)) { goto failure; } } failed = FALSE; failure: hasher->destroy(hasher); chunk_free(&hash); if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", hash_algorithm_names, alg, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", hash_algorithm_names, alg, plugin_name); return !this->required; } if (!failed) { if (speed) { *speed = bench_hasher(this, alg, create); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points", hash_algorithm_names, alg, plugin_name, tested, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", hash_algorithm_names, alg, plugin_name, tested); } } return !failed; } /** * Benchmark a PRF */ static u_int bench_prf(private_crypto_tester_t *this, pseudo_random_function_t alg, prf_constructor_t create) { prf_t *prf; prf = create(alg); if (prf) { char bytes[prf->get_block_size(prf)], key[prf->get_block_size(prf)]; chunk_t buf; struct timespec start; u_int runs; memset(key, 0x56, prf->get_block_size(prf)); if (!prf->set_key(prf, chunk_create(key, prf->get_block_size(prf)))) { prf->destroy(prf); return 0; } buf = chunk_alloc(this->bench_size); memset(buf.ptr, 0x34, buf.len); runs = 0; start_timing(&start); while (end_timing(&start) < this->bench_time) { if (prf->get_bytes(prf, buf, bytes)) { runs++; } } free(buf.ptr); prf->destroy(prf); return runs; } return 0; } METHOD(crypto_tester_t, test_prf, bool, private_crypto_tester_t *this, pseudo_random_function_t alg, prf_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; prf_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; enumerator = this->prf->create_enumerator(this->prf); while (enumerator->enumerate(enumerator, &vector)) { prf_t *prf; chunk_t key, seed, out = chunk_empty; if (vector->alg != alg) { continue; } tested++; failed = TRUE; prf = create(alg); if (!prf) { DBG1(DBG_LIB, "disabled %N[%s]: creating instance failed", pseudo_random_function_names, alg, plugin_name); break; } seed = chunk_create(vector->seed, vector->len); key = chunk_create(vector->key, vector->key_size); if (!prf->set_key(prf, key)) { goto failure; } if (alg != PRF_FIPS_SHA1_160) { /* do partial append mode and check if key gets set correctly */ if (!prf->get_bytes(prf, seed, NULL)) { goto failure; } if (!prf->set_key(prf, key)) { goto failure; } } /* allocated bytes */ if (!prf->allocate_bytes(prf, seed, &out)) { goto failure; } if (out.len != prf->get_block_size(prf)) { goto failure; } if (!memeq(vector->out, out.ptr, out.len)) { goto failure; } /* bytes to existing buffer */ memset(out.ptr, 0, out.len); if (vector->stateful) { if (!prf->set_key(prf, key)) { goto failure; } } if (!prf->get_bytes(prf, seed, out.ptr)) { goto failure; } if (!memeq(vector->out, out.ptr, out.len)) { goto failure; } /* bytes to existing buffer, using append mode */ if (alg != PRF_FIPS_SHA1_160 && seed.len > 2) { memset(out.ptr, 0, out.len); if (vector->stateful) { if (!prf->set_key(prf, key)) { goto failure; } } if (!prf->allocate_bytes(prf, chunk_create(seed.ptr, 1), NULL)) { goto failure; } if (!prf->get_bytes(prf, chunk_create(seed.ptr + 1, 1), NULL)) { goto failure; } if (!prf->get_bytes(prf, chunk_skip(seed, 2), out.ptr)) { goto failure; } if (!memeq(vector->out, out.ptr, out.len)) { goto failure; } } failed = FALSE; failure: prf->destroy(prf); chunk_free(&out); if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", pseudo_random_function_names, alg, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", pseudo_random_function_names, alg, plugin_name); return !this->required; } if (!failed) { if (speed) { *speed = bench_prf(this, alg, create); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points", pseudo_random_function_names, alg, plugin_name, tested, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", pseudo_random_function_names, alg, plugin_name, tested); } } return !failed; } /** * Benchmark an XOF */ static u_int bench_xof(private_crypto_tester_t *this, ext_out_function_t alg, xof_constructor_t create) { xof_t *xof; xof = create(alg); if (xof) { char seed[xof->get_seed_size(xof)]; char bytes[xof->get_block_size(xof)]; struct timespec start; u_int runs; memset(seed, 0x56, xof->get_seed_size(xof)); if (!xof->set_seed(xof, chunk_create(seed, xof->get_seed_size(xof)))) { xof->destroy(xof); return 0; } runs = 0; start_timing(&start); while (end_timing(&start) < this->bench_time) { if (xof->get_bytes(xof, xof->get_block_size(xof), bytes)) { runs++; } } xof->destroy(xof); return runs; } return 0; } METHOD(crypto_tester_t, test_xof, bool, private_crypto_tester_t *this, ext_out_function_t alg, xof_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; xof_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; enumerator = this->xof->create_enumerator(this->xof); while (enumerator->enumerate(enumerator, &vector)) { xof_t *xof; chunk_t seed, out = chunk_empty; if (vector->alg != alg) { continue; } tested++; failed = TRUE; xof = create(alg); if (!xof) { DBG1(DBG_LIB, "disabled %N[%s]: creating instance failed", ext_out_function_names, alg, plugin_name); break; } seed = chunk_create(vector->seed, vector->len); if (!xof->set_seed(xof, seed)) { goto failure; } /* allocated bytes */ if (!xof->allocate_bytes(xof, vector->out_len, &out)) { goto failure; } if (out.len != vector->out_len) { goto failure; } if (!memeq(vector->out, out.ptr, out.len)) { goto failure; } /* bytes to existing buffer */ memset(out.ptr, 0, out.len); if (!xof->set_seed(xof, seed)) { goto failure; } if (!xof->get_bytes(xof, vector->out_len, out.ptr)) { goto failure; } if (!memeq(vector->out, out.ptr, vector->out_len)) { goto failure; } /* bytes to existing buffer, using append mode */ /* TODO */ failed = FALSE; failure: xof->destroy(xof); chunk_free(&out); if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", ext_out_function_names, alg, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", ext_out_function_names, alg, plugin_name); return !this->required; } if (!failed) { if (speed) { *speed = bench_xof(this, alg, create); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points", ext_out_function_names, alg, plugin_name, tested, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", ext_out_function_names, alg, plugin_name, tested); } } return !failed; } /** * Create a KDF using the given arguments */ static kdf_t *create_kdf_args(kdf_constructor_t create, key_derivation_function_t alg, ...) { va_list args; kdf_t *kdf; va_start(args, alg); kdf = create(alg, args); va_end(args); return kdf; } /** * Create a KDF using arguments from the given test vector */ static kdf_t *create_kdf_vector(kdf_constructor_t create, key_derivation_function_t alg, kdf_test_vector_t *vector) { switch (alg) { case KDF_PRF: case KDF_PRF_PLUS: return create_kdf_args(create, alg, vector->arg.prf); case KDF_UNDEFINED: break; } return NULL; } /** * Check if the given test vector applies to the passed arguments */ static bool kdf_vector_applies(key_derivation_function_t alg, kdf_test_args_t *args, kdf_test_vector_t *vector) { bool applies = FALSE; switch (alg) { case KDF_PRF: case KDF_PRF_PLUS: { pseudo_random_function_t prf; VA_ARGS_VGET(args->args, prf); applies = (prf == vector->arg.prf); break; } case KDF_UNDEFINED: break; } return applies; } METHOD(crypto_tester_t, test_kdf, bool, private_crypto_tester_t *this, key_derivation_function_t alg, kdf_constructor_t create, kdf_test_args_t *args, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; kdf_test_vector_t *vector; va_list copy; bool failed = FALSE; u_int tested = 0, construction_failed = 0; enumerator = this->kdf->create_enumerator(this->kdf); while (enumerator->enumerate(enumerator, &vector)) { kdf_t *kdf; chunk_t out = chunk_empty; if (vector->alg != alg || (args && !kdf_vector_applies(alg, args, vector))) { continue; } tested++; failed = TRUE; if (args) { va_copy(copy, args->args); kdf = create(alg, copy); va_end(copy); } else { kdf = create_kdf_vector(create, alg, vector); } if (!kdf) { if (args) { DBG1(DBG_LIB, "disabled %N[%s]: creating instance failed", key_derivation_function_names, alg, plugin_name); break; } /* while there could be a problem, the constructor might just not * be able to create an instance for this test vector, we check * for that at the end */ construction_failed++; failed = FALSE; continue; } if (vector->key.len && !kdf->set_param(kdf, KDF_PARAM_KEY, vector->key)) { goto failure; } if (vector->salt.len && !kdf->set_param(kdf, KDF_PARAM_SALT, vector->salt)) { goto failure; } if (kdf_has_fixed_output_length(alg)) { if (kdf->get_length(kdf) != vector->out.len) { goto failure; } } else if (kdf->get_length(kdf) != SIZE_MAX) { goto failure; } /* allocated bytes */ if (!kdf->allocate_bytes(kdf, vector->out.len, &out)) { goto failure; } if (!chunk_equals(out, vector->out)) { goto failure; } /* allocate without knowing the length */ if (kdf_has_fixed_output_length(alg)) { chunk_free(&out); if (!kdf->allocate_bytes(kdf, 0, &out)) { goto failure; } if (!chunk_equals(out, vector->out)) { goto failure; } } /* bytes to existing buffer */ memset(out.ptr, 0, out.len); if (!kdf->get_bytes(kdf, out.len, out.ptr)) { goto failure; } if (!chunk_equals(out, vector->out)) { goto failure; } failed = FALSE; failure: kdf->destroy(kdf); chunk_free(&out); if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", key_derivation_function_names, alg, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", key_derivation_function_names, alg, plugin_name); return !this->required; } tested -= construction_failed; if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: unable to apply any available test vectors", this->required ? "disabled" : "enabled ", key_derivation_function_names, alg, plugin_name); return !this->required; } if (!failed) { if (speed) { DBG2(DBG_LIB, "benchmarking for %N is currently not supported", key_derivation_function_names, alg); } DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", key_derivation_function_names, alg, plugin_name, tested); } return !failed; } /** * Benchmark a DRBG */ static u_int bench_drbg(private_crypto_tester_t *this, drbg_type_t type, drbg_constructor_t create) { drbg_t *drbg; rng_t *entropy; uint32_t strength = 128; chunk_t seed = chunk_alloca(48); memset(seed.ptr, 0x81, seed.len); entropy = rng_tester_create(seed); drbg = create(type, strength, entropy, chunk_empty); if (drbg) { struct timespec start; u_int runs = 0; size_t out_len = 128; char out_buf[out_len]; start_timing(&start); while (end_timing(&start) < this->bench_time) { if (drbg->generate(drbg, out_len, out_buf)) { runs++; } } drbg->destroy(drbg); return runs; } return 0; } METHOD(crypto_tester_t, test_drbg, bool, private_crypto_tester_t *this, drbg_type_t type, drbg_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; drbg_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; enumerator = this->drbg->create_enumerator(this->drbg); while (enumerator->enumerate(enumerator, &vector)) { drbg_t *drbg; rng_t *entropy; chunk_t out = chunk_empty; if (vector->type != type) { continue; } tested++; failed = TRUE; entropy = rng_tester_create(vector->entropy); out = chunk_alloc(vector->out.len); drbg = create(type, vector->strength, entropy, vector->personalization_str); if (!drbg) { DBG1(DBG_LIB, "disabled %N[%s]: creating instance failed", drbg_type_names, type, plugin_name); entropy->destroy(entropy); chunk_free(&out); break; } if (!drbg->reseed(drbg)) { goto failure; } if (!drbg->generate(drbg, out.len, out.ptr)) { goto failure; } if (!drbg->generate(drbg, out.len, out.ptr)) { goto failure; } if (!chunk_equals(out, vector->out)) { goto failure; } failed = FALSE; failure: drbg->destroy(drbg); chunk_free(&out); if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", drbg_type_names, type, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? "disabled" : "enabled ", drbg_type_names, type, plugin_name); return !this->required; } if (!failed) { if (speed) { *speed = bench_drbg(this, type, create); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points", drbg_type_names, type, plugin_name, tested, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", drbg_type_names, type, plugin_name, tested); } } return !failed; } /** * Benchmark a RNG */ static u_int bench_rng(private_crypto_tester_t *this, rng_quality_t quality, rng_constructor_t create) { rng_t *rng; rng = create(quality); if (rng) { struct timespec start; chunk_t buf; u_int runs; runs = 0; buf = chunk_alloc(this->bench_size); start_timing(&start); while (end_timing(&start) < this->bench_time) { if (!rng->get_bytes(rng, buf.len, buf.ptr)) { runs = 0; break; } runs++; } free(buf.ptr); rng->destroy(rng); return runs; } return 0; } METHOD(crypto_tester_t, test_rng, bool, private_crypto_tester_t *this, rng_quality_t quality, rng_constructor_t create, u_int *speed, const char *plugin_name) { enumerator_t *enumerator; rng_test_vector_t *vector; bool failed = FALSE; u_int tested = 0; if (!this->rng_true && quality == RNG_TRUE) { DBG1(DBG_LIB, "enabled %N[%s]: skipping test (disabled by config)", rng_quality_names, quality, plugin_name); return TRUE; } enumerator = this->rng->create_enumerator(this->rng); while (enumerator->enumerate(enumerator, &vector)) { chunk_t data = chunk_empty; rng_t *rng; if (vector->quality != quality) { continue; } tested++; failed = TRUE; rng = create(quality); if (!rng) { DBG1(DBG_LIB, "disabled %N[%s]: creating instance failed", rng_quality_names, quality, plugin_name); break; } /* allocated bytes */ if (!rng->allocate_bytes(rng, vector->len, &data) || data.len != vector->len || !vector->test(vector->user, data)) { goto failure; } /* write bytes into existing buffer */ memset(data.ptr, 0, data.len); if (!rng->get_bytes(rng, vector->len, data.ptr)) { goto failure; } if (!vector->test(vector->user, data)) { goto failure; } failed = FALSE; failure: rng->destroy(rng); chunk_free(&data); if (failed) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", rng_quality_names, quality, plugin_name, get_name(vector)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found", this->required ? ", disabled" : "enabled ", rng_quality_names, quality, plugin_name); return !this->required; } if (!failed) { if (speed) { *speed = bench_rng(this, quality, create); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points", rng_quality_names, quality, plugin_name, tested, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", rng_quality_names, quality, plugin_name, tested); } } return !failed; } /** * Benchmark a key exchange backend */ static u_int bench_ke(private_crypto_tester_t *this, key_exchange_method_t method, ke_constructor_t create) { chunk_t a_pub = chunk_empty, b_pub = chunk_empty, shared = chunk_empty; key_exchange_t *a, *b; struct timespec start; u_int runs; runs = 0; start_timing(&start); while (end_timing(&start) < this->bench_time) { a = create(method); b = create(method); if (!a || !b) { DESTROY_IF(a); DESTROY_IF(b); return 0; } if (a->get_public_key(a, &a_pub) && b->set_public_key(b, a_pub) && b->get_public_key(b, &b_pub) && a->set_public_key(a, b_pub) && a->get_shared_secret(a, &shared)) { runs++; } chunk_free(&a_pub); chunk_free(&b_pub); chunk_free(&shared); a->destroy(a); b->destroy(b); } return runs; } #ifdef TESTABLE_KE static bool test_single_ke(key_exchange_method_t method, ke_test_vector_t *v, ke_constructor_t create) { rng_t *entropy = NULL; drbg_t *drbg = NULL; key_exchange_t *a = NULL, *b = NULL; chunk_t a_priv, b_priv, a_pub, b_pub, a_sec, b_sec; bool success = FALSE; a_pub = b_pub = a_sec = b_sec = chunk_empty; a = create(method); b = create(method); if (!a || !b) { goto failure; } if (key_exchange_is_kem(method)) { /* entropy instance will be owned by drbg */ entropy = rng_tester_create(v->seed); drbg = lib->crypto->create_drbg(lib->crypto, DRBG_CTR_AES256, 256, entropy, chunk_empty); if (!drbg) { entropy->destroy(entropy); goto failure; } if (!a->set_seed(a, chunk_empty, drbg) || !b->set_seed(b, chunk_empty, drbg)) { goto failure; } } else { /* the seed is the concatenation of both DH private keys */ a_priv = chunk_create(v->seed.ptr, v->seed.len/2); b_priv = chunk_create(v->seed.ptr + v->seed.len/2, v->seed.len/2); if (!a->set_seed(a, a_priv, NULL) || !b->set_seed(b, b_priv, NULL)) { goto failure; } } if (!a->get_public_key(a, &a_pub) || !chunk_equals(a_pub, v->pub_i)) { goto failure; } if (!b->set_public_key(b, a_pub)) { goto failure; } if (!b->get_shared_secret(b, &b_sec) || !chunk_equals(b_sec, v->shared)) { goto failure; } if (!b->get_public_key(b, &b_pub) || !chunk_equals(b_pub, v->pub_r)) { goto failure; } if (!a->set_public_key(a, b_pub)) { goto failure; } if (!a->get_shared_secret(a, &a_sec) || !chunk_equals(a_sec, v->shared)) { goto failure; } success = TRUE; failure: DESTROY_IF(a); DESTROY_IF(b); chunk_free(&a_pub); chunk_free(&b_pub); chunk_free(&a_sec); chunk_free(&b_sec); DESTROY_IF(drbg); return success; } #else /* TESTABLE_KE */ static bool test_single_ke(key_exchange_method_t method, ke_constructor_t create) { key_exchange_t *a = NULL, *b = NULL; chunk_t a_pub, b_pub, a_sec, b_sec; bool success = FALSE; a_pub = b_pub = a_sec = b_sec = chunk_empty; a = create(method); b = create(method); if (!a || !b) { goto failure; } if (!a->get_public_key(a, &a_pub) || !b->set_public_key(b, a_pub) || !b->get_shared_secret(b, &b_sec) || !b->get_public_key(b, &b_pub) || chunk_equals(a_pub, b_pub) || !a->set_public_key(a, b_pub) || !a->get_shared_secret(a, &a_sec) || !chunk_equals(a_sec, b_sec)) { goto failure; } success = TRUE; failure: DESTROY_IF(a); DESTROY_IF(b); chunk_free(&a_pub); chunk_free(&b_pub); chunk_free(&a_sec); chunk_free(&b_sec); return success; } #endif /* TESTABLE_KE */ METHOD(crypto_tester_t, test_ke, bool, private_crypto_tester_t *this, key_exchange_method_t method, ke_constructor_t create, u_int *speed, const char *plugin_name) { #ifdef TESTABLE_KE enumerator_t *enumerator; ke_test_vector_t *v; bool success = TRUE; u_int tested = 0; enumerator = this->ke->create_enumerator(this->ke); while (enumerator->enumerate(enumerator, &v)) { if (v->method != method) { continue; } success = test_single_ke(method, v, create); tested++; if (!success) { DBG1(DBG_LIB, "disabled %N[%s]: %s test vector failed", key_exchange_method_names, method, plugin_name, get_name(v)); break; } } enumerator->destroy(enumerator); if (!tested) { DBG1(DBG_LIB, "%s %N[%s]: no test vectors found / untestable", this->required ? "disabled" : "enabled ", key_exchange_method_names, method, plugin_name); return !this->required; } if (success) { if (speed) { *speed = bench_ke(this, method, create); DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors, %d points", key_exchange_method_names, method, plugin_name, tested, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed %u test vectors", key_exchange_method_names, method, plugin_name, tested); } } return success; #else /* TESTABLE_KE */ if (method == MODP_CUSTOM) { DBG1(DBG_LIB, "enabled %N[%s]: untestable", key_exchange_method_names, method, plugin_name); return TRUE; } if (!test_single_ke(method, create)) { DBG1(DBG_LIB, "disabled %N[%s]: failed basic test", key_exchange_method_names, method, plugin_name); return FALSE; } if (speed) { *speed = bench_ke(this, method, create); DBG1(DBG_LIB, "enabled %N[%s]: passed basic test (vector tests " "disabled), %d points", key_exchange_method_names, method, plugin_name, *speed); } else { DBG1(DBG_LIB, "enabled %N[%s]: passed basic test (vector tests " "disabled)", key_exchange_method_names, method, plugin_name); } return TRUE; #endif /* TESTABLE_KE */ } METHOD(crypto_tester_t, add_crypter_vector, void, private_crypto_tester_t *this, crypter_test_vector_t *vector) { this->crypter->insert_last(this->crypter, vector); } METHOD(crypto_tester_t, add_aead_vector, void, private_crypto_tester_t *this, aead_test_vector_t *vector) { this->aead->insert_last(this->aead, vector); } METHOD(crypto_tester_t, add_signer_vector, void, private_crypto_tester_t *this, signer_test_vector_t *vector) { this->signer->insert_last(this->signer, vector); } METHOD(crypto_tester_t, add_hasher_vector, void, private_crypto_tester_t *this, hasher_test_vector_t *vector) { this->hasher->insert_last(this->hasher, vector); } METHOD(crypto_tester_t, add_prf_vector, void, private_crypto_tester_t *this, prf_test_vector_t *vector) { this->prf->insert_last(this->prf, vector); } METHOD(crypto_tester_t, add_xof_vector, void, private_crypto_tester_t *this, xof_test_vector_t *vector) { this->xof->insert_last(this->xof, vector); } METHOD(crypto_tester_t, add_kdf_vector, void, private_crypto_tester_t *this, kdf_test_vector_t *vector) { this->kdf->insert_last(this->kdf, vector); } METHOD(crypto_tester_t, add_drbg_vector, void, private_crypto_tester_t *this, drbg_test_vector_t *vector) { this->drbg->insert_last(this->drbg, vector); } METHOD(crypto_tester_t, add_rng_vector, void, private_crypto_tester_t *this, rng_test_vector_t *vector) { this->rng->insert_last(this->rng, vector); } METHOD(crypto_tester_t, add_ke_vector, void, private_crypto_tester_t *this, ke_test_vector_t *vector) { this->ke->insert_last(this->ke, vector); } METHOD(crypto_tester_t, destroy, void, private_crypto_tester_t *this) { this->crypter->destroy(this->crypter); this->aead->destroy(this->aead); this->signer->destroy(this->signer); this->hasher->destroy(this->hasher); this->prf->destroy(this->prf); this->xof->destroy(this->xof); this->kdf->destroy(this->kdf); this->drbg->destroy(this->drbg); this->rng->destroy(this->rng); this->ke->destroy(this->ke); free(this); } /** * See header */ crypto_tester_t *crypto_tester_create() { private_crypto_tester_t *this; INIT(this, .public = { .test_crypter = _test_crypter, .test_aead = _test_aead, .test_signer = _test_signer, .test_hasher = _test_hasher, .test_prf = _test_prf, .test_xof = _test_xof, .test_kdf = _test_kdf, .test_drbg = _test_drbg, .test_rng = _test_rng, .test_ke = _test_ke, .add_crypter_vector = _add_crypter_vector, .add_aead_vector = _add_aead_vector, .add_signer_vector = _add_signer_vector, .add_hasher_vector = _add_hasher_vector, .add_prf_vector = _add_prf_vector, .add_xof_vector = _add_xof_vector, .add_kdf_vector = _add_kdf_vector, .add_drbg_vector = _add_drbg_vector, .add_rng_vector = _add_rng_vector, .add_ke_vector = _add_ke_vector, .destroy = _destroy, }, .crypter = linked_list_create(), .aead = linked_list_create(), .signer = linked_list_create(), .hasher = linked_list_create(), .prf = linked_list_create(), .xof = linked_list_create(), .kdf = linked_list_create(), .drbg = linked_list_create(), .rng = linked_list_create(), .ke = linked_list_create(), .required = lib->settings->get_bool(lib->settings, "%s.crypto_test.required", FALSE, lib->ns), .rng_true = lib->settings->get_bool(lib->settings, "%s.crypto_test.rng_true", FALSE, lib->ns), .bench_time = lib->settings->get_int(lib->settings, "%s.crypto_test.bench_time", 50, lib->ns), .bench_size = lib->settings->get_int(lib->settings, "%s.crypto_test.bench_size", 1024, lib->ns), ); /* enforce a block size of 16, should be fine for all algorithms */ this->bench_size = this->bench_size / 16 * 16; return &this->public; }