diff --git a/auto_tests/CMakeLists.txt b/auto_tests/CMakeLists.txt index fbbcb7d7..c0b6aee4 100644 --- a/auto_tests/CMakeLists.txt +++ b/auto_tests/CMakeLists.txt @@ -5,16 +5,18 @@ include_directories(${CHECK_INCLUDE_DIRS}) find_package(Check REQUIRED) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/messenger_test.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/crypto_test.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/friends_test.cmake) include( CTest ) enable_testing() add_test(messenger messenger_test) +add_test(crypto crypto_test) # TODO enable once test is fixed #add_test(friends friends_test) add_custom_target( test COMMAND ${CMAKE_CTEST_COMMAND} -V - DEPENDS messenger_test + DEPENDS messenger_test crypto_test ) diff --git a/auto_tests/cmake/crypto_test.cmake b/auto_tests/cmake/crypto_test.cmake new file mode 100644 index 00000000..9ca9a331 --- /dev/null +++ b/auto_tests/cmake/crypto_test.cmake @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.6.0) +project(crypto_test C) +set(exe_name crypto_test) + +add_executable(${exe_name} + crypto_test.c) + +linkCoreLibraries(${exe_name}) +add_dependencies(${exe_name} Check) +target_link_libraries(${exe_name} check) diff --git a/auto_tests/crypto_test.c b/auto_tests/crypto_test.c new file mode 100644 index 00000000..9ac81349 --- /dev/null +++ b/auto_tests/crypto_test.c @@ -0,0 +1,230 @@ +#include "../core/net_crypto.h" +#include +#include +#include +#include +#include +#include +#include + +void rand_bytes(uint8_t *b, size_t blen) +{ + size_t i; + for (i = 0; i < blen; i++) + { + b[i] = rand(); + } +} + +// These test vectors are from libsodium's test suite + +unsigned char alicesk[32] = { + 0x77,0x07,0x6d,0x0a,0x73,0x18,0xa5,0x7d, + 0x3c,0x16,0xc1,0x72,0x51,0xb2,0x66,0x45, + 0xdf,0x4c,0x2f,0x87,0xeb,0xc0,0x99,0x2a, + 0xb1,0x77,0xfb,0xa5,0x1d,0xb9,0x2c,0x2a +}; + +unsigned char bobpk[32] = { + 0xde,0x9e,0xdb,0x7d,0x7b,0x7d,0xc1,0xb4, + 0xd3,0x5b,0x61,0xc2,0xec,0xe4,0x35,0x37, + 0x3f,0x83,0x43,0xc8,0x5b,0x78,0x67,0x4d, + 0xad,0xfc,0x7e,0x14,0x6f,0x88,0x2b,0x4f +}; + +unsigned char nonce[24] = { + 0x69,0x69,0x6e,0xe9,0x55,0xb6,0x2b,0x73, + 0xcd,0x62,0xbd,0xa8,0x75,0xfc,0x73,0xd6, + 0x82,0x19,0xe0,0x03,0x6b,0x7a,0x0b,0x37 +}; + +unsigned char test_m[131] = { + 0xbe,0x07,0x5f,0xc5,0x3c,0x81,0xf2,0xd5, + 0xcf,0x14,0x13,0x16,0xeb,0xeb,0x0c,0x7b, + 0x52,0x28,0xc5,0x2a,0x4c,0x62,0xcb,0xd4, + 0x4b,0x66,0x84,0x9b,0x64,0x24,0x4f,0xfc, + 0xe5,0xec,0xba,0xaf,0x33,0xbd,0x75,0x1a, + 0x1a,0xc7,0x28,0xd4,0x5e,0x6c,0x61,0x29, + 0x6c,0xdc,0x3c,0x01,0x23,0x35,0x61,0xf4, + 0x1d,0xb6,0x6c,0xce,0x31,0x4a,0xdb,0x31, + 0x0e,0x3b,0xe8,0x25,0x0c,0x46,0xf0,0x6d, + 0xce,0xea,0x3a,0x7f,0xa1,0x34,0x80,0x57, + 0xe2,0xf6,0x55,0x6a,0xd6,0xb1,0x31,0x8a, + 0x02,0x4a,0x83,0x8f,0x21,0xaf,0x1f,0xde, + 0x04,0x89,0x77,0xeb,0x48,0xf5,0x9f,0xfd, + 0x49,0x24,0xca,0x1c,0x60,0x90,0x2e,0x52, + 0xf0,0xa0,0x89,0xbc,0x76,0x89,0x70,0x40, + 0xe0,0x82,0xf9,0x37,0x76,0x38,0x48,0x64, + 0x5e,0x07,0x05 +}; + +unsigned char test_c[147] = { + 0xf3,0xff,0xc7,0x70,0x3f,0x94,0x00,0xe5, + 0x2a,0x7d,0xfb,0x4b,0x3d,0x33,0x05,0xd9, + 0x8e,0x99,0x3b,0x9f,0x48,0x68,0x12,0x73, + 0xc2,0x96,0x50,0xba,0x32,0xfc,0x76,0xce, + 0x48,0x33,0x2e,0xa7,0x16,0x4d,0x96,0xa4, + 0x47,0x6f,0xb8,0xc5,0x31,0xa1,0x18,0x6a, + 0xc0,0xdf,0xc1,0x7c,0x98,0xdc,0xe8,0x7b, + 0x4d,0xa7,0xf0,0x11,0xec,0x48,0xc9,0x72, + 0x71,0xd2,0xc2,0x0f,0x9b,0x92,0x8f,0xe2, + 0x27,0x0d,0x6f,0xb8,0x63,0xd5,0x17,0x38, + 0xb4,0x8e,0xee,0xe3,0x14,0xa7,0xcc,0x8a, + 0xb9,0x32,0x16,0x45,0x48,0xe5,0x26,0xae, + 0x90,0x22,0x43,0x68,0x51,0x7a,0xcf,0xea, + 0xbd,0x6b,0xb3,0x73,0x2b,0xc0,0xe9,0xda, + 0x99,0x83,0x2b,0x61,0xca,0x01,0xb6,0xde, + 0x56,0x24,0x4a,0x9e,0x88,0xd5,0xf9,0xb3, + 0x79,0x73,0xf6,0x22,0xa4,0x3d,0x14,0xa6, + 0x59,0x9b,0x1f,0x65,0x4c,0xb4,0x5a,0x74, + 0xe3,0x55,0xa5 +}; + +START_TEST(test_known) +{ + unsigned char c[147]; + unsigned char m[131]; + int clen, mlen; + + ck_assert_msg(sizeof(c) == sizeof(m) + ENCRYPTION_PADDING * sizeof(unsigned char), "cyphertext should be ENCRYPTION_PADDING bytes longer than plaintext"); + ck_assert_msg(sizeof(test_c) == sizeof(c), "sanity check failed"); + ck_assert_msg(sizeof(test_m) == sizeof(m), "sanity check failed"); + + clen = encrypt_data(bobpk, alicesk, nonce, test_m, sizeof(test_m)/sizeof(unsigned char), c); + + ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector"); + ck_assert_msg(clen == sizeof(c)/sizeof(unsigned char), "wrong ciphertext length"); + + mlen = decrypt_data(bobpk, alicesk, nonce, test_c, sizeof(test_c)/sizeof(unsigned char), m); + + ck_assert_msg(memcmp(test_m, m, sizeof(m)) == 0, "decrypted text doesn't match test vector"); + ck_assert_msg(mlen == sizeof(m)/sizeof(unsigned char), "wrong plaintext length"); +} +END_TEST + +START_TEST(test_fast_known) +{ + unsigned char k[crypto_box_BEFORENMBYTES]; + unsigned char c[147]; + unsigned char m[131]; + int clen, mlen; + + encrypt_precompute(bobpk, alicesk, k); + + ck_assert_msg(sizeof(c) == sizeof(m) + ENCRYPTION_PADDING * sizeof(unsigned char), "cyphertext should be ENCRYPTION_PADDING bytes longer than plaintext"); + ck_assert_msg(sizeof(test_c) == sizeof(c), "sanity check failed"); + ck_assert_msg(sizeof(test_m) == sizeof(m), "sanity check failed"); + + clen = encrypt_data_fast(k, nonce, test_m, sizeof(test_m)/sizeof(unsigned char), c); + + ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector"); + ck_assert_msg(clen == sizeof(c)/sizeof(unsigned char), "wrong ciphertext length"); + + mlen = decrypt_data_fast(k, nonce, test_c, sizeof(test_c)/sizeof(unsigned char), m); + + ck_assert_msg(memcmp(test_m, m, sizeof(m)) == 0, "decrypted text doesn't match test vector"); + ck_assert_msg(mlen == sizeof(m)/sizeof(unsigned char), "wrong plaintext length"); + +} +END_TEST + +START_TEST(test_endtoend) +{ + unsigned char pk1[crypto_box_PUBLICKEYBYTES]; + unsigned char sk1[crypto_box_SECRETKEYBYTES]; + unsigned char pk2[crypto_box_PUBLICKEYBYTES]; + unsigned char sk2[crypto_box_SECRETKEYBYTES]; + unsigned char k1[crypto_box_BEFORENMBYTES]; + unsigned char k2[crypto_box_BEFORENMBYTES]; + + unsigned char n[crypto_box_NONCEBYTES]; + + unsigned char m[500]; + unsigned char c1[sizeof(m) + ENCRYPTION_PADDING]; + unsigned char c2[sizeof(m) + ENCRYPTION_PADDING]; + unsigned char c3[sizeof(m) + ENCRYPTION_PADDING]; + unsigned char c4[sizeof(m) + ENCRYPTION_PADDING]; + unsigned char m1[sizeof(m)]; + unsigned char m2[sizeof(m)]; + unsigned char m3[sizeof(m)]; + unsigned char m4[sizeof(m)]; + + int mlen; + int c1len, c2len, c3len, c4len; + int m1len, m2len, m3len, m4len; + + int testno; + + // Test 100 random messages and keypairs + for (testno = 0; testno < 100; testno++) + { + //Generate random message (random length from 100 to 500) + mlen = (rand() % 400) + 100; + rand_bytes(m, mlen); + rand_bytes(n, crypto_box_NONCEBYTES); + + //Generate keypairs + crypto_box_keypair(pk1,sk1); + crypto_box_keypair(pk2,sk2); + + //Precompute shared keys + encrypt_precompute(pk2, sk1, k1); + encrypt_precompute(pk1, sk2, k2); + + ck_assert_msg(memcmp(k1, k2, crypto_box_BEFORENMBYTES) == 0, "encrypt_precompute: bad"); + + //Encrypt all four ways + c1len = encrypt_data(pk2, sk1, n, m, mlen, c1); + c2len = encrypt_data(pk1, sk2, n, m, mlen, c2); + c3len = encrypt_data_fast(k1, n, m, mlen, c3); + c4len = encrypt_data_fast(k2, n, m, mlen, c4); + + ck_assert_msg(c1len == c2len && c1len == c3len && c1len == c4len, "cyphertext lengths differ"); + ck_assert_msg(c1len == mlen + ENCRYPTION_PADDING, "wrong cyphertext length"); + ck_assert_msg(memcmp(c1, c2, c1len) == 0 && memcmp(c1, c3, c1len) == 0 && memcmp(c1, c4, c1len) == 0, "crypertexts differ"); + + //Decrypt all four ways + m1len = decrypt_data(pk2, sk1, n, c1, c1len, m1); + m2len = decrypt_data(pk1, sk2, n, c1, c1len, m2); + m3len = decrypt_data_fast(k1, n, c1, c1len, m3); + m4len = decrypt_data_fast(k2, n, c1, c1len, m4); + + ck_assert_msg(m1len == m2len && m1len == m3len && m1len == m4len, "decrypted text lengths differ"); + ck_assert_msg(m1len == mlen, "wrong decrypted text length"); + ck_assert_msg(memcmp(m1, m2, mlen) == 0 && memcmp(m1, m3, mlen) == 0 && memcmp(m1, m4, mlen) == 0, "decrypted texts differ"); + ck_assert_msg(memcmp(m1, m, mlen) == 0, "wrong decrypted text"); + } +} +END_TEST + +#define DEFTESTCASE(NAME) \ + TCase *NAME = tcase_create(#NAME); \ + tcase_add_test(NAME, test_##NAME); \ + suite_add_tcase(s, NAME); + +Suite* crypto_suite(void) +{ + Suite *s = suite_create("Crypto"); + + DEFTESTCASE(known); + DEFTESTCASE(fast_known); + DEFTESTCASE(endtoend); + + return s; +} + +int main(int argc, char* argv[]) +{ + srand((unsigned int) time(NULL)); + + Suite *crypto = crypto_suite(); + SRunner *test_runner = srunner_create(crypto); + int number_failed = 0; + + srunner_run_all(test_runner, CK_NORMAL); + number_failed = srunner_ntests_failed(test_runner); + + srunner_free(test_runner); + + return number_failed; +} diff --git a/auto_tests/run_tests b/auto_tests/run_tests index 4448b2e2..1899f354 100755 --- a/auto_tests/run_tests +++ b/auto_tests/run_tests @@ -2,4 +2,4 @@ # run_tests - run the current tests for tox core set -e -./messenger_test && ./friends_test +./messenger_test && ./friends_test && ./crypto_test diff --git a/core/net_crypto.c b/core/net_crypto.c index 1c56b95a..1803caba 100644 --- a/core/net_crypto.c +++ b/core/net_crypto.c @@ -37,6 +37,7 @@ typedef struct { uint8_t sessionpublic_key[crypto_box_PUBLICKEYBYTES]; /* our public key for this session. */ uint8_t sessionsecret_key[crypto_box_SECRETKEYBYTES]; /* our private key for this session. */ uint8_t peersessionpublic_key[crypto_box_PUBLICKEYBYTES]; /* The public key of the peer. */ + uint8_t shared_key[crypto_box_BEFORENMBYTES]; /* the precomputed shared key from encrypt_precompute */ uint8_t status; /* 0 if no connection, 1 we have sent a handshake, 2 if connexion is not confirmed yet (we have received a handshake but no empty data packet), 3 if the connection is established. 4 if the connection is timed out. */ @@ -59,6 +60,17 @@ static Crypto_Connection crypto_connections[MAX_CRYPTO_CONNECTIONS]; /* keeps track of the connection numbers for friends request so we can check later if they were sent */ static int incoming_connections[MAX_INCOMING]; +/* Use this instead of memcmp; not vulnerable to timing attacks. */ +uint8_t crypto_iszero(uint8_t *mem, uint32_t length) +{ + uint8_t check = 0; + uint32_t i; + for (i = 0; i < length; ++i) { + check |= mem[i]; + } + return check; // We return zero if mem is made out of zeroes. +} + /* encrypts plain of length length to encrypted of length + 16 using the public key(32 bytes) of the receiver and the secret key of the sender and a 24 byte nonce return -1 if there was a problem. @@ -78,12 +90,38 @@ int encrypt_data(uint8_t *public_key, uint8_t *secret_key, uint8_t *nonce, /* if encryption is successful the first crypto_box_BOXZEROBYTES of the message will be zero apparently memcmp should not be used so we do this instead:*/ - uint32_t i; - uint32_t check = 0; - for(i = 0; i < crypto_box_BOXZEROBYTES; ++i) { - check |= temp_encrypted[i] ^ 0; - } - if(check != 0) + if(crypto_iszero(temp_encrypted, crypto_box_BOXZEROBYTES) != 0) + return -1; + + /* unpad the encrypted message */ + memcpy(encrypted, temp_encrypted + crypto_box_BOXZEROBYTES, length + crypto_box_MACBYTES); + return length - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES; +} + +/* Precomputes the shared key from their public_key and our secret_key. + This way we can avoid an expensive elliptic curve scalar multiply for each + encrypt/decrypt operation. + enc_key has to be crypto_box_BEFORENMBYTES bytes long. */ +void encrypt_precompute(uint8_t *public_key, uint8_t *secret_key, uint8_t *enc_key) +{ + crypto_box_beforenm(enc_key, public_key, secret_key); +} + +/* Fast encrypt. Depends on enc_key from encrypt_precompute. */ +int encrypt_data_fast(uint8_t *enc_key, uint8_t *nonce, + uint8_t *plain, uint32_t length, uint8_t *encrypted) +{ + if (length + crypto_box_MACBYTES > MAX_DATA_SIZE || length == 0) + return -1; + + uint8_t temp_plain[MAX_DATA_SIZE + crypto_box_BOXZEROBYTES] = {0}; + uint8_t temp_encrypted[MAX_DATA_SIZE + crypto_box_BOXZEROBYTES]; + + memcpy(temp_plain + crypto_box_ZEROBYTES, plain, length); /* pad the message with 32 0 bytes. */ + + crypto_box_afternm(temp_encrypted, temp_plain, length + crypto_box_ZEROBYTES, nonce, enc_key); + + if(crypto_iszero(temp_encrypted, crypto_box_BOXZEROBYTES) != 0) return -1; /* unpad the encrypted message */ @@ -112,12 +150,33 @@ int decrypt_data(uint8_t *public_key, uint8_t *secret_key, uint8_t *nonce, /* if decryption is successful the first crypto_box_ZEROBYTES of the message will be zero apparently memcmp should not be used so we do this instead:*/ - uint32_t i; - uint32_t check = 0; - for(i = 0; i < crypto_box_ZEROBYTES; ++i) { - check |= temp_plain[i] ^ 0; - } - if(check != 0) + if(crypto_iszero(temp_plain, crypto_box_ZEROBYTES) != 0) + return -1; + + /* unpad the plain message */ + memcpy(plain, temp_plain + crypto_box_ZEROBYTES, length - crypto_box_MACBYTES); + return length - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES; +} + +/* Fast decrypt. Depends on enc_ley from encrypt_precompute. */ +int decrypt_data_fast(uint8_t *enc_key, uint8_t *nonce, + uint8_t *encrypted, uint32_t length, uint8_t *plain) +{ + if (length > MAX_DATA_SIZE || length <= crypto_box_BOXZEROBYTES) + return -1; + + uint8_t temp_plain[MAX_DATA_SIZE + crypto_box_BOXZEROBYTES]; + uint8_t temp_encrypted[MAX_DATA_SIZE + crypto_box_BOXZEROBYTES] = {0}; + + memcpy(temp_encrypted + crypto_box_BOXZEROBYTES, encrypted, length); /* pad the message with 16 0 bytes. */ + + if (crypto_box_open_afternm(temp_plain, temp_encrypted, length + crypto_box_BOXZEROBYTES, + nonce, enc_key) == -1) + return -1; + + /* if decryption is successful the first crypto_box_ZEROBYTES of the message will be zero + apparently memcmp should not be used so we do this instead:*/ + if(crypto_iszero(temp_plain, crypto_box_ZEROBYTES) != 0) return -1; /* unpad the plain message */ @@ -161,9 +220,9 @@ int read_cryptpacket(int crypt_connection_id, uint8_t *data) return 0; if (temp_data[0] != 3) return -1; - int len = decrypt_data(crypto_connections[crypt_connection_id].peersessionpublic_key, - crypto_connections[crypt_connection_id].sessionsecret_key, - crypto_connections[crypt_connection_id].recv_nonce, temp_data + 1, length - 1, data); + int len = decrypt_data_fast(crypto_connections[crypt_connection_id].shared_key, + crypto_connections[crypt_connection_id].recv_nonce, + temp_data + 1, length - 1, data); if (len != -1) { increment_nonce(crypto_connections[crypt_connection_id].recv_nonce); return len; @@ -182,9 +241,9 @@ int write_cryptpacket(int crypt_connection_id, uint8_t *data, uint32_t length) if (crypto_connections[crypt_connection_id].status != CONN_ESTABLISHED) return 0; uint8_t temp_data[MAX_DATA_SIZE]; - int len = encrypt_data(crypto_connections[crypt_connection_id].peersessionpublic_key, - crypto_connections[crypt_connection_id].sessionsecret_key, - crypto_connections[crypt_connection_id].sent_nonce, data, length, temp_data + 1); + int len = encrypt_data_fast(crypto_connections[crypt_connection_id].shared_key, + crypto_connections[crypt_connection_id].sent_nonce, + data, length, temp_data + 1); if (len == -1) return 0; temp_data[0] = 3; @@ -408,6 +467,7 @@ int accept_crypto_inbound(int connection_id, uint8_t *public_key, uint8_t *secre random_nonce(crypto_connections[i].recv_nonce); memcpy(crypto_connections[i].sent_nonce, secret_nonce, crypto_box_NONCEBYTES); memcpy(crypto_connections[i].peersessionpublic_key, session_key, crypto_box_PUBLICKEYBYTES); + increment_nonce(crypto_connections[i].sent_nonce); memcpy(crypto_connections[i].public_key, public_key, crypto_box_PUBLICKEYBYTES); @@ -509,6 +569,7 @@ static void receive_crypto(void) if (memcmp(public_key, crypto_connections[i].public_key, crypto_box_PUBLICKEYBYTES) == 0) { memcpy(crypto_connections[i].sent_nonce, secret_nonce, crypto_box_NONCEBYTES); memcpy(crypto_connections[i].peersessionpublic_key, session_key, crypto_box_PUBLICKEYBYTES); + encrypt_precompute(crypto_connections[i].peersessionpublic_key, crypto_connections[i].sessionsecret_key, crypto_connections[i].shared_key); increment_nonce(crypto_connections[i].sent_nonce); uint32_t zero = 0; crypto_connections[i].status = CONN_ESTABLISHED; /* connection status needs to be 3 for write_cryptpacket() to work */ diff --git a/core/net_crypto.h b/core/net_crypto.h index 66634d45..135e099d 100644 --- a/core/net_crypto.h +++ b/core/net_crypto.h @@ -36,6 +36,9 @@ extern uint8_t self_secret_key[crypto_box_SECRETKEYBYTES]; #define ENCRYPTION_PADDING (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) +/* returns zero if the buffer contains only zeros */ +uint8_t crypto_iszero(uint8_t* buffer, uint32_t blen); + /* encrypts plain of length length to encrypted of length + 16 using the public key(32 bytes) of the receiver and the secret key of the sender and a 24 byte nonce return -1 if there was a problem. @@ -51,6 +54,19 @@ int encrypt_data(uint8_t *public_key, uint8_t *secret_key, uint8_t *nonce, int decrypt_data(uint8_t *public_key, uint8_t *secret_key, uint8_t *nonce, uint8_t *encrypted, uint32_t length, uint8_t *plain); +/* Fast encrypt/decrypt operations. Use if this is not a one-time communication. + encrypt_precompute does the shared-key generation once so it does not have + to be preformed on every encrypt/decrypt. */ +void encrypt_precompute(uint8_t *public_key, uint8_t *secret_key, uint8_t *enc_key); + +/* Fast encrypt. Depends on enc_key from encrypt_precompute. */ +int encrypt_data_fast(uint8_t *enc_key, uint8_t *nonce, + uint8_t *plain, uint32_t length, uint8_t *encrypted); + +/* Fast decrypt. Depends on enc_ley from encrypt_precompute. */ +int decrypt_data_fast(uint8_t *enc_key, uint8_t *nonce, + uint8_t *encrypted, uint32_t length, uint8_t *plain); + /* fill the given nonce with random bytes. */ void random_nonce(uint8_t *nonce); diff --git a/other/CMakeLists.txt b/other/CMakeLists.txt index 05b24f06..db742e3e 100644 --- a/other/CMakeLists.txt +++ b/other/CMakeLists.txt @@ -4,6 +4,6 @@ cmake_policy(SET CMP0011 NEW) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DHT_bootstrap.cmake) -if(NOT WIN32) +if(LINUX) add_subdirectory(bootstrap_serverdaemon) endif()