Merge branch 'player-persistence' into 'licenses'

Player IDs and persistence

See merge request KartKrew/Kart!1062
This commit is contained in:
Sal 2023-03-29 00:07:33 +00:00
commit 2555aac9d3
25 changed files with 4346 additions and 21 deletions

View file

@ -1594,6 +1594,38 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
--------------------------------------------------------------------------------
2-Clause BSD License
applies to:
- monocypher
Copyright (c) 2017-2020, Loup Vaillant
All rights reserved.
https://monocypher.org/
--------------------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
MIT License
applies to:
@ -1652,4 +1684,4 @@ FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
DEALINGS IN THE SOFTWARE.

View file

@ -559,6 +559,7 @@ add_subdirectory(sdl)
add_subdirectory(objects)
add_subdirectory(acs)
add_subdirectory(rhi)
add_subdirectory(monocypher)
if(SRB2_CONFIG_ENABLE_TESTS)
add_subdirectory(tests)
endif()

View file

@ -47,6 +47,8 @@
#include "lua_hook.h"
#include "md5.h"
#include "m_perfstats.h"
#include "monocypher/monocypher.h"
#include "stun.h"
// SRB2Kart
#include "k_kart.h"
@ -121,6 +123,7 @@ SINT8 nodetoplayer3[MAXNETNODES]; // say the numplayer for this node if any (spl
SINT8 nodetoplayer4[MAXNETNODES]; // say the numplayer for this node if any (splitscreen == 3)
UINT8 playerpernode[MAXNETNODES]; // used specialy for splitscreen
boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
boolean nodeneedsauth[MAXNETNODES];
tic_t servermaxping = 20; // server's max delay, in frames. Defaults to 20
static tic_t nettics[MAXNETNODES]; // what tic the client have received
@ -156,9 +159,34 @@ char connectedservername[MAXSERVERNAME];
/// \todo WORK!
boolean acceptnewnode = true;
UINT32 ourIP; // Used when populating PT_SERVERCHALLENGE (guards against signature reuse)
uint8_t lastReceivedKey[MAXNETNODES][MAXSPLITSCREENPLAYERS][PUBKEYLENGTH]; // Player's public key (join process only! active players have it on player_t)
uint8_t lastSentChallenge[MAXNETNODES][CHALLENGELENGTH]; // The random message we asked them to sign in PT_SERVERCHALLENGE, check it in PT_CLIENTJOIN
uint8_t awaitingChallenge[CHALLENGELENGTH]; // The message the server asked our client to sign when joining
uint8_t lastChallengeAll[CHALLENGELENGTH]; // The message we asked EVERYONE to sign for client-to-client identity proofs
uint8_t lastReceivedSignature[MAXPLAYERS][SIGNATURELENGTH]; // Everyone's response to lastChallengeAll
uint8_t knownWhenChallenged[MAXPLAYERS][PUBKEYLENGTH]; // Everyone a client saw at the moment a challenge should be initiated
boolean expectChallenge = false; // Were we in-game before a client-to-client challenge should have been sent?
uint8_t priorKeys[MAXPLAYERS][PUBKEYLENGTH]; // Make a note of keys before consuming a new gamestate, and if the server tries to send us a gamestate where keys differ, assume shenanigans
boolean serverisfull = false; //lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not
tic_t firstconnectattempttime = 0;
consvar_t cv_allowguests = CVAR_INIT ("allowguests", "On", CV_SAVE, CV_OnOff, NULL);
#ifdef DEVELOP
consvar_t cv_badjoin = CVAR_INIT ("badjoin", "0", 0, CV_Unsigned, NULL);
consvar_t cv_badtraffic = CVAR_INIT ("badtraffic", "0", 0, CV_Unsigned, NULL);
consvar_t cv_badresponse = CVAR_INIT ("badresponse", "0", 0, CV_Unsigned, NULL);
consvar_t cv_noresponse = CVAR_INIT ("noresponse", "0", 0, CV_Unsigned, NULL);
consvar_t cv_nochallenge = CVAR_INIT ("nochallenge", "0", 0, CV_Unsigned, NULL);
consvar_t cv_badresults = CVAR_INIT ("badresults", "0", 0, CV_Unsigned, NULL);
consvar_t cv_noresults = CVAR_INIT ("noresults", "0", 0, CV_Unsigned, NULL);
consvar_t cv_badtime = CVAR_INIT ("badtime", "0", 0, CV_Unsigned, NULL);
consvar_t cv_badip = CVAR_INIT ("badip", "0", 0, CV_Unsigned, NULL);
#endif
// engine
// Must be a power of two
@ -191,6 +219,60 @@ consvar_t cv_httpsource = CVAR_INIT ("http_source", "", CV_SAVE, NULL, NULL);
consvar_t cv_kicktime = CVAR_INIT ("kicktime", "10", CV_SAVE, CV_Unsigned, NULL);
// Generate a message for an authenticating client to sign, with some guarantees about who we are.
void GenerateChallenge(uint8_t *buf)
{
#ifndef SRB2_LITTLE_ENDIAN
#error "FIXME: 64-bit timestamp field is not supported on Big Endian"
#endif
UINT64 now = time(NULL);
csprng(buf, CHALLENGELENGTH); // Random noise as a baseline, but...
memcpy(buf, &now, sizeof(now)); // Timestamp limits the reuse window.
memcpy(buf + sizeof(now), &ourIP, sizeof(ourIP)); // IP prevents captured signatures from being used elsewhere.
#ifdef DEVELOP
if (cv_badtime.value)
{
CV_AddValue(&cv_badtime, -1);
CONS_Alert(CONS_WARNING, "cv_badtime enabled, trashing time in auth message\n");
memset(buf, 0, sizeof(now));
}
if (cv_badip.value)
{
CV_AddValue(&cv_badip, -1);
CONS_Alert(CONS_WARNING, "cv_badip enabled, trashing IP in auth message\n");
memset(buf + sizeof(now), 0, sizeof(ourIP));
}
#endif
}
// Modified servers can throw softballs or reuse challenges.
// Don't sign anything that wasn't generated just for us!
shouldsign_t ShouldSignChallenge(uint8_t *message)
{
#ifndef SRB2_LITTLE_ENDIAN
#error "FIXME: 64-bit timestamp field is not supported on Big Endian"
#endif
UINT64 then, now;
UINT32 claimedIP, realIP;
now = time(NULL);
memcpy(&then, message, sizeof(then));
memcpy(&claimedIP, message + sizeof(then), sizeof(claimedIP));
realIP = I_GetNodeAddressInt(servernode);
if ((max(now, then) - min(now, then)) > 60*5)
return SIGN_BADTIME;
if (realIP != claimedIP && I_IsExternalAddress(&realIP))
return SIGN_BADIP;
return SIGN_OK;
}
static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n)
{
const size_t d = n / sizeof(ticcmd_t);
@ -545,6 +627,8 @@ typedef enum
CL_PREPAREHTTPFILES,
CL_DOWNLOADHTTPFILES,
#endif
CL_SENDKEY,
CL_WAITCHALLENGE,
} cl_mode_t;
static void GetPackets(void);
@ -826,9 +910,80 @@ static boolean CL_SendJoin(void)
memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false, false), MAXAVAILABILITY*sizeof(UINT8));
// Don't leak old signatures from prior sessions.
memset(&netbuffer->u.clientcfg.challengeResponse, 0, sizeof(((clientconfig_pak *)0)->challengeResponse));
if (client && netgame)
{
shouldsign_t safe = ShouldSignChallenge(awaitingChallenge);
if (safe != SIGN_OK)
{
if (safe == SIGN_BADIP)
{
I_Error("External server IP didn't match the message it sent.");
}
else if (safe == SIGN_BADTIME)
{
I_Error("External server sent a message with an unusual timestamp.\nCheck your clocks!");
}
else
{
I_Error("External server asked for a signature on something strange.\nPlease notify a developer if you've seen this more than once.");
}
return false;
}
}
for (i = 0; i <= splitscreen; i++)
{
uint8_t signature[SIGNATURELENGTH];
profile_t *localProfile = PR_GetLocalPlayerProfile(i);
if (PR_IsLocalPlayerGuest(i)) // GUESTS don't have keys
{
memset(signature, 0, sizeof(signature));
}
else
{
// If our keys are garbage (corrupted profile?), fail here instead of when the server boots us, so the player knows what's going on.
crypto_eddsa_sign(signature, localProfile->secret_key, awaitingChallenge, sizeof(awaitingChallenge));
if (crypto_eddsa_check(signature, localProfile->public_key, awaitingChallenge, sizeof(awaitingChallenge)) != 0)
I_Error("Couldn't self-verify key associated with player %d, profile %d.\nProfile data may be corrupted.", i, cv_lastprofile[i].value); // I guess this is the most reasonable way to catch a malformed key.
}
#ifdef DEVELOP
if (cv_badjoin.value)
{
CV_AddValue(&cv_badjoin, -1);
CONS_Alert(CONS_WARNING, "cv_badjoin enabled, scrubbing signature from CL_SendJoin\n");
memset(signature, 0, sizeof(signature));
}
#endif
// Testing
// memset(signature, 0, sizeof(signature));
memcpy(&netbuffer->u.clientcfg.challengeResponse[i], signature, sizeof(signature));
}
return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak));
}
static boolean CL_SendKey(void)
{
int i;
netbuffer->packettype = PT_CLIENTKEY;
memset(netbuffer->u.clientkey.key, 0, sizeof(((clientkey_pak *)0)->key));
for (i = 0; i <= splitscreen; i++)
{
// GUEST profiles have all-zero keys. This will be handled at the end of the challenge process, don't worry about it.
memcpy(netbuffer->u.clientkey.key[i], PR_GetProfile(cv_lastprofile[i].value)->public_key, PUBKEYLENGTH);
}
return HSendPacket(servernode, false, 0, sizeof (clientkey_pak) );
}
static void
CopyCaretColors (char *p, const char *s, int n)
{
@ -1312,6 +1467,19 @@ static void CL_LoadReceivedSavegame(boolean reloading)
// so they know they can resume the game
netbuffer->packettype = PT_RECEIVEDGAMESTATE;
HSendPacket(servernode, true, 0, 0);
if (reloading)
{
int i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (memcmp(priorKeys[i], players[i].public_key, sizeof(priorKeys[i])) != 0)
{
HandleSigfail("Gamestate reload contained new keys");
break;
}
}
}
}
static void CL_ReloadReceivedSavegame(void)
@ -1888,7 +2056,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
{
*asksent = 0; //This ensure the first join ask is right away
firstconnectattempttime = I_GetTime();
cl_mode = CL_ASKJOIN;
cl_mode = CL_SENDKEY;
}
break;
case CL_ASKJOIN:
@ -1924,6 +2092,19 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
cl_mode = CL_ASKJOIN;
}
break;
case CL_SENDKEY:
if (I_GetTime() >= *asksent && CL_SendKey())
{
*asksent = I_GetTime() + NEWTICRATE*3;
cl_mode = CL_WAITCHALLENGE;
}
break;
case CL_WAITCHALLENGE:
if (I_GetTime() >= *asksent)
{
cl_mode = CL_SENDKEY;
}
break;
case CL_DOWNLOADSAVEGAME:
// At this state, the first (and only) needed file is the gamestate
if (fileneeded[0].status == FS_FOUND)
@ -2675,6 +2856,8 @@ void CL_Reset(void)
serverisfull = false;
connectiontimeout = (tic_t)cv_nettimeout.value; //reset this temporary hack
expectChallenge = false;
#ifdef HAVE_CURL
curl_failedwebdownload = false;
curl_transfers = 0;
@ -2758,6 +2941,8 @@ static void Command_Nodes(void)
CONS_Printf(" - %s", address);
}
CONS_Printf(" [RRID-%s] ", GetPrettyRRID(players[i].public_key, true));
if (IsPlayerAdmin(i))
CONS_Printf(M_GetText(" (verified admin)"));
@ -3113,6 +3298,10 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
HU_AddChatText(va("\x82*%s left the game (Connection timeout)", player_names[pnum]), false);
kickreason = KR_TIMEOUT;
break;
case KICK_MSG_SIGFAIL:
HU_AddChatText(va("\x82*%s left the game (Invalid signature)", player_names[pnum]), false);
kickreason = KR_TIMEOUT;
break;
case KICK_MSG_PLAYER_QUIT:
if (netgame) // not splitscreen/bots
HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false);
@ -3169,6 +3358,8 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
M_StartMessage(va(M_GetText("You have been kicked\n(%s)\nPress (B)\n"), reason), NULL, MM_NOTHING);
else if (msg == KICK_MSG_CUSTOM_BAN)
M_StartMessage(va(M_GetText("You have been banned\n(%s)\nPress (B)\n"), reason), NULL, MM_NOTHING);
else if (msg == KICK_MSG_SIGFAIL)
M_StartMessage(M_GetText("Server closed connection\n(Invalid signature)\nPress (B)\n"), NULL, MM_NOTHING);
else
M_StartMessage(M_GetText("You have been kicked by the server\n\nPress (B)\n"), NULL, MM_NOTHING);
}
@ -3382,9 +3573,7 @@ void D_ClientServerInit(void)
COM_AddCommand("drop", Command_Drop);
COM_AddCommand("droprate", Command_Droprate);
#endif
#ifdef _DEBUG
COM_AddCommand("numnodes", Command_Numnodes);
#endif
RegisterNetXCmd(XD_KICK, Got_KickCmd);
RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer);
@ -3409,6 +3598,7 @@ static void ResetNode(INT32 node)
{
nodeingame[node] = false;
nodewaiting[node] = 0;
nodeneedsauth[node] = false;
nettics[node] = gametic;
supposedtics[node] = gametic;
@ -3489,6 +3679,8 @@ void SV_ResetServer(void)
for (i = 0; i < MAXUNLOCKABLES; i++)
netUnlocked[i] = (dedicated || gamedata->unlocked[i]);
expectChallenge = false;
DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
}
@ -3573,6 +3765,8 @@ static inline void SV_AddNode(INT32 node)
// nodeingame when connected not here
if (node)
nodeingame[node] = true;
nodeneedsauth[node] = false;
}
// Xcmd XD_ADDPLAYER
@ -3614,6 +3808,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
newplayer->jointime = 0;
READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
READMEM(*p, players[newplayernum].public_key, PUBKEYLENGTH);
console = READUINT8(*p);
splitscreenplayer = READUINT8(*p);
@ -3773,10 +3968,12 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
LUA_HookInt(newplayernum, HOOK(PlayerJoin));
}
static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities, const char *name, const char *name2, const char *name3, const char *name4)
static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities,
const char *name, uint8_t *key, const char *name2, uint8_t *key2,
const char *name3, uint8_t *key3, const char *name4, uint8_t *key4)
{
INT32 n, newplayernum, i;
UINT8 buf[4 + MAXPLAYERNAME + MAXAVAILABILITY];
UINT8 buf[4 + MAXPLAYERNAME + PUBKEYLENGTH + MAXAVAILABILITY];
UINT8 *buf_p = buf;
boolean newplayer = false;
@ -3839,21 +4036,25 @@ static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities, const cha
{
nodetoplayer[node] = newplayernum;
WRITESTRINGN(buf_p, name, MAXPLAYERNAME);
WRITEMEM(buf_p, key, PUBKEYLENGTH);
}
else if (playerpernode[node] < 2)
{
nodetoplayer2[node] = newplayernum;
WRITESTRINGN(buf_p, name2, MAXPLAYERNAME);
WRITEMEM(buf_p, key2, PUBKEYLENGTH);
}
else if (playerpernode[node] < 3)
{
nodetoplayer3[node] = newplayernum;
WRITESTRINGN(buf_p, name3, MAXPLAYERNAME);
WRITEMEM(buf_p, key3, PUBKEYLENGTH);
}
else if (playerpernode[node] < 4)
{
nodetoplayer4[node] = newplayernum;
WRITESTRINGN(buf_p, name4, MAXPLAYERNAME);
WRITEMEM(buf_p, key4, PUBKEYLENGTH);
}
WRITEUINT8(buf_p, nodetoplayer[node]); // consoleplayer
@ -3888,6 +4089,15 @@ void CL_RemoveSplitscreenPlayer(UINT8 p)
SendKick(p, KICK_MSG_PLAYER_QUIT);
}
static void GotOurIP(UINT32 address)
{
const unsigned char * p = (const unsigned char *)&address;
#ifdef DEVELOP
CONS_Printf("Got IP of %u.%u.%u.%u\n", p[0], p[1], p[2], p[3]);
#endif
ourIP = address;
}
// is there a game running
boolean Playing(void)
{
@ -3924,13 +4134,18 @@ boolean SV_SpawnServer(void)
else doomcom->numslots = 1;
}
ourIP = 0;
if (netgame && server)
STUN_bind(GotOurIP);
// strictly speaking, i'm not convinced the following is necessary
// but I'm not confident enough to remove it entirely in case it breaks something
{
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false, false);
SINT8 node = 0;
for (; node < MAXNETNODES; node++)
result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring);
result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, PR_GetLocalPlayerProfile(0)->public_key, cv_playername[1].zstring, PR_GetLocalPlayerProfile(1)->public_key,
cv_playername[2].zstring, PR_GetLocalPlayerProfile(2)->public_key, cv_playername[3].zstring, PR_GetLocalPlayerProfile(3)->public_key);
}
return result;
#endif
@ -4004,6 +4219,34 @@ static size_t TotalTextCmdPerTic(tic_t tic)
return total;
}
#ifdef SIGNGAMETRAFFIC
static boolean IsSplitPlayerOnNodeGuest(int node, int split)
{
char allZero[PUBKEYLENGTH];
memset(allZero, 0, PUBKEYLENGTH);
if (split == 0)
return (memcmp(players[nodetoplayer[node]].public_key, allZero, PUBKEYLENGTH) == 0);
else if (split == 1)
return (memcmp(players[nodetoplayer2[node]].public_key, allZero, PUBKEYLENGTH) == 0);
else if (split == 2)
return (memcmp(players[nodetoplayer3[node]].public_key, allZero, PUBKEYLENGTH) == 0);
else if (split == 3)
return (memcmp(players[nodetoplayer4[node]].public_key, allZero, PUBKEYLENGTH) == 0);
else
I_Error("IsSplitPlayerOnNodeGuest: Out of bounds");
return false; // unreachable
}
#endif
static boolean IsPlayerGuest(int player)
{
char allZero[PUBKEYLENGTH];
memset(allZero, 0, PUBKEYLENGTH);
return (memcmp(players[player].public_key, allZero, PUBKEYLENGTH) == 0);
}
/** Called when a PT_CLIENTJOIN packet is received
*
* \param node The packet sender
@ -4021,6 +4264,7 @@ static void HandleConnect(SINT8 node)
UINT8 maxplayers = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value);
UINT8 connectedplayers = 0;
for (i = dedicated ? 1 : 0; i < MAXPLAYERS; i++)
if (playernode[i] != UINT8_MAX) // We use this to count players because it is affected by SV_AddWaitingPlayers when more than one client joins on the same tic, unlike playeringame and D_NumPlayers. UINT8_MAX denotes no node for that player
connectedplayers++;
@ -4108,6 +4352,7 @@ static void HandleConnect(SINT8 node)
}
else
{
int sigcheck;
boolean newnode = false;
for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++)
@ -4118,6 +4363,37 @@ static void HandleConnect(SINT8 node)
SV_SendRefuse(node, "Bad player name");
return;
}
if (node == 0) // Hey, that's us. We're always allowed to do what we want.
{
memcpy(lastReceivedKey[node][i], PR_GetLocalPlayerProfile(i)->public_key, sizeof(lastReceivedKey[node][i]));
}
else // Remote player, gotta check their signature.
{
char allZero[PUBKEYLENGTH];
memset(allZero, 0, sizeof(allZero));
if (memcmp(lastReceivedKey[node][i], allZero, PUBKEYLENGTH) == 0) // IsSplitPlayerOnNodeGuest isn't appropriate here, they're not in-game yet!
{
if (!cv_allowguests.value)
{
SV_SendRefuse(node, M_GetText("The server doesn't allow GUESTs.\nCreate a profile to join!"));
return;
}
sigcheck = 0; // Always succeeds. Yes, this is a success response. C R Y P T O
}
else
{
sigcheck = crypto_eddsa_check(netbuffer->u.clientcfg.challengeResponse[i], lastReceivedKey[node][i], lastSentChallenge[node], CHALLENGELENGTH);
}
if (netgame && sigcheck != 0)
{
SV_SendRefuse(node, M_GetText("Signature verification failed."));
return;
}
}
}
memcpy(availabilitiesbuffer, netbuffer->u.clientcfg.availabilities, sizeof(availabilitiesbuffer));
@ -4157,7 +4433,8 @@ static void HandleConnect(SINT8 node)
DEBFILE("send savegame\n");
}
SV_AddWaitingPlayers(node, availabilitiesbuffer, names[0], names[1], names[2], names[3]);
SV_AddWaitingPlayers(node, availabilitiesbuffer, names[0], lastReceivedKey[node][0], names[1], lastReceivedKey[node][1],
names[2], lastReceivedKey[node][2], names[3], lastReceivedKey[node][3]);
joindelay += cv_joindelay.value * TICRATE;
player_joining = true;
}
@ -4196,6 +4473,23 @@ static void HandleTimeout(SINT8 node)
M_StartMessage(M_GetText("Server Timeout\n\nPress (B)\n"), NULL, MM_NOTHING);
}
// Called when a signature check fails and we suspect the server is playing games.
void HandleSigfail(const char *string)
{
if (server) // This situation is basically guaranteed to be nonsense.
{
CONS_Alert(CONS_ERROR, "Auth error! %s\n", string);
return; // Keep the game running, you're probably testing.
}
LUA_HookBool(false, HOOK(GameQuit));
D_QuitNetGame();
CL_Reset();
D_ClearState();
M_StartControlPanel();
M_StartMessage(va(M_GetText("Signature check failed.\n(%s)\nPress (B)\n"), string), NULL, MM_NOTHING);
}
/** Called when a PT_SERVERINFO packet is received
*
* \param node The packet sender
@ -4228,6 +4522,14 @@ static void PT_WillResendGamestate(void)
if (server || cl_redownloadinggamestate)
return;
// Don't let the server pull a fast one with everyone's identity!
// Save the public keys we see, so if the server tries to swap one, we'll know.
int i;
for (i = 0; i < MAXPLAYERS; i++)
{
memcpy(priorKeys[i], players[i].public_key, sizeof(priorKeys[i]));
}
// Send back a PT_CANRECEIVEGAMESTATE packet to the server
// so they know they can start sending the game state
netbuffer->packettype = PT_CANRECEIVEGAMESTATE;
@ -4482,7 +4784,10 @@ static void HandlePacketFromAwayNode(SINT8 node)
case PT_NODETIMEOUT:
case PT_CLIENTQUIT:
if (server)
{
Net_CloseConnection(node);
nodeneedsauth[node] = false;
}
break;
case PT_CLIENTCMD:
@ -4493,7 +4798,28 @@ static void HandlePacketFromAwayNode(SINT8 node)
if (node == servernode)
break;
/* FALLTHRU */
case PT_CLIENTKEY:
if (server)
{
PT_ClientKey(node);
// Client's not in the server yet, but we still need to lock up the node.
// Otherwise, someone else could request a challenge on the same node and trash it.
nodeneedsauth[node] = true;
freezetimeout[node] = I_GetTime() + jointimeout;
}
break;
case PT_SERVERCHALLENGE:
if (server && serverrunning && node != servernode)
{
Net_CloseConnection(node);
break;
}
if (cl_mode != CL_WAITCHALLENGE)
break;
memcpy(awaitingChallenge, netbuffer->u.serverchallenge.secret, sizeof(awaitingChallenge));
cl_mode = CL_ASKJOIN;
break;
default:
DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
Net_CloseConnection(node);
@ -4526,6 +4852,19 @@ static boolean CheckForSpeedHacks(UINT8 p)
return false;
}
static char NodeToSplitPlayer(int node, int split)
{
if (split == 0)
return nodetoplayer[node];
else if (split == 1)
return nodetoplayer2[node];
else if (split == 2)
return nodetoplayer3[node];
else if (split == 3)
return nodetoplayer4[node];
return -1;
}
/** Handles a packet received from a node that is in game
*
* \param node The packet sender
@ -4553,6 +4892,49 @@ static void HandlePacketFromPlayer(SINT8 node)
if (netconsole >= MAXPLAYERS)
I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
#endif
#ifdef SIGNGAMETRAFFIC
if (server)
{
int splitnodes;
if (IsPacketSigned(netbuffer->packettype))
{
for (splitnodes = 0; splitnodes < MAXSPLITSCREENPLAYERS; splitnodes++)
{
int targetplayer = NodeToSplitPlayer(node, splitnodes);
if (targetplayer == -1)
continue;
const void* message = &netbuffer->u;
if (IsSplitPlayerOnNodeGuest(node, splitnodes) || demo.playback)
{
//CONS_Printf("Throwing out a guest signature from node %d player %d\n", node, splitnodes);
}
else
{
if (crypto_eddsa_check(netbuffer->signature[splitnodes], players[targetplayer].public_key, message, doomcom->datalength - BASEPACKETSIZE))
{
CONS_Alert(CONS_ERROR, "SIGFAIL! Packet type %d from node %d player %d\nkey %s size %d netconsole %d\n",
netbuffer->packettype, node, splitnodes,
GetPrettyRRID(players[targetplayer].public_key, true), doomcom->datalength - BASEPACKETSIZE, netconsole);
// Something scary can happen when multiple kicks that resolve to the same node are processed in quick succession.
// Sometimes, a kick will still be left to process after the player's been disposed, and that causes the kick to resolve on the server instead!
// This sucks, so we check for a stale/misfiring kick beforehand.
if (netconsole != -1)
SendKick(netconsole, KICK_MSG_SIGFAIL);
// Net_CloseConnection(node);
// nodeingame[node] = false;
return;
}
}
}
}
}
#endif
switch (netbuffer->packettype)
{
@ -4843,6 +5225,7 @@ static void HandlePacketFromPlayer(SINT8 node)
}
Net_CloseConnection(node);
nodeingame[node] = false;
nodeneedsauth[node] = false;
break;
case PT_CANRECEIVEGAMESTATE:
PT_CanReceiveGamestate(node);
@ -4972,6 +5355,140 @@ static void HandlePacketFromPlayer(SINT8 node)
if (client)
CL_PrepareDownloadLuaFile();
break;
case PT_CHALLENGEALL:
if (demo.playback || node != servernode) // SERVER should still respond to this to prove its own identity, just not from clients.
break;
int challengeplayers;
memcpy(lastChallengeAll, netbuffer->u.challengeall.secret, sizeof(lastChallengeAll));
shouldsign_t safe = ShouldSignChallenge(lastChallengeAll);
if (safe != SIGN_OK)
{
if (safe == SIGN_BADIP)
HandleSigfail("External server sent the wrong IP");
else if (safe == SIGN_BADTIME)
HandleSigfail("Bad timestamp - check your clocks");
else
HandleSigfail("Unknown auth error - contact a developer");
break;
}
netbuffer->packettype = PT_RESPONSEALL;
#ifdef DEVELOP
if (cv_noresponse.value)
{
CV_AddValue(&cv_noresponse, -1);
CONS_Alert(CONS_WARNING, "cv_noresponse enabled, not sending PT_RESPONSEALL\n");
break;
}
#endif
// Don't leak uninitialized memory.
memset(&netbuffer->u.responseall, 0, sizeof(netbuffer->u.responseall));
for (challengeplayers = 0; challengeplayers <= splitscreen; challengeplayers++)
{
uint8_t signature[SIGNATURELENGTH];
profile_t *localProfile = PR_GetLocalPlayerProfile(challengeplayers);
if (!PR_IsLocalPlayerGuest(challengeplayers)) // GUESTS don't have keys
{
crypto_eddsa_sign(signature, localProfile->secret_key, lastChallengeAll, sizeof(lastChallengeAll));
// If our keys are garbage (corrupted profile?), fail here instead of when the server boots us, so the player knows what's going on.
if (crypto_eddsa_check(signature, localProfile->public_key, lastChallengeAll, sizeof(lastChallengeAll)) != 0)
I_Error("Couldn't self-verify key associated with player %d, profile %d.\nProfile data may be corrupted.", challengeplayers, cv_lastprofile[challengeplayers].value);
}
#ifdef DEVELOP
if (cv_badresponse.value)
{
CV_AddValue(&cv_badresponse, -1);
CONS_Alert(CONS_WARNING, "cv_badresponse enabled, scrubbing signature from PT_RESPONSEALL\n");
memset(signature, 0, sizeof(signature));
}
#endif
memcpy(netbuffer->u.responseall.signature[challengeplayers], signature, sizeof(signature));
}
HSendPacket(servernode, true, 0, sizeof(netbuffer->u.responseall));
break;
case PT_RESPONSEALL:
if (demo.playback || client)
break;
int responseplayer;
for (responseplayer = 0; responseplayer < MAXSPLITSCREENPLAYERS; responseplayer++)
{
int targetplayer = NodeToSplitPlayer(node, responseplayer);
if (targetplayer == -1)
continue;
if (!IsPlayerGuest(targetplayer))
{
if (crypto_eddsa_check(netbuffer->u.responseall.signature[responseplayer], players[targetplayer].public_key, lastChallengeAll, sizeof(lastChallengeAll)))
{
// Something scary can happen when multiple kicks that resolve to the same node are processed in quick succession.
// Sometimes, a kick will still be left to process after the player's been disposed, and that causes the kick to resolve on the server instead!
// This sucks, so we check for a stale/misfiring kick beforehand.
if (playernode[targetplayer] != 0)
SendKick(targetplayer, KICK_MSG_SIGFAIL);
break;
}
else
{
memcpy(lastReceivedSignature[targetplayer], netbuffer->u.responseall.signature[responseplayer], sizeof(lastReceivedSignature[targetplayer]));
}
}
}
break;
case PT_RESULTSALL:
if (demo.playback || server || node != servernode || !expectChallenge)
break;
int resultsplayer;
uint8_t allZero[PUBKEYLENGTH];
memset(allZero, 0, sizeof(PUBKEYLENGTH));
for (resultsplayer = 0; resultsplayer < MAXPLAYERS; resultsplayer++)
{
if (!playeringame[resultsplayer])
{
continue;
}
else if (IsPlayerGuest(resultsplayer))
{
continue;
}
else if (memcmp(knownWhenChallenged[resultsplayer], allZero, sizeof(PUBKEYLENGTH)) == 0)
{
// Wasn't here for the challenge.
continue;
}
else if (memcmp(knownWhenChallenged[resultsplayer], players[resultsplayer].public_key, sizeof(knownWhenChallenged[resultsplayer])) != 0)
{
// A player left after the challenge process started, and someone else took their place.
// That means they haven't received a challenge either.
continue;
}
else
{
if (crypto_eddsa_check(netbuffer->u.resultsall.signature[resultsplayer],
knownWhenChallenged[resultsplayer], lastChallengeAll, sizeof(lastChallengeAll)))
{
CONS_Alert(CONS_WARNING, "PT_RESULTSALL had invalid signature %s for node %d player %d split %d, something doesn't add up!\n",
GetPrettyRRID(netbuffer->u.resultsall.signature[resultsplayer], true), playernode[resultsplayer], resultsplayer, players[resultsplayer].splitscreenindex);
HandleSigfail("Server sent invalid client signature.");
break;
}
}
}
csprng(lastChallengeAll, sizeof(lastChallengeAll));
expectChallenge = false;
break;
default:
DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
netbuffer->packettype, node));
@ -5786,6 +6303,168 @@ static void UpdatePingTable(void)
}
}
// It's that time again! Send everyone a safe message to sign, so we can show off their signature and prove we're playing fair.
static void SendChallenges(void)
{
int i;
netbuffer->packettype = PT_CHALLENGEALL;
#ifdef DEVELOP
if (cv_nochallenge.value)
{
CV_AddValue(&cv_nochallenge, -1);
CONS_Alert(CONS_WARNING, "cv_nochallenge enabled, not sending PT_CHALLENGEALL\n");
return;
}
#endif
memset(knownWhenChallenged, 0, sizeof(knownWhenChallenged));
memset(lastReceivedSignature, 0, sizeof(lastReceivedSignature));
GenerateChallenge(netbuffer->u.challengeall.secret);
memcpy(lastChallengeAll, netbuffer->u.challengeall.secret, sizeof(lastChallengeAll));
// Take note of everyone's current key, so that players who disconnect and are replaced aren't held to the old player's challenge.
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
memcpy(knownWhenChallenged[i], players[i].public_key, sizeof(knownWhenChallenged[i]));
}
for (i = 0; i < MAXNETNODES; i++)
{
if (nodeingame[i])
HSendPacket(i, true, 0, sizeof(challengeall_pak));
}
}
// Before we start sending out the results, we need to kick everyone who didn't respond.
// (If we try to do both at once, clients will still see players who failled in-game when the results arrive...)
static void KickUnverifiedPlayers(void)
{
int i;
uint8_t allZero[SIGNATURELENGTH];
memset(allZero, 0, SIGNATURELENGTH);
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
if (memcmp(lastReceivedSignature[i], allZero, SIGNATURELENGTH) == 0) // We never got a response!
{
if (!IsPlayerGuest(i) && memcmp(&knownWhenChallenged[i], &players[i].public_key, sizeof(knownWhenChallenged[i])) == 0)
{
if (playernode[i] != servernode)
SendKick(i, KICK_MSG_SIGFAIL);
}
}
}
}
//
static void SendChallengeResults(void)
{
int i;
netbuffer->packettype = PT_RESULTSALL;
#ifdef DEVELOP
if (cv_noresults.value)
{
CV_AddValue(&cv_noresults, -1);
CONS_Alert(CONS_WARNING, "cv_noresults enabled, not sending PT_RESULTSALL\n");
return;
}
#endif
uint8_t allZero[SIGNATURELENGTH];
memset(allZero, 0, sizeof(allZero));
memset(&netbuffer->u.resultsall, 0, sizeof(netbuffer->u.resultsall));
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
// Don't try to transmit signatures for players who didn't get here in time to send one.
// (Everyone who had their chance should have been kicked by KickUnverifiedPlayers by now.)
if (memcmp(lastReceivedSignature[i], allZero, SIGNATURELENGTH) == 0)
continue;
memcpy(netbuffer->u.resultsall.signature[i], lastReceivedSignature[i], sizeof(netbuffer->u.resultsall.signature[i]));
#ifdef DEVELOP
if (cv_badresults.value)
{
CV_AddValue(&cv_badresults, -1);
CONS_Alert(CONS_WARNING, "cv_badresults enabled, scrubbing signature from PT_RESULTSALL\n");
memset(netbuffer->u.resultsall.signature[i], 0, sizeof(netbuffer->u.resultsall.signature[i]));
}
#endif
}
for (i = 0; i < MAXNETNODES; i++)
{
if (nodeingame[i])
HSendPacket(i, true, 0, sizeof(resultsall_pak));
}
}
// Who should we try to verify when results come in?
// Store a public key for every active slot, so if players shuffle during challenge leniency,
// we don't incorrectly try to verify someone who didn't even get a challenge, throw a tantrum, and bail.
static void CheckPresentPlayers(void)
{
int i;
memset(knownWhenChallenged, 0, sizeof(knownWhenChallenged));
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
{
continue;
}
else if (IsPlayerGuest(i))
{
continue;
}
else
{
memcpy(knownWhenChallenged[i], players[i].public_key, sizeof(knownWhenChallenged[i]));
}
}
}
// Handle "client-to-client" auth challenge flow.
static void UpdateChallenges(void)
{
if (!(Playing() && netgame))
return;
if (server)
{
if (leveltime == CHALLENGEALL_START)
SendChallenges();
if (leveltime == CHALLENGEALL_KICKUNRESPONSIVE)
KickUnverifiedPlayers();
if (leveltime == CHALLENGEALL_SENDRESULTS)
SendChallengeResults();
}
else // client
{
if (leveltime <= CHALLENGEALL_START)
expectChallenge = true;
if (leveltime == CHALLENGEALL_START)
CheckPresentPlayers();
if (leveltime > CHALLENGEALL_CLIENTCUTOFF && expectChallenge)
HandleSigfail("Didn't receive client signatures.");
}
}
static void RenewHolePunch(void)
{
static time_t past;
@ -5807,7 +6486,7 @@ static void HandleNodeTimeouts(void)
if (server)
{
for (i = 1; i < MAXNETNODES; i++)
if (nodeingame[i] && freezetimeout[i] < I_GetTime())
if ((nodeingame[i] || nodeneedsauth[i]) && freezetimeout[i] < I_GetTime())
Net_ConnectionTimeout(i);
// In case the cvar value was lowered
@ -5937,6 +6616,8 @@ void NetUpdate(void)
UpdatePingTable();
UpdateChallenges();
if (client)
maketic = neededtic;

View file

@ -54,6 +54,11 @@ applications may follow different packet versions.
// This just works as a quick implementation.
#define MAXGENTLEMENDELAY TICRATE
#define PUBKEYLENGTH 32 // Enforced by Monocypher EdDSA
#define PRIVKEYLENGTH 64 // Enforced by Monocypher EdDSA
#define SIGNATURELENGTH 64 // Enforced by Monocypher EdDSA
#define CHALLENGELENGTH 64 // Servers verify client identity by giving them messages to sign. How long are these messages?
//
// Packet structure
//
@ -119,16 +124,29 @@ typedef enum
PT_LOGIN, // Login attempt from the client.
PT_PING, // Packet sent to tell clients the other client's latency to server.
PT_CLIENTKEY, // "Here's my public key"
PT_SERVERCHALLENGE, // "Prove it"
PT_CHALLENGEALL, // Prove to the other clients you are who you say you are, sign this random bullshit!
PT_RESPONSEALL, // OK, here is my signature on that random bullshit
PT_RESULTSALL, // Here's what everyone responded to PT_CHALLENGEALL with, if this is wrong or you don't receive it disconnect
NUMPACKETTYPE
} packettype_t;
typedef enum
{
SIGN_OK,
SIGN_BADTIME, // Timestamp differs by too much, suspect reuse of an old challenge.
SIGN_BADIP // Asked to sign the wrong IP by an external host, suspect reuse of another server's challenge.
} shouldsign_t;
#ifdef PACKETDROP
void Command_Drop(void);
void Command_Droprate(void);
#endif
#ifdef _DEBUG
void Command_Numnodes(void);
#endif
#if defined(_MSC_VER)
#pragma pack(1)
@ -252,6 +270,7 @@ struct clientconfig_pak
UINT8 mode;
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME];
UINT8 availabilities[MAXAVAILABILITY];
uint8_t challengeResponse[MAXSPLITSCREENPLAYERS][SIGNATURELENGTH];
} ATTRPACK;
#define SV_SPEEDMASK 0x03 // used to send kartspeed
@ -346,6 +365,31 @@ struct filesneededconfig_pak
UINT8 files[MAXFILENEEDED]; // is filled with writexxx (byteptr.h)
} ATTRPACK;
struct clientkey_pak
{
uint8_t key[MAXSPLITSCREENPLAYERS][PUBKEYLENGTH];
} ATTRPACK;
struct serverchallenge_pak
{
uint8_t secret[CHALLENGELENGTH];
} ATTRPACK;
struct challengeall_pak
{
uint8_t secret[CHALLENGELENGTH];
} ATTRPACK;
struct responseall_pak
{
uint8_t signature[MAXSPLITSCREENPLAYERS][SIGNATURELENGTH];
} ATTRPACK;
struct resultsall_pak
{
uint8_t signature[MAXPLAYERS][SIGNATURELENGTH];
} ATTRPACK;
//
// Network packet data
//
@ -356,6 +400,9 @@ struct doomdata_t
UINT8 ackreturn; // The return of the ack number
UINT8 packettype;
#ifdef SIGNGAMETRAFFIC
uint8_t signature[MAXSPLITSCREENPLAYERS][SIGNATURELENGTH];
#endif
UINT8 reserved; // Padding
union
{
@ -380,6 +427,11 @@ struct doomdata_t
INT32 filesneedednum; // 4 bytes
filesneededconfig_pak filesneededcfg; // ??? bytes
UINT32 pingtable[MAXPLAYERS+1]; // 68 bytes
clientkey_pak clientkey; // 32 bytes
serverchallenge_pak serverchallenge; // 256 bytes
challengeall_pak challengeall; // 256 bytes
responseall_pak responseall; // 256 bytes
resultsall_pak resultsall; // 1024 bytes. Also, you really shouldn't trust anything here.
} u; // This is needed to pack diff packet types data together
} ATTRPACK;
@ -419,6 +471,7 @@ extern consvar_t cv_playbackspeed;
#define KICK_MSG_PING_HIGH 6
#define KICK_MSG_CUSTOM_KICK 7
#define KICK_MSG_CUSTOM_BAN 8
#define KICK_MSG_SIGFAIL 9
typedef enum
{
@ -443,6 +496,20 @@ extern UINT16 software_MAXPACKETLENGTH;
extern boolean acceptnewnode;
extern SINT8 servernode;
extern char connectedservername[MAXSERVERNAME];
extern UINT32 ourIP;
extern uint8_t lastReceivedKey[MAXNETNODES][MAXSPLITSCREENPLAYERS][PUBKEYLENGTH];
extern uint8_t lastSentChallenge[MAXNETNODES][CHALLENGELENGTH];
extern uint8_t lastChallengeAll[CHALLENGELENGTH];
extern uint8_t lastReceivedSignature[MAXPLAYERS][SIGNATURELENGTH];
extern uint8_t knownWhenChallenged[MAXPLAYERS][PUBKEYLENGTH];
extern boolean expectChallenge;
// We give clients a chance to verify each other once per race.
// When is that challenge sent, and when should clients bail if they don't receive the responses?
#define CHALLENGEALL_START (TICRATE*5) // Server sends challenges here.
#define CHALLENGEALL_KICKUNRESPONSIVE (TICRATE*10) // Server kicks players that haven't submitted signatures here.
#define CHALLENGEALL_SENDRESULTS (TICRATE*15) // Server waits for kicks to process until here. (Failing players shouldn't be in-game when results are received, or clients get spooked.)
#define CHALLENGEALL_CLIENTCUTOFF (TICRATE*20) // If challenge process hasn't completed by now, clients who were in-game for CHALLENGEALL_START should leave.
void Command_Ping_f(void);
extern tic_t connectiontimeout;
@ -465,10 +532,27 @@ extern consvar_t cv_joinnextround;
extern consvar_t cv_discordinvites;
extern consvar_t cv_allowguests;
#ifdef DEVELOP
extern consvar_t cv_badjoin;
extern consvar_t cv_badtraffic;
extern consvar_t cv_badresponse;
extern consvar_t cv_noresponse;
extern consvar_t cv_nochallenge;
extern consvar_t cv_badresults;
extern consvar_t cv_noresults;
extern consvar_t cv_badtime;
extern consvar_t cv_badip;
#endif
// Used in d_net, the only dependence
tic_t ExpandTics(INT32 low, tic_t basetic);
void D_ClientServerInit(void);
void GenerateChallenge(uint8_t *buf);
shouldsign_t ShouldSignChallenge(uint8_t *message);
// Initialise the other field
void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum));
void SendNetXCmdForPlayer(UINT8 playerid, netxcmd_t id, const void *param, size_t nparam);
@ -549,6 +633,8 @@ void CL_ClearRewinds(void);
rewind_t *CL_SaveRewindPoint(size_t demopos);
rewind_t *CL_RewindToTime(tic_t time);
void HandleSigfail(const char *string);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -71,6 +71,9 @@
#include "g_input.h" // tutorial mode control scheming
#include "m_perfstats.h"
#include "monocypher/monocypher.h"
#include "stun.h"
// SRB2Kart
#include "k_grandprix.h"
#include "doomstat.h"

View file

@ -29,6 +29,8 @@
#include "z_zone.h"
#include "i_tcp.h"
#include "d_main.h" // srb2home
#include "stun.h"
#include "monocypher/monocypher.h"
//
// NETWORKING
@ -81,6 +83,7 @@ boolean (*I_NetOpenSocket)(void) = NULL;
boolean (*I_Ban) (INT32 node) = NULL;
void (*I_ClearBans)(void) = NULL;
const char *(*I_GetNodeAddress) (INT32 node) = NULL;
UINT32 (*I_GetNodeAddressInt) (INT32 node) = NULL;
const char *(*I_GetBanAddress) (size_t ban) = NULL;
const char *(*I_GetBanMask) (size_t ban) = NULL;
const char *(*I_GetBanUsername) (size_t ban) = NULL;
@ -90,6 +93,7 @@ boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL;
boolean (*I_SetBanUsername) (const char *username) = NULL;
boolean (*I_SetBanReason) (const char *reason) = NULL;
boolean (*I_SetUnbanTime) (time_t timestamp) = NULL;
boolean (*I_IsExternalAddress) (const void *p) = NULL;
bannednode_t *bannednode = NULL;
@ -629,7 +633,12 @@ static void InitAck(void)
ackpak[i].acknum = 0;
for (i = 0; i < MAXNETNODES; i++)
{
InitNode(&nodes[i]);
csprng(lastSentChallenge[i], sizeof(lastSentChallenge[i]));
csprng(lastReceivedKey[i], sizeof(lastReceivedKey[i]));
}
}
/** Removes all acks of a given packet type
@ -699,6 +708,9 @@ void Net_CloseConnection(INT32 node)
if (server)
SV_AbortLuaFileTransfer(node);
I_NetFreeNodenum(node);
csprng(lastSentChallenge[node], sizeof(lastSentChallenge[node]));
csprng(lastReceivedKey[node], sizeof(lastReceivedKey[node]));
}
//
@ -803,7 +815,14 @@ static const char *packettypename[NUMPACKETTYPE] =
"LOGIN",
"PING"
"PING",
"CLIENTKEY",
"SERVERCHALLENGE",
"CHALLENGEALL",
"RESPONSEALL",
"RESULTSALL"
};
static void DebugPrintpacket(const char *header)
@ -983,12 +1002,72 @@ static boolean ShouldDropPacket(void)
}
#endif
// Unused because Eidolon correctly pointed out that +512b on every packet was scary.
#ifdef SIGNGAMETRAFFIC
boolean IsPacketSigned(int packettype)
{
switch (packettype)
{
case PT_CLIENTCMD:
case PT_CLIENT2CMD:
case PT_CLIENT3CMD:
case PT_CLIENT4CMD:
case PT_CLIENTMIS:
case PT_CLIENT2MIS:
case PT_CLIENT3MIS:
case PT_CLIENT4MIS:
case PT_TEXTCMD:
case PT_TEXTCMD2:
case PT_TEXTCMD3:
case PT_TEXTCMD4:
case PT_LOGIN:
case PT_ASKLUAFILE:
case PT_SENDINGLUAFILE:
return true;
default:
return false;
}
}
#endif
//
// HSendPacket
//
boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlength)
{
doomcom->datalength = (INT16)(packetlength + BASEPACKETSIZE);
#ifdef SIGNGAMETRAFFIC
if (IsPacketSigned(netbuffer->packettype))
{
int i;
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
const void* message = &netbuffer->u;
//CONS_Printf("Signing packet type %d of length %d\n", netbuffer->packettype, packetlength);
if (PR_IsLocalPlayerGuest(i))
memset(netbuffer->signature[i], 0, sizeof(netbuffer->signature[i]));
else
crypto_eddsa_sign(netbuffer->signature[i], PR_GetLocalPlayerProfile(i)->secret_key, message, packetlength);
}
#ifdef DEVELOP
if (cv_badtraffic.value)
{
CV_AddValue(&cv_badtraffic, -1);
CONS_Alert(CONS_WARNING, "cv_badtraffic enabled, scrubbing signature from HSendPacket\n");
memset(netbuffer->signature, 0, sizeof(netbuffer->signature));
}
#endif
}
else
{
//CONS_Printf("NOT signing PT_%d of length %d, it doesn't need to be\n", netbuffer->packettype, packetlength);
memset(netbuffer->signature, 0, sizeof(netbuffer->signature));
}
#endif
if (node == 0) // Packet is to go back to us
{
if ((rebound_head+1) % MAXREBOUND == rebound_tail)

View file

@ -46,6 +46,7 @@ extern SINT8 nodetoplayer3[MAXNETNODES]; // Say the numplayer for this node if a
extern SINT8 nodetoplayer4[MAXNETNODES]; // Say the numplayer for this node if any (splitscreen == 3)
extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen
extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game
extern boolean nodeneedsauth[MAXNETNODES];
extern boolean serverrunning;
@ -68,6 +69,8 @@ void Net_AbortPacketType(UINT8 packettype);
void Net_SendAcks(INT32 node);
void Net_WaitAllAckReceived(UINT32 timeout);
boolean IsPacketSigned(int packettype);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -946,6 +946,20 @@ void D_RegisterClientCommands(void)
CV_RegisterVar(&cv_netticbuffer);
CV_RegisterVar(&cv_mindelay);
CV_RegisterVar(&cv_allowguests);
#ifdef DEVELOP
CV_RegisterVar(&cv_badjoin);
CV_RegisterVar(&cv_badtraffic);
CV_RegisterVar(&cv_badresponse);
CV_RegisterVar(&cv_noresponse);
CV_RegisterVar(&cv_nochallenge);
CV_RegisterVar(&cv_badresults);
CV_RegisterVar(&cv_noresults);
CV_RegisterVar(&cv_badtime);
CV_RegisterVar(&cv_badip);
#endif
// HUD
CV_RegisterVar(&cv_alttitle);
CV_RegisterVar(&cv_itemfinder);

View file

@ -53,6 +53,7 @@
#include "k_menu.h"
#include "md5.h"
#include "filesrch.h"
#include "stun.h"
#include <errno.h>
@ -1313,6 +1314,22 @@ void PT_FileReceived(void)
SV_EndFileSend(doomcom->remotenode);
}
// Someone knocked on the door with their public key.
// Give them a challenge to sign in their PT_CLIENTJOIN.
void PT_ClientKey(INT32 node)
{
clientkey_pak *packet = (void*)&netbuffer->u.clientkey;
memcpy(lastReceivedKey[node], packet->key, sizeof(lastReceivedKey[node]));
netbuffer->packettype = PT_SERVERCHALLENGE;
GenerateChallenge(lastSentChallenge[node]);
memcpy(&netbuffer->u.serverchallenge, lastSentChallenge[node], sizeof(serverchallenge_pak));
HSendPacket(node, false, 0, sizeof (serverchallenge_pak));
}
static void SendAckPacket(fileack_pak *packet, UINT8 fileid)
{
size_t packetsize;

View file

@ -106,6 +106,8 @@ boolean CL_CheckDownloadable(void);
boolean CL_SendFileRequest(void);
boolean PT_RequestFile(INT32 node);
void PT_ClientKey(INT32 node);
typedef enum
{
LFTNS_NONE, // This node is not connected

View file

@ -713,6 +713,8 @@ struct player_t
mobj_t *stumbleIndicator;
mobj_t *sliptideZipIndicator;
uint8_t public_key[PUBKEYLENGTH];
#ifdef HWRENDER
fixed_t fovadd; // adjust FOV for hw rendering
#endif
@ -721,6 +723,9 @@ struct player_t
roundconditions_t roundconditions;
};
// WARNING FOR ANYONE ABOUT TO ADD SOMETHING TO THE PLAYER STRUCT, G_PlayerReborn WANTS YOU TO SUFFER
// If data on player_t needs to persist between rounds or during the join process, modify G_PlayerReborn to preserve it.
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -2436,6 +2436,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
SINT8 xtralife;
uint8_t public_key[PUBKEYLENGTH];
// SRB2kart
itemroulette_t itemRoulette;
respawnvars_t respawn;
@ -2509,6 +2511,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
// SRB2kart
memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette));
memcpy(&respawn, &players[player].respawn, sizeof (respawn));
memcpy(&public_key, &players[player].public_key, sizeof(public_key));
if (betweenmaps || leveltime < introtime)
{
@ -2678,6 +2681,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette));
memcpy(&p->respawn, &respawn, sizeof (p->respawn));
memcpy(&p->public_key, &public_key, sizeof(p->public_key));
if (saveroundconditions)
memcpy(&p->roundconditions, &roundconditions, sizeof (p->roundconditions));

View file

@ -166,6 +166,7 @@ extern void (*I_NetRegisterHolePunch)(void);
extern boolean (*I_Ban) (INT32 node);
extern void (*I_ClearBans)(void);
extern const char *(*I_GetNodeAddress) (INT32 node);
extern UINT32 (*I_GetNodeAddressInt) (INT32 node);
extern const char *(*I_GetBanAddress) (size_t ban);
extern const char *(*I_GetBanMask) (size_t ban);
extern const char *(*I_GetBanUsername) (size_t ban);
@ -175,6 +176,7 @@ extern boolean (*I_SetBanAddress) (const char *address,const char *mask);
extern boolean (*I_SetBanUsername) (const char *username);
extern boolean (*I_SetBanReason) (const char *reason);
extern boolean (*I_SetUnbanTime) (time_t timestamp);
extern boolean (*I_IsExternalAddress) (const void *p);
struct bannednode_t
{

View file

@ -387,6 +387,20 @@ static const char *SOCK_GetNodeAddress(INT32 node)
return SOCK_AddrToStr(&clientaddress[node]);
}
static UINT32 SOCK_GetNodeAddressInt(INT32 node)
{
if (nodeconnected[node] && clientaddress[node].any.sa_family == AF_INET)
{
return clientaddress[node].ip4.sin_addr.s_addr;
}
else
{
I_Error("SOCK_GetNodeAddressInt: Node %d is not IPv4!\n", node);
}
return 0;
}
static const char *SOCK_GetBanAddress(size_t ban)
{
if (ban >= numbans)
@ -459,7 +473,7 @@ static void cleanupnodes(void)
// Why can't I start at zero?
for (j = 1; j < MAXNETNODES; j++)
if (!(nodeingame[j] || SendingFile(j)))
if (!(nodeingame[j] || nodeneedsauth[j] || SendingFile(j)))
nodeconnected[j] = false;
}
@ -489,7 +503,6 @@ static SINT8 getfreenode(void)
return -1;
}
#ifdef _DEBUG
void Command_Numnodes(void)
{
INT32 connected = 0;
@ -527,7 +540,6 @@ void Command_Numnodes(void)
"Ingame: %d\n",
connected, ingame);
}
#endif
static boolean hole_punch(ssize_t c)
{
@ -1510,6 +1522,31 @@ static void SOCK_ClearBans(void)
banned = NULL;
}
// https://github.com/jameds/holepunch/blob/master/holepunch.c#L75
static int SOCK_IsExternalAddress (const void *p)
{
const int a = ((const unsigned char*)p)[0];
const int b = ((const unsigned char*)p)[1];
if (*(const UINT32*)p == (UINT32)~0)/* 255.255.255.255 */
return 0;
switch (a)
{
case 0:
case 10:
case 127:
return 0;
case 172:
return (b & ~15) != 16;/* 16 - 31 */
case 192:
return b != 168;
default:
return 1;
}
}
boolean I_InitTcpNetwork(void)
{
char serverhostname[255];
@ -1600,6 +1637,7 @@ boolean I_InitTcpNetwork(void)
I_Ban = SOCK_Ban;
I_ClearBans = SOCK_ClearBans;
I_GetNodeAddress = SOCK_GetNodeAddress;
I_GetNodeAddressInt = SOCK_GetNodeAddressInt;
I_GetBanAddress = SOCK_GetBanAddress;
I_GetBanMask = SOCK_GetBanMask;
I_GetBanUsername = SOCK_GetBanUsername;
@ -1609,6 +1647,8 @@ boolean I_InitTcpNetwork(void)
I_SetBanUsername = SOCK_SetBanUsername;
I_SetBanReason = SOCK_SetBanReason;
I_SetUnbanTime = SOCK_SetUnbanTime;
I_IsExternalAddress = SOCK_IsExternalAddress;
bannednode = SOCK_bannednode;
return ret;

View file

@ -1768,7 +1768,8 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
if (p != NULL)
{
V_DrawProfileNum(x + 37 + 10, y + 131, 0, PR_GetProfileNum(p));
V_DrawCenteredThinString(x, y + 151, V_GRAYMAP|V_6WIDTHSPACE, p->playername);
V_DrawCenteredThinString(x, y + 141, V_GRAYMAP|V_6WIDTHSPACE, p->playername);
V_DrawCenteredThinString(x, y + 151, V_GRAYMAP|V_6WIDTHSPACE, GetPrettyRRID(p->public_key, true));
}
}

View file

@ -18,6 +18,8 @@
#include "k_profiles.h"
#include "z_zone.h"
#include "r_skins.h"
#include "monocypher/monocypher.h"
#include "stun.h"
// List of all the profiles.
static profile_t *profilesList[MAXPROFILES+1]; // +1 because we're gonna add a default "GUEST' profile.
@ -28,6 +30,13 @@ INT32 PR_GetNumProfiles(void)
return numprofiles;
}
static void PR_GenerateProfileKeys(profile_t *new)
{
static uint8_t seed[32];
csprng(seed, 32);
crypto_eddsa_key_pair(new->secret_key, new->public_key, seed);
}
profile_t* PR_MakeProfile(
const char *prname,
const char *pname,
@ -41,6 +50,14 @@ profile_t* PR_MakeProfile(
new->version = PROFILEVER;
memset(new->secret_key, 0, sizeof(new->secret_key));
memset(new->public_key, 0, sizeof(new->public_key));
if (!guest)
{
PR_GenerateProfileKeys(new);
}
strcpy(new->profilename, prname);
new->profilename[sizeof new->profilename - 1] = '\0';
@ -238,8 +255,10 @@ void PR_SaveProfiles(void)
for (i = 1; i < numprofiles; i++)
{
// Names.
// Names and keys, all the string data up front
WRITESTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN);
WRITEMEM(save.p, profilesList[i]->public_key, sizeof(((profile_t *)0)->public_key));
WRITEMEM(save.p, profilesList[i]->secret_key, sizeof(((profile_t *)0)->secret_key));
WRITESTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
@ -329,8 +348,21 @@ void PR_LoadProfiles(void)
// Version. (We always update this on successful forward step)
profilesList[i]->version = PROFILEVER;
// Names.
// Names and keys, all the identity stuff up front
READSTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN);
// Profile update 2-->3: Add profile keys.
if (version < 3)
{
// Generate missing keys.
PR_GenerateProfileKeys(profilesList[i]);
}
else
{
READMEM(save.p, profilesList[i]->public_key, sizeof(((profile_t *)0)->public_key));
READMEM(save.p, profilesList[i]->secret_key, sizeof(((profile_t *)0)->secret_key));
}
READSTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
@ -550,3 +582,39 @@ profile_t *PR_GetPlayerProfile(player_t *player)
return NULL;
}
profile_t *PR_GetLocalPlayerProfile(INT32 player)
{
if (player >= MAXSPLITSCREENPLAYERS)
return NULL;
return PR_GetProfile(cv_lastprofile[player].value);
}
boolean PR_IsLocalPlayerGuest(INT32 player)
{
return !(cv_lastprofile[player].value);
}
static char rrid_buf[256];
char *GetPrettyRRID(const unsigned char *bin, boolean brief)
{
size_t i;
size_t len = PUBKEYLENGTH;
if (brief)
len = 8;
if (bin == NULL || len == 0)
return NULL;
for (i=0; i<len; i++)
{
rrid_buf[i*2] = "0123456789ABCDEF"[bin[i] >> 4];
rrid_buf[i*2+1] = "0123456789ABCDEF"[bin[i] & 0x0F];
}
rrid_buf[len*2] = '\0';
return rrid_buf;
}

View file

@ -31,7 +31,7 @@ extern "C" {
#define SKINNAMESIZE 16
#define PROFILENAMELEN 6
#define PROFILEVER 2
#define PROFILEVER 3
#define MAXPROFILES 16
#define PROFILESFILE "ringprofiles.prf"
#define PROFILE_GUEST 0
@ -59,6 +59,9 @@ struct profile_t
// Profile header
char profilename[PROFILENAMELEN+1]; // Profile name (not to be confused with player name)
uint8_t public_key[PUBKEYLENGTH]; // Netgame authentication
uint8_t secret_key[PRIVKEYLENGTH];
// Player data
char playername[MAXPLAYERNAME+1]; // Player name
char skinname[SKINNAMESIZE+1]; // Default Skin
@ -156,6 +159,12 @@ SINT8 PR_ProfileUsedBy(profile_t *p);
profile_t *PR_GetPlayerProfile(player_t *player);
profile_t *PR_GetLocalPlayerProfile(INT32 player);
boolean PR_IsLocalPlayerGuest(INT32 player);
char *GetPrettyRRID(const unsigned char *bin, boolean brief);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -22,6 +22,7 @@
#include "lua_libs.h"
#include "lua_hud.h" // hud_running errors
#include "lua_hook.h" // hook_cmd_running errors
#include "k_profiles.h" // GetPrettyRRID
static int lib_iteratePlayers(lua_State *L)
{
@ -507,6 +508,8 @@ static int player_get(lua_State *L)
#endif
else if (fastcmp(field,"ping"))
lua_pushinteger(L, playerpingtable[( plr - players )]);
else if (fastcmp(field, "public_key"))
lua_pushstring(L, GetPrettyRRID(plr->public_key, false));
else {
lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
I_Assert(lua_istable(L, -1));
@ -876,7 +879,7 @@ static int player_set(lua_State *L)
else if (fastcmp(field,"bot"))
return NOSET;
else if (fastcmp(field,"jointime"))
plr->jointime = (tic_t)luaL_checkinteger(L, 3);
return NOSET;
else if (fastcmp(field,"splitscreenindex"))
return NOSET;
#ifdef HWRENDER

View file

@ -0,0 +1,4 @@
target_sources(SRB2SDL2 PRIVATE
monocypher.c
monocypher.h
)

2938
src/monocypher/monocypher.c Normal file

File diff suppressed because it is too large Load diff

321
src/monocypher/monocypher.h Normal file
View file

@ -0,0 +1,321 @@
// Monocypher version 4.0.0
//
// This file is dual-licensed. Choose whichever licence you want from
// the two licences listed below.
//
// The first licence is a regular 2-clause BSD licence. The second licence
// is the CC-0 from Creative Commons. It is intended to release Monocypher
// to the public domain. The BSD licence serves as a fallback option.
//
// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
//
// ------------------------------------------------------------------------
//
// Copyright (c) 2017-2019, Loup Vaillant
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ------------------------------------------------------------------------
//
// Written in 2017-2019 by Loup Vaillant
//
// To the extent possible under law, the author(s) have dedicated all copyright
// and related neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along
// with this software. If not, see
// <https://creativecommons.org/publicdomain/zero/1.0/>
#ifndef MONOCYPHER_H
#define MONOCYPHER_H
#include <stddef.h>
#include <stdint.h>
#ifdef MONOCYPHER_CPP_NAMESPACE
namespace MONOCYPHER_CPP_NAMESPACE {
#elif defined(__cplusplus)
extern "C" {
#endif
// Constant time comparisons
// -------------------------
// Return 0 if a and b are equal, -1 otherwise
int crypto_verify16(const uint8_t a[16], const uint8_t b[16]);
int crypto_verify32(const uint8_t a[32], const uint8_t b[32]);
int crypto_verify64(const uint8_t a[64], const uint8_t b[64]);
// Erase sensitive data
// --------------------
void crypto_wipe(void *secret, size_t size);
// Authenticated encryption
// ------------------------
void crypto_aead_lock(uint8_t *cipher_text,
uint8_t mac [16],
const uint8_t key [32],
const uint8_t nonce[24],
const uint8_t *ad, size_t ad_size,
const uint8_t *plain_text, size_t text_size);
int crypto_aead_unlock(uint8_t *plain_text,
const uint8_t mac [16],
const uint8_t key [32],
const uint8_t nonce[24],
const uint8_t *ad, size_t ad_size,
const uint8_t *cipher_text, size_t text_size);
// Authenticated stream
// --------------------
typedef struct {
uint64_t counter;
uint8_t key[32];
uint8_t nonce[8];
} crypto_aead_ctx;
void crypto_aead_init_x(crypto_aead_ctx *ctx,
const uint8_t key[32], const uint8_t nonce[24]);
void crypto_aead_init_djb(crypto_aead_ctx *ctx,
const uint8_t key[32], const uint8_t nonce[8]);
void crypto_aead_init_ietf(crypto_aead_ctx *ctx,
const uint8_t key[32], const uint8_t nonce[12]);
void crypto_aead_write(crypto_aead_ctx *ctx,
uint8_t *cipher_text,
uint8_t mac[16],
const uint8_t *ad , size_t ad_size,
const uint8_t *plain_text, size_t text_size);
int crypto_aead_read(crypto_aead_ctx *ctx,
uint8_t *plain_text,
const uint8_t mac[16],
const uint8_t *ad , size_t ad_size,
const uint8_t *cipher_text, size_t text_size);
// General purpose hash (BLAKE2b)
// ------------------------------
// Direct interface
void crypto_blake2b(uint8_t *hash, size_t hash_size,
const uint8_t *message, size_t message_size);
void crypto_blake2b_keyed(uint8_t *hash, size_t hash_size,
const uint8_t *key, size_t key_size,
const uint8_t *message, size_t message_size);
// Incremental interface
typedef struct {
// Do not rely on the size or contents of this type,
// for they may change without notice.
uint64_t hash[8];
uint64_t input_offset[2];
uint64_t input[16];
size_t input_idx;
size_t hash_size;
} crypto_blake2b_ctx;
void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size);
void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size,
const uint8_t *key, size_t key_size);
void crypto_blake2b_update(crypto_blake2b_ctx *ctx,
const uint8_t *message, size_t message_size);
void crypto_blake2b_final(crypto_blake2b_ctx *ctx, uint8_t *hash);
// Password key derivation (Argon2)
// --------------------------------
#define CRYPTO_ARGON2_D 0
#define CRYPTO_ARGON2_I 1
#define CRYPTO_ARGON2_ID 2
typedef struct {
uint32_t algorithm; // Argon2d, Argon2i, Argon2id
uint32_t nb_blocks; // memory hardness, >= 8 * nb_lanes
uint32_t nb_passes; // CPU hardness, >= 1 (>= 3 recommended for Argon2i)
uint32_t nb_lanes; // parallelism level (single threaded anyway)
} crypto_argon2_config;
typedef struct {
const uint8_t *pass;
const uint8_t *salt;
uint32_t pass_size;
uint32_t salt_size; // 16 bytes recommended
} crypto_argon2_inputs;
typedef struct {
const uint8_t *key; // may be NULL if no key
const uint8_t *ad; // may be NULL if no additional data
uint32_t key_size; // 0 if no key (32 bytes recommended otherwise)
uint32_t ad_size; // 0 if no additional data
} crypto_argon2_extras;
extern const crypto_argon2_extras crypto_argon2_no_extras;
void crypto_argon2(uint8_t *hash, uint32_t hash_size, void *work_area,
crypto_argon2_config config,
crypto_argon2_inputs inputs,
crypto_argon2_extras extras);
// Key exchange (X-25519)
// ----------------------
// Shared secrets are not quite random.
// Hash them to derive an actual shared key.
void crypto_x25519_public_key(uint8_t public_key[32],
const uint8_t secret_key[32]);
void crypto_x25519(uint8_t raw_shared_secret[32],
const uint8_t your_secret_key [32],
const uint8_t their_public_key [32]);
// Conversion to EdDSA
void crypto_x25519_to_eddsa(uint8_t eddsa[32], const uint8_t x25519[32]);
// scalar "division"
// Used for OPRF. Be aware that exponential blinding is less secure
// than Diffie-Hellman key exchange.
void crypto_x25519_inverse(uint8_t blind_salt [32],
const uint8_t private_key[32],
const uint8_t curve_point[32]);
// "Dirty" versions of x25519_public_key().
// Use with crypto_elligator_rev().
// Leaks 3 bits of the private key.
void crypto_x25519_dirty_small(uint8_t pk[32], const uint8_t sk[32]);
void crypto_x25519_dirty_fast (uint8_t pk[32], const uint8_t sk[32]);
// Signatures
// ----------
// EdDSA with curve25519 + BLAKE2b
void crypto_eddsa_key_pair(uint8_t secret_key[64],
uint8_t public_key[32],
uint8_t seed[32]);
void crypto_eddsa_sign(uint8_t signature [64],
const uint8_t secret_key[64],
const uint8_t *message, size_t message_size);
int crypto_eddsa_check(const uint8_t signature [64],
const uint8_t public_key[32],
const uint8_t *message, size_t message_size);
// Conversion to X25519
void crypto_eddsa_to_x25519(uint8_t x25519[32], const uint8_t eddsa[32]);
// EdDSA building blocks
void crypto_eddsa_trim_scalar(uint8_t out[32], const uint8_t in[32]);
void crypto_eddsa_reduce(uint8_t reduced[32], const uint8_t expanded[64]);
void crypto_eddsa_mul_add(uint8_t r[32],
const uint8_t a[32],
const uint8_t b[32],
const uint8_t c[32]);
void crypto_eddsa_scalarbase(uint8_t point[32], const uint8_t scalar[32]);
int crypto_eddsa_check_equation(const uint8_t signature[64],
const uint8_t public_key[32],
const uint8_t h_ram[32]);
// Chacha20
// --------
// Specialised hash.
// Used to hash X25519 shared secrets.
void crypto_chacha20_h(uint8_t out[32],
const uint8_t key[32],
const uint8_t in [16]);
// Unauthenticated stream cipher.
// Don't forget to add authentication.
uint64_t crypto_chacha20_djb(uint8_t *cipher_text,
const uint8_t *plain_text,
size_t text_size,
const uint8_t key[32],
const uint8_t nonce[8],
uint64_t ctr);
uint32_t crypto_chacha20_ietf(uint8_t *cipher_text,
const uint8_t *plain_text,
size_t text_size,
const uint8_t key[32],
const uint8_t nonce[12],
uint32_t ctr);
uint64_t crypto_chacha20_x(uint8_t *cipher_text,
const uint8_t *plain_text,
size_t text_size,
const uint8_t key[32],
const uint8_t nonce[24],
uint64_t ctr);
// Poly 1305
// ---------
// This is a *one time* authenticator.
// Disclosing the mac reveals the key.
// See crypto_lock() on how to use it properly.
// Direct interface
void crypto_poly1305(uint8_t mac[16],
const uint8_t *message, size_t message_size,
const uint8_t key[32]);
// Incremental interface
typedef struct {
// Do not rely on the size or contents of this type,
// for they may change without notice.
uint8_t c[16]; // chunk of the message
size_t c_idx; // How many bytes are there in the chunk.
uint32_t r [4]; // constant multiplier (from the secret key)
uint32_t pad[4]; // random number added at the end (from the secret key)
uint32_t h [5]; // accumulated hash
} crypto_poly1305_ctx;
void crypto_poly1305_init (crypto_poly1305_ctx *ctx, const uint8_t key[32]);
void crypto_poly1305_update(crypto_poly1305_ctx *ctx,
const uint8_t *message, size_t message_size);
void crypto_poly1305_final (crypto_poly1305_ctx *ctx, uint8_t mac[16]);
// Elligator 2
// -----------
// Elligator mappings proper
void crypto_elligator_map(uint8_t curve [32], const uint8_t hidden[32]);
int crypto_elligator_rev(uint8_t hidden[32], const uint8_t curve [32],
uint8_t tweak);
// Easy to use key pair generation
void crypto_elligator_key_pair(uint8_t hidden[32], uint8_t secret_key[32],
uint8_t seed[32]);
#ifdef __cplusplus
}
#endif
#endif // MONOCYPHER_H

View file

@ -407,6 +407,8 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].sliptideZipDelay);
WRITEUINT16(save->p, players[i].sliptideZipBoost);
WRITEMEM(save->p, players[i].public_key, PUBKEYLENGTH);
// respawnvars_t
WRITEUINT8(save->p, players[i].respawn.state);
WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].respawn.wp));
@ -787,6 +789,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].sliptideZipDelay = READUINT8(save->p);
players[i].sliptideZipBoost = READUINT16(save->p);
READMEM(save->p, players[i].public_key, PUBKEYLENGTH);
// respawnvars_t
players[i].respawn.state = READUINT8(save->p);
players[i].respawn.wp = (waypoint_t *)(size_t)READUINT32(save->p);

View file

@ -84,7 +84,7 @@ STUN_node (void)
return node;
}
static void
void
csprng
(
void * const buffer,

View file

@ -18,6 +18,8 @@ extern "C" {
typedef void (*stun_callback_t)(UINT32 address);
void csprng (void * const buffer, const size_t size);
void STUN_bind (stun_callback_t);
boolean STUN_got_response (const char * const buffer, const size_t size);

View file

@ -71,6 +71,11 @@ TYPEDEF (filesneededconfig_pak);
TYPEDEF (doomdata_t);
TYPEDEF (serverelem_t);
TYPEDEF (rewind_t);
TYPEDEF (clientkey_pak);
TYPEDEF (serverchallenge_pak);
TYPEDEF (challengeall_pak);
TYPEDEF (responseall_pak);
TYPEDEF (resultsall_pak);
// d_event.h
TYPEDEF (event_t);