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
This commit is contained in:
+237
-4
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user