From ad9d4e194280058af542c8f7211f0023902ea302 Mon Sep 17 00:00:00 2001 From: Loup Vaillant Date: Tue, 4 Jul 2017 22:13:53 +0200 Subject: [PATCH] added random self-consistency tests --- .gitignore | 3 +- makefile | 17 +-- test.sh | 29 +++-- tests/properties.c | 207 ++++++++++++++++++++++++++++++++++++ tests/sodium.c | 10 +- tests/{test.c => vectors.c} | 39 ------- 6 files changed, 242 insertions(+), 63 deletions(-) create mode 100644 tests/properties.c rename tests/{test.c => vectors.c} (82%) diff --git a/.gitignore b/.gitignore index 682c054..8614aa6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ *.sav bin/* frama-c/* -test +properties +vectors sodium donna rename_* diff --git a/makefile b/makefile index 2faecac..6737d57 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,5 @@ CC=gcc -CFLAGS= -I src -O2 -Wall -Wextra -std=c11 -pedantic +CFLAGS= -I src -O2 -Wall -Wextra -std=c11 -pedantic -g #-fno-stack-protector @@ -7,14 +7,15 @@ CFLAGS= -I src -O2 -Wall -Wextra -std=c11 -pedantic # disable implicit rules .SUFFIXES: -all: test sodium donna +all: vectors properties sodium donna clean: rm -rf bin frama-c rm -f src/*.gch src/rename_* - rm -f test sodium donna + rm -f vectors properties sodium donna -TEST_DEPS=tests/test.c bin/monocypher.o bin/sha512.o +TEST_DEPS=tests/vectors.c bin/monocypher.o bin/sha512.o +PROP_DEPS=tests/properties.c bin/classic_monocypher.o GEN_HEADERS=bin/argon2i.h \ bin/blake2b.h \ bin/blake2b_easy.h \ @@ -29,7 +30,7 @@ GEN_HEADERS=bin/argon2i.h \ bin/x_chacha20.h # Test suite based on test vectors -test: $(TEST_DEPS) $(GEN_HEADERS) +vectors: $(TEST_DEPS) $(GEN_HEADERS) $(CC) $(CFLAGS) -I bin -o $@ $(TEST_DEPS) bin/vector: tests/vector_to_header.c @@ -40,6 +41,10 @@ bin/%.h: tests/vectors/% bin/vector @echo generate $@ @bin/vector $$(basename $<) <$< >$@ +# Property based tests (consistency) +properties: $(PROP_DEPS) $(GEN_HEADERS) + $(CC) $(CFLAGS) -I bin -o $@ $(PROP_DEPS) + # Test suite based on comparison with libsodium C_SODIUM_FLAGS=$$(pkg-config --cflags libsodium) LD_SODIUM_FLAGS=$$(pkg-config --libs libsodium) @@ -88,7 +93,7 @@ rename_%.h: %.h frama-c: $(GEN_HEADERS) \ src/monocypher.c src/monocypher.h \ src/sha512.c src/sha512.h \ - tests/test.c + tests/vectors.c @echo copy sources to frama-c directory for analysis @mkdir -p frama-c @cp $^ frama-c diff --git a/test.sh b/test.sh index 8fae752..91f9220 100755 --- a/test.sh +++ b/test.sh @@ -3,32 +3,43 @@ echo echo "Build" echo "-----" -make test sodium donna +make vectors properties sodium donna echo -echo "Independent tests (with vectors)" -echo "--------------------------------" -./test +echo "Tests against vectors" +echo "---------------------" +./vectors retval_test=$? echo -echo "Fuzz tests (compare with libsodium)" -echo "-----------------------------------" +echo "Random self-consistency tests" +echo "-----------------------------" +./properties +retval_prop=$? + +echo +echo "Random comparison tests with libsodium" +echo "--------------------------------------" ./sodium retval_sodium=$? + echo -echo "Fuzz tests (compare with ed25519-donna)" -echo "---------------------------------------" +echo "Random comparison tests with ed25519-donna)" +echo "-------------------------------------------" ./donna retval_donna=$? echo if [ "$retval_test" -ne 0 ] ||\ + [ "$retval_prop" -ne 0 ] ||\ [ "$retval_sodium" -ne 0 ] ||\ [ "$retval_donna" -ne 0 ] then echo "TESTS FAILED. VERIFY IMPLEMENTATION. REPORT BUG" echo "DO. NOT. USE." else - echo "All tests OK!" + echo "-------------------" + echo "-- All tests OK! --" + echo "-------------------" + echo "" fi diff --git a/tests/properties.c b/tests/properties.c new file mode 100644 index 0000000..910f8f3 --- /dev/null +++ b/tests/properties.c @@ -0,0 +1,207 @@ +#include +#include +#include +#include "monocypher.h" +#include "sha512.h" + +#define FOR(i, start, end) for (size_t (i) = (start); (i) < (end); (i)++) +typedef uint8_t u8; +typedef uint32_t u32; +typedef int32_t i32; +typedef int64_t i64; +typedef uint64_t u64; + +// Deterministic "random" number generator, so we can make "random", yet +// reproducible tests. To change the random stream, change the seed. +void random(u8 *stream, size_t size) +{ + static crypto_chacha_ctx ctx; + static int is_init = 0; + if (!is_init) { + static const u8 seed[32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + crypto_chacha20_init(&ctx, seed, seed); + is_init = 1; + } + crypto_chacha20_stream(&ctx, stream, size); +} + +static u32 load32_le(const u8 s[4]) +{ + return (u32)s[0] + | ((u32)s[1] << 8) + | ((u32)s[2] << 16) + | ((u32)s[3] << 24); +} + +// Random number between 0 and max. +// Unbalanced if max is not a power of 2. +u32 rand_mod(u32 max) +{ + u8 n[4]; + random(n, 4); + return load32_le(n) % max; +} + +// Tests that encrypting in chunks yields the same result than +// encrypting all at once. +static int chacha20() +{ + static const size_t block_size = 64; // Chacha Block size + static const size_t input_size = block_size * 4; // total input size + static const size_t c_max_size = block_size * 2; // maximum chunk size + int status = 0; + FOR (i, 0, 1000) { + size_t offset = 0; + // outputs + u8 output_chunk[input_size]; + u8 output_whole[input_size]; + // inputs + u8 input [input_size]; random(input, input_size); + u8 key [32]; random(key , 32); + u8 nonce [8]; random(nonce, 8); + + // Encrypt in chunks + crypto_chacha_ctx ctx; + crypto_chacha20_init(&ctx, key, nonce); + while (1) { + size_t chunk_size = rand_mod(c_max_size); + if (offset + chunk_size > input_size) { break; } + u8 *out = output_chunk + offset; + u8 *in = input + offset; + crypto_chacha20_encrypt(&ctx, out, in, chunk_size); + offset += chunk_size; + } + // Encrypt all at once + crypto_chacha20_init(&ctx, key, nonce); + crypto_chacha20_encrypt(&ctx, output_whole, input, offset); + + // Compare the results (must be the same) + status |= crypto_memcmp(output_chunk, output_whole, offset); + } + printf("%s: Chacha20\n", status != 0 ? "FAILED" : "OK"); + return status; +} + +// Tests that authenticating bit by bit yields the same mac than +// authenticating all at once +static int poly1305() +{ + static const size_t block_size = 16; // poly1305 block size + static const size_t input_size = block_size * 4; // total input size + static const size_t c_max_size = block_size * 2; // maximum chunk size + int status = 0; + FOR (i, 0, 1000) { + size_t offset = 0; + // outputs + u8 mac_chunk[16]; + u8 mac_whole[16]; + // inputs + u8 input[input_size]; random(input, input_size); + u8 key [32]; random(key , 32); + + // Authenticate bit by bit + crypto_poly1305_ctx ctx; + crypto_poly1305_init(&ctx, key); + while (1) { + size_t chunk_size = rand_mod(c_max_size); + if (offset + chunk_size > input_size) { break; } + crypto_poly1305_update(&ctx, input + offset, chunk_size); + offset += chunk_size; + } + crypto_poly1305_final(&ctx, mac_chunk); + + // Authenticate all at once + crypto_poly1305_auth(mac_whole, input, offset, key); + + // Compare the results (must be the same) + status |= crypto_memcmp(mac_chunk, mac_whole, 16); + } + printf("%s: Poly1305\n", status != 0 ? "FAILED" : "OK"); + return status; +} + +// Tests that hashing bit by bit yields the same hash than hashing all +// at once. Note: I figured we didn't need to test keyed mode, or +// different hash sizes, again. This test sticks to the simplified +// interface. +static int blake2b() +{ + static const size_t block_size = 128; // Blake2b block size + static const size_t input_size = block_size * 4; // total input size + static const size_t c_max_size = block_size * 2; // maximum chunk size + int status = 0; + FOR (i, 0, 1000) { + size_t offset = 0; + // outputs + u8 hash_chunk[64]; + u8 hash_whole[64]; + // inputs + u8 input[input_size]; random(input, input_size); + + // Authenticate bit by bit + crypto_blake2b_ctx ctx; + crypto_blake2b_init(&ctx); + while (1) { + size_t chunk_size = rand_mod(c_max_size); + if (offset + chunk_size > input_size) { break; } + crypto_blake2b_update(&ctx, input + offset, chunk_size); + offset += chunk_size; + } + crypto_blake2b_final(&ctx, hash_chunk); + + // Authenticate all at once + crypto_blake2b(hash_whole, input, offset); + + // Compare the results (must be the same) + status |= crypto_memcmp(hash_chunk, hash_whole, 64); + } + printf("%s: Blake2b\n", status != 0 ? "FAILED" : "OK"); + return status; +} + +static int aead() +{ + int status = 0; + FOR (i, 0, 1000) { + u8 key [32]; random(key , 32); + u8 nonce [24]; random(nonce , 24); + u8 ad [ 4]; random(ad , 4); + u8 plaintext[ 8]; random(plaintext, 8); + u8 box[24], box2[24]; + u8 out[8]; + // AEAD roundtrip + crypto_aead_lock(box, box+16, key, nonce, ad, 4, plaintext, 8); + status |= crypto_aead_unlock(out, key, nonce, box, ad, 4, box+16, 8); + status |= crypto_memcmp(plaintext, out, 8); + box[0]++; + status |= !crypto_aead_unlock(out, key, nonce, box, ad, 4, box+16, 8); + + // Authenticated roundtrip (easy interface) + // Make and accept message + crypto_lock(box, box + 16, key, nonce, plaintext, 8); + status |= crypto_unlock(out, key, nonce, box, box + 16, 8); + // Make sure decrypted text and original text are the same + status |= crypto_memcmp(plaintext, out, 8); + // Make and reject forgery + box[0]++; + status |= !crypto_unlock(out, key, nonce, box, box + 16, 8); + box[0]--; // undo forgery + + // Same result for both interfaces + crypto_aead_lock(box2, box2 + 16, key, nonce, 0, 0, plaintext, 8); + status |= crypto_memcmp(box, box2, 24); + } + printf("%s: aead\n", status != 0 ? "FAILED" : "OK"); + return status; +} + +int main(void) +{ + int status = 0; + status |= chacha20(); + status |= poly1305(); + status |= blake2b(); + status |= aead(); + return status; +} diff --git a/tests/sodium.c b/tests/sodium.c index bcf080c..332154f 100644 --- a/tests/sodium.c +++ b/tests/sodium.c @@ -6,13 +6,7 @@ #include #define FOR(i, start, end) for (size_t (i) = (start); (i) < (end); (i)++) -#define sv static void -typedef int8_t i8; -typedef uint8_t u8; -typedef uint32_t u32; -typedef int32_t i32; -typedef int64_t i64; -typedef uint64_t u64; +typedef uint8_t u8; // Deterministic "random" number generator, so we can make "random", yet // reproducible tests. To change the random stream, change the seed. @@ -20,7 +14,7 @@ void random(u8 *stream, u8 size) { static rename_chacha_ctx ctx; static int is_init = 0; - if (!is_init){ + if (!is_init) { static const u8 seed[32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; rename_chacha20_init(&ctx, seed, seed); diff --git a/tests/test.c b/tests/vectors.c similarity index 82% rename from tests/test.c rename to tests/vectors.c index 21fa9e0..33f563a 100644 --- a/tests/test.c +++ b/tests/vectors.c @@ -253,47 +253,9 @@ sv key_exchange(const vector in[], vector *out) crypto_key_exchange(out->buf, secret_key->buf, public_key->buf); } -static int test_aead() -{ - u8 key[32] = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7 }; - u8 nonce[24] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1 }; - u8 ad [4] = { 3, 2, 1, 0 }; - u8 plaintext[8] = { 7, 6, 5, 4, 3, 2, 1, 0 }; - u8 box[24], box2[24]; - u8 out[8]; - int status = 0; - // AEAD roundtrip - crypto_aead_lock(box, box+16, key, nonce, ad, 4, plaintext, 8); - status |= crypto_aead_unlock(out, key, nonce, box, ad, 4, box+16, 8); - status |= crypto_memcmp(plaintext, out, 8); - box[0]++; - status |= !crypto_aead_unlock(out, key, nonce, box, ad, 4, box+16, 8); - printf("%s: aead (detached)\n", status != 0 ? "FAILED" : "OK"); - - // Authenticated roundtrip (easy interface) - crypto_lock(box, box + 16, key, nonce, plaintext, 8); // make message - status |= crypto_unlock(out, key, nonce, box, box + 16, 8); // accept message - status |= crypto_memcmp(plaintext, out, 8); // roundtrip - box[0]++; // make forgery - status |= !crypto_unlock(out, key, nonce, box, box + 16, 8);// reject forgery - printf("%s: aead (simplified)\n", status != 0 ? "FAILED" : "OK"); - box[0]--; // undo forgery - - // Same result for both interfaces - crypto_aead_lock(box2, box2 + 16, key, nonce, 0, 0, plaintext, 8); - status |= crypto_memcmp(box, box2, 24); - printf("%s: aead (compared)\n", status != 0 ? "FAILED" : "OK"); - - return status; -} - int main(void) { int status = 0; - /* status |= generic_test(equal, "tests/vectors/test_equal" , 2); */ - /* status |= generic_test(diff , "tests/vectors/test_diff" , 2); */ status |= TEST(chacha20 , 2); status |= TEST(h_chacha20 , 2); status |= TEST(x_chacha20 , 2); @@ -307,6 +269,5 @@ int main(void) status |= TEST(ed25519_key , 1); status |= TEST(ed25519_sign, 3); status |= test_x25519(); - status |= test_aead(); return status; } -- 2.47.3