From 1ea860909ea670451b5200ccd30aa19241cd081a Mon Sep 17 00:00:00 2001 From: divverent Date: Fri, 15 Oct 2010 13:47:19 +0000 Subject: [PATCH] Cryptographic authentication support for the d0_blind_id library available on http://github.com/divVerent/d0_blind_id git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@10534 d7cf8633-e32d-0410-b094-e92efae38249 --- client.h | 3 + crypto-keygen-standalone-brute.sh | 53 + crypto-keygen-standalone.c | 578 +++++++ crypto.c | 2359 +++++++++++++++++++++++++++++ crypto.h | 152 ++ dpdefs/dpextensions.qc | 12 + dpdefs/menudefs.qc | 11 + fs.c | 3 +- fs.h | 5 +- hmac.c | 6 +- hmac.h | 7 +- host.c | 5 + makefile.inc | 17 +- mdfour.c | 8 +- mdfour.h | 4 +- menu.c | 2 +- menu.h | 2 +- mvm_cmds.c | 99 ++ netconn.c | 256 +++- netconn.h | 13 + progsvm.h | 6 + prvm_edict.c | 6 + quakedef.h | 3 + server.h | 1 - sv_main.c | 47 + svvm_cmds.c | 1 + 26 files changed, 3598 insertions(+), 61 deletions(-) create mode 100755 crypto-keygen-standalone-brute.sh create mode 100644 crypto-keygen-standalone.c create mode 100644 crypto.c create mode 100644 crypto.h diff --git a/client.h b/client.h index 2ab8079c..f840b331 100644 --- a/client.h +++ b/client.h @@ -715,6 +715,9 @@ typedef struct client_static_s // video capture stuff capturevideostate_t capturevideo; + + // crypto channel + crypto_t crypto; } client_static_t; diff --git a/crypto-keygen-standalone-brute.sh b/crypto-keygen-standalone-brute.sh new file mode 100755 index 00000000..a081b068 --- /dev/null +++ b/crypto-keygen-standalone-brute.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +outfile=$1; shift +hosts=$1; shift + +on() +{ + case "$1" in + localhost) + shift + exec "$@" + ;; + *) + exec ssh "$@" + ;; + esac +} + +pids= +mainpid=$$ +trap 'kill $pids' EXIT +trap 'exit 1' INT USR1 + +n=0 +for h in $hosts; do + nn=`on "$h" cat /proc/cpuinfo | grep -c '^processor[ :]'` + n=$(($nn + $n)) +done + +rm -f bruteforce-* +i=0 +for h in $hosts; do + nn=`on "$h" cat /proc/cpuinfo | grep -c '^processor[ :]'` + ii=$(($nn + $i)) + while [ $i -lt $ii ]; do + i=$(($i+1)) + ( + on "$h" ./crypto-keygen-standalone -n $n -o /dev/stdout "$@" > bruteforce-$i & + pid=$! + trap 'kill $pid' TERM + wait + if [ -s "bruteforce-$i" ]; then + trap - TERM + mv "bruteforce-$i" "$outfile" + kill -USR1 $mainpid + else + rm -f "bruteforce-$i" + fi + ) & + pids="$pids $!" + done +done +wait diff --git a/crypto-keygen-standalone.c b/crypto-keygen-standalone.c new file mode 100644 index 00000000..c43ce0b4 --- /dev/null +++ b/crypto-keygen-standalone.c @@ -0,0 +1,578 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// BEGIN stuff shared with crypto.c +#define FOURCC_D0PK (('d' << 0) | ('0' << 8) | ('p' << 16) | ('k' << 24)) +#define FOURCC_D0SK (('d' << 0) | ('0' << 8) | ('s' << 16) | ('k' << 24)) +#define FOURCC_D0PI (('d' << 0) | ('0' << 8) | ('p' << 16) | ('i' << 24)) +#define FOURCC_D0SI (('d' << 0) | ('0' << 8) | ('s' << 16) | ('i' << 24)) +#define FOURCC_D0IQ (('d' << 0) | ('0' << 8) | ('i' << 16) | ('q' << 24)) +#define FOURCC_D0IR (('d' << 0) | ('0' << 8) | ('i' << 16) | ('r' << 24)) +#define FOURCC_D0ER (('d' << 0) | ('0' << 8) | ('e' << 16) | ('r' << 24)) +#define FOURCC_D0IC (('d' << 0) | ('0' << 8) | ('i' << 16) | ('c' << 24)) + +static unsigned long Crypto_LittleLong(const char *data) +{ + return + ((unsigned char) data[0]) | + (((unsigned char) data[1]) << 8) | + (((unsigned char) data[2]) << 16) | + (((unsigned char) data[3]) << 24); +} + +static void Crypto_UnLittleLong(char *data, unsigned long l) +{ + data[0] = l & 0xFF; + data[1] = (l >> 8) & 0xFF; + data[2] = (l >> 16) & 0xFF; + data[3] = (l >> 24) & 0xFF; +} + +static size_t Crypto_ParsePack(const char *buf, size_t len, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + if(Crypto_LittleLong(buf) != header) + return 0; + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 > len) + return 0; + lumpsize[i] = Crypto_LittleLong(&buf[pos]); + pos += 4; + if(pos + lumpsize[i] > len) + return 0; + lumps[i] = &buf[pos]; + pos += lumpsize[i]; + } + return pos; +} + +static size_t Crypto_UnParsePack(char *buf, size_t len, unsigned long header, const char *const *lumps, const size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + Crypto_UnLittleLong(buf, header); + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 + lumpsize[i] > len) + return 0; + Crypto_UnLittleLong(&buf[pos], lumpsize[i]); + pos += 4; + memcpy(&buf[pos], lumps[i], lumpsize[i]); + pos += lumpsize[i]; + } + return pos; +} + +void file2lumps(const char *fn, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) +{ + FILE *f; + char buf[65536]; + size_t n; + f = fopen(fn, "rb"); + if(!f) + { + fprintf(stderr, "could not open %s\n", fn); + exit(1); + } + n = fread(buf, 1, sizeof(buf), f); + fclose(f); + if(!Crypto_ParsePack(buf, n, header, lumps, lumpsize, nlumps)) + { + fprintf(stderr, "could not parse %s as %c%c%c%c (%d lumps expected)\n", fn, (int) header & 0xFF, (int) (header >> 8) & 0xFF, (int) (header >> 16) & 0xFF, (int) (header >> 24) & 0xFF, (int) nlumps); + exit(1); + } +} + +mode_t umask_save; +void lumps2file(const char *fn, unsigned long header, const char *const *lumps, size_t *lumpsize, size_t nlumps, D0_BOOL private) +{ + FILE *f; + char buf[65536]; + size_t n; + if(private) + umask(umask_save | 0077); + else + umask(umask_save); + f = fopen(fn, "wb"); + if(!f) + { + fprintf(stderr, "could not open %s\n", fn); + exit(1); + } + if(!(n = Crypto_UnParsePack(buf, sizeof(buf), header, lumps, lumpsize, nlumps))) + { + fprintf(stderr, "could not unparse for %s\n", fn); + exit(1); + } + n = fwrite(buf, n, 1, f); + if(fclose(f) || !n) + { + fprintf(stderr, "could not write %s\n", fn); + exit(1); + } +} + +void USAGE(const char *me) +{ + printf("Usage:\n" + "%s [-F] [-b bits] [-n progress-denominator] [-x prefix] [-X infix] [-C] -o private.d0sk\n" + "%s -P private.d0sk -o public.d0pk\n" + "%s [-n progress-denominator] [-x prefix] [-X infix] [-C] -p public.d0pk -o idkey-unsigned.d0si\n" + "%s -p public.d0pk -I idkey-unsigned.d0si -o request.d0iq -O camouflage.d0ic\n" + "%s -P private.d0sk -j request.d0iq -o response.d0ir\n" + "%s -p public.d0pk -I idkey-unsigned.d0si -c camouflage.d0ic -J response.d0ir -o idkey.d0si\n" + "%s -P private.d0sk -I idkey-unsigned.d0si -o idkey.d0si\n" + "%s -I idkey.d0si -o id.d0pi\n" + "%s -p public.d0pk\n" + "%s -P private.d0sk\n" + "%s -p public.d0pk -i id.d0pi\n" + "%s -p public.d0pk -I idkey.d0si\n", + me, me, me, me, me, me, me, me, me, me, me, me + ); +} + +unsigned int seconds; +unsigned int generated; +unsigned int ntasks = 1; +double generated_offset; +double guesscount; +double guessfactor; +void print_generated(int signo) +{ + (void) signo; + ++seconds; + if(generated >= 1000000000) + { + generated_offset += generated; + generated = 0; + } + fprintf(stderr, "Generated: %.0f (about %.0f, %.1f/s, about %.2f hours for %.0f)\n", + // nasty and dishonest hack: + // we are adjusting the values "back", so the total count is + // divided by guessfactor (as the check function is called + // guessfactor as often as it would be if no fastreject were + // done) + // so the values indicate the relative speed of fastreject vs + // normal! + (generated + generated_offset) / guessfactor, + (generated + generated_offset) * ntasks / guessfactor, + (generated + generated_offset) * ntasks / (guessfactor * seconds), + guesscount * ((guessfactor * seconds) / (generated + generated_offset) / ntasks) / 3600.0, + guesscount); + alarm(1); +} + +#define CHECK(x) if(!(x)) { fprintf(stderr, "error exit: error returned by %s\n", #x); exit(2); } + +const char *prefix = NULL, *infix = NULL; +size_t prefixlen = 0; +int ignorecase; +typedef D0_BOOL (*fingerprint_func) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +D0_BOOL fastreject(const d0_blind_id_t *ctx, void *pass) +{ + static char fp64[513]; size_t fp64size = 512; + CHECK(((fingerprint_func) pass)(ctx, fp64, &fp64size)); + ++generated; + if(ignorecase) + { + if(prefixlen) + if(strncasecmp(fp64, prefix, prefixlen)) + return 1; + if(infix) + { + fp64[fp64size] = 0; + if(!strcasestr(fp64, infix)) + return 1; + } + } + else + { + if(prefixlen) + if(memcmp(fp64, prefix, prefixlen)) + return 1; + if(infix) + { + fp64[fp64size] = 0; + if(!strstr(fp64, infix)) + return 1; + } + } + return 0; +} + +int main(int argc, char **argv) +{ + int opt; + size_t lumpsize[2]; + const char *lumps[2]; + char lumps_w0[65536]; + char lumps_w1[65536]; + const char *pubkeyfile = NULL, *privkeyfile = NULL, *pubidfile = NULL, *prividfile = NULL, *idreqfile = NULL, *idresfile = NULL, *outfile = NULL, *outfile2 = NULL, *camouflagefile = NULL; + char fp64[513]; size_t fp64size = 512; + int mask = 0; + int bits = 1024; + int i; + D0_BOOL do_fastreject = 1; + d0_blind_id_t *ctx; + if(!d0_blind_id_INITIALIZE()) + { + fprintf(stderr, "could not initialize\n"); + exit(1); + } + + umask_save = umask(0022); + + ctx = d0_blind_id_new(); + while((opt = getopt(argc, argv, "p:P:i:I:j:J:o:O:c:b:x:X:y:Fn:C")) != -1) + { + switch(opt) + { + case 'C': + ignorecase = 1; + break; + case 'n': + ntasks = atoi(optarg); + break; + case 'b': + bits = atoi(optarg); + break; + case 'p': // d0pk = + pubkeyfile = optarg; + mask |= 1; + break; + case 'P': // d0sk = + privkeyfile = optarg; + mask |= 2; + break; + case 'i': // d0pi = + pubidfile = optarg; + mask |= 4; + break; + case 'I': // d0si = + prividfile = optarg; + mask |= 8; + break; + case 'j': // d0iq = + idreqfile = optarg; + mask |= 0x10; + break; + case 'J': // d0ir = + idresfile = optarg; + mask |= 0x20; + break; + case 'o': + outfile = optarg; + mask |= 0x40; + break; + case 'O': + outfile2 = optarg; + mask |= 0x80; + break; + case 'c': + camouflagefile = optarg; + mask |= 0x100; + break; + case 'x': + prefix = optarg; + prefixlen = strlen(prefix); + break; + case 'X': + infix = optarg; + break; + case 'F': + do_fastreject = 0; + break; + default: + USAGE(*argv); + return 1; + } + } + + // fastreject is a slight slowdown when rejecting nothing at all + if(!infix && !prefixlen) + do_fastreject = 0; + + guesscount = pow(64.0, prefixlen); + if(infix) + guesscount /= (1 - pow(1 - pow(1/64.0, strlen(infix)), 44 - prefixlen - strlen(infix))); + // 44 chars; prefix is assumed to not match the infix (although it theoretically could) + // 43'th char however is always '=' and does not count + if(ignorecase) + { + if(infix) + for(i = 0; infix[i]; ++i) + if(toupper(infix[i]) != tolower(infix[i])) + guesscount /= 2; + for(i = 0; i < prefixlen; ++i) + if(toupper(prefix[i]) != tolower(prefix[i])) + guesscount /= 2; + } + + if(do_fastreject) + { + // fastreject: reject function gets called about log(2^bits) times more often + guessfactor = bits * log(2) / 2; + // so guess function gets called guesscount * guessfactor times, and it tests as many valid keys as guesscount + } + + if(mask & 1) + { + file2lumps(pubkeyfile, FOURCC_D0PK, lumps, lumpsize, 2); + if(!d0_blind_id_read_public_key(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode public key\n"); + exit(1); + } + if(!d0_blind_id_read_private_id_modulus(ctx, lumps[1], lumpsize[1])) + { + fprintf(stderr, "could not decode modulus\n"); + exit(1); + } + } + else if(mask & 2) + { + file2lumps(privkeyfile, FOURCC_D0SK, lumps, lumpsize, 2); + if(!d0_blind_id_read_private_key(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode private key\n"); + exit(1); + } + if(!d0_blind_id_read_private_id_modulus(ctx, lumps[1], lumpsize[1])) + { + fprintf(stderr, "could not decode modulus\n"); + exit(1); + } + } + + if(mask & 4) + { + file2lumps(pubidfile, FOURCC_D0PI, lumps, lumpsize, 1); + if(!d0_blind_id_read_public_id(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode public ID\n"); + exit(1); + } + } + if(mask & 8) + { + file2lumps(prividfile, FOURCC_D0SI, lumps, lumpsize, 1); + if(!d0_blind_id_read_private_id(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode private ID\n"); + exit(1); + } + } + + if(mask & 0x10) + { + file2lumps(idreqfile, FOURCC_D0IQ, lumps, lumpsize, 1); + lumpsize[1] = sizeof(lumps_w1); + lumps[1] = lumps_w1; + if(!d0_blind_id_answer_private_id_request(ctx, lumps[0], lumpsize[0], lumps_w1, &lumpsize[1])) + { + fprintf(stderr, "could not answer private ID request\n"); + exit(1); + } + } + else if((mask & 0x120) == 0x120) + { + file2lumps(camouflagefile, FOURCC_D0IC, lumps, lumpsize, 1); + if(!d0_blind_id_read_private_id_request_camouflage(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode camouflage\n"); + exit(1); + } + + file2lumps(idresfile, FOURCC_D0IR, lumps, lumpsize, 1); + if(!d0_blind_id_finish_private_id_request(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not finish private ID request\n"); + exit(1); + } + } + + switch(mask) + { + // modes of operation: + case 0x40: + // nothing -> private key file (incl modulus), print fingerprint + generated = 0; + generated_offset = 0; + seconds = 0; + signal(SIGALRM, print_generated); + alarm(1); + if(do_fastreject) + { + CHECK(d0_blind_id_generate_private_key_fastreject(ctx, bits, fastreject, d0_blind_id_fingerprint64_public_key)); + } + else + { + guessfactor = 1; // no fastreject here + do + { + CHECK(d0_blind_id_generate_private_key(ctx, bits)); + } + while(fastreject(ctx, d0_blind_id_fingerprint64_public_key)); + } + alarm(0); + signal(SIGALRM, NULL); + CHECK(d0_blind_id_generate_private_id_modulus(ctx)); + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + lumps[1] = lumps_w1; + lumpsize[1] = sizeof(lumps_w1); + CHECK(d0_blind_id_write_private_key(ctx, lumps_w0, &lumpsize[0])); + CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); + lumps2file(outfile, FOURCC_D0SK, lumps, lumpsize, 2, 1); + break; + case 0x42: + // private key file -> public key file (incl modulus) + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + lumps[1] = lumps_w1; + lumpsize[1] = sizeof(lumps_w1); + CHECK(d0_blind_id_write_public_key(ctx, lumps_w0, &lumpsize[0])); + CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); + lumps2file(outfile, FOURCC_D0PK, lumps, lumpsize, 2, 0); + break; + case 0x41: + // public key file -> unsigned private ID file + generated = 0; + generated_offset = 0; + seconds = 0; + signal(SIGALRM, print_generated); + alarm(1); + guessfactor = 1; // no fastreject here + do + { + CHECK(d0_blind_id_generate_private_id_start(ctx)); + } + while(fastreject(ctx, d0_blind_id_fingerprint64_public_id)); + alarm(0); + signal(SIGALRM, 0); + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); + break; + case 0xC9: + // public key file, unsigned private ID file -> ID request file and camouflage file + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_generate_private_id_request(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0IQ, lumps, lumpsize, 1, 0); + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id_request_camouflage(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile2, FOURCC_D0IC, lumps, lumpsize, 1, 1); + break; + case 0x52: + // private key file, ID request file -> ID response file + lumps2file(outfile, FOURCC_D0IR, lumps+1, lumpsize+1, 1, 0); + break; + case 0x169: + // public key file, ID response file, private ID file -> signed private ID file + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); + break; + case 0x4A: + // private key file, private ID file -> signed private ID file + { + char buf[65536]; size_t bufsize; + char buf2[65536]; size_t buf2size; + D0_BOOL status; + d0_blind_id_t *ctx2 = d0_blind_id_new(); + CHECK(d0_blind_id_copy(ctx2, ctx)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status)); + CHECK(status == 0); + CHECK(d0_blind_id_authenticate_with_private_id_generate_missing_signature(ctx2)); + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id(ctx2, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); + } + break; + case 0x48: + // private ID file -> public ID file + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_public_id(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0PI, lumps, lumpsize, 1, 0); + break; + case 0x01: + case 0x02: + // public/private key file -> fingerprint + CHECK(d0_blind_id_fingerprint64_public_key(ctx, fp64, &fp64size)); + printf("%.*s\n", (int)fp64size, fp64); + break; + case 0x05: + case 0x09: + // public/private ID file -> fingerprint + CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); + printf("%.*s\n", (int)fp64size, fp64); + break; +/* + case 0x09: + // public key, private ID file -> test whether key is properly signed + { + char buf[65536]; size_t bufsize; + char buf2[65536]; size_t buf2size; + D0_BOOL status; + d0_blind_id_t *ctx2 = d0_blind_id_new(); + CHECK(d0_blind_id_copy(ctx2, ctx)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status)); + if(status) + printf("OK\n"); + else + printf("EPIC FAIL\n"); + } + break; +*/ + default: + USAGE(*argv); + exit(1); + break; + } + d0_blind_id_SHUTDOWN(); + return 0; +} diff --git a/crypto.c b/crypto.c new file mode 100644 index 00000000..896c3aa6 --- /dev/null +++ b/crypto.c @@ -0,0 +1,2359 @@ +// TODO key loading, generating, saving +#include "quakedef.h" +#include "crypto.h" +#include "common.h" + +#include "hmac.h" +#include "libcurl.h" + +cvar_t crypto_developer = {CVAR_SAVE, "crypto_developer", "0", "print extra info about crypto handshake"}; +cvar_t crypto_servercpupercent = {CVAR_SAVE, "crypto_servercpupercent", "10", "allowed crypto CPU load in percent for server operation (0 = no limit, faster)"}; +cvar_t crypto_servercpumaxtime = {CVAR_SAVE, "crypto_servercpumaxtime", "0.01", "maximum allowed crypto CPU time per frame (0 = no limit)"}; +cvar_t crypto_servercpudebug = {CVAR_SAVE, "crypto_servercpudebug", "0", "print statistics about time usage by crypto"}; +static double crypto_servercpu_accumulator = 0; +static double crypto_servercpu_lastrealtime = 0; +cvar_t crypto_aeslevel = {CVAR_SAVE, "crypto_aeslevel", "1", "whether to support AES encryption in authenticated connections (0 = no, 1 = supported, 2 = requested, 3 = required)"}; +int crypto_keyfp_recommended_length; +static const char *crypto_idstring = NULL; +static char crypto_idstring_buf[512]; + +#define PROTOCOL_D0_BLIND_ID FOURCC_D0PK +#define PROTOCOL_VLEN (('v' << 0) | ('l' << 8) | ('e' << 16) | ('n' << 24)) + +// BEGIN stuff shared with crypto-keygen-standalone +#define FOURCC_D0PK (('d' << 0) | ('0' << 8) | ('p' << 16) | ('k' << 24)) +#define FOURCC_D0SK (('d' << 0) | ('0' << 8) | ('s' << 16) | ('k' << 24)) +#define FOURCC_D0PI (('d' << 0) | ('0' << 8) | ('p' << 16) | ('i' << 24)) +#define FOURCC_D0SI (('d' << 0) | ('0' << 8) | ('s' << 16) | ('i' << 24)) +#define FOURCC_D0IQ (('d' << 0) | ('0' << 8) | ('i' << 16) | ('q' << 24)) +#define FOURCC_D0IR (('d' << 0) | ('0' << 8) | ('i' << 16) | ('r' << 24)) +#define FOURCC_D0ER (('d' << 0) | ('0' << 8) | ('e' << 16) | ('r' << 24)) +#define FOURCC_D0IC (('d' << 0) | ('0' << 8) | ('i' << 16) | ('c' << 24)) + +static unsigned long Crypto_LittleLong(const char *data) +{ + return + ((unsigned char) data[0]) | + (((unsigned char) data[1]) << 8) | + (((unsigned char) data[2]) << 16) | + (((unsigned char) data[3]) << 24); +} + +static void Crypto_UnLittleLong(char *data, unsigned long l) +{ + data[0] = l & 0xFF; + data[1] = (l >> 8) & 0xFF; + data[2] = (l >> 16) & 0xFF; + data[3] = (l >> 24) & 0xFF; +} + +static size_t Crypto_ParsePack(const char *buf, size_t len, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + if(Crypto_LittleLong(buf) != header) + return 0; + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 > len) + return 0; + lumpsize[i] = Crypto_LittleLong(&buf[pos]); + pos += 4; + if(pos + lumpsize[i] > len) + return 0; + lumps[i] = &buf[pos]; + pos += lumpsize[i]; + } + return pos; +} + +static size_t Crypto_UnParsePack(char *buf, size_t len, unsigned long header, const char *const *lumps, const size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + Crypto_UnLittleLong(buf, header); + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 + lumpsize[i] > len) + return 0; + Crypto_UnLittleLong(&buf[pos], lumpsize[i]); + pos += 4; + memcpy(&buf[pos], lumps[i], lumpsize[i]); + pos += lumpsize[i]; + } + return pos; +} +// END stuff shared with xonotic-keygen + +#define USE_AES + +#ifdef CRYPTO_STATIC + +#include + +#define d0_blind_id_dll 1 +#define Crypto_OpenLibrary() true +#define Crypto_CloseLibrary() + +#define qd0_blind_id_new d0_blind_id_new +#define qd0_blind_id_free d0_blind_id_free +//#define qd0_blind_id_clear d0_blind_id_clear +#define qd0_blind_id_copy d0_blind_id_copy +//#define qd0_blind_id_generate_private_key d0_blind_id_generate_private_key +//#define qd0_blind_id_generate_private_key_fastreject d0_blind_id_generate_private_key_fastreject +//#define qd0_blind_id_read_private_key d0_blind_id_read_private_key +#define qd0_blind_id_read_public_key d0_blind_id_read_public_key +//#define qd0_blind_id_write_private_key d0_blind_id_write_private_key +//#define qd0_blind_id_write_public_key d0_blind_id_write_public_key +#define qd0_blind_id_fingerprint64_public_key d0_blind_id_fingerprint64_public_key +//#define qd0_blind_id_generate_private_id_modulus d0_blind_id_generate_private_id_modulus +#define qd0_blind_id_read_private_id_modulus d0_blind_id_read_private_id_modulus +//#define qd0_blind_id_write_private_id_modulus d0_blind_id_write_private_id_modulus +#define qd0_blind_id_generate_private_id_start d0_blind_id_generate_private_id_start +#define qd0_blind_id_generate_private_id_request d0_blind_id_generate_private_id_request +//#define qd0_blind_id_answer_private_id_request d0_blind_id_answer_private_id_request +#define qd0_blind_id_finish_private_id_request d0_blind_id_finish_private_id_request +//#define qd0_blind_id_read_private_id_request_camouflage d0_blind_id_read_private_id_request_camouflage +//#define qd0_blind_id_write_private_id_request_camouflage d0_blind_id_write_private_id_request_camouflage +#define qd0_blind_id_read_private_id d0_blind_id_read_private_id +//#define qd0_blind_id_read_public_id d0_blind_id_read_public_id +#define qd0_blind_id_write_private_id d0_blind_id_write_private_id +//#define qd0_blind_id_write_public_id d0_blind_id_write_public_id +#define qd0_blind_id_authenticate_with_private_id_start d0_blind_id_authenticate_with_private_id_start +#define qd0_blind_id_authenticate_with_private_id_challenge d0_blind_id_authenticate_with_private_id_challenge +#define qd0_blind_id_authenticate_with_private_id_response d0_blind_id_authenticate_with_private_id_response +#define qd0_blind_id_authenticate_with_private_id_verify d0_blind_id_authenticate_with_private_id_verify +#define qd0_blind_id_fingerprint64_public_id d0_blind_id_fingerprint64_public_id +#define qd0_blind_id_sessionkey_public_id d0_blind_id_sessionkey_public_id +#define qd0_blind_id_INITIALIZE d0_blind_id_INITIALIZE +#define qd0_blind_id_SHUTDOWN d0_blind_id_SHUTDOWN +#define qd0_blind_id_util_sha256 d0_blind_id_util_sha256 + +#else + +// d0_blind_id interface +#define D0_EXPORT +#ifdef __GNUC__ +#define D0_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define D0_WARN_UNUSED_RESULT +#endif +#define D0_BOOL int + +typedef struct d0_blind_id_s d0_blind_id_t; +typedef D0_BOOL (*d0_fastreject_function) (const d0_blind_id_t *ctx, void *pass); +static D0_EXPORT D0_WARN_UNUSED_RESULT d0_blind_id_t *(*qd0_blind_id_new) (void); +static D0_EXPORT void (*qd0_blind_id_free) (d0_blind_id_t *a); +//static D0_EXPORT void (*qd0_blind_id_clear) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_copy) (d0_blind_id_t *ctx, const d0_blind_id_t *src); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key) (d0_blind_id_t *ctx, int k); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key_fastreject) (d0_blind_id_t *ctx, int k, d0_fastreject_function reject, void *pass); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_modulus) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_modulus) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_modulus) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_start) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_request) (d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_answer_private_id_request) (const d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_finish_private_id_request) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_request_camouflage) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_request_camouflage) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_start) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_challenge) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL recv_modulus, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen, D0_BOOL *status); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_response) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_verify) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *msg, size_t *msglen, D0_BOOL *status); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sessionkey_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); // can only be done after successful key exchange, this performs a modpow; key length is limited by SHA_DIGESTSIZE for now; also ONLY valid after successful d0_blind_id_authenticate_with_private_id_verify/d0_blind_id_fingerprint64_public_id +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_INITIALIZE) (void); +static D0_EXPORT void (*qd0_blind_id_SHUTDOWN) (void); +static D0_EXPORT void (*qd0_blind_id_util_sha256) (char *out, const char *in, size_t n); +static dllfunction_t d0_blind_id_funcs[] = +{ + {"d0_blind_id_new", (void **) &qd0_blind_id_new}, + {"d0_blind_id_free", (void **) &qd0_blind_id_free}, + //{"d0_blind_id_clear", (void **) &qd0_blind_id_clear}, + {"d0_blind_id_copy", (void **) &qd0_blind_id_copy}, + //{"d0_blind_id_generate_private_key", (void **) &qd0_blind_id_generate_private_key}, + //{"d0_blind_id_generate_private_key_fastreject", (void **) &qd0_blind_id_generate_private_key_fastreject}, + //{"d0_blind_id_read_private_key", (void **) &qd0_blind_id_read_private_key}, + {"d0_blind_id_read_public_key", (void **) &qd0_blind_id_read_public_key}, + //{"d0_blind_id_write_private_key", (void **) &qd0_blind_id_write_private_key}, + //{"d0_blind_id_write_public_key", (void **) &qd0_blind_id_write_public_key}, + {"d0_blind_id_fingerprint64_public_key", (void **) &qd0_blind_id_fingerprint64_public_key}, + //{"d0_blind_id_generate_private_id_modulus", (void **) &qd0_blind_id_generate_private_id_modulus}, + {"d0_blind_id_read_private_id_modulus", (void **) &qd0_blind_id_read_private_id_modulus}, + //{"d0_blind_id_write_private_id_modulus", (void **) &qd0_blind_id_write_private_id_modulus}, + {"d0_blind_id_generate_private_id_start", (void **) &qd0_blind_id_generate_private_id_start}, + {"d0_blind_id_generate_private_id_request", (void **) &qd0_blind_id_generate_private_id_request}, + //{"d0_blind_id_answer_private_id_request", (void **) &qd0_blind_id_answer_private_id_request}, + {"d0_blind_id_finish_private_id_request", (void **) &qd0_blind_id_finish_private_id_request}, + //{"d0_blind_id_read_private_id_request_camouflage", (void **) &qd0_blind_id_read_private_id_request_camouflage}, + //{"d0_blind_id_write_private_id_request_camouflage", (void **) &qd0_blind_id_write_private_id_request_camouflage}, + {"d0_blind_id_read_private_id", (void **) &qd0_blind_id_read_private_id}, + //{"d0_blind_id_read_public_id", (void **) &qd0_blind_id_read_public_id}, + {"d0_blind_id_write_private_id", (void **) &qd0_blind_id_write_private_id}, + //{"d0_blind_id_write_public_id", (void **) &qd0_blind_id_write_public_id}, + {"d0_blind_id_authenticate_with_private_id_start", (void **) &qd0_blind_id_authenticate_with_private_id_start}, + {"d0_blind_id_authenticate_with_private_id_challenge", (void **) &qd0_blind_id_authenticate_with_private_id_challenge}, + {"d0_blind_id_authenticate_with_private_id_response", (void **) &qd0_blind_id_authenticate_with_private_id_response}, + {"d0_blind_id_authenticate_with_private_id_verify", (void **) &qd0_blind_id_authenticate_with_private_id_verify}, + {"d0_blind_id_fingerprint64_public_id", (void **) &qd0_blind_id_fingerprint64_public_id}, + {"d0_blind_id_sessionkey_public_id", (void **) &qd0_blind_id_sessionkey_public_id}, + {"d0_blind_id_INITIALIZE", (void **) &qd0_blind_id_INITIALIZE}, + {"d0_blind_id_SHUTDOWN", (void **) &qd0_blind_id_SHUTDOWN}, + {"d0_blind_id_util_sha256", (void **) &qd0_blind_id_util_sha256}, + {NULL, NULL} +}; +// end of d0_blind_id interface + +static dllhandle_t d0_blind_id_dll = NULL; +static qboolean Crypto_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libd0_blind_id-0.dll", +#elif defined(MACOSX) + "libd0_blind_id.0.dylib", +#else + "libd0_blind_id.so.0", + "libd0_blind_id.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (d0_blind_id_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &d0_blind_id_dll, d0_blind_id_funcs); +} + +static void Crypto_CloseLibrary (void) +{ + Sys_UnloadLibrary (&d0_blind_id_dll); +} + +#endif + +#ifdef CRYPTO_RIJNDAEL_STATIC + +#include + +#define d0_rijndael_dll 1 +#define Crypto_Rijndael_OpenLibrary() true +#define Crypto_Rijndael_CloseLibrary() + +#define qd0_rijndael_setup_encrypt d0_rijndael_setup_encrypt +#define qd0_rijndael_setup_decrypt d0_rijndael_setup_decrypt +#define qd0_rijndael_encrypt d0_rijndael_encrypt +#define qd0_rijndael_decrypt d0_rijndael_decrypt + +#else + +// no need to do the #define dance here, as the upper part declares out macros either way + +D0_EXPORT int (*qd0_rijndael_setup_encrypt) (unsigned long *rk, const unsigned char *key, + int keybits); +D0_EXPORT int (*qd0_rijndael_setup_decrypt) (unsigned long *rk, const unsigned char *key, + int keybits); +D0_EXPORT void (*qd0_rijndael_encrypt) (const unsigned long *rk, int nrounds, + const unsigned char plaintext[16], unsigned char ciphertext[16]); +D0_EXPORT void (*qd0_rijndael_decrypt) (const unsigned long *rk, int nrounds, + const unsigned char ciphertext[16], unsigned char plaintext[16]); +#define D0_RIJNDAEL_KEYLENGTH(keybits) ((keybits)/8) +#define D0_RIJNDAEL_RKLENGTH(keybits) ((keybits)/8+28) +#define D0_RIJNDAEL_NROUNDS(keybits) ((keybits)/32+6) +static dllfunction_t d0_rijndael_funcs[] = +{ + {"d0_rijndael_setup_decrypt", (void **) &qd0_rijndael_setup_decrypt}, + {"d0_rijndael_setup_encrypt", (void **) &qd0_rijndael_setup_encrypt}, + {"d0_rijndael_decrypt", (void **) &qd0_rijndael_decrypt}, + {"d0_rijndael_encrypt", (void **) &qd0_rijndael_encrypt}, + {NULL, NULL} +}; +// end of d0_blind_id interface + +static dllhandle_t d0_rijndael_dll = NULL; +static qboolean Crypto_Rijndael_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libd0_rijndael-0.dll", +#elif defined(MACOSX) + "libd0_rijndael.0.dylib", +#else + "libd0_rijndael.so.0", + "libd0_rijndael.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (d0_rijndael_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &d0_rijndael_dll, d0_rijndael_funcs); +} + +static void Crypto_Rijndael_CloseLibrary (void) +{ + Sys_UnloadLibrary (&d0_rijndael_dll); +} + +#endif + +// various helpers +void sha256(unsigned char *out, const unsigned char *in, int n) +{ + qd0_blind_id_util_sha256((char *) out, (const char *) in, n); +} + +static size_t Crypto_LoadFile(const char *path, char *buf, size_t nmax) +{ + qfile_t *f = NULL; + ssize_t n; + if(*fs_userdir) + f = FS_SysOpen(va("%s%s", fs_userdir, path), "rb", false); + if(!f) + f = FS_SysOpen(va("%s%s", fs_basedir, path), "rb", false); + if(!f) + return 0; + n = FS_Read(f, buf, nmax); + if(n < 0) + n = 0; + FS_Close(f); + return (size_t) n; +} + +static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes) +{ + unsigned char i0 = (bytes > 0) ? in[0] : 0; + unsigned char i1 = (bytes > 1) ? in[1] : 0; + unsigned char i2 = (bytes > 2) ? in[2] : 0; + unsigned char o0 = base64[i0 >> 2]; + unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077]; + unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077]; + unsigned char o3 = base64[i2 & 077]; + out[0] = (bytes > 0) ? o0 : '?'; + out[1] = (bytes > 0) ? o1 : '?'; + out[2] = (bytes > 1) ? o2 : '='; + out[3] = (bytes > 2) ? o3 : '='; +} + +size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen) +{ + size_t blocks, i; + // expand the out-buffer + blocks = (buflen + 2) / 3; + if(blocks*4 > outbuflen) + return 0; + for(i = blocks; i > 0; ) + { + --i; + base64_3to4(buf + 3*i, buf + 4*i, buflen - 3*i); + } + return blocks * 4; +} + +static qboolean PutWithNul(char **data, size_t *len, const char *str) +{ + // invariant: data points to insertion point + size_t l = strlen(str); + if(l >= *len) + return false; + memcpy(*data, str, l+1); + *data += l+1; + *len -= l+1; + return true; +} + +static const char *GetUntilNul(const char **data, size_t *len) +{ + // invariant: data points to next character to take + const char *data_save = *data; + size_t n; + const char *p; + + if(!*data) + return NULL; + + if(!*len) + { + *data = NULL; + return NULL; + } + + p = (const char *) memchr(*data, 0, *len); + if(!p) // no terminating NUL + { + *data = NULL; + *len = 0; + return NULL; + } + else + { + n = (p - *data) + 1; + *len -= n; + *data += n; + if(*len == 0) + *data = NULL; + return (const char *) data_save; + } + *data = NULL; + return NULL; +} + +// d0pk reading +static d0_blind_id_t *Crypto_ReadPublicKey(char *buf, size_t len) +{ + d0_blind_id_t *pk = NULL; + const char *p[2]; + size_t l[2]; + if(Crypto_ParsePack(buf, len, FOURCC_D0PK, p, l, 2)) + { + pk = qd0_blind_id_new(); + if(pk) + if(qd0_blind_id_read_public_key(pk, p[0], l[0])) + if(qd0_blind_id_read_private_id_modulus(pk, p[1], l[1])) + return pk; + } + if(pk) + qd0_blind_id_free(pk); + return NULL; +} + +// d0si reading +static qboolean Crypto_AddPrivateKey(d0_blind_id_t *pk, char *buf, size_t len) +{ + const char *p[1]; + size_t l[1]; + if(Crypto_ParsePack(buf, len, FOURCC_D0SI, p, l, 1)) + { + if(qd0_blind_id_read_private_id(pk, p[0], l[0])) + return true; + } + return false; +} + +#define MAX_PUBKEYS 16 +static d0_blind_id_t *pubkeys[MAX_PUBKEYS]; +static char pubkeys_fp64[MAX_PUBKEYS][FP64_SIZE+1]; +static qboolean pubkeys_havepriv[MAX_PUBKEYS]; +static char pubkeys_priv_fp64[MAX_PUBKEYS][FP64_SIZE+1]; +static char challenge_append[1400]; +static size_t challenge_append_length; + +static int keygen_i = -1; +static char keygen_buf[8192]; + +#define MAX_CRYPTOCONNECTS 16 +#define CRYPTOCONNECT_NONE 0 +#define CRYPTOCONNECT_PRECONNECT 1 +#define CRYPTOCONNECT_CONNECT 2 +#define CRYPTOCONNECT_RECONNECT 3 +#define CRYPTOCONNECT_DUPLICATE 4 +typedef struct server_cryptoconnect_s +{ + double lasttime; + lhnetaddress_t address; + crypto_t crypto; + int next_step; +} +server_cryptoconnect_t; +static server_cryptoconnect_t cryptoconnects[MAX_CRYPTOCONNECTS]; + +static int cdata_id = 0; +typedef struct +{ + d0_blind_id_t *id; + int s, c; + int next_step; + char challenge[2048]; + char wantserver_idfp[FP64_SIZE+1]; + qboolean wantserver_aes; + int cdata_id; +} +crypto_data_t; + +// crypto specific helpers +#define CDATA ((crypto_data_t *) crypto->data) +#define MAKE_CDATA if(!crypto->data) crypto->data = Z_Malloc(sizeof(crypto_data_t)) +#define CLEAR_CDATA if(crypto->data) { if(CDATA->id) qd0_blind_id_free(CDATA->id); Z_Free(crypto->data); } crypto->data = NULL + +static crypto_t *Crypto_ServerFindInstance(lhnetaddress_t *peeraddress, qboolean allow_create) +{ + crypto_t *crypto; + int i, best; + + if(!d0_blind_id_dll) + return NULL; // no support + + for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) + if(LHNETADDRESS_Compare(peeraddress, &cryptoconnects[i].address)) + break; + if(i < MAX_CRYPTOCONNECTS && (allow_create || cryptoconnects[i].crypto.data)) + { + crypto = &cryptoconnects[i].crypto; + cryptoconnects[i].lasttime = realtime; + return crypto; + } + if(!allow_create) + return NULL; + best = 0; + for(i = 1; i < MAX_CRYPTOCONNECTS; ++i) + if(cryptoconnects[i].lasttime < cryptoconnects[best].lasttime) + best = i; + crypto = &cryptoconnects[best].crypto; + cryptoconnects[best].lasttime = realtime; + memcpy(&cryptoconnects[best].address, peeraddress, sizeof(cryptoconnects[best].address)); + CLEAR_CDATA; + return crypto; +} + +qboolean Crypto_ServerFinishInstance(crypto_t *out, crypto_t *crypto) +{ + // no check needed here (returned pointers are only used in prefilled fields) + if(!crypto || !crypto->authenticated) + { + Con_Printf("Passed an invalid crypto connect instance\n"); + memset(out, 0, sizeof(*out)); + return false; + } + CLEAR_CDATA; + memcpy(out, crypto, sizeof(*out)); + memset(crypto, 0, sizeof(crypto)); + return true; +} + +crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress) +{ + // no check needed here (returned pointers are only used in prefilled fields) + return Crypto_ServerFindInstance(peeraddress, false); +} + +typedef struct crypto_storedhostkey_s +{ + struct crypto_storedhostkey_s *next; + lhnetaddress_t addr; + int keyid; + char idfp[FP64_SIZE+1]; + int aeslevel; +} +crypto_storedhostkey_t; +static crypto_storedhostkey_t *crypto_storedhostkey_hashtable[CRYPTO_HOSTKEY_HASHSIZE]; + +static void Crypto_InitHostKeys(void) +{ + int i; + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + crypto_storedhostkey_hashtable[i] = NULL; +} + +static void Crypto_ClearHostKeys(void) +{ + int i; + crypto_storedhostkey_t *hk, *hkn; + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + { + for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hkn) + { + hkn = hk->next; + Z_Free(hk); + } + crypto_storedhostkey_hashtable[i] = NULL; + } +} + +static qboolean Crypto_ClearHostKey(lhnetaddress_t *peeraddress) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t **hkp; + qboolean found = false; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hkp = &crypto_storedhostkey_hashtable[hashindex]; *hkp && LHNETADDRESS_Compare(&((*hkp)->addr), peeraddress); hkp = &((*hkp)->next)); + + if(*hkp) + { + crypto_storedhostkey_t *hk = *hkp; + *hkp = hk->next; + Z_Free(hk); + found = true; + } + + return found; +} + +static void Crypto_StoreHostKey(lhnetaddress_t *peeraddress, const char *keystring, qboolean complain) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t *hk; + int keyid; + char idfp[FP64_SIZE+1]; + int aeslevel; + + if(!d0_blind_id_dll) + return; + + // syntax of keystring: + // aeslevel id@key id@key ... + + if(!*keystring) + return; + aeslevel = bound(0, *keystring - '0', 3); + while(*keystring && *keystring != ' ') + ++keystring; + + keyid = -1; + while(*keystring && keyid < 0) + { + // id@key + const char *idstart, *idend, *keystart, *keyend; + ++keystring; // skip the space + idstart = keystring; + while(*keystring && *keystring != ' ' && *keystring != '@') + ++keystring; + idend = keystring; + if(!*keystring) + break; + ++keystring; + keystart = keystring; + while(*keystring && *keystring != ' ') + ++keystring; + keyend = keystring; + + if(idend - idstart == FP64_SIZE && keyend - keystart == FP64_SIZE) + { + for(keyid = 0; keyid < MAX_PUBKEYS; ++keyid) + if(pubkeys[keyid]) + if(!memcmp(pubkeys_fp64[keyid], keystart, FP64_SIZE)) + { + memcpy(idfp, idstart, FP64_SIZE); + idfp[FP64_SIZE] = 0; + break; + } + if(keyid >= MAX_PUBKEYS) + keyid = -1; + } + } + + if(keyid < 0) + return; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); + + if(hk) + { + if(complain) + { + if(hk->keyid != keyid || memcmp(hk->idfp, idfp, FP64_SIZE+1)) + Con_Printf("Server %s tried to change the host key to a value not in the host cache. Connecting to it will fail. To accept the new host key, do crypto_hostkey_clear %s\n", buf, buf); + if(hk->aeslevel > aeslevel) + Con_Printf("Server %s tried to reduce encryption status, not accepted. Connecting to it will fail. To accept, do crypto_hostkey_clear %s\n", buf, buf); + } + hk->aeslevel = max(aeslevel, hk->aeslevel); + return; + } + + // great, we did NOT have it yet + hk = (crypto_storedhostkey_t *) Z_Malloc(sizeof(*hk)); + memcpy(&hk->addr, peeraddress, sizeof(hk->addr)); + hk->keyid = keyid; + memcpy(hk->idfp, idfp, FP64_SIZE+1); + hk->next = crypto_storedhostkey_hashtable[hashindex]; + hk->aeslevel = aeslevel; + crypto_storedhostkey_hashtable[hashindex] = hk; +} + +qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t *hk; + + if(!d0_blind_id_dll) + return false; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); + + if(!hk) + return false; + + if(keyid) + *keyid = hk->keyid; + if(keyfp) + strlcpy(keyfp, pubkeys_fp64[hk->keyid], keyfplen); + if(idfp) + strlcpy(idfp, hk->idfp, idfplen); + if(aeslevel) + *aeslevel = hk->aeslevel; + + return true; +} +int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen) // return value: -1 if more to come, +1 if valid, 0 if end of list +{ + if(keyid < 0 || keyid > MAX_PUBKEYS) + return 0; + if(keyfp) + *keyfp = 0; + if(idfp) + *idfp = 0; + if(!pubkeys[keyid]) + return -1; + if(keyfp) + strlcpy(keyfp, pubkeys_fp64[keyid], keyfplen); + if(idfp) + if(pubkeys_havepriv[keyid]) + strlcpy(idfp, pubkeys_priv_fp64[keyid], keyfplen); + return 1; +} +// end + +// init/shutdown code +static void Crypto_BuildChallengeAppend(void) +{ + char *p, *lengthptr, *startptr; + size_t n; + int i; + p = challenge_append; + n = sizeof(challenge_append); + Crypto_UnLittleLong(p, PROTOCOL_VLEN); + p += 4; + n -= 4; + lengthptr = p; + Crypto_UnLittleLong(p, 0); + p += 4; + n -= 4; + Crypto_UnLittleLong(p, PROTOCOL_D0_BLIND_ID); + p += 4; + n -= 4; + startptr = p; + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys_havepriv[i]) + PutWithNul(&p, &n, pubkeys_fp64[i]); + PutWithNul(&p, &n, ""); + for(i = 0; i < MAX_PUBKEYS; ++i) + if(!pubkeys_havepriv[i] && pubkeys[i]) + PutWithNul(&p, &n, pubkeys_fp64[i]); + Crypto_UnLittleLong(lengthptr, p - startptr); + challenge_append_length = p - challenge_append; +} + +static void Crypto_LoadKeys(void) +{ + char buf[8192]; + size_t len, len2; + int i; + + // load keys + // note: we are just a CLIENT + // so we load: + // PUBLIC KEYS to accept (including modulus) + // PRIVATE KEY of user + + crypto_idstring = NULL; + dpsnprintf(crypto_idstring_buf, sizeof(crypto_idstring_buf), "%d", d0_rijndael_dll ? crypto_aeslevel.integer : 0); + for(i = 0; i < MAX_PUBKEYS; ++i) + { + memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); + memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); + pubkeys_havepriv[i] = false; + len = Crypto_LoadFile(va("key_%d.d0pk", i), buf, sizeof(buf)); + if((pubkeys[i] = Crypto_ReadPublicKey(buf, len))) + { + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_key(pubkeys[i], pubkeys_fp64[i], &len2)) // keeps final NUL + { + Con_Printf("Loaded public key key_%d.d0pk (fingerprint: %s)\n", i, pubkeys_fp64[i]); + len = Crypto_LoadFile(va("key_%d.d0si", i), buf, sizeof(buf)); + if(len) + { + if(Crypto_AddPrivateKey(pubkeys[i], buf, len)) + { + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_id(pubkeys[i], pubkeys_priv_fp64[i], &len2)) // keeps final NUL + { + Con_Printf("Loaded private ID key_%d.d0si for key_%d.d0pk (fingerprint: %s)\n", i, i, pubkeys_priv_fp64[i]); + pubkeys_havepriv[i] = true; + strlcat(crypto_idstring_buf, va(" %s@%s", pubkeys_priv_fp64[i], pubkeys_fp64[i]), sizeof(crypto_idstring_buf)); + } + else + { + // can't really happen + // but nothing leaked here + } + } + } + } + else + { + // can't really happen + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + } + } + } + crypto_idstring = crypto_idstring_buf; + + keygen_i = -1; + Crypto_BuildChallengeAppend(); + + // find a good prefix length for all the keys we know (yes, algorithm is not perfect yet, may yield too long prefix length) + crypto_keyfp_recommended_length = 0; + memset(buf+256, 0, MAX_PUBKEYS + MAX_PUBKEYS); + while(crypto_keyfp_recommended_length < FP64_SIZE) + { + memset(buf, 0, 256); + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + ++buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]]; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + ++buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]]; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + if(buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]] < 2) + buf[256 + i] = 1; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + if(buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]] < 2) + buf[256 + MAX_PUBKEYS + i] = 1; + } + ++crypto_keyfp_recommended_length; + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + break; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + break; + } + if(i >= MAX_PUBKEYS) + break; + } + if(crypto_keyfp_recommended_length < 7) + crypto_keyfp_recommended_length = 7; +} + +static void Crypto_UnloadKeys(void) +{ + int i; + keygen_i = -1; + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + pubkeys_havepriv[i] = false; + memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); + memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); + challenge_append_length = 0; + } + crypto_idstring = NULL; +} + +void Crypto_Shutdown(void) +{ + crypto_t *crypto; + int i; + + Crypto_Rijndael_CloseLibrary(); + + if(d0_blind_id_dll) + { + // free memory + for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) + { + crypto = &cryptoconnects[i].crypto; + CLEAR_CDATA; + } + memset(cryptoconnects, 0, sizeof(cryptoconnects)); + crypto = &cls.crypto; + CLEAR_CDATA; + + Crypto_UnloadKeys(); + + qd0_blind_id_SHUTDOWN(); + + Crypto_CloseLibrary(); + } +} + +void Crypto_Init(void) +{ + if(!Crypto_OpenLibrary()) + return; + + if(!qd0_blind_id_INITIALIZE()) + { + Crypto_Rijndael_CloseLibrary(); + Crypto_CloseLibrary(); + Con_Printf("libd0_blind_id initialization FAILED, cryptography support has been disabled\n"); + return; + } + + Crypto_Rijndael_OpenLibrary(); // if this fails, it's uncritical + + Crypto_InitHostKeys(); + Crypto_LoadKeys(); +} +// end + +// keygen code +static void Crypto_KeyGen_Finished(int code, size_t length_received, unsigned char *buffer, void *cbdata) +{ + const char *p[1]; + size_t l[1]; + static char buf[8192]; + static char buf2[8192]; + size_t bufsize, buf2size; + qfile_t *f = NULL; + d0_blind_id_t *ctx, *ctx2; + D0_BOOL status; + size_t len2; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + keygen_i = -1; + return; + } + + if(keygen_i >= MAX_PUBKEYS || !pubkeys[keygen_i]) + { + Con_Printf("overflow of keygen_i\n"); + keygen_i = -1; + return; + } + if(keygen_i < 0) + { + Con_Printf("Unexpected response from keygen server:\n"); + Com_HexDumpToConsole(buffer, length_received); + return; + } + if(!Crypto_ParsePack((const char *) buffer, length_received, FOURCC_D0IR, p, l, 1)) + { + if(length_received >= 5 && Crypto_LittleLong((const char *) buffer) == FOURCC_D0ER) + { + Con_Printf("Error response from keygen server: %.*s\n", (int)(length_received - 5), buffer + 5); + } + else + { + Con_Printf("Invalid response from keygen server:\n"); + Com_HexDumpToConsole(buffer, length_received); + } + keygen_i = -1; + return; + } + if(!qd0_blind_id_finish_private_id_request(pubkeys[keygen_i], p[0], l[0])) + { + Con_Printf("d0_blind_id_finish_private_id_request failed\n"); + keygen_i = -1; + return; + } + + // verify the key we just got (just in case) + ctx = qd0_blind_id_new(); + if(!ctx) + { + Con_Printf("d0_blind_id_new failed\n"); + keygen_i = -1; + return; + } + ctx2 = qd0_blind_id_new(); + if(!ctx2) + { + Con_Printf("d0_blind_id_new failed\n"); + qd0_blind_id_free(ctx); + keygen_i = -1; + return; + } + if(!qd0_blind_id_copy(ctx, pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_copy failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + if(!qd0_blind_id_copy(ctx2, pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_copy failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + bufsize = sizeof(buf); + if(!qd0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_start failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + buf2size = sizeof(buf2); + if(!qd0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status) || !status) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_challenge failed (server does not have the requested private key)\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + bufsize = sizeof(buf); + if(!qd0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_response failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + buf2size = sizeof(buf2); + if(!qd0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status) || !status) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_verify failed (server does not have the requested private key)\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + + // we have a valid key now! + // make the rest of crypto.c know that + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_id(pubkeys[keygen_i], pubkeys_priv_fp64[keygen_i], &len2)) // keeps final NUL + { + Con_Printf("Received private ID key_%d.d0pk (fingerprint: %s)\n", keygen_i, pubkeys_priv_fp64[keygen_i]); + pubkeys_havepriv[keygen_i] = true; + strlcat(crypto_idstring_buf, va(" %s@%s", pubkeys_priv_fp64[keygen_i], pubkeys_fp64[keygen_i]), sizeof(crypto_idstring_buf)); + crypto_idstring = crypto_idstring_buf; + Crypto_BuildChallengeAppend(); + } + // write the key to disk + p[0] = buf; + l[0] = sizeof(buf); + if(!qd0_blind_id_write_private_id(pubkeys[keygen_i], buf, &l[0])) + { + Con_Printf("d0_blind_id_write_private_id failed\n"); + keygen_i = -1; + return; + } + if(!(buf2size = Crypto_UnParsePack(buf2, sizeof(buf2), FOURCC_D0SI, p, l, 1))) + { + Con_Printf("Crypto_UnParsePack failed\n"); + keygen_i = -1; + return; + } + + if(*fs_userdir) + { + FS_CreatePath(va("%skey_%d.d0si", fs_userdir, keygen_i)); + f = FS_SysOpen(va("%skey_%d.d0si", fs_userdir, keygen_i), "wb", false); + } + if(!f) + { + FS_CreatePath(va("%skey_%d.d0si", fs_basedir, keygen_i)); + f = FS_SysOpen(va("%skey_%d.d0si", fs_basedir, keygen_i), "wb", false); + } + if(!f) + { + Con_Printf("Cannot open key_%d.d0si\n", keygen_i); + keygen_i = -1; + return; + } + FS_Write(f, buf2, buf2size); + FS_Close(f); + + Con_Printf("Saved to key_%d.d0si\n", keygen_i); + keygen_i = -1; +} + +static void Crypto_KeyGen_f(void) +{ + int i; + const char *p[1]; + size_t l[1]; + static char buf[8192]; + static char buf2[8192]; + size_t buf2l, buf2pos; + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + if(Cmd_Argc() != 3) + { + Con_Printf("usage:\n%s id url\n", Cmd_Argv(0)); + return; + } + i = atoi(Cmd_Argv(1)); + if(!pubkeys[i]) + { + Con_Printf("there is no public key %d\n", i); + return; + } + if(pubkeys_havepriv[i]) + { + Con_Printf("there is already a private key for %d\n", i); + return; + } + if(keygen_i >= 0) + { + Con_Printf("there is already a keygen run on the way\n"); + return; + } + keygen_i = i; + if(!qd0_blind_id_generate_private_id_start(pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_start failed\n"); + keygen_i = -1; + return; + } + p[0] = buf; + l[0] = sizeof(buf); + if(!qd0_blind_id_generate_private_id_request(pubkeys[keygen_i], buf, &l[0])) + { + Con_Printf("d0_blind_id_generate_private_id_request failed\n"); + keygen_i = -1; + return; + } + buf2pos = strlen(Cmd_Argv(2)); + memcpy(buf2, Cmd_Argv(2), buf2pos); + if(!(buf2l = Crypto_UnParsePack(buf2 + buf2pos, sizeof(buf2) - buf2pos - 1, FOURCC_D0IQ, p, l, 1))) + { + Con_Printf("Crypto_UnParsePack failed\n"); + keygen_i = -1; + return; + } + if(!(buf2l = base64_encode((unsigned char *) (buf2 + buf2pos), buf2l, sizeof(buf2) - buf2pos - 1))) + { + Con_Printf("base64_encode failed\n"); + keygen_i = -1; + return; + } + buf2l += buf2pos; + buf[buf2l] = 0; + if(!Curl_Begin_ToMemory(buf2, 0, (unsigned char *) keygen_buf, sizeof(keygen_buf), Crypto_KeyGen_Finished, NULL)) + { + Con_Printf("curl failed\n"); + keygen_i = -1; + return; + } + Con_Printf("key generation in progress\n"); +} +// end + +// console commands +static void Crypto_Reload_f(void) +{ + Crypto_ClearHostKeys(); + Crypto_UnloadKeys(); + Crypto_LoadKeys(); +} + +static void Crypto_Keys_f(void) +{ + int i; + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + { + Con_Printf("%2d: public key key_%d.d0pk (fingerprint: %s)\n", i, i, pubkeys_fp64[i]); + if(pubkeys_havepriv[i]) + Con_Printf(" private key key_%d.d0si (fingerprint: %s)\n", i, pubkeys_priv_fp64[i]); + } + } +} + +static void Crypto_HostKeys_f(void) +{ + int i; + crypto_storedhostkey_t *hk; + char buf[128]; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + { + for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hk->next) + { + LHNETADDRESS_ToString(&hk->addr, buf, sizeof(buf), 1); + Con_Printf("%d %s@%.*s %s\n", + hk->aeslevel, + hk->idfp, + crypto_keyfp_recommended_length, pubkeys_fp64[hk->keyid], + buf); + } + } +} + +static void Crypto_HostKey_Clear_f(void) +{ + lhnetaddress_t addr; + int i; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + + for(i = 1; i < Cmd_Argc(); ++i) + { + LHNETADDRESS_FromString(&addr, Cmd_Argv(i), 26000); + if(Crypto_ClearHostKey(&addr)) + { + Con_Printf("cleared host key for %s\n", Cmd_Argv(i)); + } + } +} + +void Crypto_Init_Commands(void) +{ + if(d0_blind_id_dll) + { + Cmd_AddCommand("crypto_reload", Crypto_Reload_f, "reloads cryptographic keys"); + Cmd_AddCommand("crypto_keygen", Crypto_KeyGen_f, "generates and saves a cryptographic key"); + Cmd_AddCommand("crypto_keys", Crypto_Keys_f, "lists the loaded keys"); + Cmd_AddCommand("crypto_hostkeys", Crypto_HostKeys_f, "lists the cached host keys"); + Cmd_AddCommand("crypto_hostkey_clear", Crypto_HostKey_Clear_f, "clears a cached host key"); + Cvar_RegisterVariable(&crypto_developer); + if(d0_rijndael_dll) + Cvar_RegisterVariable(&crypto_aeslevel); + else + crypto_aeslevel.integer = 0; // make sure + Cvar_RegisterVariable(&crypto_servercpupercent); + Cvar_RegisterVariable(&crypto_servercpumaxtime); + Cvar_RegisterVariable(&crypto_servercpudebug); + } +} +// end + +// AES encryption +static void aescpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) +{ + const unsigned char *xorpos = iv; + unsigned char xorbuf[16]; + unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; + size_t i; + qd0_rijndael_setup_encrypt(rk, key, DHKEY_SIZE * 8); + while(len > 16) + { + for(i = 0; i < 16; ++i) + xorbuf[i] = src[i] ^ xorpos[i]; + qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); + xorpos = dst; + len -= 16; + src += 16; + dst += 16; + } + if(len > 0) + { + for(i = 0; i < len; ++i) + xorbuf[i] = src[i] ^ xorpos[i]; + for(; i < 16; ++i) + xorbuf[i] = xorpos[i]; + qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); + } +} +static void seacpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) +{ + const unsigned char *xorpos = iv; + unsigned char xorbuf[16]; + unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; + size_t i; + qd0_rijndael_setup_decrypt(rk, key, DHKEY_SIZE * 8); + while(len > 16) + { + qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); + for(i = 0; i < 16; ++i) + dst[i] = xorbuf[i] ^ xorpos[i]; + xorpos = src; + len -= 16; + src += 16; + dst += 16; + } + if(len > 0) + { + qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); + for(i = 0; i < len; ++i) + dst[i] = xorbuf[i] ^ xorpos[i]; + } +} + +const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) +{ + unsigned char h[32]; + if(crypto->authenticated) + { + if(crypto->use_aes) + { + // AES packet = 1 byte length overhead, 15 bytes from HMAC-SHA-256, data, 0..15 bytes padding + // 15 bytes HMAC-SHA-256 (112bit) suffice as the attacker can't do more than forge a random-looking packet + // HMAC is needed to not leak information about packet content + if(developer_networking.integer) + { + Con_Print("To be encrypted:\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + } + if(len_src + 32 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, len_src, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = ((len_src + 15) / 16) * 16 + 16; // add 16 for HMAC, then round to 16-size for AES + ((unsigned char *) data_dst)[0] = *len_dst - len_src; + memcpy(((unsigned char *) data_dst)+1, h, 15); + aescpy(crypto->dhkey, (const unsigned char *) data_dst, ((unsigned char *) data_dst) + 16, (const unsigned char *) data_src, len_src); + // IV dst src len + } + else + { + // HMAC packet = 16 bytes HMAC-SHA-256 (truncated to 128 bits), data + if(len_src + 16 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, len_src, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src + 16; + memcpy(data_dst, h, 16); + memcpy(((unsigned char *) data_dst) + 16, (unsigned char *) data_src, len_src); + } + return data_dst; + } + else + { + *len_dst = len_src; + return data_src; + } +} + +const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) +{ + unsigned char h[32]; + if(crypto->authenticated) + { + if(crypto->use_aes) + { + if(len_src < 16 || ((len_src - 16) % 16)) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src - ((unsigned char *) data_src)[0]; + if(len < *len_dst || *len_dst > len_src - 16) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); + return NULL; + } + seacpy(crypto->dhkey, (unsigned char *) data_src, (unsigned char *) data_dst, ((const unsigned char *) data_src) + 16, *len_dst); + // IV dst src len + if(!HMAC_SHA256_32BYTES(h, (const unsigned char *) data_dst, *len_dst, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("HMAC fail\n"); + return NULL; + } + if(memcmp(((const unsigned char *) data_src)+1, h, 15)) // ignore first byte, used for length + { + Con_Printf("HMAC mismatch\n"); + return NULL; + } + if(developer_networking.integer) + { + Con_Print("Decrypted:\n"); + Com_HexDumpToConsole((const unsigned char *) data_dst, *len_dst); + } + return data_dst; // no need to copy + } + else + { + if(len_src < 16) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src - 16; + if(len < *len_dst) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); + return NULL; + } + //memcpy(data_dst, data_src + 16, *len_dst); + if(!HMAC_SHA256_32BYTES(h, ((const unsigned char *) data_src) + 16, *len_dst, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("HMAC fail\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + if(memcmp((const unsigned char *) data_src, h, 16)) // ignore first byte, used for length + { + Con_Printf("HMAC mismatch\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + return ((const unsigned char *) data_src) + 16; // no need to copy, so data_dst is not used + } + } + else + { + *len_dst = len_src; + return data_src; + } +} +// end + +const char *Crypto_GetInfoResponseDataString(void) +{ + crypto_idstring_buf[0] = '0' + crypto_aeslevel.integer; + return crypto_idstring; +} + +// network protocol +qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen_out) +{ + // cheap op, all is precomputed + if(!d0_blind_id_dll) + return false; // no support + // append challenge + if(maxlen_out <= *len_out + challenge_append_length) + return false; + memcpy(data_out + *len_out, challenge_append, challenge_append_length); + *len_out += challenge_append_length; + return false; +} + +static int Crypto_ServerError(char *data_out, size_t *len_out, const char *msg, const char *msg_client) +{ + if(!msg_client) + msg_client = msg; + Con_DPrintf("rejecting client: %s\n", msg); + if(*msg_client) + dpsnprintf(data_out, *len_out, "reject %s", msg_client); + *len_out = strlen(data_out); + return CRYPTO_DISCARD; +} + +static int Crypto_SoftServerError(char *data_out, size_t *len_out, const char *msg) +{ + *len_out = 0; + Con_DPrintf("%s\n", msg); + return CRYPTO_DISCARD; +} + +static int Crypto_ServerParsePacket_Internal(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + // if "connect": reject if in the middle of crypto handshake + crypto_t *crypto = NULL; + char *data_out_p = data_out; + const char *string = data_in; + int aeslevel; + D0_BOOL aes; + D0_BOOL status; + + if(!d0_blind_id_dll) + return CRYPTO_NOMATCH; // no support + + if (len_in > 8 && !memcmp(string, "connect\\", 8) && d0_rijndael_dll && crypto_aeslevel.integer >= 3) + { + const char *s; + int i; + // sorry, we have to verify the challenge here to not reflect network spam + + if (!(s = SearchInfostring(string + 4, "challenge"))) + return CRYPTO_NOMATCH; // will be later accepted if encryption was set up + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) // challenge mismatch is silent + return CRYPTO_DISCARD; // pre-challenge: rather be silent + + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto || !crypto->authenticated) + return Crypto_ServerError(data_out, len_out, "This server requires authentication and encryption to be supported by your client", NULL); + } + else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && ((LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP) || sv_public.integer > -3)) + { + const char *cnt, *s, *p; + int id; + int clientid = -1, serverid = -1; + cnt = SearchInfostring(string + 4, "id"); + id = (cnt ? atoi(cnt) : -1); + cnt = SearchInfostring(string + 4, "cnt"); + if(!cnt) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + GetUntilNul(&data_in, &len_in); + if(!data_in) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + if(!strcmp(cnt, "0")) + { + int i; + if (!(s = SearchInfostring(string + 4, "challenge"))) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) // challenge mismatch is silent + return CRYPTO_DISCARD; // pre-challenge: rather be silent + + if (!(s = SearchInfostring(string + 4, "aeslevel"))) + aeslevel = 0; // not supported + else + aeslevel = bound(0, atoi(s), 3); + switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) + { + default: // dummy, never happens, but to make gcc happy... + case 0: + if(aeslevel >= 3) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL); + aes = false; + break; + case 1: + aes = (aeslevel >= 2); + break; + case 2: + aes = (aeslevel >= 1); + break; + case 3: + if(aeslevel <= 0) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL); + aes = true; + break; + } + + p = GetUntilNul(&data_in, &len_in); + if(p && *p) + { + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + if(pubkeys_havepriv[i]) + if(serverid < 0) + serverid = i; + } + if(serverid < 0) + return Crypto_ServerError(data_out, len_out, "Invalid server key", NULL); + } + p = GetUntilNul(&data_in, &len_in); + if(p && *p) + { + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + if(clientid < 0) + clientid = i; + } + if(clientid < 0) + return Crypto_ServerError(data_out, len_out, "Invalid client key", NULL); + } + + crypto = Crypto_ServerFindInstance(peeraddress, true); + if(!crypto) + return Crypto_ServerError(data_out, len_out, "Could not create a crypto connect instance", NULL); + MAKE_CDATA; + CDATA->cdata_id = id; + CDATA->s = serverid; + CDATA->c = clientid; + memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); + CDATA->challenge[0] = 0; + crypto->client_keyfp[0] = 0; + crypto->client_idfp[0] = 0; + crypto->server_keyfp[0] = 0; + crypto->server_idfp[0] = 0; + crypto->use_aes = aes; + + if(CDATA->s >= 0) + { + // I am the server, and my key is ok... so let's set server_keyfp and server_idfp + strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); + strlcpy(crypto->server_idfp, pubkeys_priv_fp64[CDATA->s], sizeof(crypto->server_idfp)); + + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\1\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed", "Internal error"); + } + CDATA->next_step = 2; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(CDATA->c >= 0) + { + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\5\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); + } + CDATA->next_step = 6; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "Missing client and server key", NULL); + } + } + else if(!strcmp(cnt, "2")) + { + size_t fpbuflen; + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 2) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\3\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed", "Internal error"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); + } + if(CDATA->c >= 0) + { + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + CDATA->next_step = 4; + } + else + { + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + } + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "4")) + { + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 4) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\5\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); + } + CDATA->next_step = 6; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "6")) + { + static char msgbuf[32]; + size_t msgbuflen = sizeof(msgbuf); + size_t fpbuflen; + int i; + unsigned char dhkey[DHKEY_SIZE]; + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 6) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (authentication error)", "Authentication error"); + } + if(status) + strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); + else + crypto->client_keyfp[0] = 0; + memset(crypto->client_idfp, 0, sizeof(crypto->client_idfp)); + fpbuflen = FP64_SIZE; + if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->client_idfp, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed", "Internal error"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); + } + // XOR the two DH keys together to make one + for(i = 0; i < DHKEY_SIZE; ++i) + crypto->dhkey[i] ^= dhkey[i]; + + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + // send a challenge-less challenge + PutWithNul(&data_out_p, len_out, "challenge "); + *len_out = data_out_p - data_out; + --*len_out; // remove NUL terminator + return CRYPTO_MATCH; + } + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + } + return CRYPTO_NOMATCH; +} + +int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + int ret; + double t = 0; + static double complain_time = 0; + const char *cnt; + qboolean do_time = false; + qboolean do_reject = false; + if(crypto_servercpupercent.value > 0 || crypto_servercpumaxtime.value > 0) + if(len_in > 5 && !memcmp(data_in, "d0pk\\", 5)) + { + do_time = true; + cnt = SearchInfostring(data_in + 4, "cnt"); + if(cnt) + if(!strcmp(cnt, "0")) + do_reject = true; + } + if(do_time) + { + // check if we may perform crypto... + if(crypto_servercpupercent.value > 0) + { + crypto_servercpu_accumulator += (realtime - crypto_servercpu_lastrealtime) * crypto_servercpupercent.value * 0.01; + if(crypto_servercpumaxtime.value) + if(crypto_servercpu_accumulator > crypto_servercpumaxtime.value) + crypto_servercpu_accumulator = crypto_servercpumaxtime.value; + } + else + { + if(crypto_servercpumaxtime.value > 0) + if(realtime != crypto_servercpu_lastrealtime) + crypto_servercpu_accumulator = crypto_servercpumaxtime.value; + } + crypto_servercpu_lastrealtime = realtime; + if(do_reject && crypto_servercpu_accumulator < 0) + { + if(realtime > complain_time + 5) + Con_Printf("crypto: cannot perform requested crypto operations; denial service attack or crypto_servercpupercent/crypto_servercpumaxtime are too low\n"); + *len_out = 0; + return CRYPTO_DISCARD; + } + t = Sys_DoubleTime(); + } + ret = Crypto_ServerParsePacket_Internal(data_in, len_in, data_out, len_out, peeraddress); + if(do_time) + { + t = Sys_DoubleTime() - t; + if(crypto_servercpudebug.integer) + Con_Printf("crypto: accumulator was %.1f ms, used %.1f ms for crypto, ", crypto_servercpu_accumulator * 1000, t * 1000); + crypto_servercpu_accumulator -= t; + if(crypto_servercpudebug.integer) + Con_Printf("is %.1f ms\n", crypto_servercpu_accumulator * 1000); + } + return ret; +} + +static int Crypto_ClientError(char *data_out, size_t *len_out, const char *msg) +{ + dpsnprintf(data_out, *len_out, "reject %s", msg); + *len_out = strlen(data_out); + return CRYPTO_REPLACE; +} + +static int Crypto_SoftClientError(char *data_out, size_t *len_out, const char *msg) +{ + *len_out = 0; + Con_Printf("%s\n", msg); + return CRYPTO_DISCARD; +} + +int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + crypto_t *crypto = &cls.crypto; + const char *string = data_in; + const char *s; + D0_BOOL aes; + char *data_out_p = data_out; + D0_BOOL status; + + if(!d0_blind_id_dll) + return CRYPTO_NOMATCH; // no support + + // if "challenge": verify challenge, and discard message, send next crypto protocol message instead + // otherwise, just handle actual protocol messages + + if (len_in == 6 && !memcmp(string, "accept", 6) && cls.connect_trying && d0_rijndael_dll) + { + int wantserverid = -1; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL); + if(!crypto || !crypto->authenticated) + { + if(wantserverid >= 0) + return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); + if(crypto_aeslevel.integer >= 3) + return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); + } + return CRYPTO_NOMATCH; + } + else if (len_in >= 1 && string[0] == 'j' && cls.connect_trying && d0_rijndael_dll && crypto_aeslevel.integer >= 3) + { + int wantserverid = -1; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL); + if(!crypto || !crypto->authenticated) + { + if(wantserverid >= 0) + return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); + if(crypto_aeslevel.integer >= 3) + return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); + } + return CRYPTO_NOMATCH; + } + else if (len_in >= 13 && !memcmp(string, "infoResponse\x0A", 13)) + { + s = SearchInfostring(string + 13, "d0_blind_id"); + if(s) + Crypto_StoreHostKey(peeraddress, s, true); + return CRYPTO_NOMATCH; + } + else if (len_in >= 15 && !memcmp(string, "statusResponse\x0A", 15)) + { + char save = 0; + const char *p; + p = strchr(string + 15, '\n'); + if(p) + { + save = *p; + * (char *) p = 0; // cut off the string there + } + s = SearchInfostring(string + 15, "d0_blind_id"); + if(s) + Crypto_StoreHostKey(peeraddress, s, true); + if(p) + { + * (char *) p = save; + // invoking those nasal demons again (do not run this on the DS9k) + } + return CRYPTO_NOMATCH; + } + else if(len_in > 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) + { + const char *vlen_blind_id_ptr = NULL; + size_t len_blind_id_ptr = 0; + unsigned long k, v; + const char *challenge = data_in + 10; + const char *p; + int i; + int clientid = -1, serverid = -1, wantserverid = -1; + qboolean server_can_auth = true; + char wantserver_idfp[FP64_SIZE+1]; + int wantserver_aeslevel; + + // if we have a stored host key for the server, assume serverid to already be selected! + // (the loop will refuse to overwrite this one then) + wantserver_idfp[0] = 0; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, wantserver_idfp, sizeof(wantserver_idfp), &wantserver_aeslevel); + // requirement: wantserver_idfp is a full ID if wantserverid set + + // if we leave, we have to consider the connection + // unauthenticated; NOTE: this may be faked by a clever + // attacker to force an unauthenticated connection; so we have + // a safeguard check in place when encryption is required too + // in place, or when authentication is required by the server + crypto->authenticated = false; + + GetUntilNul(&data_in, &len_in); + if(!data_in) + return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present") : + (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + + // FTEQW extension protocol + while(len_in >= 8) + { + k = Crypto_LittleLong(data_in); + v = Crypto_LittleLong(data_in + 4); + data_in += 8; + len_in -= 8; + switch(k) + { + case PROTOCOL_VLEN: + if(len_in >= 4 + v) + { + k = Crypto_LittleLong(data_in); + data_in += 4; + len_in -= 4; + switch(k) + { + case PROTOCOL_D0_BLIND_ID: + vlen_blind_id_ptr = data_in; + len_blind_id_ptr = v; + break; + } + data_in += v; + len_in -= v; + } + break; + default: + break; + } + } + + if(!vlen_blind_id_ptr) + return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though authentication is required") : + (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + + data_in = vlen_blind_id_ptr; + len_in = len_blind_id_ptr; + + // parse fingerprints + // once we found a fingerprint we can auth to (ANY), select it as clientfp + // once we found a fingerprint in the first list that we know, select it as serverfp + + for(;;) + { + p = GetUntilNul(&data_in, &len_in); + if(!p) + break; + if(!*p) + { + if(!server_can_auth) + break; // other protocol message may follow + server_can_auth = false; + if(clientid >= 0) + break; + continue; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + { + if(pubkeys_havepriv[i]) + if(clientid < 0) + clientid = i; + if(server_can_auth) + if(serverid < 0) + if(wantserverid < 0 || i == wantserverid) + serverid = i; + } + } + if(clientid >= 0 && serverid >= 0) + break; + } + + // if stored host key is not found: + if(wantserverid >= 0 && serverid < 0) + return Crypto_ClientError(data_out, len_out, "Server CA does not match stored host key, refusing to connect"); + + if(serverid >= 0 || clientid >= 0) + { + // TODO at this point, fill clientside crypto struct! + MAKE_CDATA; + CDATA->cdata_id = ++cdata_id; + CDATA->s = serverid; + CDATA->c = clientid; + memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); + strlcpy(CDATA->challenge, challenge, sizeof(CDATA->challenge)); + crypto->client_keyfp[0] = 0; + crypto->client_idfp[0] = 0; + crypto->server_keyfp[0] = 0; + crypto->server_idfp[0] = 0; + memcpy(CDATA->wantserver_idfp, wantserver_idfp, sizeof(crypto->server_idfp)); + + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) + { + default: // dummy, never happens, but to make gcc happy... + case 0: + if(wantserver_aeslevel >= 3) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL); + CDATA->wantserver_aes = false; + break; + case 1: + CDATA->wantserver_aes = (wantserver_aeslevel >= 2); + break; + case 2: + CDATA->wantserver_aes = (wantserver_aeslevel >= 1); + break; + case 3: + if(wantserver_aeslevel <= 0) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL); + CDATA->wantserver_aes = true; + break; + } + + // build outgoing message + // append regular stuff + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\0\\id\\%d\\aeslevel\\%d\\challenge\\%s", CDATA->cdata_id, d0_rijndael_dll ? crypto_aeslevel.integer : 0, challenge)); + PutWithNul(&data_out_p, len_out, serverid >= 0 ? pubkeys_fp64[serverid] : ""); + PutWithNul(&data_out_p, len_out, clientid >= 0 ? pubkeys_fp64[clientid] : ""); + + if(clientid >= 0) + { + // I am the client, and my key is ok... so let's set client_keyfp and client_idfp + strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); + strlcpy(crypto->client_idfp, pubkeys_priv_fp64[CDATA->c], sizeof(crypto->client_idfp)); + } + + if(serverid >= 0) + { + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + CDATA->next_step = 1; + *len_out = data_out_p - data_out; + } + else if(clientid >= 0) + { + // skip over server auth, perform client auth only + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); + } + CDATA->next_step = 5; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + } + else + *len_out = data_out_p - data_out; + + return CRYPTO_DISCARD; + } + else + { + if(wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(wantserver_aeslevel >= 3) + return Crypto_ClientError(data_out, len_out, "Server insists on encryption, but neither can authenticate to the other"); + return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + } + } + else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && cls.connect_trying) + { + const char *cnt; + int id; + cnt = SearchInfostring(string + 4, "id"); + id = (cnt ? atoi(cnt) : -1); + cnt = SearchInfostring(string + 4, "cnt"); + if(!cnt) + return Crypto_ClientError(data_out, len_out, "d0pk\\ message without cnt"); + GetUntilNul(&data_in, &len_in); + if(!data_in) + return Crypto_ClientError(data_out, len_out, "d0pk\\ message without attachment"); + + if(!strcmp(cnt, "1")) + { + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 1) + return Crypto_SoftClientError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if((s = SearchInfostring(string + 4, "aes"))) + aes = atoi(s); + else + aes = false; + // we CANNOT toggle the AES status any more! + // as the server already decided + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(!aes && CDATA->wantserver_aes) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); + } + if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); + } + if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); + } + crypto->use_aes = aes; + + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\2\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed"); + } + CDATA->next_step = 3; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "3")) + { + static char msgbuf[32]; + size_t msgbuflen = sizeof(msgbuf); + size_t fpbuflen; + + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 3) + return Crypto_SoftClientError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (server authentication error)"); + } + if(status) + strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); + else + crypto->server_keyfp[0] = 0; + memset(crypto->server_idfp, 0, sizeof(crypto->server_idfp)); + fpbuflen = FP64_SIZE; + if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->server_idfp, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed"); + } + if(CDATA->wantserver_idfp[0]) + if(memcmp(CDATA->wantserver_idfp, crypto->server_idfp, sizeof(crypto->server_idfp))) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server ID does not match stored host key, refusing to connect"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); + } + + // cache the server key + Crypto_StoreHostKey(&cls.connect_address, va("%d %s@%s", crypto->use_aes ? 1 : 0, crypto->server_idfp, pubkeys_fp64[CDATA->s]), false); + + if(CDATA->c >= 0) + { + // client will auth next + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\4\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); + } + CDATA->next_step = 5; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else + { + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + // assume we got the empty challenge to finish the protocol + PutWithNul(&data_out_p, len_out, "challenge "); + *len_out = data_out_p - data_out; + --*len_out; // remove NUL terminator + return CRYPTO_REPLACE; + } + } + else if(!strcmp(cnt, "5")) + { + size_t fpbuflen; + unsigned char dhkey[DHKEY_SIZE]; + int i; + + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 5) + return Crypto_SoftClientError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if(CDATA->s < 0) // only if server didn't auth + { + if((s = SearchInfostring(string + 4, "aes"))) + aes = atoi(s); + else + aes = false; + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(!aes && CDATA->wantserver_aes) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); + } + if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); + } + if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); + } + crypto->use_aes = aes; + } + + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\6\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); + } + // XOR the two DH keys together to make one + for(i = 0; i < DHKEY_SIZE; ++i) + crypto->dhkey[i] ^= dhkey[i]; + // session key is FINISHED! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + return Crypto_SoftClientError(data_out, len_out, "Got unknown d0_blind_id message from server"); + } + + return CRYPTO_NOMATCH; +} diff --git a/crypto.h b/crypto.h new file mode 100644 index 00000000..19e9735d --- /dev/null +++ b/crypto.h @@ -0,0 +1,152 @@ +#ifndef CRYPTO_H +#define CRYPTO_H + +extern cvar_t crypto_developer; +extern cvar_t crypto_aeslevel; +#define ENCRYPTION_REQUIRED (crypto_aeslevel.integer >= 3) + +extern int crypto_keyfp_recommended_length; // applies to LOCAL IDs, and to ALL keys + +#define CRYPTO_HEADERSIZE 31 +// AES case causes 16 to 31 bytes overhead +// SHA256 case causes 16 bytes overhead as we truncate to 128bit + +#include "lhnet.h" + +#define FP64_SIZE 44 +#define DHKEY_SIZE 16 + +typedef struct +{ + unsigned char dhkey[DHKEY_SIZE]; // shared key, not NUL terminated + char client_idfp[FP64_SIZE+1]; + char client_keyfp[FP64_SIZE+1]; // NULL if signature fail + char server_idfp[FP64_SIZE+1]; + char server_keyfp[FP64_SIZE+1]; // NULL if signature fail + qboolean authenticated; + qboolean use_aes; + void *data; +} +crypto_t; + +void Crypto_Init(void); +void Crypto_Init_Commands(void); +void Crypto_Shutdown(void); +const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); +const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); +#define CRYPTO_NOMATCH 0 // process as usual (packet was not used) +#define CRYPTO_MATCH 1 // process as usual (packet was used) +#define CRYPTO_DISCARD 2 // discard this packet +#define CRYPTO_REPLACE 3 // make the buffer the current packet +int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); +int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); + +// if len_out is nonzero, the packet is to be sent to the client + +qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen); +crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress); +qboolean Crypto_ServerFinishInstance(crypto_t *out, crypto_t *in); // also clears allocated memory +const char *Crypto_GetInfoResponseDataString(void); + +// retrieves a host key for an address (can be exposed to menuqc, or used by the engine to look up stored keys e.g. for server bookmarking) +// pointers may be NULL +qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel); +int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen); // return value: -1 if more to come, +1 if valid, 0 if end of list + +// netconn protocol: +// non-crypto: +// getchallenge > +// < challenge +// connect > +// < accept (or: reject) +// crypto: +// getchallenge > +// < challenge SP NUL vlen d0pk NUL NUL +// +// IF serverfp: +// d0pk\cnt\0\challenge\\aeslevel\ NUL NUL +// > +// check if client would get accepted; if not, do "reject" now +// require non-control packets to be encrypted require non-control packets to be encrypted +// do not send anything yet do not send anything yet +// RESET to serverfp RESET to serverfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// < d0pk\cnt\1\aes\ NUL *startdata* +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// d0pk\cnt\2 NUL *challengedata* > +// d0_blind_id_authenticate_with_private_id_response() = 0 +// < d0pk\cnt\3 NUL *responsedata* +// d0_blind_id_authenticate_with_private_id_verify() = 1 +// store server's fingerprint NOW +// d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 +// +// IF clientfp AND NOT serverfp: +// RESET to clientfp RESET to clientfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// d0pk\cnt\0\challenge\\aeslevel\ NUL NUL NUL *startdata* +// > +// check if client would get accepted; if not, do "reject" now +// require non-control packets to be encrypted require non-control packets to be encrypted +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// < d0pk\cnt\5\aes\ NUL *challengedata* +// +// IF clientfp AND serverfp: +// RESET to clientfp RESET to clientfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// d0pk\cnt\4 NUL *startdata* > +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// < d0pk\cnt\5 NUL *challengedata* +// +// IF clientfp: +// d0_blind_id_authenticate_with_private_id_response() = 0 +// d0pk\cnt\6 NUL *responsedata* > +// d0_blind_id_authenticate_with_private_id_verify() = 1 +// store client's fingerprint NOW +// d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 +// note: the ... is the "connect" message, except without the challenge. Reinterpret as regular connect message on server side +// +// enforce encrypted transmission (key is XOR of the two DH keys) +// +// IF clientfp: +// < challenge (mere sync message) +// +// connect\... > +// < accept (ALWAYS accept if connection is encrypted, ignore challenge as it had been checked before) +// +// commence with ingame protocol + +// in short: +// server: +// getchallenge NUL d0_blind_id: reply with challenge with added fingerprints +// cnt=0: IF server will auth, cnt=1, ELSE cnt=5 +// cnt=2: cnt=3 +// cnt=4: cnt=5 +// cnt=6: send "challenge" +// client: +// challenge with added fingerprints: cnt=0; if client will auth but not server, append client auth start +// cnt=1: cnt=2 +// cnt=3: IF client will auth, cnt=4, ELSE rewrite as "challenge" +// cnt=5: cnt=6, server will continue by sending "challenge" (let's avoid sending two packets as response to one) +// other change: +// accept empty "challenge", and challenge-less connect in case crypto protocol has executed and finished +// statusResponse and infoResponse get an added d0_blind_id key that lists +// the keys the server can auth with and to in key@ca SPACE key@ca notation +// any d0pk\ message has an appended "id" parameter; messages with an unexpected "id" are ignored to prevent errors from multiple concurrent auth runs + + +// comparison to OTR: +// - encryption: yes +// - authentication: yes +// - deniability: no (attacker requires the temporary session key to prove you +// have sent a specific message, the private key itself does not suffice), no +// measures are taken to provide forgeability to even provide deniability +// against an attacker who knows the temporary session key, as using CTR mode +// for the encryption - which, together with deriving the MAC key from the +// encryption key, and MACing the ciphertexts instead of the plaintexts, +// would provide forgeability and thus deniability - requires longer +// encrypted packets and deniability was not a goal of this, as we may e.g. +// reserve the right to capture packet dumps + extra state info to prove a +// client/server has sent specific packets to prove cheating) +// - perfect forward secrecy: yes (session key is derived via DH key exchange) + +#endif diff --git a/dpdefs/dpextensions.qc b/dpdefs/dpextensions.qc index 4c57054a..602465c3 100644 --- a/dpdefs/dpextensions.qc +++ b/dpdefs/dpextensions.qc @@ -2369,3 +2369,15 @@ float JOINTTYPE_HINGE2 = 5; // hinge2; uses origin (anchor), angles (axis1), vel //description: //various physics properties can be defined in an entity and are executed via //ODE + +//DP_CRYPTO +//idea: divVerent +//darkplaces implementation: divVerent +//field definitions: (SVQC) +.string crypto_keyfp; // fingerprint of CA key the player used to authenticate, or string_null if not verified +.string crypto_mykeyfp; // fingerprint of CA key the server used to authenticate to the player, or string_null if not verified +.string crypto_idfp; // fingerprint of ID used by the player entity, or string_null if not identified +.string crypto_encryptmethod; // the string "AES128" if encrypting, and string_null if plaintext +.string crypto_signmethod; // the string "HMAC-SHA256" if signing, and string_null if plaintext +// there is no field crypto_myidfp, as that info contains no additional information the QC may have a use for +//description: diff --git a/dpdefs/menudefs.qc b/dpdefs/menudefs.qc index 45e74a14..e34082cd 100644 --- a/dpdefs/menudefs.qc +++ b/dpdefs/menudefs.qc @@ -448,3 +448,14 @@ float(string key) stringtokeynum = #341; // string(float keynum) keynumtostring = #340; //description: key bind setting/getting including support for switchable //bindmaps. + +//DP_CRYPTO +//idea: divVerent +//darkplaces implementation: divVerent +//field definitions: (MENUQC) +string crypto_getkeyfp(string serveraddress) = #633; // retrieves the cached host key's CA fingerprint of a server given by IP address +string crypto_getidfp(string serveraddress) = #634; // retrieves the cached host key fingerprint of a server given by IP address +string crypto_getencryptlevel(string serveraddress) = #635; // 0 if never encrypting, 1 supported, 2 requested, 3 required, appended by list of allowed methods in order of preference ("AES128"), preceded by a space each +string crypto_getmykeyfp(float i) = #636; // retrieves the CA key fingerprint of a given CA slot, or "" if slot is unused but more to come, or string_null if end of list +string crypto_getmyidfp(float i) = #637; // retrieves the ID fingerprint of a given CA slot, or "" if slot is unused but more to come, or string_null if end of list +//description: diff --git a/fs.c b/fs.c index a5281f58..abe57f42 100644 --- a/fs.c +++ b/fs.c @@ -1500,7 +1500,6 @@ void FS_GameDir_f (void) FS_ChangeGameDirs(numgamedirs, gamedirs, true, true); } -static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking); static const char *FS_SysCheckGameDir(const char *gamedir) { static char buf[8192]; @@ -1928,7 +1927,7 @@ FS_SysOpen Internal function used to create a qfile_t and open the relevant non-packed file on disk ==================== */ -static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking) +qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking) { qfile_t* file; diff --git a/fs.h b/fs.h index 42be6a7b..7ffba78e 100644 --- a/fs.h +++ b/fs.h @@ -43,6 +43,7 @@ typedef long long fs_offset_t; extern char fs_gamedir [MAX_OSPATH]; extern char fs_basedir [MAX_OSPATH]; +extern char fs_userdir [MAX_OSPATH]; // list of active game directories (empty if not running a mod) #define MAX_GAMEDIRS 16 @@ -57,7 +58,9 @@ extern char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH]; qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs); // already_loaded may be NULL if caller does not care const char *FS_WhichPack(const char *filename); -int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking); +void FS_CreatePath (char *path); +int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking); // uses absolute path +qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking); // uses absolute path qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet); qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet); qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet); diff --git a/hmac.c b/hmac.c index bd97ba43..af0d1196 100644 --- a/hmac.c +++ b/hmac.c @@ -4,14 +4,14 @@ qboolean hmac( hashfunc_t hfunc, int hlen, int hblock, unsigned char *out, - unsigned char *in, int n, - unsigned char *key, int k + const unsigned char *in, int n, + const unsigned char *key, int k ) { static unsigned char hashbuf[32]; static unsigned char k_xor_ipad[128]; static unsigned char k_xor_opad[128]; - static unsigned char catbuf[4096]; + static unsigned char catbuf[65600]; // 65535 bytes max quake packet size + 64 for the hash int i; if(sizeof(hashbuf) < (size_t) hlen) diff --git a/hmac.h b/hmac.h index 4d6358ae..44939002 100644 --- a/hmac.h +++ b/hmac.h @@ -1,14 +1,15 @@ #ifndef HMAC_H #define HMAC_H -typedef void (*hashfunc_t) (unsigned char *out, unsigned char *in, int n); +typedef void (*hashfunc_t) (unsigned char *out, const unsigned char *in, int n); qboolean hmac( hashfunc_t hfunc, int hlen, int hblock, unsigned char *out, - unsigned char *in, int n, - unsigned char *key, int k + const unsigned char *in, int n, + const unsigned char *key, int k ); #define HMAC_MDFOUR_16BYTES(out, in, n, key, k) hmac(mdfour, 16, 64, out, in, n, key, k) +#define HMAC_SHA256_32BYTES(out, in, n, key, k) hmac(sha256, 32, 64, out, in, n, key, k) #endif diff --git a/host.c b/host.c index 1b776d7b..cbe96a07 100644 --- a/host.c +++ b/host.c @@ -1114,6 +1114,10 @@ static void Host_Init (void) // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name) FS_Init(); + // must be after FS_Init + Crypto_Init(); + Crypto_Init_Commands(); + NetConn_Init(); Curl_Init(); //PR_Init(); @@ -1290,6 +1294,7 @@ void Host_Shutdown(void) CL_Shutdown(); Sys_Shutdown(); Log_Close(); + Crypto_Shutdown(); FS_Shutdown(); Con_Shutdown(); Memory_Shutdown(); diff --git a/makefile.inc b/makefile.inc index b98e2b25..7341be97 100644 --- a/makefile.inc +++ b/makefile.inc @@ -51,6 +51,12 @@ STRIP?=strip OBJ_SND_COMMON=snd_main.o snd_mem.o snd_mix.o snd_ogg.o snd_wav.o snd_modplug.o +# statically loading d0_blind_id +LIB_CRYPTO=`[ -n "$(DP_CRYPTO_STATIC_LIBDIR)" ] && echo \ $(DP_CRYPTO_STATIC_LIBDIR)/libd0_blind_id.a\ $(DP_CRYPTO_STATIC_LIBDIR)/libgmp.a` +CFLAGS_CRYPTO=`[ -n "$(DP_CRYPTO_STATIC_LIBDIR)" ] && echo \ -I$(DP_CRYPTO_STATIC_LIBDIR)/../include\ -DCRYPTO_STATIC` +LIB_CRYPTO_RIJNDAEL=`[ -n "$(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)" ] && echo \ $(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)/libd0_rijndael.a` +CFLAGS_CRYPTO_RIJNDAEL=`[ -n "$(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)" ] && echo \ -I$(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)/../include\ -DCRYPTO_RIJNDAEL_STATIC` + # Additional stuff for libmodplug LIB_SND_MODPLUG=`[ -n "$(DP_MODPLUG_STATIC_LIBDIR)" ] && echo \ $(DP_MODPLUG_STATIC_LIBDIR)/libmodplug.a\ -lstdc++` CFLAGS_SND_MODPLUG=`[ -n "$(DP_MODPLUG_STATIC_LIBDIR)" ] && echo \ -I$(DP_MODPLUG_STATIC_LIBDIR)/../include\ -DSND_MODPLUG_STATIC` @@ -95,6 +101,7 @@ OBJ_COMMON= \ cap_avi.o \ cap_ogg.o \ cd_shared.o \ + crypto.o \ cl_collision.o \ cl_demo.o \ cl_dyntexture.o \ @@ -183,7 +190,7 @@ OBJ_SDL= builddate.c sys_sdl.o vid_sdl.o $(OBJ_SND_COMMON) snd_sdl.o cd_sdl.o $( # Compilation -CFLAGS_COMMON=$(CFLAGS_MAKEDEP) $(CFLAGS_PRELOAD) $(CFLAGS_FS) $(CFLAGS_CG) $(CFLAGS_WARNINGS) $(CFLAGS_LIBJPEG) $(CFLAGS_D3D) +CFLAGS_COMMON=$(CFLAGS_MAKEDEP) $(CFLAGS_PRELOAD) $(CFLAGS_FS) $(CFLAGS_CG) $(CFLAGS_WARNINGS) $(CFLAGS_LIBJPEG) $(CFLAGS_D3D) $(CFLAGS_CRYPTO) CFLAGS_DEBUG=-ggdb CFLAGS_PROFILE=-g -pg -ggdb -fprofile-arcs CFLAGS_RELEASE= @@ -213,7 +220,7 @@ LDFLAGS_RELEASE=$(OPTIM_RELEASE) -DSVNREVISION=`test -d .svn && svnversion || ec OBJ_GLX= builddate.c sys_linux.o vid_glx.o keysym2ucs.o $(OBJ_SOUND) $(OBJ_CD) $(OBJ_COMMON) -LDFLAGS_UNIXCOMMON=-lm $(LIB_ODE) $(LIB_CG) $(LIB_JPEG) +LDFLAGS_UNIXCOMMON=-lm $(LIB_ODE) $(LIB_CG) $(LIB_JPEG) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) LDFLAGS_UNIXCL=-L$(UNIX_X11LIBPATH) -lX11 -lXpm -lXext -lXxf86dga -lXxf86vm $(LIB_SOUND) LDFLAGS_UNIXCL_PRELOAD=-lz -ljpeg -lpng -logg -ltheora -lvorbis -lvorbisenc -lvorbisfile -lcurl -lmodplug LDFLAGS_UNIXSV_PRELOAD=-lz -ljpeg -lpng -lcurl @@ -298,9 +305,9 @@ OBJ_WGL= builddate.c sys_win.o vid_wgl.o $(OBJ_SND_WIN) $(OBJ_WINCD) $(OBJ_COMMO # Link # see LDFLAGS_WINCOMMON in makefile -LDFLAGS_WINCL=$(LDFLAGS_WINCOMMON) -mwindows -lwinmm -luser32 -lgdi32 -ldxguid -ldinput -lcomctl32 -lws2_32 $(LDFLAGS_D3D) $(LIB_JPEG) -LDFLAGS_WINSV=$(LDFLAGS_WINCOMMON) -mconsole -lwinmm -lws2_32 $(LIB_JPEG) -LDFLAGS_WINSDL=$(LDFLAGS_WINCOMMON) $(SDLCONFIG_LIBS) $(LIB_SND_MODPLUG) -lwinmm -lws2_32 $(LIB_JPEG) +LDFLAGS_WINCL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) -mwindows -lwinmm -luser32 -lgdi32 -ldxguid -ldinput -lcomctl32 -lws2_32 $(LDFLAGS_D3D) $(LIB_JPEG) +LDFLAGS_WINSV=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) -mconsole -lwinmm -lws2_32 $(LIB_JPEG) +LDFLAGS_WINSDL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) $(SDLCONFIG_LIBS) $(LIB_SND_MODPLUG) -lwinmm -lws2_32 $(LIB_JPEG) EXE_WINCL=darkplaces.exe EXE_WINSV=darkplaces-dedicated.exe EXE_WINSDL=darkplaces-sdl.exe diff --git a/mdfour.c b/mdfour.c index c550651e..e057a94b 100644 --- a/mdfour.c +++ b/mdfour.c @@ -106,7 +106,7 @@ static void mdfour64(uint32 *M) m->A = A; m->B = B; m->C = C; m->D = D; } -static void copy64(uint32 *M, unsigned char *in) +static void copy64(uint32 *M, const unsigned char *in) { int i; @@ -133,7 +133,7 @@ void mdfour_begin(struct mdfour *md) } -static void mdfour_tail(unsigned char *in, int n) +static void mdfour_tail(const unsigned char *in, int n) { unsigned char buf[128]; uint32 M[16]; @@ -160,7 +160,7 @@ static void mdfour_tail(unsigned char *in, int n) } } -void mdfour_update(struct mdfour *md, unsigned char *in, int n) +void mdfour_update(struct mdfour *md, const unsigned char *in, int n) { uint32 M[16]; @@ -194,7 +194,7 @@ void mdfour_result(struct mdfour *md, unsigned char *out) } -void mdfour(unsigned char *out, unsigned char *in, int n) +void mdfour(unsigned char *out, const unsigned char *in, int n) { struct mdfour md; mdfour_begin(&md); diff --git a/mdfour.h b/mdfour.h index 69ca6f78..3ef654c8 100644 --- a/mdfour.h +++ b/mdfour.h @@ -46,9 +46,9 @@ struct mdfour { }; void mdfour_begin(struct mdfour *md); // old: MD4Init -void mdfour_update(struct mdfour *md, unsigned char *in, int n); //old: MD4Update +void mdfour_update(struct mdfour *md, const unsigned char *in, int n); //old: MD4Update void mdfour_result(struct mdfour *md, unsigned char *out); // old: MD4Final -void mdfour(unsigned char *out, unsigned char *in, int n); +void mdfour(unsigned char *out, const unsigned char *in, int n); #endif // _MDFOUR_H diff --git a/menu.c b/menu.c index 864dcbbb..a2f24779 100644 --- a/menu.c +++ b/menu.c @@ -33,7 +33,7 @@ static cvar_t forceqmenu = { 0, "forceqmenu", "0", "enables the quake menu inste static int NehGameType; enum m_state_e m_state; -char m_return_reason[32]; +char m_return_reason[128]; void M_Menu_Main_f (void); void M_Menu_SinglePlayer_f (void); diff --git a/menu.h b/menu.h index 7fdf78ba..b9e4cb10 100644 --- a/menu.h +++ b/menu.h @@ -52,7 +52,7 @@ enum m_state_e { }; extern enum m_state_e m_state; -extern char m_return_reason[32]; +extern char m_return_reason[128]; void M_Update_Return_Reason(const char *s); /* diff --git a/mvm_cmds.c b/mvm_cmds.c index e9cc68a9..044dedc7 100644 --- a/mvm_cmds.c +++ b/mvm_cmds.c @@ -13,6 +13,7 @@ const char *vm_m_extensions = "BX_WAL_SUPPORT " "DP_CINEMATIC_DPV " "DP_CSQC_BINDMAPS " +"DP_CRYPTO " "DP_GFX_FONTS " "DP_GFX_FONTS_FREETYPE " "DP_UTF8 " @@ -750,6 +751,99 @@ static void VM_M_getmousepos(void) VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); } +void VM_M_crypto_getkeyfp(void) +{ + lhnetaddress_t addr; + const char *s; + char keyfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getkeyfp); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, keyfp, sizeof(keyfp), NULL, 0, NULL)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( keyfp ); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +void VM_M_crypto_getidfp(void) +{ + lhnetaddress_t addr; + const char *s; + char idfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getidfp); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, idfp, sizeof(idfp), NULL)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( idfp ); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +void VM_M_crypto_getencryptlevel(void) +{ + lhnetaddress_t addr; + const char *s; + int aeslevel; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getencryptlevel); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, NULL, 0, &aeslevel)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(aeslevel ? va("%d AES128", aeslevel) : "0"); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +void VM_M_crypto_getmykeyfp(void) +{ + int i; + char keyfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); + + i = PRVM_G_FLOAT( OFS_PARM0 ); + switch(Crypto_RetrieveLocalKey(i, keyfp, sizeof(keyfp), NULL, 0)) + { + case -1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(""); + break; + case 0: + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + break; + default: + case 1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(keyfp); + break; + } +} +void VM_M_crypto_getmyidfp(void) +{ + int i; + char idfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); + + i = PRVM_G_FLOAT( OFS_PARM0 ); + switch(Crypto_RetrieveLocalKey(i, NULL, 0, idfp, sizeof(idfp))) + { + case -1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(""); + break; + case 0: + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + break; + default: + case 1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(idfp); + break; + } +} + prvm_builtin_t vm_m_builtins[] = { NULL, // #0 NULL function (not callable) VM_checkextension, // #1 @@ -1411,6 +1505,11 @@ NULL, // #629 VM_setkeybind, // #630 float(float key, string bind[, float bindmap]) setkeybind VM_getbindmaps, // #631 vector(void) getbindmap VM_setbindmaps, // #632 float(vector bm) setbindmap +VM_M_crypto_getkeyfp, // #633 string(string addr) crypto_getkeyfp +VM_M_crypto_getidfp, // #634 string(string addr) crypto_getidfp +VM_M_crypto_getencryptlevel, // #635 string(string addr) crypto_getencryptlevel +VM_M_crypto_getmykeyfp, // #636 string(float addr) crypto_getmykeyfp +VM_M_crypto_getmyidfp, // #637 string(float addr) crypto_getmyidfp NULL }; diff --git a/netconn.c b/netconn.c index 46e4e436..73de1f4c 100755 --- a/netconn.c +++ b/netconn.c @@ -112,6 +112,8 @@ int masterreplycount = 0; int serverquerycount = 0; int serverreplycount = 0; +challenge_t challenge[MAX_CHALLENGES]; + /// this is only false if there are still servers left to query static qboolean serverlist_querysleep = true; static qboolean serverlist_paused = false; @@ -122,6 +124,8 @@ static double serverlist_querywaittime = 0; static unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; static unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; +static unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; +static unsigned char cryptoreadbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; static int cl_numsockets; static lhnetsocket_t *cl_sockets[16]; @@ -162,16 +166,29 @@ qboolean serverlist_consoleoutput; static int nFavorites = 0; static lhnetaddress_t favorites[MAX_FAVORITESERVERS]; +static int nFavorites_idfp = 0; +static char favorites_idfp[MAX_FAVORITESERVERS][FP64_SIZE+1]; void NetConn_UpdateFavorites(void) { const char *p; nFavorites = 0; + nFavorites_idfp = 0; p = net_slist_favorites.string; while((size_t) nFavorites < sizeof(favorites) / sizeof(*favorites) && COM_ParseToken_Console(&p)) { - if(LHNETADDRESS_FromString(&favorites[nFavorites], com_token, 26000)) - ++nFavorites; + if(com_token[0] != '[' && strlen(com_token) == FP64_SIZE && !strchr(com_token, '.')) + // currently 44 bytes, longest possible IPv6 address: 39 bytes, so this works + // (if v6 address contains port, it must start with '[') + { + strlcpy(favorites_idfp[nFavorites_idfp], com_token, sizeof(favorites_idfp[nFavorites_idfp])); + ++nFavorites_idfp; + } + else + { + if(LHNETADDRESS_FromString(&favorites[nFavorites], com_token, 26000)) + ++nFavorites; + } } } @@ -405,6 +422,7 @@ static void ServerList_ViewList_Insert( serverlist_entry_t *entry ) entry->info.isfavorite = false; if(LHNETADDRESS_FromString(&addr, entry->info.cname, 26000)) { + char idfp[FP64_SIZE+1]; for(i = 0; i < nFavorites; ++i) { if(LHNETADDRESS_Compare(&addr, &favorites[i]) == 0) @@ -413,6 +431,17 @@ static void ServerList_ViewList_Insert( serverlist_entry_t *entry ) break; } } + if(Crypto_RetrieveHostKey(&addr, 0, NULL, 0, idfp, sizeof(idfp), NULL)) + { + for(i = 0; i < nFavorites_idfp; ++i) + { + if(!strcmp(idfp, favorites_idfp[i])) + { + entry->info.isfavorite = true; + break; + } + } + } } // FIXME: change this to be more readable (...) @@ -715,6 +744,8 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers unsigned int packetLen; unsigned int dataLen; unsigned int eom; + const void *sendme; + size_t sendmelen; // if a reliable message fragment has been lost, send it again if (conn->sendMessageLength && (realtime - conn->lastSendTime) > 1.0) @@ -738,13 +769,14 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; - if (NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress) == (int)packetLen) + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen) { conn->lastSendTime = realtime; packetsReSent++; } - totallen += packetLen + 28; + totallen += sendmelen + 28; } // if we have a new reliable message to send, do so @@ -788,13 +820,15 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; - NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); conn->lastSendTime = realtime; packetsSent++; reliableMessagesSent++; - totallen += packetLen + 28; + totallen += sendmelen + 28; } // if we have an unreliable message to send, do so @@ -816,12 +850,14 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28; - NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); packetsSent++; unreliableMessagesSent++; - totallen += packetLen + 28; + totallen += sendmelen + 28; } } @@ -1087,7 +1123,7 @@ void NetConn_UpdateSockets(void) } } -static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int length, protocolversion_t protocol, double newtimeout) +static int NetConn_ReceivedMessage(netconn_t *conn, const unsigned char *data, size_t length, protocolversion_t protocol, double newtimeout) { int originallength = length; if (length < 8) @@ -1171,7 +1207,16 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len unsigned int count; unsigned int flags; unsigned int sequence; - int qlength; + size_t qlength; + const void *sendme; + size_t sendmelen; + + originallength = length; + data = (const unsigned char *) Crypto_DecryptPacket(&conn->crypto, data, length, cryptoreadbuffer, &length, sizeof(cryptoreadbuffer)); + if(!data) + return 0; + if(length < 8) + return 0; qlength = (unsigned int)BuffBigLong(data); flags = qlength & ~NETFLAG_LENGTH_MASK; @@ -1262,7 +1307,8 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len conn->nq.sendSequence++; - if (NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress) == (int)packetLen) + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen) { conn->lastSendTime = realtime; packetsSent++; @@ -1285,7 +1331,9 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes += 8 + 28; StoreBigLong(temppacket, 8 | NETFLAG_ACK); StoreBigLong(temppacket + 4, sequence); - NetConn_Write(conn->mysocket, (unsigned char *)temppacket, 8, &conn->peeraddress); + sendme = Crypto_EncryptPacket(&conn->crypto, temppacket, 8, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); if (sequence == conn->nq.receiveSequence) { conn->lastMessageTime = realtime; @@ -1325,6 +1373,7 @@ static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int len void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, protocolversion_t initialprotocol) { + crypto_t *crypto; cls.connect_trying = false; M_Update_Return_Reason(""); // the connection request succeeded, stop current connection and set up a new connection @@ -1334,6 +1383,19 @@ void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peer Host_ShutdownServer (); // allocate a net connection to keep track of things cls.netcon = NetConn_Open(mysocket, peeraddress); + crypto = &cls.crypto; + if(crypto && crypto->authenticated) + { + Crypto_ServerFinishInstance(&cls.netcon->crypto, crypto); + Con_Printf("%s connection to %s has been established: server is %s@%.*s, I am %.*s@%.*s\n", + crypto->use_aes ? "Encrypted" : "Authenticated", + cls.netcon->address, + crypto->server_idfp[0] ? crypto->server_idfp : "-", + crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-", + crypto_keyfp_recommended_length, crypto->client_idfp[0] ? crypto->client_idfp : "-", + crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-" + ); + } Con_Printf("Connection accepted to %s\n", cls.netcon->address); key_dest = key_game; m_state = m_none; @@ -1581,6 +1643,8 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat const char *s; char *string, addressstring2[128], ipstring[32]; char stringbuf[16384]; + char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + size_t sendlength; // quakeworld ingame packet fromserver = cls.netcon && mysocket == cls.netcon->mysocket && !LHNETADDRESS_Compare(&cls.netcon->peeraddress, peeraddress); @@ -1605,7 +1669,34 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat Com_HexDumpToConsole(data, length); } - if (length > 10 && !memcmp(string, "challenge ", 10) && cls.rcon_trying) + sendlength = sizeof(senddata) - 4; + switch(Crypto_ClientParsePacket(string, length, senddata+4, &sendlength, peeraddress)) + { + case CRYPTO_NOMATCH: + // nothing to do + break; + case CRYPTO_MATCH: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + break; + case CRYPTO_DISCARD: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + return true; + break; + case CRYPTO_REPLACE: + string = senddata+4; + length = sendlength; + break; + } + + if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.rcon_trying) { int i = 0, j; for (j = 0;j < MAX_RCONS;j++) @@ -1656,7 +1747,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat } } } - if (length > 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) + if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) { // darkplaces or quake3 char protocolnames[1400]; @@ -1678,7 +1769,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat } if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying) { - char rejectreason[32]; + char rejectreason[128]; cls.connect_trying = false; string += 7; length = min(length - 7, (int)sizeof(rejectreason) - 1); @@ -1925,7 +2016,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat return ret; } // netquake control packets, supported for compatibility only - if (length >= 5 && (control = BuffBigLong(data)) && (control & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (control & NETFLAG_LENGTH_MASK) == length) + if (length >= 5 && (control = BuffBigLong(data)) && (control & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (control & NETFLAG_LENGTH_MASK) == length && !ENCRYPTION_REQUIRED) { int n; serverlist_info_t *info; @@ -2148,15 +2239,6 @@ void NetConn_ClientFrame(void) } } -#define MAX_CHALLENGES 128 -struct challenge_s -{ - lhnetaddress_t address; - double time; - char string[12]; -} -challenge[MAX_CHALLENGES]; - static void NetConn_BuildChallengeString(char *buffer, int bufferlength) { int i; @@ -2179,6 +2261,7 @@ static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg unsigned int nb_clients = 0, nb_bots = 0, i; int length; char teambuf[3]; + const char *crypto_idstring; SV_VM_Begin(); @@ -2202,7 +2285,7 @@ static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg char *p; const char *q; p = qcstatus; - for(q = str; *q; ++q) + for(q = str; *q && p - qcstatus < (ssize_t)(sizeof(qcstatus)) - 1; ++q) if(*q != '\\' && *q != '\n') *p++ = *q; *p = 0; @@ -2210,18 +2293,21 @@ static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg } /// \TODO: we should add more information for the full status string + crypto_idstring = Crypto_GetInfoResponseDataString(); length = dpsnprintf(out_msg, out_size, "\377\377\377\377%s\x0A" "\\gamename\\%s\\modname\\%s\\gameversion\\%d\\sv_maxclients\\%d" "\\clients\\%d\\bots\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d" "%s%s" "%s%s" + "%s%s" "%s", fullstatus ? "statusResponse" : "infoResponse", gamename, com_modname, gameversion.integer, svs.maxclients, nb_clients, nb_bots, sv.worldbasename, hostname.string, NET_PROTOCOL_VERSION, *qcstatus ? "\\qcstatus\\" : "", qcstatus, challenge ? "\\challenge\\" : "", challenge ? challenge : "", + crypto_idstring ? "\\d0_blind_id\\" : "", crypto_idstring ? crypto_idstring : "", fullstatus ? "\n" : ""); // Make sure it fits in the buffer @@ -2597,6 +2683,8 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat char *s, *string, response[1400], addressstring2[128]; static char stringbuf[16384]; qboolean islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP); + char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + size_t sendlength, response_len; if (!sv.active) return false; @@ -2630,6 +2718,33 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat Com_HexDumpToConsole(data, length); } + sendlength = sizeof(senddata) - 4; + switch(Crypto_ServerParsePacket(string, length, senddata+4, &sendlength, peeraddress)) + { + case CRYPTO_NOMATCH: + // nothing to do + break; + case CRYPTO_MATCH: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + break; + case CRYPTO_DISCARD: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + return true; + break; + case CRYPTO_REPLACE: + string = senddata+4; + length = sendlength; + break; + } + if (length >= 12 && !memcmp(string, "getchallenge", 12) && (islocal || sv_public.integer > -3)) { for (i = 0, best = 0, besttime = realtime;i < MAX_CHALLENGES;i++) @@ -2650,24 +2765,50 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat } challenge[i].time = realtime; // send the challenge - NetConn_WriteString(mysocket, va("\377\377\377\377challenge %s", challenge[i].string), peeraddress); + dpsnprintf(response, sizeof(response), "\377\377\377\377challenge %s", challenge[i].string); + response_len = strlen(response) + 1; + Crypto_ServerAppendToChallenge(string, length, response, &response_len, sizeof(response)); + NetConn_Write(mysocket, response, response_len, peeraddress); return true; } if (length > 8 && !memcmp(string, "connect\\", 8)) { + crypto_t *crypto = Crypto_ServerGetInstance(peeraddress); string += 7; length -= 7; - if (!(s = SearchInfostring(string, "challenge"))) - return true; - // validate the challenge - for (i = 0;i < MAX_CHALLENGES;i++) - if(challenge[i].time > 0) - if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) - break; - // if the challenge is not recognized, drop the packet - if (i == MAX_CHALLENGES) - return true; + if(crypto && crypto->authenticated) + { + // no need to check challenge + if(crypto_developer.integer) + { + Con_Printf("%s connection to %s is being established: client is %s@%.*s, I am %.*s@%.*s\n", + crypto->use_aes ? "Encrypted" : "Authenticated", + addressstring2, + crypto->client_idfp[0] ? crypto->client_idfp : "-", + crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-", + crypto_keyfp_recommended_length, crypto->server_idfp[0] ? crypto->server_idfp : "-", + crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-" + ); + } + } + else + { + if ((s = SearchInfostring(string, "challenge"))) + { + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) + return true; + } + } + + if((s = SearchInfostring(string, "message"))) + Con_DPrintf("Connecting client %s sent us the message: %s\n", addressstring2, s); if(!(islocal || sv_public.integer > -2)) { @@ -2693,6 +2834,39 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0) { // this is a known client... + if(crypto && crypto->authenticated) + { + // reject if changing key! + if(client->netconnection->crypto.authenticated) + { + if( + strcmp(client->netconnection->crypto.client_idfp, crypto->client_idfp) + || + strcmp(client->netconnection->crypto.server_idfp, crypto->server_idfp) + || + strcmp(client->netconnection->crypto.client_keyfp, crypto->client_keyfp) + || + strcmp(client->netconnection->crypto.server_keyfp, crypto->server_keyfp) + ) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to change key of crypto.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to change key of crypto.", peeraddress); + return true; + } + } + } + else + { + // reject if downgrading! + if(client->netconnection->crypto.authenticated) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to downgrade crypto.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to downgrade crypto.", peeraddress); + return true; + } + } if (client->spawned) { // client crashed and is coming back, @@ -2700,6 +2874,8 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto); SV_VM_Begin(); SV_SendServerinfo(client); SV_VM_End(); @@ -2710,6 +2886,8 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat // so we send a duplicate reply if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending duplicate accept to %s.\n", addressstring2); + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto); NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); } return true; @@ -2730,6 +2908,8 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address); NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); // now set up the client + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&conn->crypto, crypto); SV_VM_Begin(); SV_ConnectClient(clientnum, conn); SV_VM_End(); @@ -2861,7 +3041,7 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat // protocol // (this protects more modern protocols against being used for // Quake packet flood Denial Of Service attacks) - if (length >= 5 && (i = BuffBigLong(data)) && (i & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (i & NETFLAG_LENGTH_MASK) == length && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3)) + if (length >= 5 && (i = BuffBigLong(data)) && (i & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (i & NETFLAG_LENGTH_MASK) == length && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) && !ENCRYPTION_REQUIRED) { int c; int protocolnumber; diff --git a/netconn.h b/netconn.h index b0eb6da2..f93d297e 100755 --- a/netconn.h +++ b/netconn.h @@ -34,6 +34,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define NETFLAG_EOM 0x00080000 #define NETFLAG_UNRELIABLE 0x00100000 #define NETFLAG_CTL 0x80000000 +#define NETFLAG_CRYPTO 0x40000000 #define NET_PROTOCOL_VERSION 3 @@ -219,6 +220,7 @@ typedef struct netconn_s netgraphitem_t outgoing_netgraph[NETGRAPH_PACKETS]; char address[128]; + crypto_t crypto; } netconn_t; extern netconn_t *netconn_list; @@ -441,5 +443,16 @@ void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryq /// called whenever net_slist_favorites changes void NetConn_UpdateFavorites(void); +#define MAX_CHALLENGES 128 +typedef struct challenge_s +{ + lhnetaddress_t address; + double time; + char string[12]; +} +challenge_t; + +extern challenge_t challenge[MAX_CHALLENGES]; + #endif diff --git a/progsvm.h b/progsvm.h index 5bf4080d..ee738fe0 100644 --- a/progsvm.h +++ b/progsvm.h @@ -274,6 +274,12 @@ typedef struct prvm_prog_fieldoffsets_s int userwavefunc_param1; // csqc (userwavefunc) int userwavefunc_param2; // csqc (userwavefunc) int userwavefunc_param3; // csqc (userwavefunc) + + int crypto_keyfp; // svqc (crypto) + int crypto_mykeyfp; // svqc (crypto) + int crypto_idfp; // svqc (crypto) + int crypto_encryptmethod; // svqc (crypto) + int crypto_signmethod; // svqc (crypto) } prvm_prog_fieldoffsets_t; diff --git a/prvm_edict.c b/prvm_edict.c index 7a81d008..1d97bb37 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -1662,6 +1662,12 @@ void PRVM_FindOffsets(void) prog->fieldoffsets.userwavefunc_param2 = PRVM_ED_FindFieldOffset("userwavefunc_param2"); prog->fieldoffsets.userwavefunc_param3 = PRVM_ED_FindFieldOffset("userwavefunc_param3"); + prog->fieldoffsets.crypto_keyfp = PRVM_ED_FindFieldOffset("crypto_keyfp"); + prog->fieldoffsets.crypto_mykeyfp = PRVM_ED_FindFieldOffset("crypto_mykeyfp"); + prog->fieldoffsets.crypto_idfp = PRVM_ED_FindFieldOffset("crypto_idfp"); + prog->fieldoffsets.crypto_encryptmethod = PRVM_ED_FindFieldOffset("crypto_encryptmethod"); + prog->fieldoffsets.crypto_signmethod = PRVM_ED_FindFieldOffset("crypto_signmethod"); + prog->funcoffsets.CSQC_ConsoleCommand = PRVM_ED_FindFunctionOffset("CSQC_ConsoleCommand"); prog->funcoffsets.CSQC_Ent_Remove = PRVM_ED_FindFunctionOffset("CSQC_Ent_Remove"); prog->funcoffsets.CSQC_Ent_Spawn = PRVM_ED_FindFunctionOffset("CSQC_Ent_Spawn"); diff --git a/quakedef.h b/quakedef.h index 771804b3..06ab2a94 100644 --- a/quakedef.h +++ b/quakedef.h @@ -88,6 +88,7 @@ extern char engineversion[128]; #define MAX_LEVELNETWORKEYES 0 // no portal support #define MAX_OCCLUSION_QUERIES 256 +#define CRYPTO_HOSTKEY_HASHSIZE 256 #define MAX_NETWM_ICON 1026 // one 32x32 #define MAX_WATERPLANES 2 @@ -154,6 +155,7 @@ extern char engineversion[128]; #define MAX_LEVELNETWORKEYES 512 ///< max number of locations that can be added to pvs when culling network entities (must be at least 2 for prediction) #define MAX_OCCLUSION_QUERIES 4096 ///< max number of GL_ARB_occlusion_query objects that can be used in one frame +#define CRYPTO_HOSTKEY_HASHSIZE 8192 ///< number of hash buckets for accelerating host key lookups #define MAX_NETWM_ICON 352822 // 16x16, 22x22, 24x24, 32x32, 48x48, 64x64, 128x128, 256x256, 512x512 #define MAX_WATERPLANES 16 ///< max number of water planes visible (each one causes additional view renders) @@ -374,6 +376,7 @@ extern char engineversion[128]; #include "r_textures.h" +#include "crypto.h" #include "draw.h" #include "screen.h" #include "netconn.h" diff --git a/server.h b/server.h index 7b1ce0c6..c9da9a8c 100644 --- a/server.h +++ b/server.h @@ -53,7 +53,6 @@ typedef struct server_static_s unsigned char *csqc_progdata; size_t csqc_progsize_deflated; unsigned char *csqc_progdata_deflated; - } server_static_t; //============================================================================= diff --git a/sv_main.c b/sv_main.c index db56fde3..7a2a31cd 100644 --- a/sv_main.c +++ b/sv_main.c @@ -997,6 +997,18 @@ void SV_ConnectClient (int clientnum, netconn_t *netconnection) Con_DPrintf("Client %s connected\n", client->netconnection ? client->netconnection->address : "botclient"); + if(client->netconnection && client->netconnection->crypto.authenticated) + { + Con_Printf("%s connection to %s has been established: client is %s@%.*s, I am %.*s@%.*s\n", + client->netconnection->crypto.use_aes ? "Encrypted" : "Authenticated", + client->netconnection->address, + client->netconnection->crypto.client_idfp[0] ? client->netconnection->crypto.client_idfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.client_keyfp[0] ? client->netconnection->crypto.client_keyfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.server_idfp[0] ? client->netconnection->crypto.server_idfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.server_keyfp[0] ? client->netconnection->crypto.server_keyfp : "-" + ); + } + strlcpy(client->name, "unconnected", sizeof(client->name)); strlcpy(client->old_name, "unconnected", sizeof(client->old_name)); client->spawned = false; @@ -3426,6 +3438,41 @@ static void SV_VM_CB_InitEdict(prvm_edict_t *e) // Invalid / Bot PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.netaddress)->string = PRVM_SetEngineString("null/botclient"); } + if(prog->fieldoffsets.crypto_idfp >= 0) + { // Valid Field; Process + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_idfp[0]) + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_idfp)->string = PRVM_SetEngineString(svs.clients[num].netconnection->crypto.client_idfp); + else + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_idfp)->string = 0; + } + if(prog->fieldoffsets.crypto_keyfp >= 0) + { // Valid Field; Process + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_keyfp[0]) + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_keyfp)->string = PRVM_SetEngineString(svs.clients[num].netconnection->crypto.client_keyfp); + else + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_keyfp)->string = 0; + } + if(prog->fieldoffsets.crypto_mykeyfp >= 0) + { // Valid Field; Process + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.server_keyfp[0]) + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_mykeyfp)->string = PRVM_SetEngineString(svs.clients[num].netconnection->crypto.server_keyfp); + else + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_mykeyfp)->string = 0; + } + if(prog->fieldoffsets.crypto_encryptmethod >= 0) + { // Valid Field; Process + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.use_aes) + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_encryptmethod)->string = PRVM_SetEngineString("AES128"); + else + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_encryptmethod)->string = 0; + } + if(prog->fieldoffsets.crypto_signmethod >= 0) + { // Valid Field; Process + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated) + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_signmethod)->string = PRVM_SetEngineString("HMAC-SHA256"); + else + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.crypto_signmethod)->string = 0; + } } } diff --git a/svvm_cmds.c b/svvm_cmds.c index 459c73f4..c3319365 100644 --- a/svvm_cmds.c +++ b/svvm_cmds.c @@ -19,6 +19,7 @@ const char *vm_sv_extensions = "DP_CON_SET " "DP_CON_SETA " "DP_CON_STARTMAP " +"DP_CRYPTO " "DP_CSQC_BINDMAPS " "DP_CSQC_ENTITYNOCULL " "DP_CSQC_ENTITYTRANSPARENTSORTING_OFFSET " -- 2.39.2