identity: Give players unique unspoofable player IDs.

Players generate private keys (stored in the .sta files). Replies to NET_PING are now signed, so if the ping of a
player is not ∞, and you recognise the player ID of the player, then you know the ping is from the same player as
before.
master
Cyp 2013-06-03 21:11:06 +02:00
parent e67fde4eec
commit 69e0e280b9
10 changed files with 412 additions and 113 deletions

View File

@ -20,6 +20,12 @@
#include "crc.h"
#include "lib/netplay/netsocket.h" // For htonl
#include <openssl/sha.h>
#include <openssl/obj_mac.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/err.h>
// Invariant:
// crcTable[0] = 0;
@ -67,99 +73,15 @@ uint32_t crcSumVector2i(uint32_t crc, const Vector2i *data, size_t dataLen)
return crc;
}
// Let primes[n] = {2, 3, 5, 7, 11, ...}.
// Invariant: sha256TableH[n] = sqrt(primes[n]) << 32
static const uint32_t sha256TableH[8] = {0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19};
// Invariant: sha256TableK[n] = pow(primes[n], 1/3.) << 32
static const uint32_t sha256TableK[64] = {0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2};
// Rotate right.
static inline uint32_t ror(uint32_t a, int shift)
{
return (a >> shift) | (a << (32 - shift)); // This is correctly optimised to a rotate-right instruction, in GCC, at least.
}
static void sha256SumBlock(uint32_t *w, uint32_t *h)
{
for (unsigned i = 0; i < 16; ++i)
{
w[i] = ntohl(w[i]); // Interpret data as big-endian 32-bit numbers.
}
// Extend w block from length 16 to 64.
for (unsigned i = 16; i < 64; ++i)
{
uint32_t s0 = ror(w[i - 15], 7) ^ ror(w[i - 15], 18) ^ (w[i - 15] >> 3);
uint32_t s1 = ror(w[i - 2], 17) ^ ror(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], j = h[7];
for (unsigned i = 0; i < 64; ++i)
{
uint32_t s0 = ror(a, 2) ^ ror(a, 13) ^ ror(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c); // Can be simplified to ((a ^ b) & c) ^ (a & b), but GCC does this at compile time.
uint32_t t2 = s0 + maj;
uint32_t s1 = ror(e, 6) ^ ror(e, 11) ^ ror(e, 25);
uint32_t ch = (e & (f ^ g)) ^ g; // Equivalent to (e & f) ^ (~e & g), but saves a 'notl' instruction.
uint32_t t1 = j + s1 + ch + sha256TableK[i] + w[i];
j = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
h[5] += f;
h[6] += g;
h[7] += j;
}
Sha256 sha256Sum(void const *data, size_t dataLen)
{
uint32_t h[8];
std::copy(sha256TableH, sha256TableH + 8, h);
static_assert(Sha256::Bytes == SHA256_DIGEST_LENGTH, "Size mismatch.");
// Add the suffix to the data.
char suffix[64 + 8];
size_t suffixLen = ((-9 - dataLen) & 63) + 9;
suffix[0] = 0x80;
memset(suffix + 1, 0x00, suffixLen - 1 - 8);
uint32_t size[2] = {htonl(dataLen >> 29), htonl(dataLen << 3)}; // Convert dataLen to number of bits, in big-endian format.
memcpy(suffix + suffixLen - 8, (char *)size, 8);
// Hash the data.
uint32_t w[64]; // 16 elements are passed to sha256SumBlock(), rest is used as scratch space by sha256SumBlock().
for (size_t z = 0; z + 63 < dataLen; z += 64)
{
memcpy((char *)w, (char const *)data + z, 64);
sha256SumBlock(w, h);
}
memcpy((char *)w, (char const *)data + (dataLen & ~63), dataLen & 63);
memcpy((char *)w + (dataLen & 63), suffix, ((suffixLen - 1) & 63) + 1);
sha256SumBlock(w, h);
if (suffixLen > 64)
{
memcpy((char *)w, suffix + (suffixLen & 63), 64);
sha256SumBlock(w, h);
}
// Convert result to big-endian, and return it.
for (unsigned i = 0; i < 8; ++i)
{
h[i] = htonl(h[i]);
}
Sha256 ret;
memcpy(ret.bytes, h, 32);
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, data, dataLen);
SHA256_Final(ret.bytes, &ctx);
return ret;
}
@ -183,7 +105,7 @@ std::string Sha256::toString() const
std::string str;
str.resize(Bytes*2);
char const *hexDigits = "0123456789abcdef";
for (unsigned n = 0; n < Bytes; ++n)
for (int n = 0; n < Bytes; ++n)
{
str[n*2 ] = hexDigits[bytes[n] >> 4];
str[n*2 + 1] = hexDigits[bytes[n] & 15];
@ -218,3 +140,222 @@ void Sha256::fromString(std::string const &s)
bytes[n/2] |= h << (n%2? 0 : 4);
}
}
const int EcKey::curveId = NID_secp224r1;
EcKey::EcKey()
: vKey(nullptr)
{}
EcKey::EcKey(EcKey const &b)
{
vKey = (void *)EC_KEY_dup((EC_KEY *)b.vKey);
}
#ifdef WZ_CXX11
EcKey::EcKey(EcKey &&b)
: vKey(nullptr)
{
std::swap(vKey, b.vKey);
}
#endif
EcKey::~EcKey()
{
clear();
}
EcKey &EcKey::operator =(EcKey const &b)
{
clear();
vKey = (void *)EC_KEY_dup((EC_KEY *)b.vKey);
return *this;
}
#ifdef WZ_CXX11
EcKey &EcKey::operator =(EcKey &&b)
{
std::swap(vKey, b.vKey);
return *this;
}
#endif
void EcKey::clear()
{
EC_KEY_free((EC_KEY *)vKey);
vKey = nullptr;
}
bool EcKey::empty() const
{
return vKey == nullptr;
}
bool EcKey::hasPrivate() const
{
return vKey != nullptr && EC_KEY_get0_private_key((EC_KEY *)vKey) != nullptr;
}
EcKey::Sig EcKey::sign(void const *data, size_t dataLen) const
{
if (vKey == nullptr)
{
debug(LOG_ERROR, "No key");
return Sig();
}
ECDSA_SIG *isig = ECDSA_do_sign((unsigned char const *)data, dataLen, (EC_KEY *)vKey);
if (isig == nullptr)
{
debug(LOG_ERROR, "%s", ERR_error_string(ERR_get_error(), nullptr));
return Sig();
}
Sig sig(ECDSA_size((EC_KEY *)vKey));
unsigned char *sigPtr = &sig[0];
sig.resize(i2d_ECDSA_SIG(isig, &sigPtr));
ECDSA_SIG_free(isig);
return sig;
}
bool EcKey::verify(Sig const &sig, void const *data, size_t dataLen) const
{
if (vKey == nullptr)
{
debug(LOG_ERROR, "No key");
return false;
}
unsigned char const *sigPtr = &sig[0];
ECDSA_SIG *isig = d2i_ECDSA_SIG(nullptr, &sigPtr, sig.size());
if (isig == nullptr)
{
debug(LOG_ERROR, "%s", ERR_error_string(ERR_get_error(), nullptr));
return false;
}
int valid = ECDSA_do_verify((unsigned char const *)data, dataLen, isig, (EC_KEY *)vKey);
if (valid < 0)
{
debug(LOG_ERROR, "%s", ERR_error_string(ERR_get_error(), nullptr));
}
ECDSA_SIG_free(isig);
return valid == 1;
}
EcKey::Key EcKey::toBytes(Privacy privacy) const
{
int (*toBytesFunc)(EC_KEY *key, unsigned char **out) = nullptr;
switch (privacy)
{
case Private: toBytesFunc = i2d_ECPrivateKey; break; // Note that the format for private keys is somewhat bloated, and even contains the public key which could be (efficiently) computed from the private key.
case Public: toBytesFunc = i2o_ECPublicKey; break;
}
if (empty())
{
debugBacktrace(LOG_ERROR, "No key");
return Key();
}
Key bytes(toBytesFunc((EC_KEY *)vKey, nullptr));
unsigned char *keyPtr = &bytes[0];
if (0 >= toBytesFunc((EC_KEY *)vKey, &keyPtr)) // Should return 1 here on success, according to documentation, but actually returns a larger number...
{
debug(LOG_ERROR, "%s", ERR_error_string(ERR_get_error(), nullptr));
bytes.clear();
}
return bytes;
}
void EcKey::fromBytes(EcKey::Key const &key, EcKey::Privacy privacy)
{
EC_KEY *(*fromBytesFunc)(EC_KEY **key, unsigned char const **in, long len) = nullptr;
switch (privacy)
{
case Private: fromBytesFunc = d2i_ECPrivateKey; break;
case Public: fromBytesFunc = o2i_ECPublicKey; break;
}
clear();
unsigned char const *keyPtr = &key[0];
EC_KEY *ikey = EC_KEY_new_by_curve_name(curveId);
vKey = (void *)fromBytesFunc(&ikey, &keyPtr, key.size());
if (vKey == nullptr)
{
debug(LOG_ERROR, "%s", ERR_error_string(ERR_get_error(), nullptr));
}
}
EcKey EcKey::generate()
{
EC_KEY *key = EC_KEY_new_by_curve_name(curveId);
if (key == nullptr)
{
debug(LOG_ERROR, "%s", ERR_error_string(ERR_get_error(), nullptr));
return EcKey();
}
if (1 != EC_KEY_generate_key(key))
{
EC_KEY_free(key);
debug(LOG_ERROR, "%s", ERR_error_string(ERR_get_error(), nullptr));
return EcKey();
}
EcKey ret;
ret.vKey = (void *)key;
return ret;
}
std::string base64Encode(std::vector<uint8_t> const &bytes)
{
std::string str((bytes.size() + 2)/3 * 4, '\0');
for (unsigned n = 0; n*3 < bytes.size(); ++n)
{
unsigned rem = bytes.size() - n*3;
unsigned block = bytes[0 + n*3]<<16 | (rem > 1? bytes[1 + n*3] : 0)<<8 | (rem > 2? bytes[2 + n*3] : 0);
for (unsigned i = 0; i < 4; ++i)
{
str[i + n*4] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(block >> (6*(3 - i))) & 0x3F];
}
if (rem <= 2)
{
str[3 + n*4] = '=';
if (rem <= 1)
{
str[2 + n*4] = '=';
}
}
}
return str;
}
std::vector<uint8_t> base64Decode(std::string const &str)
{
std::vector<uint8_t> bytes(str.size()/4 * 3);
for (unsigned n = 0; n < str.size()/4; ++n)
{
unsigned block = 0;
for (unsigned i = 0; i < 4; ++i)
{
uint8_t ch = str[i + n*4];
unsigned val = ch >= 'A' && ch <= 'Z'? ch - 'A' + 0 :
ch >= 'a' && ch <= 'z'? ch - 'a' + 26 :
ch >= '0' && ch <= '9'? ch - '0' + 52 :
ch == '+'? 62 :
ch == '/'? 63 :
0;
block |= val << (6*(3 - i));
}
bytes[0 + n*3] = block>>16;
bytes[1 + n*3] = block>>8;
bytes[2 + n*3] = block;
}
if (str.size() >= 4)
{
if (str[str.size()/4*4 - 2] == '=')
{
bytes.resize(bytes.size() - 2);
}
else if (str[str.size()/4*4 - 1] == '=')
{
bytes.resize(bytes.size() - 1);
}
}
return bytes;
}

View File

@ -44,4 +44,43 @@ struct Sha256
};
Sha256 sha256Sum(void const *data, size_t dataLen);
class EcKey
{
public:
typedef std::vector<uint8_t> Sig;
typedef std::vector<uint8_t> Key;
enum Privacy { Public, Private };
EcKey();
EcKey(EcKey const &b);
#ifdef WZ_CXX11
EcKey(EcKey &&b);
#endif
~EcKey();
EcKey &operator =(EcKey const &b);
#ifdef WZ_CXX11
EcKey &operator =(EcKey &&b);
#endif
void clear();
bool empty() const;
bool hasPrivate() const;
Sig sign(void const *data, size_t dataLen) const;
bool verify(Sig const &sig, void const *data, size_t dataLen) const;
Key toBytes(Privacy privacy) const;
void fromBytes(Key const &key, Privacy privacy);
static EcKey generate();
private:
void *vKey;
static const int curveId;
};
std::string base64Encode(std::vector<uint8_t> const &bytes);
std::vector<uint8_t> base64Decode(std::string const &str);
#endif //_CRC_H_

View File

@ -138,6 +138,10 @@ template<> class StaticAssert<true>{};
#define STATIC_ASSERT( expr ) \
(void)STATIC_ASSERT_EXPR(expr)
#ifndef WZ_CXX11
#define static_assert(expr, str) STATIC_ASSERT(expr)
#endif
/***
***

View File

@ -695,6 +695,34 @@ void NETstring(char const *str, uint16_t maxlen)
NETstring(const_cast<char *>(str), maxlen);
}
void NETbytes(std::vector<uint8_t> *vec, unsigned maxLen)
{
/*
* Strings sent over the network are prefixed with their length, sent as an
* unsigned 16-bit integer, not including \0 termination.
*/
uint32_t len = NETgetPacketDir() == PACKET_ENCODE ? vec->size() : 0;
queueAuto(len);
if (len > maxLen)
{
debug(LOG_ERROR, "NETstring: %s packet, length %u truncated at %u", NETgetPacketDir() == PACKET_ENCODE ? "Encoding" : "Decoding", len, maxLen);
}
len = std::min<unsigned>(len, maxLen); // Truncate length if necessary.
if (NETgetPacketDir() == PACKET_DECODE)
{
vec->clear();
vec->resize(len); // vec->assign(len, 0) would call the wrong version of assign, here.
}
for (unsigned i = 0; i < len; ++i)
{
queueAuto((*vec)[i]);
}
}
void NETbin(uint8_t *str, uint32_t len)
{
for (unsigned i = 0; i < len; ++i)

View File

@ -92,6 +92,7 @@ void NETqstring(QString &str);
void NETstring(char *str, uint16_t maxlen);
void NETstring(char const *str, uint16_t maxlen); ///< Encode-only version of NETstring.
void NETbin(uint8_t *str, uint32_t len);
void NETbytes(std::vector<uint8_t> *vec, unsigned maxLen = 10000);
PACKETDIR NETgetPacketDir(void);

View File

@ -96,6 +96,12 @@
# define WZ_DATADIR "data"
#endif
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
enum FOCUS_STATE
{
FOCUS_OUT, // Window does not have the focus
@ -982,6 +988,10 @@ void mainLoop(void)
}
realTimeUpdate(); // Update realTime.
}
// Feed a bit of randomness into libcrypto.
unsigned buf[] = {mouseX(), mouseY(), realTime, graphicsTime, gameTime, rand(), 4}; // http://xkcd.com/221/
RAND_add(buf, sizeof(buf), 1);
}
bool getUTF8CmdLine(int* const utfargc, const char*** const utfargv) // explicitely pass by reference
@ -1035,6 +1045,14 @@ extern const char *BACKEND;
int realmain(int argc, char *argv[])
{
// The libcrypto startup stuff... May or may not actually be needed for anything at all.
ERR_load_crypto_strings(); // This is needed for descriptive error messages.
OpenSSL_add_all_algorithms(); // Don't actually use the EVP functions, so probably not needed.
OPENSSL_config(nullptr); // What does this actually do?
#ifdef WZ_OS_WIN
RAND_screen(); // Uses a screenshot as a random seed, on systems lacking /dev/random.
#endif
wzMain(argc, argv);
int utfargc = argc;
const char** utfargv = (const char**)argv;

View File

@ -2483,6 +2483,16 @@ void addPlayerBox(bool players)
{
sButInit.pTip = _("Click to change AI");
}
if (NetPlay.players[i].allocated && !getMultiStats(i).identity.empty())
{
if (!sButInit.pTip.isEmpty())
{
sButInit.pTip += "\n";
}
EcKey::Key bytes = getMultiStats(i).identity.toBytes(EcKey::Public);
sButInit.pTip += _("Player ID: ");
sButInit.pTip += sha256Sum(&bytes[0], bytes.size()).toString().substr(0, 20).c_str();
}
sButInit.pDisplay = displayPlayer;
sButInit.UserData = i;
widgAddButton(psWScreen, &sButInit);

View File

@ -38,9 +38,19 @@
// ////////////////////////////////////////////////////////////////////////////
static PLAYERSTATS playerStats[MAX_PLAYERS];
PLAYERSTATS::PLAYERSTATS()
: played(0)
, wins(0)
, losses(0)
, totalKills(0)
, totalScore(0)
, recentKills(0)
, recentScore(0)
{}
// ////////////////////////////////////////////////////////////////////////////
// Get Player's stats
PLAYERSTATS getMultiStats(UDWORD player)
PLAYERSTATS const &getMultiStats(UDWORD player)
{
return playerStats[player];
}
@ -73,6 +83,12 @@ bool setMultiStats(uint32_t playerIndex, PLAYERSTATS plStats, bool bLocal)
NETuint32_t(&playerStats[playerIndex].totalScore);
NETuint32_t(&playerStats[playerIndex].recentKills);
NETuint32_t(&playerStats[playerIndex].recentScore);
EcKey::Key identity;
if (!playerStats[playerIndex].identity.empty())
{
identity = playerStats[playerIndex].identity.toBytes(EcKey::Public);
}
NETbytes(&identity);
NETend();
}
@ -113,6 +129,22 @@ void recvMultiStats(NETQUEUE queue)
NETuint32_t(&playerStats[playerIndex].totalScore);
NETuint32_t(&playerStats[playerIndex].recentKills);
NETuint32_t(&playerStats[playerIndex].recentScore);
EcKey::Key identity;
NETbytes(&identity);
EcKey::Key prevIdentity;
if (!playerStats[playerIndex].identity.empty())
{
prevIdentity = playerStats[playerIndex].identity.toBytes(EcKey::Public);
}
playerStats[playerIndex].identity.clear();
if (!identity.empty())
{
playerStats[playerIndex].identity.fromBytes(identity, EcKey::Public);
}
if (identity != prevIdentity)
{
ingame.PingTimes[playerIndex] = PING_LIMIT;
}
}
NETend();
}
@ -125,7 +157,7 @@ bool loadMultiStats(char *sPlayerName, PLAYERSTATS *st)
UDWORD size;
char *pFileData;
memset(st, 0, sizeof(PLAYERSTATS)); // clear in case we don't get to load
*st = PLAYERSTATS(); // clear in case we don't get to load
// Prevent an empty player name (where the first byte is a 0x0 terminating char already)
if (!*sPlayerName)
@ -138,17 +170,8 @@ bool loadMultiStats(char *sPlayerName, PLAYERSTATS *st)
debug(LOG_WZ, "loadMultiStats: %s", fileName);
// check player already exists
if ( !PHYSFS_exists( fileName ) )
if (PHYSFS_exists(fileName))
{
PLAYERSTATS blankstats;
memset(&blankstats, 0, sizeof(PLAYERSTATS));
saveMultiStats(sPlayerName,sPlayerName,&blankstats); // didnt exist so create.
}
else
{
int num = 0;
loadFile(fileName,&pFileData,&size);
if (strncmp(pFileData, "WZ.STA.v3", 9) != 0)
@ -156,13 +179,21 @@ bool loadMultiStats(char *sPlayerName, PLAYERSTATS *st)
return false; // wrong version or not a stats file
}
num = sscanf(pFileData, "WZ.STA.v3\n%u %u %u %u %u",
&st->wins, &st->losses, &st->totalKills, &st->totalScore, &st->played);
if (num < 5)
{
st->played = 0; // must be old, buggy format still
}
char identity[1001];
identity[0] = '\0';
sscanf(pFileData, "WZ.STA.v3\n%u %u %u %u %u\n%1000[A-Za-z0-9+/=]",
&st->wins, &st->losses, &st->totalKills, &st->totalScore, &st->played, identity);
free(pFileData);
if (identity[0] != '\0')
{
st->identity.fromBytes(base64Decode(identity), EcKey::Private);
}
}
if (st->identity.empty())
{
st->identity = EcKey::generate(); // Generate new identity.
saveMultiStats(sPlayerName, sPlayerName, st); // Save new identity.
}
// reset recent scores
@ -181,14 +212,13 @@ bool loadMultiStats(char *sPlayerName, PLAYERSTATS *st)
// ////////////////////////////////////////////////////////////////////////////
// Save Player Stats
#define MAX_STA_SIZE 500
bool saveMultiStats(const char *sFileName, const char *sPlayerName, const PLAYERSTATS *st)
{
char buffer[MAX_STA_SIZE];
char buffer[1000];
char fileName[255] = "";
snprintf(buffer, MAX_STA_SIZE, "WZ.STA.v3\n%u %u %u %u %u",
st->wins, st->losses, st->totalKills, st->totalScore, st->played);
ssprintf(buffer, "WZ.STA.v3\n%u %u %u %u %u\n%s\n",
st->wins, st->losses, st->totalKills, st->totalScore, st->played, base64Encode(st->identity.toBytes(EcKey::Private)).c_str());
snprintf(fileName, sizeof(fileName), "%s%s.sta", MultiPlayersPath, sFileName);

View File

@ -29,6 +29,8 @@
struct PLAYERSTATS
{
PLAYERSTATS();
uint32_t played; /// propogated stats.
uint32_t wins;
uint32_t losses;
@ -37,11 +39,13 @@ struct PLAYERSTATS
uint32_t recentKills; // score/kills in last game.
uint32_t recentScore;
EcKey identity;
};
bool saveMultiStats(const char *sFName, const char *sPlayerName, const PLAYERSTATS *playerStats); // to disk
bool loadMultiStats(char *sPlayerName, PLAYERSTATS *playerStats); // form disk
PLAYERSTATS getMultiStats(UDWORD player); // get from net
PLAYERSTATS const &getMultiStats(UDWORD player); // get from net
bool setMultiStats(uint32_t player, PLAYERSTATS plStats, bool bLocal); // send to net.
void updateMultiStatsDamage(UDWORD attacker, UDWORD defender, UDWORD inflicted);
void updateMultiStatsGames(void);

View File

@ -48,6 +48,7 @@ static UDWORD averagePing(void);
#define PING_FREQUENCY 4000 // how often to update pingtimes. in approx millisecs.
static UDWORD PingSend[MAX_PLAYERS]; //stores the time the ping was called.
static uint8_t pingChallenge[8]; // Random data sent with the last ping.
// ////////////////////////////////////////////////////////////////////////
@ -153,9 +154,13 @@ bool sendPing(void)
}
}
uint64_t pingChallengei = (uint64_t)rand() << 32 | rand();
memcpy(pingChallenge, &pingChallengei, sizeof(pingChallenge));
NETbeginEncode(NETbroadcastQueue(), NET_PING);
NETuint8_t(&player);
NETbool(&isNew);
NETbin(pingChallenge, sizeof(pingChallenge));
NETend();
// Note when we sent the ping
@ -172,10 +177,20 @@ bool recvPing(NETQUEUE queue)
{
bool isNew;
uint8_t sender, us = selectedPlayer;
uint8_t challenge[sizeof(pingChallenge)];
EcKey::Sig challengeResponse;
NETbeginDecode(queue, NET_PING);
NETuint8_t(&sender);
NETbool(&isNew);
if (isNew)
{
NETbin(challenge, sizeof(pingChallenge));
}
else
{
NETbytes(&challengeResponse);
}
NETend();
if (sender >= MAX_PLAYERS)
@ -187,17 +202,26 @@ bool recvPing(NETQUEUE queue)
// If this is a new ping, respond to it
if (isNew)
{
challengeResponse = getMultiStats(us).identity.sign(&challenge, sizeof(pingChallenge));
NETbeginEncode(NETnetQueue(sender), NET_PING);
// We are responding to a new ping
isNew = false;
NETuint8_t(&us);
NETbool(&isNew);
NETbytes(&challengeResponse);
NETend();
}
// They are responding to one of our pings
else
{
if (!getMultiStats(sender).identity.empty() && !getMultiStats(sender).identity.verify(challengeResponse, pingChallenge, sizeof(pingChallenge)))
{
debug(LOG_ERROR, "Bad NET_PING packet, alleged sender is %d", (int)sender);
return false;
}
// Work out how long it took them to respond
ingame.PingTimes[sender] = (realTime - PingSend[sender]) / 2;