]> git.xonotic.org Git - xonotic/darkplaces.git/commitdiff
Cryptographic authentication support for the d0_blind_id library available on http...
authordivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 15 Oct 2010 13:47:19 +0000 (13:47 +0000)
committerdivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 15 Oct 2010 13:47:19 +0000 (13:47 +0000)
git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@10534 d7cf8633-e32d-0410-b094-e92efae38249

26 files changed:
client.h
crypto-keygen-standalone-brute.sh [new file with mode: 0755]
crypto-keygen-standalone.c [new file with mode: 0644]
crypto.c [new file with mode: 0644]
crypto.h [new file with mode: 0644]
dpdefs/dpextensions.qc
dpdefs/menudefs.qc
fs.c
fs.h
hmac.c
hmac.h
host.c
makefile.inc
mdfour.c
mdfour.h
menu.c
menu.h
mvm_cmds.c
netconn.c
netconn.h
progsvm.h
prvm_edict.c
quakedef.h
server.h
sv_main.c
svvm_cmds.c

index 2ab8079c991786b1683073dfa8fe5fe06695b319..f840b331b3b1e30b171b059b6854c1eb2b07e1a4 100644 (file)
--- 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 (executable)
index 0000000..a081b06
--- /dev/null
@@ -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 (file)
index 0000000..c43ce0b
--- /dev/null
@@ -0,0 +1,578 @@
+#include <d0_blind_id/d0_blind_id.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+// 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 = <pubkey> <modulus>
+                               pubkeyfile = optarg;
+                               mask |= 1;
+                               break;
+                       case 'P': // d0sk = <privkey> <modulus>
+                               privkeyfile = optarg;
+                               mask |= 2;
+                               break;
+                       case 'i': // d0pi = <pubid>
+                               pubidfile = optarg;
+                               mask |= 4;
+                               break;
+                       case 'I': // d0si = <privid>
+                               prividfile = optarg;
+                               mask |= 8;
+                               break;
+                       case 'j': // d0iq = <req>
+                               idreqfile = optarg;
+                               mask |= 0x10;
+                               break;
+                       case 'J': // d0ir = <resp>
+                               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 (file)
index 0000000..896c3aa
--- /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 <d0_blind_id/d0_blind_id.h>
+
+#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 <d0_blind_id/d0_rijndael.h>
+
+#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 (file)
index 0000000..19e9735
--- /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 <challenge> NUL vlen <size> d0pk <fingerprints I can auth to> NUL NUL <other fingerprints I accept>
+//
+//     IF serverfp:
+//     d0pk\cnt\0\challenge\<challenge>\aeslevel\<level> NUL <serverfp> NUL <clientfp>
+//                                                             >
+//                                                               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\<aesenabled> 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\<challenge>\aeslevel\<level> NUL NUL <clientfp> 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\<aesenabled> 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
index 4c57054af7a1c2ff4875390ca50dcdfe01ae3e7f..602465c3501da45685bbffb41db027781b9496ea 100644 (file)
@@ -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:
index 45e74a14993c493317d6500e3d699fcc8f626a77..e34082cd35fde1f50b5f301ec905bbc3255b19d1 100644 (file)
@@ -448,3 +448,14 @@ float(string key) stringtokeynum = #341;
 //<also allowed builtin number to match EXT_CSQC> 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 a5281f584be580bb33e8073bfdc906094730c974..abe57f424c7c5a4d2fb5630caab0aa182199b4af 100644 (file)
--- 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 42be6a7b6a65244e1d9952403cdb916d99f7e33e..7ffba78eab5c028eaa756bec7be349dcdeb4fdcd 100644 (file)
--- 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 bd97ba43fd2c7321d6d5229e80c98c4c0a27b01e..af0d11968e64bca065d1212d05118a0621453792 100644 (file)
--- 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 4d6358ae1cf436ceb436bf23e357a9404b61c717..44939002f66561a67b04bb30325030dd66291bdb 100644 (file)
--- 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 1b776d7b5e761da4e48191033c170b0496debdc2..cbe96a07f9ba37e6f7e7e5fdcb0eaa8233b83340 100644 (file)
--- 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();
index b98e2b25bee52c37b79c6bef9490dc16830419c5..7341be977d09748f966cf0948acb1c7f04030b7d 100644 (file)
@@ -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
index c550651ec37b8eba38d1f747631e2ba425baef5f..e057a94bde771a84ad1d21331893b406ce5bd344 100644 (file)
--- 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);
index 69ca6f78f92fda63d95568cdba546e1fcad56950..3ef654c87e157456fe4e41a0899f9a24d5056a7d 100644 (file)
--- 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 864dcbbb9ebafa02352c6b480feb6d1890668980..a2f24779cc68bc451a487cca5adf6723629bae89 100644 (file)
--- 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 7fdf78bad073826dbf7b0092a799e1356088a3d9..b9e4cb10f455a2a9ea0a57085cf2af5127bf32a3 100644 (file)
--- 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);
 
 /*
index e9cc68a986177b5ffc1609f6a51acc9ee4f1596d..044dedc73ca2f10f6c6ce8f831f98f87998c1f93 100644 (file)
@@ -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
 };
 
index 46e4e43601af0f02ca1655de0f7550cefad4e988..73de1f4c532e37e6c4e350bba0988ecc3cbef4ff 100755 (executable)
--- 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;
index b0eb6da22a3b0d1dd711d361c551416bb005a2bb..f93d297eb6f52d78f49af8b5c9f004c65442d943 100755 (executable)
--- 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
 
index 5bf4080d78bcb60e18ffdab93ff700b6b0b65a95..ee738fe05032b6f6869c3a61f4b3a6fca4ee30b0 100644 (file)
--- 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;
 
index 7a81d008c498be1caa1ba29c08dafce5311f9294..1d97bb374b52d1e1c2fe28ec22b09d52b4647fee 100644 (file)
@@ -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");
index 771804b35fc6d36954e14e14ea70ae46535d843d..06ab2a941d5d9d7104bc280c3051ea883b5872b7 100644 (file)
@@ -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"
index 7b1ce0c61570c0428480322f96777b56e5200c11..c9da9a8c765597059212e6a35f744fb5d4f3c02c 100644 (file)
--- 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;
 
 //=============================================================================
index db56fde35da9ef2243d3d2db0ee6948f6770c937..7a2a31cd2c186fadfde12b04e79f3f079fa56e06 100644 (file)
--- 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;
+               }
        }
 }
 
index 459c73f4e745ffc797d6fbe142d7694158075fce..c3319365d6c0b36c99a0cbf0d275645729090453 100644 (file)
@@ -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 "