From 381e7702103401d621877d869ecfa6b447fafe6d Mon Sep 17 00:00:00 2001 From: ac-stef Date: Thu, 8 Jan 2015 10:58:37 +0100 Subject: [PATCH] add authsetup command authsetup contains all the funtionality needed to generate, load and save private and public keys, and to lock and unlock the private key with a password. Private and public keys are used to authenticate the player to servers, and are loaded from config/authprivate.cfg. Private keys can be stored in an "encrypted with password" version - in which case the player has to enter the password after game start. To prevent brute-force cracking of the password, the key-derivation function uses a constant time to encode (usually several seconds). Because of that, the decoding at game start can be done in the background. The private key is actually itself a public key generated from the prepriv key. The prepriv key should be kept away from the computer, either as hardcopy or on a thumbdrive. The prepriv key is not required to play the game. It can be used to prove ownership of the private key, in case the private key gets stolen. It can also be used to regenerate private and public keys, in case they are lost. The prepriv key can also be encrypted by its own password. authsetup checks private and public key and returns "1", if they match. authsetup pre preprivhex [psalthex pwdcfg] loads prepriv key into memory. optionally supports encrypted keys. authsetup priv privhex [salthex pwdcfg] loads private key into memory. optionally supports encrypted keys. authsetup pub pubhex loads public key into memory. authsetup ppass preprivpass decrypts prepriv key with password authsetup pass privpass decrypts private key with password authsetup passd privpass [commandwhendone] decrypts private key with password in background. optionally executes cubescript command when done. authsetup needpass returns "1" if the private key needs decryption. authsetup genpre [prelen] generates new prepriv key. authsetup genpriv generates private key from prepriv key. authsetup genpub generates public key from private key. authsetup newppass preprivpass [preprivfilename] encrypts prepriv key with new password and saves the result to file. The default filename is config/authpreprivate.cfg and an existing file gets overwritten - but the former file content is kept and also written, each line commented out. authsetup newpass privpass [privatefilename] encrypts private key with new password and saves the encrypted private key and the public key to file. The default filename is config/authprivate.cfg and an existing file gets overwritten - but the former file content is kept and also written, each line commented out. authsetup unarmed sets private and public keys to a fixed value. For testing only. Can be used to "log out". authmemusage, authrounds, authmaxtime are only used, when new passwords are created (authsetup new[p]pass). authmemusage is the number of megabytes of RAM that are used for the password hash calculation. If authrounds is zero (as is default), then the hash algorithm calculates as many rounds as possible in the number of milliseconds specified in authmaxtime. If authrounds is a positive number, then that is the number of rounds to be done, regardless the required time. Note that, since the password hash algorithm is not endianness-aware, it is not possible to move an encrypted password to a machine with different endianness. Moving between 32- and 64-bit machines should be no problem. FIXME: the command is not blocked against use in multiplayer. It has to be determined later, if this is a problem or feature. Especially the background password calculation may be a problem. DOCREF authsetup cryptodebug authmemusage authrounds authmaxtime --- .gitignore | 2 + source/src/crypto.cpp | 241 +++++++++++++++++++++++++++++++++++++++++- source/src/main.cpp | 1 + 3 files changed, 240 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 68ce0b65..ad5fff61 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ shadows.dat bin_unix/native_client bin_unix/native_server +config/authpreprivate.cfg +config/authprivate.cfg config/history config/init.cfg config/saved*.cfg diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index c0143a6a..1f9ddfba 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -1,5 +1,12 @@ #include "cube.h" +#ifdef STANDALONE +#define DEBUGCOND (true) +#else +VARP(cryptodebug, 0, 0, 1); +#define DEBUGCOND (cryptodebug==1) +#endif + static const char *hexdigits = "0123456789abcdef"; @@ -405,14 +412,16 @@ void passphrase2key(const char *pass, const uchar *salt, int saltlen, uchar *key { memset(key, 0, keylen); if(!*pass) return; // password "" -> - memusage = clamp(memusage, 1, 1024); // keep memory usage between 1MB and 1GB - int memsize = (1<<20) * memusage, passlen = strlen(pass), tmpbuflen = 2 * SHA512SIZE, pplen = 2 * saltlen + passlen; + 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, salt, saltlen); + 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)]; @@ -448,7 +457,7 @@ void passphrase2key(const char *pass, const uchar *salt, int saltlen, uchar *key 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 -conoutf("passphrase2key: %d ms, %d rounds, %d bytes used", watch.elapsed(), roundsdone, memsize); + DEBUG("passphrase2key: " << watch.elapsed() << " ms, " << roundsdone << " rounds, " << memsize << " bytes used"); *iterations = roundsdone; delete[] hugebuf; delete[] tmpbuf; @@ -644,6 +653,230 @@ COMMAND(ed25519speedtest, ""); #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"); +#endif + + + diff --git a/source/src/main.cpp b/source/src/main.cpp index a43b736f..e5a898fa 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -1283,6 +1283,7 @@ int main(int argc, char **argv) exec("config/admin.cfg"); execfile("config/servers.cfg"); execfile("config/pcksources.cfg"); + execfile("config/authprivate.cfg"); loadhistory(); setupautodownload(); int xmn = loadallxmaps();