diff --git a/go/native/CoreTests.cpp b/attic/CoreTests.cpp similarity index 100% rename from go/native/CoreTests.cpp rename to attic/CoreTests.cpp diff --git a/go/native/CMakeLists.txt b/go/native/CMakeLists.txt index feb37d644..96b0bb36f 100644 --- a/go/native/CMakeLists.txt +++ b/go/native/CMakeLists.txt @@ -3,7 +3,6 @@ project(zt_go_native) set(src GoGlue.cpp - CoreTests.cpp ) set(headers diff --git a/node/AES.hpp b/node/AES.hpp index 6becbbcb4..a4cec7183 100644 --- a/node/AES.hpp +++ b/node/AES.hpp @@ -64,22 +64,24 @@ public: * * @param key 256-bit key */ - explicit ZT_ALWAYS_INLINE AES(const uint8_t key[32]) noexcept { this->init(key); } + explicit ZT_ALWAYS_INLINE AES(const void *const key) noexcept { this->init(key); } ZT_ALWAYS_INLINE ~AES() { Utils::burn(&_k,sizeof(_k)); } /** * Set (or re-set) this AES256 cipher's key + * + * @param key 256-bit / 32-byte key */ - ZT_ALWAYS_INLINE void init(const uint8_t key[32]) noexcept + ZT_ALWAYS_INLINE void init(const void *key) noexcept { #ifdef ZT_AES_AESNI if (likely(Utils::CPUID.aes)) { - _init_aesni(key); + _init_aesni(reinterpret_cast(key)); return; } #endif - _initSW(key); + _initSW(reinterpret_cast(key)); } /** @@ -88,7 +90,7 @@ public: * @param in Input block * @param out Output block (can be same as input) */ - ZT_ALWAYS_INLINE void encrypt(const uint8_t in[16],uint8_t out[16]) const noexcept + ZT_ALWAYS_INLINE void encrypt(const void *const in,void *const out) const noexcept { #ifdef ZT_AES_AESNI if (likely(Utils::CPUID.aes)) { @@ -96,7 +98,7 @@ public: return; } #endif - _encryptSW(in,out); + _encryptSW(reinterpret_cast(in),reinterpret_cast(out)); } /** @@ -105,7 +107,7 @@ public: * @param in Input block * @param out Output block (can be same as input) */ - ZT_ALWAYS_INLINE void decrypt(const uint8_t in[16],uint8_t out[16]) const noexcept + ZT_ALWAYS_INLINE void decrypt(const void *const in,void *const out) const noexcept { #ifdef ZT_AES_AESNI if (likely(Utils::CPUID.aes)) { @@ -113,7 +115,7 @@ public: return; } #endif - _decryptSW(in,out); + _decryptSW(reinterpret_cast(in),reinterpret_cast(out)); } /** diff --git a/node/Tests.cpp b/node/Tests.cpp index 35608d302..a39c2d50b 100644 --- a/node/Tests.cpp +++ b/node/Tests.cpp @@ -37,9 +37,9 @@ #include "LZ4.hpp" #include "Hashtable.hpp" #include "FCV.hpp" +#include "SHA512.hpp" #include -#include #include #include #include @@ -98,7 +98,7 @@ struct C25519TestVector uint8_t priv1[64]; uint8_t pub2[64]; uint8_t priv2[64]; - uint8_t agreement[64]; + uint8_t agreementSha512[64]; uint8_t agreementSignedBy1[96]; uint8_t agreementSignedBy2[96]; }; @@ -144,6 +144,13 @@ static const C25519TestVector C25519_TEST_VECTORS[ZT_NUM_C25519_TEST_VECTORS] = // -------------------------------------------------------------------------------------------------------------------- +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define ZT_ENDIAN_S "little" +#else +#define ZT_ENDIAN_S "big" +#endif + +// This is basically a unit test for compiler packed structure behavior. ZT_PACKED_STRUCT(struct StructPackingTestSample { uint8_t foo; uint16_t bar; @@ -155,13 +162,41 @@ ZT_PACKED_STRUCT(struct StructPackingTestSample { uint8_t lol; }); -#define ZT_T_ASSERT(e) if (!(e)) { ZT_T_PRINTF("FAILED (simple assert: " #e ")" ZT_EOL_S); return "simple assert: " #e; } +// Increments and decrements a counter based on object create/destroy +class LifeCycleTracker +{ +public: + ZT_ALWAYS_INLINE LifeCycleTracker() : + cnt(nullptr) + { + } + ZT_ALWAYS_INLINE LifeCycleTracker(const LifeCycleTracker <c) : + cnt(ltc.cnt) + { + if (*cnt) ++*cnt; + } + explicit ZT_ALWAYS_INLINE LifeCycleTracker(long &c) : + cnt(&c) + { + ++c; + } + ZT_ALWAYS_INLINE ~LifeCycleTracker() + { + if (cnt) --*cnt; + } + ZT_ALWAYS_INLINE LifeCycleTracker &operator=(const LifeCycleTracker <c) + { + if (<c != this) { + if (*cnt) --*cnt; + cnt = ltc.cnt; + if (*cnt) ++*cnt; + } + return *this; + } + long *cnt; +}; -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define ZT_ENDIAN_S "little" -#else -#define ZT_ENDIAN_S "big" -#endif +#define ZT_T_ASSERT(e) if (!(e)) { ZT_T_PRINTF("FAILED (simple assert: " #e ")" ZT_EOL_S); return "simple assert: " #e; } extern "C" const char *ZTT_general() { @@ -206,6 +241,9 @@ extern "C" const char *ZTT_general() ZT_T_ASSERT(sizeof(Protocol::Header) == ZT_PROTO_MIN_PACKET_LENGTH); ZT_T_ASSERT(sizeof(Protocol::FragmentHeader) == ZT_PROTO_MIN_FRAGMENT_LENGTH); ZT_T_ASSERT(sizeof(sockaddr_storage) == sizeof(InetAddress)); + ZT_T_ASSERT(sizeof(sockaddr_in) <= sizeof(InetAddress)); + ZT_T_ASSERT(sizeof(sockaddr_in6) <= sizeof(InetAddress)); + ZT_T_ASSERT(sizeof(sockaddr) <= sizeof(InetAddress)); ZT_T_PRINTF("OK" ZT_EOL_S); } @@ -270,6 +308,92 @@ extern "C" const char *ZTT_general() } ZT_T_PRINTF("OK" ZT_EOL_S); } + + { + ZT_T_PRINTF("[general] Testing FCV (fixed capacity vector)... "); + long cnt = 0; + FCV test,test2; + for(unsigned int i=0;i<512;++i) + test.push_back(LifeCycleTracker(cnt)); + if (cnt != 512) { + ZT_T_PRINTF("FAILED (expected 512 objects, got %lu (1))" ZT_EOL_S,cnt); + return "FCV object life cycle test failed (1)"; + } + test2 = test; + if (cnt != 1024) { + ZT_T_PRINTF("FAILED (expected 1024 objects, got %lu (2))" ZT_EOL_S,cnt); + return "FCV object life cycle test failed (2)"; + } + test.clear(); + if (cnt != 512) { + ZT_T_PRINTF("FAILED (expected 512 objects, got %lu (3))" ZT_EOL_S,cnt); + return "FCV object life cycle test failed (3)"; + } + for(unsigned int i=0;i<512;++i) + test.push_back(LifeCycleTracker(cnt)); + if (cnt != 1024) { + ZT_T_PRINTF("FAILED (expected 1024 objects, got %lu (4))" ZT_EOL_S,cnt); + return "FCV object life cycle test failed (4)"; + } + test.clear(); + test2.clear(); + if (cnt != 0) { + ZT_T_PRINTF("FAILED (expected 0 objects, got %lu (5))" ZT_EOL_S,cnt); + return "FCV object life cycle test failed (5)"; + } + ZT_T_PRINTF("OK" ZT_EOL_S); + } + + { + ZT_T_PRINTF("[general] Testing Hashtable... "); + + long cnt = 0; + Hashtable< unsigned int,LifeCycleTracker > test,test2; + for(unsigned int i=0;i<512;++i) + test[i] = LifeCycleTracker(cnt); + if (cnt != 512) { + ZT_T_PRINTF("FAILED (expected 512 objects, got %lu (1))" ZT_EOL_S,cnt); + return "Hashtable test failed (1)"; + } + test2 = test; + if (cnt != 1024) { + ZT_T_PRINTF("FAILED (expected 1024 objects, got %lu (2))" ZT_EOL_S,cnt); + return "Hashtable test failed (2)"; + } + test.clear(); + if (cnt != 512) { + ZT_T_PRINTF("FAILED (expected 512 objects, got %lu (3))" ZT_EOL_S,cnt); + return "Hashtable test failed (3)"; + } + for(unsigned int i=0;i<512;++i) + test[i] = LifeCycleTracker(cnt); + if (cnt != 1024) { + ZT_T_PRINTF("FAILED (expected 1024 objects, got %lu (4))" ZT_EOL_S,cnt); + return "Hashtable test failed (4)"; + } + test.clear(); + test2.clear(); + if (cnt != 0) { + ZT_T_PRINTF("FAILED (expected 0 objects, got %lu (5))" ZT_EOL_S,cnt); + return "Hashtable test failed (5)"; + } + + Hashtable< unsigned int,unsigned int > test3; + for(unsigned int i=0;i<1111;++i) + test3[i] = i; + if (test3.size() != 1111) { + ZT_T_PRINTF("FAILED (size() incorrect)" ZT_EOL_S); + return "Hashtable test failed (size() incorrect)"; + } + for(unsigned int i=0;i<1111;++i) { + if (test3[i] != i) { + ZT_T_PRINTF("FAILED (lookup test)" ZT_EOL_S); + return "Hashtable test failed (lookup)"; + } + } + + ZT_T_PRINTF("OK" ZT_EOL_S); + } } catch (std::exception &e) { ZT_T_PRINTF(ZT_EOL_S "[general] Unexpected exception: %s" ZT_EOL_S,e.what()); return e.what(); @@ -277,12 +401,129 @@ extern "C" const char *ZTT_general() ZT_T_PRINTF(ZT_EOL_S "[general] Unexpected exception: unknown exception" ZT_EOL_S); return "an unknown exception occurred"; } - return nullptr; } extern "C" const char *ZTT_crypto() { + try { + { + ZT_T_PRINTF("[crypto] Testing SHA384 and SHA512... "); + uint8_t h[64]; + SHA512(h,SHA512_TV0_INPUT,strlen(SHA512_TV0_INPUT)); + if (memcmp(h,SHA512_TV0_DIGEST,64) != 0) { + ZT_T_PRINTF("FAILED (SHA512)" ZT_EOL_S); + return "SHA512 test vector failed"; + } + ZT_T_PRINTF("OK" ZT_EOL_S); + SHA384(h,SHA512_TV0_INPUT,strlen(SHA512_TV0_INPUT)); + if (memcmp(h,SHA512_TV0_SHA384_DIGEST,48) != 0) { + ZT_T_PRINTF("FAILED (SHA384)" ZT_EOL_S); + return "SHA384 test vector failed"; + } + ZT_T_PRINTF("OK" ZT_EOL_S); + } + + { + uint8_t agree0[32],agree1[32],kh[64],sig[96]; + ZT_T_PRINTF("[crypto] Testing C25519/Ed25519... "); + for(int t=0;t= 65536) { + if (randomPtr >= sizeof(randomBuf)) { randomPtr = 0; if (!initialized) { initialized = true; + + // Fill both randomState and randomBuf from system random source. Failure here + // is fatal to the running application and indicates a serious system problem. + // This is some of the only OS-specific code in the core. #ifdef __WINDOWS__ HCRYPTPROV cryptProvider = NULL; if (!CryptAcquireContextA(&cryptProvider,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { @@ -292,12 +302,12 @@ void getSecureRandom(void *buf,unsigned int bytes) noexcept fprintf(stderr,"FATAL: Utils::getSecureRandom() unable to open /dev/urandom\n"); exit(1); } - if ((int)::read(devURandomFd,randomState,sizeof(randomState)) != (int)sizeof(randomState)) { + if ((long)::read(devURandomFd,randomState,sizeof(randomState)) != (long)sizeof(randomState)) { ::close(devURandomFd); fprintf(stderr,"FATAL: Utils::getSecureRandom() unable to read from /dev/urandom\n"); exit(1); } - if ((int)::read(devURandomFd,randomBuf,sizeof(randomBuf)) != (int)sizeof(randomBuf)) { + if ((long)::read(devURandomFd,randomBuf,sizeof(randomBuf)) != (long)sizeof(randomBuf)) { ::close(devURandomFd); fprintf(stderr,"FATAL: Utils::getSecureRandom() unable to read from /dev/urandom\n"); exit(1); @@ -305,29 +315,40 @@ void getSecureRandom(void *buf,unsigned int bytes) noexcept close(devURandomFd); #endif - // Mix in additional entropy just in case the standard random source is wonky somehow + // Mix in additional entropy from time, the address of 'buf', rdrand if present, etc. randomState[0] ^= (uint64_t)time(nullptr); randomState[1] ^= (uint64_t)((uintptr_t)buf); +#ifdef __UNIX_LIKE__ + randomState[2] ^= (uint64_t)getpid(); + randomState[3] ^= (uint64_t)getppid(); +#endif #if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64)) if (CPUID.rdrand) { uint64_t tmp = 0; - _rdrand64_step((unsigned long long *)&tmp); - randomState[2] ^= tmp; - _rdrand64_step((unsigned long long *)&tmp); - randomState[3] ^= tmp; + for(int i=0;i<16;++i) { + _rdrand64_step((unsigned long long *)&tmp); + randomState[i] ^= tmp; + } } #endif } - ++randomState[0]; - SHA512(randomState,randomState,sizeof(randomState)); - AES aes(reinterpret_cast(randomState)); + // Generate a new randomBuf: + // + // (1) Generate next randomState by perturbing, hashing, and replacing the first 384 bits with the hash. + // (2) Initialize AES using the first 256 bits of the new randomState as its key. + // (3) Initialize a 128-bit counter field using the following 128 bits of randomState. + // (4) Encrypt randomBuf with AES-CTR (machine-endian counter since spec conformance doesn't matter). + + ++randomState[15]; + SHA384(randomState,randomState,sizeof(randomState)); + AES aes(randomState); uint64_t ctr[2],tmp[2]; - ctr[0] = randomState[6]; - ctr[1] = randomState[7]; + ctr[0] = randomState[4]; + ctr[1] = randomState[5]; // AES key + CTR/nonce = part replaced each time by SHA384 for(int k=0;k<8192;) { ++ctr[0]; - aes.encrypt(reinterpret_cast(ctr),reinterpret_cast(tmp)); + aes.encrypt(ctr,tmp); randomBuf[k] ^= tmp[0]; randomBuf[k+1] ^= tmp[1]; k += 2; @@ -421,7 +442,6 @@ int b32d(const char *encoded,uint8_t *result,int bufSize) noexcept return count; } -#define ROL64(x,k) (((x) << (k)) | ((x) >> (64 - (k)))) uint64_t random() noexcept { // https://en.wikipedia.org/wiki/Xorshift#xoshiro256** @@ -434,14 +454,15 @@ uint64_t random() noexcept uint64_t s1 = s_s1; uint64_t s2 = s_s2; uint64_t s3 = s_s3; - const uint64_t result = ROL64(s1 * 5,7U) * 9; + const uint64_t s1x5 = s1 * 5; + const uint64_t result = ((s1x5 << 7U)|(s1x5 >> 57U)) * 9; const uint64_t t = s1 << 17U; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; - s3 = ROL64(s3,45U); + s3 = ((s3 << 45U)|(s3 >> 19U)); s_s0 = s0; s_s1 = s1; s_s2 = s2;