#include "cube.h" #ifdef STANDALONE #define DEBUGCOND (true) #else VARP(cryptodebug, 0, 1, 1); #define DEBUGCOND (cryptodebug==1) #endif static const char *hexdigits = "0123456789abcdef"; ///////////////////////// cryptography ///////////////////////////////// /* Based off the reference implementation of Tiger, a cryptographically * secure 192 bit hash function by Ross Anderson and Eli Biham. More info at: * http://www.cs.technion.ac.il/~biham/Reports/Tiger/ */ #define TIGER_PASSES 3 namespace tiger { typedef unsigned long long int chunk; union hashval { uchar bytes[3*8]; chunk chunks[3]; }; chunk sboxes[4*256]; void compress(const chunk *str, chunk state[3]) { chunk a, b, c; chunk aa, bb, cc; chunk x0, x1, x2, x3, x4, x5, x6, x7; a = state[0]; b = state[1]; c = state[2]; x0=str[0]; x1=str[1]; x2=str[2]; x3=str[3]; x4=str[4]; x5=str[5]; x6=str[6]; x7=str[7]; aa = a; bb = b; cc = c; loop(pass_no, TIGER_PASSES) { if(pass_no) { x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5ULL; x1 ^= x0; x2 += x1; x3 -= x2 ^ ((~x1)<<19); x4 ^= x3; x5 += x4; x6 -= x5 ^ ((~x4)>>23); x7 ^= x6; x0 += x7; x1 -= x0 ^ ((~x7)<<19); x2 ^= x1; x3 += x2; x4 -= x3 ^ ((~x2)>>23); x5 ^= x4; x6 += x5; x7 -= x6 ^ 0x0123456789ABCDEFULL; } #define sb1 (sboxes) #define sb2 (sboxes+256) #define sb3 (sboxes+256*2) #define sb4 (sboxes+256*3) #define tround(a, b, c, x) \ c ^= x; \ a -= sb1[((c)>>(0*8))&0xFF] ^ sb2[((c)>>(2*8))&0xFF] ^ \ sb3[((c)>>(4*8))&0xFF] ^ sb4[((c)>>(6*8))&0xFF] ; \ b += sb4[((c)>>(1*8))&0xFF] ^ sb3[((c)>>(3*8))&0xFF] ^ \ sb2[((c)>>(5*8))&0xFF] ^ sb1[((c)>>(7*8))&0xFF] ; \ b *= mul; uint mul = !pass_no ? 5 : (pass_no==1 ? 7 : 9); tround(a, b, c, x0) tround(b, c, a, x1) tround(c, a, b, x2) tround(a, b, c, x3) tround(b, c, a, x4) tround(c, a, b, x5) tround(a, b, c, x6) tround(b, c, a, x7) chunk tmp = a; a = c; c = b; b = tmp; } a ^= aa; b -= bb; c += cc; state[0] = a; state[1] = b; state[2] = c; } void gensboxes() { const char *str = "Tiger - A Fast New Hash Function, by Ross Anderson and Eli Biham"; chunk state[3] = { 0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL, 0xF096A5B4C3B2E187ULL }; uchar temp[64]; if(!*(const uchar *)&islittleendian) loopj(64) temp[j^7] = str[j]; else loopj(64) temp[j] = str[j]; loopi(1024) loop(col, 8) ((uchar *)&sboxes[i])[col] = i&0xFF; int abc = 2; loop(pass, 5) loopi(256) for(int sb = 0; sb < 1024; sb += 256) { abc++; if(abc >= 3) { abc = 0; compress((chunk *)temp, state); } loop(col, 8) { uchar val = ((uchar *)&sboxes[sb+i])[col]; ((uchar *)&sboxes[sb+i])[col] = ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col]; ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col] = val; } } } void hash(const uchar *str, int length, hashval &val) { static bool init = false; if(!init) { gensboxes(); init = true; } uchar temp[64]; val.chunks[0] = 0x0123456789ABCDEFULL; val.chunks[1] = 0xFEDCBA9876543210ULL; val.chunks[2] = 0xF096A5B4C3B2E187ULL; int i = length; for(; i >= 64; i -= 64, str += 64) { if(!*(const uchar *)&islittleendian) { loopj(64) temp[j^7] = str[j]; compress((chunk *)temp, val.chunks); } else compress((chunk *)str, val.chunks); } int j; if(!*(const uchar *)&islittleendian) { for(j = 0; j < i; j++) temp[j^7] = str[j]; temp[j^7] = 0x01; while(++j&7) temp[j^7] = 0; } else { for(j = 0; j < i; j++) temp[j] = str[j]; temp[j] = 0x01; while(++j&7) temp[j] = 0; } if(j > 56) { while(j < 64) temp[j++] = 0; compress((chunk *)temp, val.chunks); j = 0; } while(j < 56) temp[j++] = 0; *(chunk *)(temp+56) = (chunk)length<<3; compress((chunk *)temp, val.chunks); lilswap(val.chunks, 3); } } void tigerhash(uchar *hash, const uchar *msg, int len) { tiger::hash(msg, len, *((tiger::hashval *) hash)); } #undef sb1 #undef sb2 #undef sb3 #undef sb4 #undef tround /////////////////////////////////////////// SHA512 //////////////////////////////////////////////////////////////////// // adapted from pseudocode and various public domain sources (probably all originating in LibTomCrypt) // (consider this code public domain, too) #define RR64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) #define SHA512ROUND(a, b, c, d, e, f, g, h, i) \ d += h = h + (RR64(e,14) ^ RR64(e,18) ^ RR64(e,41)) + (g ^ (e & (f ^ g))) + K[i] + W[i]; \ h += (RR64(a,28) ^ RR64(a,34) ^ RR64(a,39)) + (((a | b) & c) | (a & b)); #define SHA512SIZE (512 / 8) void sha512_compress(uint64_t *state, const uchar *inbuf) // sha512 main function: compress one chunk of 128 bytes { static const uint64_t K[80] = { 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL }; uint64_t S[8], W[80]; loopi(8) S[i] = state[i]; loopi(16) W[i] = bigswap(((uint64_t*)inbuf)[i]); loopi(64) W[i + 16] = W[i] + W[i + 9] + (RR64(W[i + 14],19) ^ RR64(W[i + 14],61) ^ (W[i + 14] >> 6)) + (RR64(W[i + 1],1) ^ RR64(W[i + 1], 8) ^ (W[i + 1] >> 7)); for(int i = 0; i < 80; i += 8) { SHA512ROUND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0); SHA512ROUND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1); SHA512ROUND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2); SHA512ROUND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3); SHA512ROUND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4); SHA512ROUND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5); SHA512ROUND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6); SHA512ROUND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); } loopi(8) state[i] += S[i]; } void sha512(uchar *hash, const uchar *msg, int msglen) { uint64_t state[8] = { 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL }; uint64_t total = msglen * 8ULL; for(; msglen >= 128; msglen -= 128) { sha512_compress(state, msg); msg += 128; } uchar endmsg[128] = { 0 }; memcpy(endmsg, msg, msglen); endmsg[msglen] = 0x80; if(msglen > 111) { sha512_compress(state, endmsg); memset(endmsg, 0, 128); } *((uint64_t*)(endmsg + 120)) = bigswap(total); sha512_compress(state, endmsg); loopi(8) ((uint64_t*)hash)[i] = bigswap(state[i]); } #undef RR64 #undef SHA512ROUND #ifndef STANDALONE #ifdef _DEBUG COMMANDF(sha512, "s", (char *msg) // SHA512 debug command { uchar hash[SHA512SIZE]; sha512(hash, (uchar*)msg, strlen(msg)); defformatstring(erg)("SHA512: "); loopi(SHA512SIZE) concatformatstring(erg, "%02x", hash[i]); conoutf("%s", erg); }); COMMANDF(tiger, "s", (char *msg) // tiger debug command { tiger::hashval hash; tiger::hash((uchar *)msg, (int)strlen(msg), hash); defformatstring(erg)("TIGER: "); loopi(24) concatformatstring(erg, "%02x", hash.bytes[i]); conoutf("%s", erg); }); #endif #endif ///////////////////////////////////////////////////////// FNV-1a /////////////////////////////////////////////////////////////////// // Fowler–Noll–Vo is a non-cryptographic hash function created by Glenn Fowler, Landon Curt Noll, and Phong Vo. It is public domain. // We use the 32-bit-version here. inline void fnv1a_init(uint32_t &hash) { hash = 2166136261UL; } inline void fnv1a_add(uint32_t &hash, uchar byte) { hash ^= byte; hash *= 16777619; } ////////////////////////// crypto rand (Mersenne twister) //////////////////////////////////////// #define MT_N (624) #define MT_M (397) #define MT_K (0x9908B0DFU) static uint mt_state[2 * MT_N]; static int mt_next = MT_N; void seedMT_do(uint seed, uint *statearray, uint arraysize) { statearray[0] = seed; for(uint i = 1; i < arraysize; i++) statearray[i] = seed = 1812433253U * (seed ^ (seed >> 30)) + i; } void seedMT(uint seed) { loopi(MT_N) mt_state[i + MT_N] = mt_state[i]; mt_state[0] = seed; seedMT_do(seed, mt_state, MT_N); mt_next = 0; } uint randomMT() { int cur = mt_next; if(++mt_next >= MT_N) { if(mt_next > MT_N) { seedMT(5489U + time(NULL)); cur = mt_next++; } else mt_next = 0; } uint y = (mt_state[cur] & 0x80000000U) | (mt_state[mt_next] & 0x7FFFFFFFU); mt_state[cur] = y = mt_state[cur < MT_N - MT_M ? cur + MT_M : cur + MT_M - MT_N] ^ (y >> 1) ^ (-int(y & 1U) & MT_K); y ^= (y >> 11); y ^= (y << 7) & 0x9D2C5680U; y ^= (y << 15) & 0xEFC60000U; y ^= (y >> 18); return y; } void popMT() // undo last seedMT() { loopi(MT_N) mt_state[i] = mt_state[i + MT_N]; } /////////////////////////////////////////////// Entropy collector //////////////////////////////////////////////////////////////////////////////// // * collects entropy (true random data) through mouse moves and keyboard action (and from other sources) // * used to seed the pseudorandom generator // * used to generate very good private keys #define ENTCHUNKS 32 // pool size (in 128-bytes chunks) #define ENTPOOLSIZE (ENTCHUNKS * 128) #define ENTROPYSAVEFILE "config" PATHDIVS "entropy.dat" static uchar *entpool = NULL; static void xor_block(uchar *d, const uchar *s, int len) { uint64_t *dd = (uint64_t *)d, *ss = (uint64_t *)s; for( ; len > 7; len -= 8) *dd++ ^= *ss++; d = (uchar*)dd, s = (uchar*)ss; while(len-- > 0) *d++ ^= *s++; } static void mix_block(uchar *d, const uchar *s, int len) { static uchar sum = 0; int t; while(len-- > 0) { t = *d * *s++ + sum; *d++ += t >> 4; sum += t >> 7; } } void entropy_init(uint seed) { DELETEP(entpool); entpool = new uchar[ENTPOOLSIZE + 128]; seedMT_do(seed, (uint*)entpool, ENTPOOLSIZE / sizeof(uint)); int len = 0; char *buf = loadfile(ENTROPYSAVEFILE, &len); if(buf && len) entropy_add_block((uchar*)buf, len); DELETEA(buf); entropy_get((uchar*)mt_state, MT_N * sizeof(uint)); } void entropy_save() { stream *f = openfile(ENTROPYSAVEFILE, "w"); if(f) f->write(entpool, ENTPOOLSIZE); DELETEP(f); } void entropy_add_byte(uchar b) { static int next = 0; int r = entpool[next] & 7; entpool[next++] ^= b << r | b >> (8 - r); if(next >= ENTPOOLSIZE) next = 0; } void entropy_add_block(const uchar *s, int len) { for( ; len > 0; s += 128, len -= 128) { int bl = min(len, 128); xor_block(entpool + rnd(ENTPOOLSIZE / 8 - 16) * 8, s, bl); // use pseudorandom to mix entropy into the pool in a hard to predict fashion mix_block(entpool + rnd(ENTPOOLSIZE - bl), s, bl); } } void entropy_get(uchar *buf, int len) { ASSERT(len <= ENTPOOLSIZE); uchar *tempbuf = new uchar[len + SHA512SIZE]; memcpy(tempbuf, entpool, len); memcpy(entpool + ENTPOOLSIZE, entpool, 128); sha512(tempbuf, entpool, ENTPOOLSIZE); loopi(len) sha512_compress((uint64_t*)(tempbuf + (i & ~7)), entpool + rnd(ENTPOOLSIZE / 8) * 8); memcpy(buf, tempbuf, len); delete[] tempbuf; } #ifndef STANDALONE /////////////////////////////////////////////// key derivation function //////////////////////////////////////////////////////////////////////// // * basically a hash function that delivers keylen bytes calculated from pass and salt // * but uses a lot of memory and a quite complex algorithm, to make brute-force offline-attacks on short passwords as hard as possible (even if someone uses fpgas) // * last line of defense against "evil little brothers" void passphrase2key(const char *pass, const uchar *salt, int saltlen, uchar *key, int keylen, int *iterations, int maxtime, int memusage) { memset(key, 0, keylen); if(!*pass) return; // password "" -> memusage = clamp(memusage, 2, 1024); // keep memory usage between 2MB and 1GB int memsize = (1<<20) * memusage, passlen = strlen(pass), tmpbuflen = 2 * SHA512SIZE, pplen = 2 * (saltlen + passlen); // prepare the passphrase (including salt) uchar *pp = new uchar[pplen]; memcpy(pp, salt, saltlen); memcpy(pp + saltlen, pass, passlen); memcpy(pp + saltlen + passlen, pass, passlen); memcpy(pp + saltlen + 2 * passlen, salt, saltlen); if((memusage & 1) && passlen + saltlen >= 8) identhash((uint64_t *)(pp + saltlen + passlen)); // mix up the passphrase a bit uchar *tmpbuf = new uchar[tmpbuflen + sizeof(uint)]; memset(tmpbuf, 0, tmpbuflen + sizeof(uint)); sha512(tmpbuf, pp, pplen); xor_block(pp, tmpbuf, min(pplen, tmpbuflen)); sha512(tmpbuf + SHA512SIZE, pp, pplen); tiger::hash(pp, pplen, *((tiger::hashval *)tmpbuf)); // initialise the huge buffer uchar *hugebuf = new uchar[memsize]; memset(hugebuf, 0, memsize); int memchunk = memsize / tmpbuflen; loopi(tmpbuflen) seedMT_do(*((uint*)(tmpbuf + i)), (uint*)(hugebuf + memchunk * i), memchunk / sizeof(uint)); // spread the passphrase all over the huge buffer // start mixing #define mixit(x, y) (((((uint*)tmpbuf)[(roundsdone ^ (x)) % (tmpbuflen / sizeof(uint))]) % ((y) / 8 - (x) % 11)) * 8) stopwatch watch; watch.start(); int roundsdone = 0; uint shortseed = 0; do { loopirev(memsize / 127) shortseed = ((uint*)hugebuf)[shortseed % (memsize / sizeof(uint))]; seedMT_do(shortseed, (uint*)tmpbuf, tmpbuflen / sizeof(uint)); loopi(2999) sha512_compress((uint64_t*)(hugebuf + mixit(i * 5, memsize - SHA512SIZE)), hugebuf + mixit(i * 17, memsize - 128)); roundsdone++; } while((!*iterations && watch.elapsed() < maxtime) || (*iterations && *iterations > roundsdone)); // *iterations == 0: time-limited, otherwise rounds-limited // extract the key if(keylen * 8 > 192) sha512(tmpbuf, hugebuf, memsize); else tiger::hash(hugebuf, memsize, *((tiger::hashval *)tmpbuf)); loopi(SHA512SIZE) sha512_compress(((uint64_t*)tmpbuf) + (i / 8), hugebuf + (((tmpbuf[i] * 68111) % (memsize - 128)) & ~7)); memcpy(key, tmpbuf, min(keylen, tmpbuflen)); // max keylen is 2 * SHA512SIZE DEBUG("passphrase2key: " << watch.elapsed() << " ms, " << roundsdone << " rounds, " << memsize << " bytes used"); *iterations = roundsdone; delete[] hugebuf; delete[] tmpbuf; delete[] pp; #undef mixit } #endif /////////////////////////////////////////////// hex converters ///////////////////////////////////////////////////////////////////////////////////// const char *bin2hex(char *d, const uchar *s, int len) { // d needs to be at least of size 2 * len + 1 len *= 2; loopi(len) d[i] = hexdigits[(s[i / 2] >> ((i & 1) ? 0 : 4)) & 0xf]; d[len] = '\0'; return d; } int hex2bin(uchar *d, const char *s, int maxlen) { memset(d, 0, maxlen); int len; for(len = 0; s[len] && len < maxlen * 2; len++) { const char *hd = strchr(hexdigits, tolower(s[len])); if(!hd) break; // -> not a hex digit d[len / 2] |= (hd - hexdigits) << ((len & 1) ? 0 : 4); } return len / 2; } /////////////////////////////////////////////// Ed25519: high-speed high-security signatures ////////////////////////////////////////////////////// #include "crypto_tools.h" void ed25519_pubkey_from_private(uchar *pubkey, const uchar *privkey) { unsigned char az[64]; sc25519 scsk; ge25519 gepk; sha512(az, privkey, 32); az[0] &= 248; az[31] &= 127; az[31] |= 64; sc25519_from32bytes(&scsk,az); ge25519_scalarmult_base(&gepk, &scsk); ge25519_pack(pubkey, &gepk); } void privkey_from_prepriv(unsigned char *privkey, const unsigned char *prepriv, int preprivlen, unsigned char *privpriv = NULL) // derive private key from even more secret bulk of entropy { uchar temp[SHA512SIZE], pub[32], testpriv[32]; sha512(temp, prepriv, preprivlen); int esc, minesc = 32, minescpos = 0; loopi(33) { // find privte key with the public key with the least getint() escape codes (yes, that decreases the keyrange. sue me.) ed25519_pubkey_from_private(testpriv, temp + i); ed25519_pubkey_from_private(pub, testpriv); esc = 0; loopj(32) if((pub[j] & 0xfe) == 0x80) esc++; if(esc < minesc) { minesc = esc; minescpos = i; } if(esc == 0) break; } ed25519_pubkey_from_private(privkey, temp + minescpos); // (the resulting private key is actually itself a public key of a private derived from prepriv) if(privpriv) memcpy(privpriv, temp + minescpos, 32); } void ed25519_sign(uchar *sm, int *smlen, const uchar *m, int mlen, const uchar *sk) { // sk: 32-byte private key + 32-byte public key uchar pk[32], az[64], nonce[64], hram[64]; sc25519 sck, scs, scsk; ge25519 ger; memmove(pk, sk + 32, 32); // pk: 32-byte public key A sha512(az, sk, 32); az[0] &= 248; az[31] &= 127; az[31] |= 64; // az: 32-byte scalar a, 32-byte randomizer z if(smlen) *smlen = mlen + 64; memmove(sm + 64, m, mlen); // need memmove here: m and sm may overlap memmove(sm + 32, az + 32, 32); // sm: 32-byte uninit, 32-byte z, mlen-byte m sha512(nonce, sm + 32, mlen + 32); // nonce: 64-byte H(z,m) sc25519_from64bytes(&sck, nonce); ge25519_scalarmult_base(&ger, &sck); ge25519_pack(sm, &ger); // sm: 32-byte R, 32-byte z, mlen-byte m memmove(sm + 32, pk, 32); // sm: 32-byte R, 32-byte A, mlen-byte m sha512(hram, sm, mlen + 64); // hram: 64-byte H(R,A,m) sc25519_from64bytes(&scs, hram); sc25519_from32bytes(&scsk, az); sc25519_mul(&scs, &scs, &scsk); sc25519_add(&scs, &scs, &sck); // scs: S = nonce + H(R,A,m)a sc25519_to32bytes(sm + 32, &scs); // sm: 32-byte R, 32-byte S, mlen-byte m } uchar *ed25519_sign_check(uchar *sm, int smlen, const uchar *pk) { uchar scopy[32], hram[64], rcheck[32]; ge25519 get1, get2; sc25519 schram, scs; if(smlen < 64 || (sm[63] & 224) || ge25519_unpackneg_vartime(&get1, pk)) return NULL; // frame error memmove(scopy, sm + 32, 32); sc25519_from32bytes(&scs, sm + 32); memmove(sm + 32, pk, 32); sha512(hram, sm, smlen); sc25519_from64bytes(&schram, hram); ge25519_double_scalarmult_vartime(&get2, &get1, &schram, &ge25519_base, &scs); ge25519_pack(rcheck, &get2); memmove(sm + 32, scopy, 32); // restore sm return memcmp(rcheck, sm, 32) ? NULL : sm + 64; } #ifndef STANDALONE #ifdef _DEBUG void ed25519test(char *vectorfilename) // check ed25519 functions with test vectors from http://ed25519.cr.yp.to/python/sign.input { path(vectorfilename); stopwatch watch; int vectorfilesize = 0; char *vectorfile = loadfile(vectorfilename, &vectorfilesize); if(!vectorfile) { conoutf("could not read test vector file %s", vectorfilename); return; } conoutf("loaded %d bytes from %s", vectorfilesize, vectorfilename); watch.start(); int lines = 0, inputerrors = 0, pubfail = 0, signfail = 0, verifyfail = 0, verifyfail2 = 0; for(char *l = strtok(vectorfile, "\n"); l; l = strtok(NULL, "\n")) { // a line consists of 32 bytes private key, 32 byte public key, ":", 32 byte public key (again), ":", message, ":", signed message, ":" char *vprivpub = l, *vpub = strchr(l, ':'), *vmsg, *vsmsg, hextemp[65]; int linelen = strlen(l), msglen = 0; uchar privpub[64], pub[32], *msg = new uchar[linelen], *smsg = new uchar[linelen], temp[64]; lines++; if(!vpub || !(vmsg = strchr(vpub + 1, ':')) || !(vsmsg = strchr(vmsg + 1, ':')) || hex2bin(privpub, vprivpub, 64) != 64 || hex2bin(pub, ++vpub, 32) != 32 || (msglen = hex2bin(msg, ++vmsg, linelen)) < 0 || hex2bin(smsg, ++vsmsg, linelen) != (msglen + 64) || memcmp(msg, smsg + 64, msglen) || memcmp(privpub + 32, pub, 32)) { inputerrors++; continue; } // line does not match template ed25519_pubkey_from_private(temp, privpub); if(strncmp(bin2hex(hextemp, temp, 32), vpub, 64)) { pubfail++; continue; } // private and public key don't match ed25519_sign(msg, &linelen, msg, msglen, privpub); if(linelen != msglen + 64 || memcmp(msg, smsg, linelen)) { signfail++; continue; } // newly signed message doesn't match vector data if(!ed25519_sign_check(smsg, msglen + 64, pub)) { verifyfail++; continue; } // verification of signed message failed smsg[msglen % 64]++; if(ed25519_sign_check(smsg, msglen + 64, pub)) { verifyfail2++; continue; } // verification of altered signed message didn't fail (...but should've) smsg[msglen % 64]--; if(msglen) { smsg[42 % msglen]++; if(ed25519_sign_check(smsg, msglen + 64, pub)) { verifyfail2++; continue; } // verification of altered signed message didn't fail (...but should've) smsg[42 % msglen]--; } pub[msglen % 32]++; if(ed25519_sign_check(smsg, msglen + 64, pub)) { verifyfail2++; continue; } // verification of altered signed message didn't fail (...but should've) delete[] smsg; delete[] msg; } DELETEA(vectorfile); conoutf("processed %d lines in %d milliseconds", lines, watch.elapsed()); conoutf("%d vector file errors, %d private-pubkey mismatches, %d sign fails, %d verify fails, %d missed verify fails", inputerrors, pubfail, signfail, verifyfail, verifyfail2); } COMMAND(ed25519test, "s"); void ed25519speedtest() // check speed of ed25519 functions { uchar priv[32], pub[32], msg[64], smsg[128], sk[64]; entropy_get(priv, 32); entropy_get(msg, 32); stopwatch watch; watch.start(); loopi(200) ed25519_pubkey_from_private(pub, priv); conoutf("create public key from private key: %.2f ms", watch.elapsed() / 200.0f); memcpy(sk, priv, 32); memcpy(sk + 32, pub, 32); watch.start(); loopi(200) ed25519_sign(smsg, NULL, msg, 64, sk); conoutf("signing 64-byte message: %.2f ms", watch.elapsed() / 200.0f); watch.start(); loopi(200) ed25519_sign_check(smsg, 128, pub); conoutf("verify signature of 64-byte message: %.2f ms", watch.elapsed() / 200.0f); } COMMAND(ed25519speedtest, ""); #endif #endif #ifndef STANDALONE //////////////////////////////////// game key management //////////////////////////////////////////////////////////////////////// // * the game key is the primary player key that is associated with his player account // * it is used during playing to authenticate the player to the server // * needs special protection... #define AUTHPREPRIVATECFGFILE "config" PATHDIVS "authpreprivate.cfg" #define AUTHPRIVATECFGFILE "config" PATHDIVS "authprivate.cfg" VARP(authmemusage, 2, 24, (1<<10) - 1); // megabytes of RAM to use for password hash (when using a new password) VARP(authrounds, 0, 0, INT_MAX - 1); // create new password hashes with a fixed number of rounds (if authrounds > 0) VARP(authmaxtime, 1<<9, 1<<12, 1<<16); // create new password hashes with a fixed amount of time (in ms) static uchar *sk = NULL; // game key static struct { uchar *salt, *priv; uint pwdcfg; } passdargs; static int passdeferred(void *pass) // decrypt the private key in the background { uchar keyhash[32]; int iterations = passdargs.pwdcfg >> 10; passphrase2key((char*)pass, passdargs.salt, 16, keyhash, 32, &iterations, 0, passdargs.pwdcfg & 0x3ff); xor_block(passdargs.priv, keyhash, 32); delstring((char *)pass); extern void authsetup(char **args, int numargs); authsetup(NULL, -1); // tell authsetup, that the passwd is done return 0; } void authsetup(char **args, int numargs) // set up private and public keys { const int preprivminlen = 32, preprivmaxlen = 128; static uchar *buf = NULL, pub[32], keyhash[128], psalt[16], salt[16], preprivlen = 0; static uint preprivpwdcfg = 0, privpwdcfg = 0; static void *passdrunning = NULL, *passdcleanup = NULL; static char *sleepcmd = NULL; if(!buf) entropy_get((buf = new uchar[1536]), 1536); uint32_t offs = 7337; loopi(16) fnv1a_add(offs, buf[i]); uchar *priv = buf + 24 + (offs & 0xfc), *prepriv = priv + 112 + ((offs >> 9) & 0xfc), res = 0; char hextemp[2 * preprivmaxlen + 1]; if(passdrunning) { if(numargs < 0) { // finish password decryption (background mode) privpwdcfg = 0; passdcleanup = passdrunning; passdrunning = NULL; if(sleepcmd) addsleep(0, sleepcmd); DELSTRING(sleepcmd); } else return; // ignore further "authsetup" commands while a password decryption is running in the background } else { if(numargs < 0) return; // should not happen else if(passdcleanup) { sl_waitthread(passdcleanup); passdcleanup = NULL; } } if(numargs > 0) { if(!strcasecmp(args[0], "PRE")) { // authsetup pre preprivhex [psalthex pwdcfg] preprivlen = numargs > 1 ? clamp(int(strlen(args[1])) / 2, preprivminlen, preprivmaxlen) : 32; if(numargs > 1) hex2bin(prepriv, args[1], preprivlen); if(numargs > 2) hex2bin(psalt, args[2], 16); preprivpwdcfg = numargs > 3 ? atoi(args[3]) : 0; } else if(!strcasecmp(args[0], "PRIV")) { // authsetup priv privhex [salthex pwdcfg] if(numargs > 1) hex2bin(priv, args[1], 32); if(numargs > 2) hex2bin(salt, args[2], 16); privpwdcfg = numargs > 3 ? atoi(args[3]) : 0; sk = NULL; } else if(!strcasecmp(args[0], "PUB")) { // authsetup pub pubhex if(numargs > 1) hex2bin(pub, args[1], 32); sk = NULL; } else if(!strcasecmp(args[0], "PPASS")) { // authsetup ppass preprivpass int iterations = preprivpwdcfg >> 10; if(iterations) passphrase2key(numargs > 1 ? args[1] : "", psalt, 16, keyhash, preprivlen, &iterations, 0, preprivpwdcfg & 0x3fe); xor_block(prepriv, keyhash, preprivlen); preprivpwdcfg = 0; } else if(!strcasecmp(args[0], "PASS")) { // authsetup pass privpass int iterations = privpwdcfg >> 10; if(iterations) passphrase2key(numargs > 1 ? args[1] : "", salt, 16, keyhash, 32, &iterations, 0, privpwdcfg & 0x3ff); xor_block(priv, keyhash, 32); privpwdcfg = 0; } else if(!strcasecmp(args[0], "PASSD")) { // authsetup passd privpass [commandwhendone] if(!passdrunning && privpwdcfg) { passdargs.salt = salt; passdargs.priv = priv; passdargs.pwdcfg = privpwdcfg; passdrunning = sl_createthread(passdeferred, newstring(numargs > 1 ? args[1] : "")); sleepcmd = numargs > 2 ? newstring(args[2]) : NULL; } } else if(!strcasecmp(args[0], "NEEDPASS")) { // authsetup needpass if(privpwdcfg) res = 1; } else if(!strcasecmp(args[0], "GENPRE")) { // authsetup genpre prelen preprivlen = clamp((numargs > 1) ? atoi(args[1]) : 42, preprivminlen, preprivmaxlen); entropy_get(prepriv, preprivlen); preprivpwdcfg = 0; } else if(!strcasecmp(args[0], "GENPRIV")) { // authsetup genpriv if(!preprivpwdcfg) privkey_from_prepriv(priv, prepriv, preprivlen); privpwdcfg = 0; } else if(!strcasecmp(args[0], "GENPUB")) { // authsetup genpub ed25519_pubkey_from_private(pub, priv); privpwdcfg = 0; } else if(!strcasecmp(args[0], "NEWPPASS")) { // authsetup newppass preprivpass [preprivfilename] if(preprivlen && !preprivpwdcfg) { if(numargs > 1 && args[1][0]) // new password { entropy_get(psalt, 16); int iterations = authrounds; passphrase2key(args[1], psalt, 16, keyhash, preprivlen, &iterations, authmaxtime, authmemusage & 0x3fe); preprivpwdcfg = (iterations << 10) | authmemusage; xor_block(prepriv, keyhash, preprivlen); } const char *fn = numargs > 2 && args[2][0] ? path(args[2]) : AUTHPREPRIVATECFGFILE; char *oldfile = loadfile(fn, NULL); stream *f = openfile(fn, "wb"); if(f) { f->printf("\n" "// remove this file from your computer immediately!\n" "// either print it out and delete it or move it to a thumbdrive.\n" "// YOU DO NOT NEED THIS FILE TO PLAY AC!\n" "\n"); if(oldfile) for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); f->printf("authsetup pre %s", bin2hex(hextemp, prepriv, preprivlen)); if(preprivpwdcfg) f->printf(" %s %u", bin2hex(hextemp, psalt, 16), preprivpwdcfg & ~1); f->printf("\n\n"); delete f; } DELETEA(oldfile); } } else if(!strcasecmp(args[0], "NEWPASS")) { // authsetup newpass privpass [privatefilename] if(!privpwdcfg) { ed25519_pubkey_from_private(keyhash + 32, priv); if(numargs > 1 && args[1][0]) // new password { entropy_get(salt, 16); int iterations = authrounds; passphrase2key(args[1], salt, 16, keyhash, 32, &iterations, authmaxtime, authmemusage); privpwdcfg = (iterations << 10) | authmemusage; xor_block(priv, keyhash, 32); } const char *fn = numargs > 2 && args[2][0] ? path(args[2]) : AUTHPRIVATECFGFILE; char *oldfile = loadfile(fn, NULL); stream *f = openfile(fn, "wb"); if(f) { if(oldfile) for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); f->printf("\nauthsetup priv %s", bin2hex(hextemp, priv, 32)); if(privpwdcfg) f->printf(" %s %u", bin2hex(hextemp, salt, 16), privpwdcfg); f->printf("\nauthsetup pub %s\n\n", bin2hex(hextemp, keyhash + 32, 32)); delete f; } DELETEA(oldfile); } } else if(!strcasecmp(args[0], "UNARMED")) // FIXME: this is for testing purposes only - to test simultaneous logins from the same account - delete before release! { // authsetup unarmed // pub aa55aab6d746c2000f3f6dc133c49c5d0485e0e44cbf036757e7945bca20106f priv aa55aa55aa55aa55aa55aa55aa55aae5a973aa55aa55aa55aa55aa55aa55aa55 hex2bin(priv, "aa55aa55aa55aa55aa55aa55aa55aae5a973aa55aa55aa55aa55aa55aa55aa55", 32); ed25519_pubkey_from_private(pub, priv); privpwdcfg = 0; } } ed25519_pubkey_from_private(keyhash, priv); if(!privpwdcfg && !memcmp(pub, keyhash, 32)) { // priv matches pub: yay memcpy(priv + 32, pub, 32); sk = priv; if(!numargs) res = 1; // "authsetup" with no arguments just checks the status DEBUG("successfully found keypair for pub " << bin2hex(hextemp, pub, 32)); } else sk = NULL; intret(res); } COMMAND(authsetup, "v"); void mypubkey() { string res = ""; if(sk) bin2hex(res, sk + 32, 32); result(res); } COMMAND(mypubkey, ""); ///////////////////////////////////////////////// misc key management ////////////////////////////////////////////////////// // * used to sign certs or other stuff // * dev/admin/master keys // * clanboss keys // #define AUTHKEYSCFGFILE "config" PATHDIVS "authkeys.cfg" vector authkeys; authkey::authkey(const char *aname, const char *privkey) : name(NULL) { if(*aname && strlen(privkey) == 64 && hex2bin(sk, privkey, 32) == 32) { ed25519_pubkey_from_private(sk + 32, sk); name = newstring(aname); } } bool delauthkey(const char *name) { int before = authkeys.length(); loopvrev(authkeys) if(!strcmp(authkeys[i]->name, name)) delete authkeys.remove(i); return authkeys.length() < before; } const uchar *getauthkey(const char *name) { loopvrev(authkeys) if(!strcmp(authkeys[i]->name, name)) return authkeys[i]->sk; return NULL; } void authkey_(char **args, int numargs) // set up misc keys { if(numargs > 0) { string buf; if(!strcasecmp(args[0], "CLEAR")) { // authkey clear if(authkeys.length()) { char *oldfile = loadfile(AUTHKEYSCFGFILE, NULL); if(oldfile) { stream *f = openfile(AUTHKEYSCFGFILE, "wb"); if(f) { // don't really delete the old keys, just comment them out for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); delete f; } delete[] oldfile; } authkeys.shrink(0); conoutf("all authkeys deleted"); } } else if(!strcasecmp(args[0], "LIST")) { // authkey list if(authkeys.length()) { conoutf("name\tpubkey"); loopv(authkeys) conoutf("%s\t%s", authkeys[i]->name, bin2hex(buf, authkeys[i]->sk + 32, 32)); } else conoutf("no authkeys"); } else if(!strcasecmp(args[0], "DELETE")) { // authkey delete name if(numargs > 1) delauthkey(args[1]); } else if(!strcasecmp(args[0], "NEW")) { // authkey new name if(numargs > 1 && args[1][0]) { uchar prepriv[42], priv[32], pub[32]; delauthkey(args[1]); entropy_get(prepriv, 42); privkey_from_prepriv(priv, prepriv, 42); ed25519_pubkey_from_private(pub, priv); authkey *ak = new authkey(args[1], bin2hex(buf, priv, 32)); if(ak->name) { authkeys.add(ak); stream *f = openfile(AUTHKEYSCFGFILE, "ab"); if(f) { f->printf("// authkey \"%s\", generated %s\n", ak->name, timestring("%c")); f->printf("// prepriv %s\n", bin2hex(buf, prepriv, 42)); f->printf("// priv %s\n", bin2hex(buf, priv, 32)); f->printf("// pub %s\n", bin2hex(buf, pub, 32)); f->printf("authkey add %s %s\n\n", ak->name, bin2hex(buf, priv, 32)); delete f; } conoutf("authkey %s generated.", ak->name); } } } else if(!strcasecmp(args[0], "ADD")) { // authkey add name privkey if(numargs > 2) { delauthkey(args[1]); authkey *ak = new authkey(args[1], args[2]); if(ak->name) authkeys.add(ak); } } else if(!strcasecmp(args[0], "SELFCERT")) { // authkey selfcert name const uchar *sk; if(numargs > 1 && (sk = getauthkey(args[1]))) { defformatstring(cmd)("newcert start\n" "newcert line pubkey %s\n" "newcert line name \"%s\" \"%s\"\n" "newcert sign \"%s\" \"self-signed cert\"\n" "\n", bin2hex(buf, sk + 32, 32), args[1], numargs > 2 ? args[2] : "", args[1]); execute(cmd); } } } } COMMANDN(authkey,authkey_, "v"); #endif ///////////////////////// manage tree of certificates /////////////////////////////////////////////////// #define CERTFILEDIR "config" PATHDIVS "certs" PATHDIVS #define CERTFILEEXT "acc" #define CERTTIMEDAY (24 * 60) #define CERTTIMEMONTH (30 * 24 * 60) // FIXME: replace this for release // pub aa558eb043105536d72a433d8fa121797f134805c526727c43f57cefdaec289b priv aa55aa55aa55aa55aa55aa55aa55aac97034aa55aa55aa55aa55aa55aa55aa55 uchar rootcert[] = { 0xaa, 0x55, 0x8e, 0xb0, 0x43, 0x10, 0x55, 0x36, 0xd7, 0x2a, 0x43, 0x3d, 0x8f, 0xa1, 0x21, 0x79, 0x7f, 0x13, 0x48, 0x05, 0xc5, 0x26, 0x72, 0x7c, 0x43, 0xf5, 0x7c, 0xef, 0xda, 0xec, 0x28, 0x9b }; static const char *certheader = "AC-CERT v1 "; static const int certheaderlen = strlen(certheader), certheaderlenfull = certheaderlen + 128 + 1; static const char *certtypekeywords[] = { "misc", "dev", "master", "server", "player", "clanboss", "clan" }; // needs to match the enums! static int certtypemaxrank[] = { 0, 1, 2, 3, 3, 3, 0 }; // max allowed rank of some cert types static int certtypeexpiration[] = { 0, 0, 0, 2 * CERTTIMEMONTH, CERTTIMEMONTH, 3 * CERTTIMEMONTH, CERTTIMEMONTH }; // time after which certs are usually re-signed, after 3x the time they expire vector certs; hashtable certblacklist; static inline int aktcerttime() { return time(NULL) / (time_t) 60; } // use minutes instead of seconds as timebase, so int will be more than enough bool cert::parse() // parse orgmsg[] and check the signature { isvalid = ischecked = needsrenewal = expired = false; pubkey = signedby = NULL; name = NULL; type = CERT_MISC; DELETEA(workmsg); lines.setsize(0); if(orglen > certheaderlenfull && orglen < (1<<20) && !strncmp(orgmsg, certheader, certheaderlen) && orgmsg[certheaderlen + 128] == '\n') { // frame ok workmsg = new char[orglen + 1]; memcpy(workmsg, orgmsg, orglen + 1); uchar *signature = (uchar*) orgmsg + certheaderlenfull - 64; if(hex2bin(signature, workmsg + certheaderlen, 64) == 64) { // parse all lines certline line; for(char *l = strtok(workmsg + certheaderlenfull, "\n\r"); l; l = strtok(NULL, "\n\r")) { // get comment line.comment = strstr(l, "//"); if(line.comment) { *line.comment = '\0'; line.comment += 2 + strspn(line.comment + 2, " "); trimtrailingwhitespace(line.comment); } trimtrailingwhitespace(l); // get keyword and value, separated by ":" l += strspn(l, " \t"); line.val = strchr(l, ':'); if(line.val && line.val > l) { line.key = l; *line.val++ = '\0'; line.val += strspn(line.val, " \t"); trimtrailingwhitespace(line.key); trimtrailingwhitespace(line.val); if(!strcasecmp(line.key, "signed-by")) { // parse "signed-by" key directly if(!(strlen(line.val) == 64 && (signedby = (uchar*)line.val) && hex2bin(signedby, orgmsg + (line.val - workmsg), 32) == 32)) signedby = NULL; } else if(!strcasecmp(line.key, "signed-date")) { // parse "signed-date" key directly signeddate = atoi(line.val); } else if(!strcasecmp(line.key, "pubkey")) { // parse "pubkey" key directly if(!(strlen(line.val) == 64 && (pubkey = (uchar*)line.val) && hex2bin(pubkey, orgmsg + (line.val - workmsg), 32) == 32)) pubkey = NULL; } else if(!strcasecmp(line.key, "name")) { // parse "name" key directly name = line.val; } else { // add to list lines.add(line); if(!strcasecmp(line.key, "type")) type = getlistindex(line.val, certtypekeywords, false, 0); } } } if(signedby) { // check signature (if signature contained a "signed-by" line) if(ed25519_sign_check(signature, orglen - certheaderlenfull + 64, signedby)) { ischecked = true; if(certtypeexpiration[type]) { int age = aktcerttime() - signeddate; if(age > certtypeexpiration[type]) needsrenewal = true; if(age > 3 * certtypeexpiration[type]) expired = true; } isvalid = !expired; } } } memcpy(orgmsg, workmsg, certheaderlenfull); // restore original message if(ischecked && pubkey && !name) bin2hex((name = workmsg), pubkey, 32); // if cert has no name, use pubkey as name } return ischecked; } struct certline *cert::getline(const char *key) { loopv(lines) if(!strcasecmp(lines[i].key, key)) return &lines[i]; return NULL; } const char *cert::getval(const char *key) { loopv(lines) if(!strcasecmp(lines[i].key, key)) return lines[i].val; return NULL; } char *cert::getcertfilename(const char *subpath) { if(!orgfilename) return NULL; defformatstring(s)(CERTFILEDIR "%s%s%s." CERTFILEEXT, subpath ? subpath : "", subpath ? PATHDIVS : "", orgfilename); return newstring(s); } char *cert::getnewcertfilename(const char *subpath) { string fname; filtertext(fname, name ? name : "", FTXT__CERTFILENAME); defformatstring(s)(CERTFILEDIR "%s%s%s_%s_%d." CERTFILEEXT, subpath ? subpath : "", subpath ? PATHDIVS : "", certtypekeywords[type], fname, signeddate); return newstring(s); } void cert::movecertfile(const char *subpath) { char *ofn = getcertfilename(NULL), *nfn = getcertfilename(subpath); if(subpath && ofn && nfn) backup(ofn, nfn); DELSTRING(ofn); DELSTRING(nfn); } makecert::makecert(int bufsize) { curbuf = buf = new char[1 << bufsize]; endbuf = buf + (1 << bufsize) - 1; c = new cert(NULL); } makecert::~makecert() { DELETEP(c); DELETEA(buf); } void makecert::addline(const char *key, const char *val, const char *com) { if(!com || !*com) com = ""; int keylen = strlen(key), vallen = strlen(val), comlen = strlen(com); if(*key && *val && endbuf - curbuf > keylen + vallen + comlen + 3) { certline &l = c->lines.add(); l.key = curbuf; strcpy(curbuf, key); curbuf += keylen + 1; l.val = curbuf; strcpy(curbuf, val); curbuf += vallen + 1; l.comment = curbuf; strcpy(curbuf, com); curbuf += comlen + 1; // extract name and type for the filename if(!strcasecmp(l.key, "name")) c->name = l.val; else if(!strcasecmp(l.key, "type")) c->type = getlistindex(l.val, certtypekeywords, false, 0); } } char *makecert::sign(const uchar *keypair, const char *com) { char hextemp[129]; vector msg; cvecprintf(msg, "signed-by: %s%s%s\n", bin2hex(hextemp, keypair + 32, 32), com && *com ? " // " : "", com ? com : ""); loopv(c->lines) cvecprintf(msg, "%s: %s%s%s\n", c->lines[i].key, c->lines[i].val, c->lines[i].comment[0] ? " // " : "", c->lines[i].comment); cvecprintf(msg, "signed-date: %d // %s\n", (c->signeddate = aktcerttime()), timestring("%c")); int len = msg.length(); loopi(64) msg.add(0); // make room for the signature uchar *msgbuf = (uchar*)msg.getbuf(); ed25519_sign(msgbuf, NULL, msgbuf, len, keypair); char *signedmsg = new char[certheaderlenfull + len + 1]; sprintf(signedmsg, "%s%s\n", certheader, bin2hex(hextemp, msgbuf, 64)); // header line memcpy(signedmsg + certheaderlenfull, msgbuf + 64, len); // body signedmsg[certheaderlenfull + len] = '\0'; return signedmsg; } #ifndef STANDALONE COMMANDF(newcert, "v", (char **args, int numargs) // create and sign certs from cubescript { static makecert *mc = NULL; if(!mc) mc = new makecert(20); if(numargs > 0) { if(!strcasecmp(args[0], "CLEAR")) { DELETEP(mc); } else if(!strcasecmp(args[0], "LINE")) { // newcert line key val [comment] if(numargs > 2 && args[1][0] && args[2][0]) mc->addline(args[1], args[2], numargs > 3 ? args[3] : ""); } else if(!strcasecmp(args[0], "SIGN")) { // newcert sign privkey|authkeyname [comment] uchar keypair[64]; // 32byte private + 32byte public if(numargs > 1 && ((getauthkey(args[1]) && memcpy(keypair, getauthkey(args[1]), 32)) || (strlen(args[1]) == 64 && hex2bin(keypair, args[1], 32) == 32))) { // sign cert and write to file ed25519_pubkey_from_private(keypair + 32, keypair); char *msg = mc->sign(keypair, numargs > 2 ? args[2] : NULL); char *fname = mc->c->getnewcertfilename(NULL); stream *f = openfile(fname, "wb"); f->write(msg, strlen(msg)); delete f; DELETEP(mc); } } } }); #endif void marksuperseded(cert *chain) // checks all { for(cert *c = chain; c; c = c->next) { if(c->isvalid) for(cert *r = c->next; r; r = r->next) { if(r->isvalid && c->isvalid) { if((c->type == r->type && c->name && r->name && !strcmp(c->name, r->name)) || (c->pubkey && r->pubkey && !memcmp(c->pubkey, r->pubkey, 32))) { // same name and type or same pubkey cert *x = c->signeddate < r->signeddate ? c : r; // older one has to go x->superseded = true; x->isvalid = false; } } } } for(cert *c = chain; c; c = c->next) { // check, if certs lost their parent during the cleanup above if(c->isvalid && c->parent && (!c->parent->isvalid || !c->parent->rank)) c->rank = 0; } } void rebuildcerttree() // determines the rank of all certs; ages them; removes all kinds of illegal certs { int act = aktcerttime(); cert *treechain = NULL; // all nested list searches are done on a chain of relevant certs, to keep complexity from reaching n^2 loopv(certs) // 1st run: cleanup, age, check if signed by root { cert *c = certs[i]; c->parent = c->next = NULL; c->rank = !memcmp(rootcert, c->signedby, 32) ? 1 : 0; c->superseded = false; c->days2expire = c->days2renew = 365; if(certtypeexpiration[c->type]) // age the certs { int age = act - c->signeddate; c->days2renew = certtypeexpiration[c->type] - age; c->days2expire = 3 * certtypeexpiration[c->type] - age; if(c->days2renew < 0) c->needsrenewal = true; if(c->days2expire < 0) { c->expired = true; c->isvalid = false; } } if(c->pubkey && certblacklist.access(c->pubkey)) { c->blacklisted = true; c->isvalid = false; } if(c->isvalid && c->rank) { c->next = treechain; treechain = c; } } marksuperseded(treechain); loopk(200) { bool onemoreround = false; loopv(certs) // build tree, rank by rank { cert *c = certs[i]; if(c->isvalid && !c->rank) { for(cert *r = treechain; r; r = r->next) { if(r->rank && r->isvalid && r->pubkey && !memcmp(r->pubkey, c->signedby, 32)) { c->rank = r->rank + 1; if(c->rank > certtypemaxrank[c->type]) { // maxrank exceeded (no, you can not sign your own master cert...) c->isvalid = false; c->illegal = true; } else { // include cert into tree c->parent = r; c->next = treechain; treechain = c; if(c->pubkey) onemoreround = true; // more public keys in the tree -> need to check the remaining certs again } break; } } } } marksuperseded(treechain); if(!onemoreround) break; } // tree itself is now complete and valid - clean up the rest a bit treechain = NULL; loopv(certs) { cert *c = certs[i]; if(c->isvalid && !c->rank) { c->next = treechain; treechain = c; } } marksuperseded(treechain); // of the valid certs that are not in the tree: remove outdated ones, where newer versions exist loopv(certs) // move files of invalid certs to subdirectories { cert *c = certs[i]; if(!c->isvalid) { if(c->superseded) c->movecertfile("superseded"); else if(c->blacklisted) c->movecertfile("blacklisted"); else if(c->illegal) c->movecertfile("illegal"); else if(c->expired) {}// c->movecertfile("expired"); else c->movecertfile("invalid2"); } } } void loadcert(const char *fname) { cert *c = new cert(fname); if(c->ischecked && c->name) { certs.add(c); // certs in "certs" are required to have a name DEBUG("loaded cert " << c->name << ", type " << certtypekeywords[c->type] << ", signed-date " << c->signeddate); } else delete c; } void loadcertdir() // load all certs in "config/certs" { vector certfiles; listfiles(CERTFILEDIR, CERTFILEEXT, certfiles); loopv(certfiles) { loadcert(certfiles[i]); delstring(certfiles[i]); } rebuildcerttree(); } #ifndef STANDALONE void listcerts() { loopv(certs) { cert *c = certs[i]; conoutf("certs[%d]: %16s, t:%s, r:%d, l:%d, sd:%d, d2e:%d, d2r:%d, parent:%d%s%s%s%s%s%s%s", i, c->name, certtypekeywords[c->type], c->rank, c->lines.length(), c->signeddate, c->days2expire, c->days2renew, certs.find(c->parent), c->ischecked ? ", ischecked" : "", c->isvalid ? ", isvalid" : "", c->needsrenewal ? ", needsrenewal" : "", c->expired ? ", expired" : "", c->superseded ? ", superseded" : "", c->blacklisted ? ", blacklisted" : "", c->illegal ? ", illegal" : ""); } } COMMAND(listcerts, ""); #endif /////////////////// misc helper functions //////////////////////////////////////////////////////////////// static const char *hashchunktoa(tiger::chunk h) // portable solution instead of printf("%llx") { // use next protocol bump to switch to hashstring() above! static string buf; static int bufidx; bufidx = (bufidx + 1) & 0x3; char *s = buf + (bufidx * 33) + 33; *s-- = '\0'; while(h) { *s-- = "0123456789abcdef"[h & 0xf]; h >>= 4; } return s + 1; } const char *genpwdhash(const char *name, const char *pwd, int salt) { static string temp; formatstring(temp)("%s %d %s %s %d", pwd, salt, name, pwd, iabs(PROTOCOL_VERSION)); tiger::hashval hash; tiger::hash((uchar *)temp, (int)strlen(temp), hash); formatstring(temp)("%s %s %s", hashchunktoa(hash.chunks[0]), hashchunktoa(hash.chunks[1]), hashchunktoa(hash.chunks[2])); return temp; }