Files
assaultcube-/source/src/crypto.cpp
T

1550 lines
57 KiB
C++

#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 uint64_t chunk;
union hashval
{
uchar bytes[3*8];
chunk chunks[3];
};
chunk sboxes[4*256];
void compress(const chunk *str, chunk state[3])
{
ASSERT(sizeof(chunk) == 8);
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 = lilswap(str[0]); x1 = lilswap(str[1]); x2 = lilswap(str[2]); x3 = lilswap(str[3]);
x4 = lilswap(str[4]); x5 = lilswap(str[5]); x6 = lilswap(str[6]); x7 = lilswap(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 };
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((const chunk *)str, 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;
}
}
}
struct incremental_buffer { int len, total; union { uchar u[64]; chunk c[8]; }; incremental_buffer() { len = total = 0; } };
incremental_buffer *hash_init(hashval &val)
{
static bool init = false;
if(!init) { gensboxes(); init = true; }
val.chunks[0] = 0x0123456789ABCDEFULL;
val.chunks[1] = 0xFEDCBA9876543210ULL;
val.chunks[2] = 0xF096A5B4C3B2E187ULL;
return new incremental_buffer;
}
void hash_incremental(const uchar *msg, int len, hashval &val, incremental_buffer *b)
{
ASSERT(b && b->len >= 0 && b->len < 64);
b->total += len;
while(b->len + len >= 64)
{
if(b->len > 0)
{ // fill up buffer and compress
int fill = 64 - b->len;
memcpy(b->u + b->len, msg, fill);
compress(b->c, val.chunks);
b->len = 0;
len -= fill;
msg += fill;
}
else
{ // compress directly from msg
compress((chunk *)msg, val.chunks);
len -= 64;
msg += 64;
}
}
if(len > 0)
{ // pur rest in buffer
memcpy(b->u + b->len, msg, len);
b->len += len;
}
}
void hash_finish(hashval &val, incremental_buffer *b)
{
ASSERT(b && b->len >= 0 && b->len < 64);
memset(b->u + b->len, 0, 64 - b->len);
b->u[b->len] = 0x01;
if(b->len >= 56)
{
compress(b->c, val.chunks);
memset(b->u, 0, 64);
b->len = 0;
}
b->c[7] = lilswap(chunk(b->total << 3));
compress(b->c, val.chunks);
lilswap(val.chunks, 3);
delete b;
}
void hash(const uchar *str, int length, hashval &val)
{
incremental_buffer *b = hash_init(val);
int i = length;
for(; i >= 64; i -= 64, str += 64)
{
compress((chunk *)str, val.chunks);
}
memcpy(b->u, str, i);
b->len = i;
b->total = length;
hash_finish(val, b);
}
}
void tigerhash(uchar *hash, const uchar *msg, int len)
{
tiger::hash(msg, len, *((tiger::hashval *) hash));
}
void *tigerhash_init(uchar *hash)
{
return (void *)tiger::hash_init(*((tiger::hashval *) hash));
}
void tigerhash_add(uchar *hash, const void *msg, int len, void *state)
{
tiger::hash_incremental((const uchar *)msg, len, *((tiger::hashval *) hash), (tiger::incremental_buffer *)state);
}
void tigerhash_finish(uchar *hash, void *state)
{
if(hash) tiger::hash_finish(*((tiger::hashval *) hash), (tiger::incremental_buffer *)state);
else delete (tiger::incremental_buffer *)state;
}
#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);
void *s = tigerhash_init((uchar *)&hash);
tigerhash_add((uchar *)&hash, msg, (int)strlen(msg), s);
tigerhash_finish((uchar *)&hash, s);
defformatstring(erg)("TIGER: ");
loopi(24) concatformatstring(erg, "%02x", hash.bytes[i]);
conoutf("%s", erg);
});
#endif
#endif
#ifdef _DEBUG
int __hashtest() // crash, if hash functions don't work properly - no one ever tested the big endian code
{
const char *msg = "The quick brown fox jumps over the lazy dog";
const uchar ht[] = { 0x6d, 0x12, 0xa4, 0x1e, 0x72, 0xe6, 0x44, 0xf0, 0x17, 0xb6, 0xf0, 0xe2, 0xf7, 0xb4, 0x4c, 0x62, 0x85, 0xf0, 0x6d, 0xd5, 0xd2, 0xc5, 0xb0, 0x75 };
const uchar hs[] = { 0x07, 0xe5, 0x47, 0xd9, 0x58, 0x6f, 0x6a, 0x73, 0xf7, 0x3f, 0xba, 0xc0, 0x43, 0x5e, 0xd7, 0x69, 0x51, 0x21, 0x8f, 0xb7, 0xd0, 0xc8, 0xd7, 0x88, 0xa3, 0x09, 0xd7, 0x85, 0x43, 0x6b, 0xbb, 0x64,
0x2e, 0x93, 0xa2, 0x52, 0xa9, 0x54, 0xf2, 0x39, 0x12, 0x54, 0x7d, 0x1e, 0x8a, 0x3b, 0x5e, 0xd6, 0xe1, 0xbf, 0xd7, 0x09, 0x78, 0x21, 0x23, 0x3f, 0xa0, 0x53, 0x8f, 0x3d, 0xb8, 0x54, 0xfe, 0xe6 };
tiger::hashval hash;
tiger::hash((uchar *)msg, (int)strlen(msg), hash);
uchar shash[SHA512SIZE];
sha512(shash, (uchar*)msg, strlen(msg));
int res = memcmp(hash.bytes, ht, TIGERHASHSIZE) | memcmp(shash, hs, SHA512SIZE);
ASSERT(!res);
return res;
}
static int __ht = __hashtest();
#endif
////////////////////////// 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 nxt = (mt_next + 1) % MT_N, cur = (nxt + MT_N -1) % MT_N;
if(mt_next >= MT_N) seedMT(5489U + time(NULL));
mt_next = nxt;
uint y = (mt_state[cur] & 0x80000000U) | (mt_state[nxt] & 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 "private" 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);
#ifndef STANDALONE
else
{
extern int bootstrapentropy;
bootstrapentropy += 7 + rnd(13);
clientlogf(ENTROPYSAVEFILE " not found.");
}
#endif
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), *b;
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_r(vectorfile, "\n", &b); l; l = strtok_r(NULL, "\n", &b))
{ // 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 "private" PATHDIVS "authpreprivate.cfg"
#define AUTHPRIVATECFGFILE "private" 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)
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 savepreprivkey(const char *filename, uchar *preprivkey, uchar preprivlen, uchar *psalt, uint preprivpwdcfg) {
char hextemp[128 * 2 + 1];
char *oldfile = loadfile(filename, NULL), *b;
stream *f = openfile(filename, "wb");
if(f)
{
if(!oldfile)
{
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");
}
else
{
for(char *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b))
{
if(*l && l[0] != '/')
f->printf("// %s\n", l);
if(*l && l[0] == '/')
f->printf("%s\n", l);
}
}
f->printf("authsetup pre %s", bin2hex(hextemp, preprivkey, preprivlen));
if(preprivpwdcfg) f->printf(" %s %u", bin2hex(hextemp, psalt, 16), preprivpwdcfg & ~1);
f->printf("\n\n");
delete f;
}
DELETEA(oldfile);
}
void saveprivkey(const char *filename, uchar *privkey, uchar *pubkey, uchar salt[16], uint privpwdcfg) {
char hextemp[65];
char *oldfile = loadfile(filename, NULL), *b;
stream *f = openfile(filename, "wb");
if(f)
{
if(oldfile)
{
for(char *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b))
{
if(*l && l[0] != '/')
f->printf("// %s\n", l);
if(*l && l[0] == '/')
f->printf("%s\n", l);
}
}
f->printf("\nauthsetup priv %s", bin2hex(hextemp, privkey, 32));
if(privpwdcfg) f->printf(" %s %u", bin2hex(hextemp, salt, 16), privpwdcfg);
f->printf("\nauthsetup pub %s\n\n", bin2hex(hextemp, pubkey, 32));
delete f;
}
DELETEA(oldfile);
}
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;
#ifdef _DEBUG
char hextemp[2 * preprivmaxlen + 1];
#endif
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);
}
}
savepreprivkey(numargs > 2 && args[2][0] ? path(args[2]) : AUTHPREPRIVATECFGFILE,
prepriv, preprivlen, psalt, preprivpwdcfg);
}
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);
}
saveprivkey(numargs > 2 && args[2][0] ? path(args[2]) : AUTHPRIVATECFGFILE,
priv, keyhash + 32, salt, privpwdcfg);
}
}
else if(!strcasecmp(args[0], "SAVEPRIV"))
{
ed25519_pubkey_from_private(keyhash + 32, priv);
saveprivkey(numargs > 1 && args[1][0] ? path(args[1]) : AUTHPRIVATECFGFILE,
priv, keyhash + 32, salt, privpwdcfg);
}
else if(!strcasecmp(args[0], "SAVEPRE"))
{
savepreprivkey(numargs > 1 && args[1][0] ? path(args[1 ]) : AUTHPREPRIVATECFGFILE,
prepriv, preprivlen, psalt, preprivpwdcfg);
}
#ifndef PRODUCTION
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;
}
#endif
}
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<authkey *> 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 *b, *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) 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
{
delete ak;
}
}
}
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 delete 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<cert *> certs;
hashtable<const uchar32, char> 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 *b, *l = strtok_r(workmsg + certheaderlenfull, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b))
{
// 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<char> 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(*((uchar32*)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<char *> 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;
}