more refactoring

This commit is contained in:
Adam Ierymenko 2020-02-21 07:11:16 -08:00
parent c3b5c45fea
commit 5275a34b0b
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
5 changed files with 301 additions and 38 deletions

View file

@ -3,7 +3,6 @@ project(zt_go_native)
set(src
GoGlue.cpp
CoreTests.cpp
)
set(headers

View file

@ -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<const uint8_t *>(key));
return;
}
#endif
_initSW(key);
_initSW(reinterpret_cast<const uint8_t *>(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<const uint8_t *>(in),reinterpret_cast<uint8_t *>(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<const uint8_t *>(in),reinterpret_cast<uint8_t *>(out));
}
/**

View file

@ -37,9 +37,9 @@
#include "LZ4.hpp"
#include "Hashtable.hpp"
#include "FCV.hpp"
#include "SHA512.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <set>
@ -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 &ltc) :
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 &ltc)
{
if (&ltc != 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<LifeCycleTracker,1024> 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<ZT_NUM_C25519_TEST_VECTORS;++t) {
C25519::agree(C25519_TEST_VECTORS[t].priv1,C25519_TEST_VECTORS[t].pub2,agree0);
C25519::agree(C25519_TEST_VECTORS[t].priv2,C25519_TEST_VECTORS[t].pub1,agree1);
if (memcmp(agree0,agree1,32) != 0) {
ZT_T_PRINTF("FAILED (keys do not agree, vector %d)" ZT_EOL_S,t);
return "Curve25519 key agreement test failed (a/b does not agree with b/a)";
}
SHA512(kh,agree0,32);
if (memcmp(kh,C25519_TEST_VECTORS[t].agreementSha512,64) != 0) {
ZT_T_PRINTF("FAILED (hash of agreement does not match test vector %d)" ZT_EOL_S,t);
return "Curve25519 key agreement test failed (does not match expected value)";
}
C25519::sign(C25519_TEST_VECTORS[t].priv1,C25519_TEST_VECTORS[t].pub1,kh,64,sig);
if (memcmp(sig,C25519_TEST_VECTORS[t].agreementSignedBy1,96) != 0) {
ZT_T_PRINTF("FAILED (signature of agreement by key 1 does not match test vector %d)" ZT_EOL_S,t);
return "Curve25519 signature test failed (signature by key 1 does not match expected value)";
}
C25519::sign(C25519_TEST_VECTORS[t].priv2,C25519_TEST_VECTORS[t].pub2,kh,64,sig);
if (memcmp(sig,C25519_TEST_VECTORS[t].agreementSignedBy1,96) != 0) {
ZT_T_PRINTF("FAILED (signature of agreement by key 2 does not match test vector %d)" ZT_EOL_S,t);
return "Curve25519 signature test failed (signature by key 2 does not match expected value)";
}
}
ZT_T_PRINTF("OK" ZT_EOL_S);
}
{
uint8_t key[48];
ZT_T_PRINTF("[crypto] Testing ECC384 (NIST P-384)... ");
ECC384ECDH(ECC384_TV0_PUBLIC,ECC384_TV0_PRIVATE,key);
if (memcmp(key,ECC384_TV0_DH_SELF_AGREE,48) != 0) {
ZT_T_PRINTF("FAILED (test vector 0, self-agree)" ZT_EOL_S);
return "ECC384 test vector 0 self-agree failed";
}
if (!ECC384ECDSAVerify(ECC384_TV0_PUBLIC,ECC384_TV0_PUBLIC,ECC384_TV0_SIG)) {
ZT_T_PRINTF("FAILED (test vector 0, signature check)" ZT_EOL_S);
return "ECC384 test vector 0 signature check failed";
}
ZT_T_PRINTF("OK" ZT_EOL_S);
}
{
uint8_t ks[64];
ZT_T_PRINTF("[crypto] Testing Salsa20... ");
Salsa20 s20;
s20.init(SALSA20_TV0_KEY,SALSA20_TV0_IV);
memset(ks,0,sizeof(ks));
s20.crypt20(ks,ks,sizeof(ks));
if (memcmp(ks,SALSA20_TV0_KS,64) != 0) {
ZT_T_PRINTF("FAILED (Salsa20 test vector)" ZT_EOL_S);
return "Salsa20 test vector failed";
}
s20.init(SALSA12_TV0_KEY,SALSA12_TV0_IV);
memset(ks,0,sizeof(ks));
s20.crypt12(ks,ks,sizeof(ks));
if (memcmp(ks,SALSA12_TV0_KS,64) != 0) {
ZT_T_PRINTF("FAILED (Salsa12 test vector)" ZT_EOL_S);
return "Salsa12 test vector failed";
}
ZT_T_PRINTF("OK" ZT_EOL_S);
}
{
uint8_t tag[16];
ZT_T_PRINTF("[crypto] Testing Poly1305... ");
poly1305(tag,POLY1305_TV0_INPUT,sizeof(POLY1305_TV0_INPUT),POLY1305_TV0_KEY);
if (memcmp(tag,POLY1305_TV0_TAG,16) != 0) {
ZT_T_PRINTF("FAILED (test vector 0)");
return "poly1305 test vector 0 failed";
}
poly1305(tag,POLY1305_TV1_INPUT,sizeof(POLY1305_TV1_INPUT),POLY1305_TV1_KEY);
if (memcmp(tag,POLY1305_TV1_TAG,16) != 0) {
ZT_T_PRINTF("FAILED (test vector 1)");
return "poly1305 test vector 1 failed";
}
ZT_T_PRINTF("OK" ZT_EOL_S);
}
{
uint8_t out[16];
ZT_T_PRINTF("[crypto] Testing AES (hardware acceleration: %s)... ",AES::accelerated() ? "enabled" : "disabled");
AES aes(AES_TEST_VECTOR_0_KEY);
aes.encrypt(AES_TEST_VECTOR_0_IN,out);
if (memcmp(AES_TEST_VECTOR_0_OUT,out,16) != 0) {
}
aes.decrypt(out,out);
if (memcmp(AES_TEST_VECTOR_0_IN,out,16) != 0) {
}
ZT_T_PRINTF("OK" ZT_EOL_S);
}
} catch (std::exception &e) {
ZT_T_PRINTF(ZT_EOL_S "[crypto] Unexpected exception: %s" ZT_EOL_S,e.what());
return e.what();
} catch ( ... ) {
ZT_T_PRINTF(ZT_EOL_S "[crypto] Unexpected exception: unknown exception" ZT_EOL_S);
return "an unknown exception occurred";
}
return nullptr;
}

View file

@ -259,18 +259,28 @@ void getSecureRandom(void *buf,unsigned int bytes) noexcept
{
static Mutex globalLock;
static bool initialized = false;
static uint64_t randomState[8];
static uint64_t randomBuf[8192];
static unsigned int randomPtr = 65536;
static uint64_t randomState[16]; // secret state
static uint64_t randomBuf[8192]; // next batch of random bytes
static unsigned long randomPtr = sizeof(randomBuf); // refresh on first iteration
// This secure random function gets entropy from the system random source (e.g. /dev/urandom),
// CPU random instructions if present, and other sources and uses them to initialize a SHA/AES
// based CSPRNG with a large state. System random sources are not used directly to mitigate
// against cases where the system random source is broken in some way, which does happen from
// time to time.
Mutex::Lock gl(globalLock);
for(unsigned int i=0;i<bytes;++i) {
if (randomPtr >= 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<const uint8_t *>(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<const uint8_t *>(ctr),reinterpret_cast<uint8_t *>(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;