Skip to content

Commit

Permalink
Update DTLS stuff, fixing certificate pinning bugs.
Browse files Browse the repository at this point in the history
We should now support dtls1.2 under win10 (otherwise win7+8 is still stuck with 1.0).
SSQC can now query client certificate info via infokey - *cert_sha1 or *cert_dn
Server addresses can be postfixed with eg ip:port?fp=BASE64 to provide a fingerprint to verify the server without depending on cert authorities.
  • Loading branch information
Shpoike committed Apr 17, 2023
1 parent 4d06516 commit f2d54f3
Show file tree
Hide file tree
Showing 17 changed files with 1,332 additions and 399 deletions.
361 changes: 229 additions & 132 deletions engine/client/cl_main.c

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion engine/client/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,8 @@ typedef struct
infobuf_t userinfo[MAX_SPLITS];
infosync_t userinfosync;

char servername[MAX_OSPATH]; // name of server from original connect
char serverurl[MAX_OSPATH*4]; // eg qw://foo:27500/join?fp=blah
char servername[MAX_OSPATH]; // internal parsing, eg dtls://foo:27500

struct ftenet_connections_s *sockets;

Expand Down
27 changes: 27 additions & 0 deletions engine/client/m_multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,20 @@ void M_Menu_GameOptions_f (void)
y+=4;
info->hostnameedit = MC_AddEdit (menu, 64, 160, y, "Hostname", name.string);y+=info->hostnameedit->common.height;
info->publicgame = MC_AddCombo (menu, 64, 160, y, "Public", publicoptions, bound(0, sv_public.ival+1, 4));y+=8;
#if !defined(FTE_TARGET_WEB) && defined(HAVE_DTLS)
{
extern cvar_t net_enable_dtls;
static const char *encoptions[] =
{
"None",
"Accept",
"Request",
"Require",
NULL
};
MC_AddCvarCombo (menu, 64, 160, y, "DTLS Encryption", &net_enable_dtls, encoptions, NULL);y+=8;
}
#endif
y+=4;

for (players = 0; players < sizeof(numplayeroptions)/ sizeof(numplayeroptions[0]); players++)
Expand Down Expand Up @@ -1110,6 +1124,16 @@ void M_Menu_Network_f (void)
"Smooth Demos Only",
NULL
};
#ifdef HAVE_DTLS
extern cvar_t net_enable_dtls;
static const char *dtlsopts[] = {
"Disabled",
"Accept",
"Request",
"Require",
NULL
};
#endif
static const char *smoothingvalues[] = {"0", "1", "2", NULL};
extern cvar_t cl_download_csprogs, cl_download_redirection, requiredownloads, cl_solid_players;
extern cvar_t cl_predict_players, cl_lerp_smooth, cl_predict_extrapolate;
Expand All @@ -1122,6 +1146,9 @@ void M_Menu_Network_f (void)
MB_EDITCVARSLIM("Network FPS", "cl_netfps", "Sets ammount of FPS used to communicate with server (sent and received)"),
MB_EDITCVARSLIM("Rate", "rate", "Maximum bytes per second that the server should send to the client"),
MB_EDITCVARSLIM("Download Rate", "drate", "Maximum bytes per second that the server should send maps and demos to the client"),
#ifdef HAVE_DTLS
MB_COMBOCVAR("DTLS Encryption", net_enable_dtls, dtlsopts, NULL, "Use this to avoid snooping. Certificates will be pinned."),
#endif
MB_SPACING(4),
MB_CHECKBOXCVARTIP("Require Download", requiredownloads, 0, "Ignore downloaded content sent to the client and connect immediately"),
MB_CHECKBOXCVARTIP("Redirect Download", cl_download_redirection, 0, "Whether the client will ignore download redirection from servers"),
Expand Down
6 changes: 5 additions & 1 deletion engine/client/net_master.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ clientside master queries and server ping/polls

#include "quakedef.h"
#include "cl_master.h"
#include "netinc.h"

#define FAVOURITESFILE "favourites.txt"

Expand Down Expand Up @@ -496,7 +497,10 @@ static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b)
{
//tcp masters require a route
if (NET_AddrIsReliable(na))
NET_EnsureRoute(svs.sockets, master->cv.name, master->cv.string, na);
{
struct dtlspeercred_s cred = {master->cv.string};
NET_EnsureRoute(svs.sockets, master->cv.name, &cred, na);
}

//q2+qw masters are given a ping to verify that they're still up
switch (master->protocol)
Expand Down
21 changes: 19 additions & 2 deletions engine/common/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -6891,9 +6891,9 @@ static int Base64_Decode(char inp)
return (inp-'a') + 26;
if (inp >= '0' && inp <= '9')
return (inp-'0') + 52;
if (inp == '+')
if (inp == '+' || inp == '-')
return 62;
if (inp == '/')
if (inp == '/' || inp == '_')
return 63;
//if (inp == '=') //padding char
return 0; //invalid
Expand Down Expand Up @@ -6929,6 +6929,23 @@ size_t Base64_EncodeBlock(const qbyte *in, size_t length, char *out, size_t outs
*out = 0;
return out-start;
}
size_t Base64_EncodeBlockURI(const qbyte *in, size_t length, char *out, size_t outsize)
{ //special uri-safe version (also trims)
outsize = Base64_EncodeBlock(in, length, out, outsize);
for (length = 0; length < outsize; length++)
{
if (out[length] == '+')
out[length] = '-';
else if (out[length] == '/')
out[length] = '_';
else if (out[length] == '=')
{ //truncate it here.
out[length] = 0;
return length;
}
}
return outsize;
}
size_t Base64_DecodeBlock(const char *in, const char *in_end, qbyte *out, size_t outsize)
{
qbyte *start = out;
Expand Down
17 changes: 16 additions & 1 deletion engine/common/log.c
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,10 @@ static void CertLog_Write(void)
VFS_PUTS(f, certhex);
VFS_PRINTF(f, "\" %i\n", l->trusted?true:false);
}
VFS_CLOSE(f);
}
else
Con_Printf(CON_ERROR"Unable to write %s\n", CERTLOG_FILENAME);
}
static void CertLog_Purge(void)
{
Expand Down Expand Up @@ -753,6 +756,7 @@ static void CertLog_Import(const char *filename)
}
CertLog_Update(addressstring, certdata, certsize, atoi(trusted));
}
VFS_CLOSE(f);
}
static void CertLog_UntrustAll_f(void)
{
Expand Down Expand Up @@ -795,6 +799,8 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize,
extern cvar_t net_enable_dtls;
struct certlog_s *l;
qboolean trusted = (net_enable_dtls.ival >= 2);
char digest[DIGEST_MAXSIZE];
char fp[DIGEST_MAXSIZE*2+1];

if (certlog_curprompt)
return false;
Expand All @@ -808,19 +814,28 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize,
{ //cert is new, but we don't care about full trust. don't bother to prompt when the user doesn't much care.
//(but do pin so we at least know when its MITMed after the fact)
Con_Printf(CON_WARNING"Auto-Pinning certificate for %s."CON_DEFAULT" ^[/seta %s 2^]+ for actual security.\n", hostname, net_enable_dtls.name);
if (certsize)
Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), fp, sizeof(fp));
else
strcpy(fp, "<No Certificate>");
Con_Printf(S_COLOR_GRAY" fp: %s\n", fp);
CertLog_Update(hostname, cert, certsize, false);
CertLog_Write();
}
else if (!l || l->certsize != certsize || memcmp(l->cert, cert, certsize) || (trusted && !l->trusted))
{ //new or different
if (certsize)
Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), fp, sizeof(fp));
else
strcpy(fp, "<No Certificate>");
if (qrenderer)
{
unsigned int i;
size_t len;
char *text;
const char *accepttext;
const char *lines[] = {
va(localtext("Certificate for %s\n"), hostname),
va(localtext("Certificate for %s\n(fp:"S_COLOR_GRAY"%s"S_COLOR_WHITE")\n"), hostname, fp),
(certlogproblems&CERTLOG_WRONGHOST)?localtext("^1Certificate does not match host\n"):"",
((certlogproblems&(CERTLOG_MISSINGCA|CERTLOG_WRONGHOST))==CERTLOG_MISSINGCA)?localtext("^1Certificate authority is untrusted.\n"):"",
(certlogproblems&CERTLOG_EXPIRED)?localtext("^1Expired Certificate\n"):"",
Expand Down
13 changes: 9 additions & 4 deletions engine/common/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ neterr_t NET_SendPacket (struct ftenet_connections_s *col, int length, const voi
int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_t *remote, netadr_t *local, int idx);
void NET_PrintAddresses(struct ftenet_connections_s *collection);
qboolean NET_AddressSmellsFunny(netadr_t *a);
qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, char *host, netadr_t *adr);
struct dtlspeercred_s;
qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr);
void NET_TerminateRoute(struct ftenet_connections_s *collection, netadr_t *adr);
void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection);

Expand Down Expand Up @@ -191,9 +192,13 @@ qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *na

enum certprops_e
{
QCERT_PEERFINGERPRINT
QCERT_ISENCRYPTED, //0 or error
QCERT_PEERSUBJECT, //null terminated. should be a hash of the primary cert, ignoring chain.
QCERT_PEERCERTIFICATE, //should be the primary cert, ignoring chain. no fixed maximum size required, mostly 2k but probably best to allow at leasy 5k.. or 8k.

QCERT_LOCALCERTIFICATE, //the cert we're using/advertising. may have no context. to tell people what fp to expect.
};
size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize);
int NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize);

#ifdef HAVE_DTLS
struct dtlscred_s;
Expand All @@ -207,7 +212,7 @@ extern cvar_t dtls_psk_hint, dtls_psk_user, dtls_psk_key;
#ifdef SUPPORT_ICE
neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to);
void ICE_Terminate(netadr_t *to); //if we kicked the client/etc, kill their ICE too.
qboolean ICE_IsEncrypted(netadr_t *to);
int ICE_GetPeerCertificate(netadr_t *to, enum certprops_e prop, char *out, size_t outsize);
void ICE_Init(void);
#endif
extern cvar_t timeout;
Expand Down
57 changes: 47 additions & 10 deletions engine/common/net_ice.c
Original file line number Diff line number Diff line change
Expand Up @@ -2103,9 +2103,12 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch
}
else if (!strcmp(value, STRINGIFY(ICE_FAILED)))
{
con->state = ICE_FAILED;
if (net_ice_debug.ival >= 1)
Con_Printf(S_COLOR_GRAY"[%s]: ice state failed\n", con->friendlyname);
if (con->state != ICE_FAILED)
{
con->state = ICE_FAILED;
if (net_ice_debug.ival >= 1)
Con_Printf(S_COLOR_GRAY"[%s]: ice state failed\n", con->friendlyname);
}
}
else if (!strcmp(value, STRINGIFY(ICE_CONNECTED)))
{
Expand Down Expand Up @@ -4805,20 +4808,22 @@ qboolean ICE_WasStun(ftenet_connections_t *col)
return false;
}
#ifdef SUPPORT_ICE
qboolean ICE_IsEncrypted(netadr_t *to)
int ICE_GetPeerCertificate(netadr_t *to, enum certprops_e prop, char *out, size_t outsize)
{
#ifdef HAVE_DTLS
struct icestate_s *con;
for (con = icelist; con; con = con->next)
{
if (NET_CompareAdr(to, &con->qadr))
{
if (con->dtlsstate)
return true;
if (con->dtlsstate && con->dtlsfuncs->GetPeerCertificate)
return con->dtlsfuncs->GetPeerCertificate(con->dtlsstate, prop, out, outsize);
else if (prop==QCERT_ISENCRYPTED && con->dtlsstate)
return 0;
}
}
#endif
return false;
return -1;
}
void ICE_Terminate(netadr_t *to)
{
Expand Down Expand Up @@ -5243,14 +5248,16 @@ static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon)
if (cl == -1)
{
b->error = true;
// Con_Printf("Broker closed connection: %s\n", data);
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Broker lost connection: %s\n", b->ice?b->ice->friendlyname:"?", *data?data:"<NO REASON>");
}
else if (cl >= 0 && cl < b->numclients)
{
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Broker lost connection: %s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", *data?data:"<NO REASON>");
if (b->clients[cl].ice)
iceapi.Close(b->clients[cl].ice, false);
b->clients[cl].ice = NULL;
// Con_Printf("Broker closing connection: %s\n", data);
}
break;
case ICEMSG_NAMEINUSE:
Expand All @@ -5273,13 +5280,23 @@ static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon)
Z_ReallocElements((void**)&b->clients, &b->numclients, cl+1, sizeof(b->clients[0]));
}
if (cl >= 0 && cl < b->numclients)
{
FTENET_ICE_Establish(b, cl, &b->clients[cl].ice);

if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: New client spotted...\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?");
}
else if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: New client spotted, but index is unusable\n", "?");
}
else
{
// Con_DPrintf("Server found: %s\n", data);
FTENET_ICE_Establish(b, cl, &b->ice);
b->serverid = cl;

if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Relay to server now open\n", b->ice?b->ice->friendlyname:"?");
}
break;
case ICEMSG_OFFER: //we received an offer from a client
Expand All @@ -5299,19 +5316,31 @@ static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon)
{
if (cl >= 0 && cl < b->numclients && b->clients[cl].ice)
{
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got offer:\n%s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", data);
iceapi.Set(b->clients[cl].ice, "sdpoffer", data);
iceapi.Set(b->clients[cl].ice, "state", STRINGIFY(ICE_CONNECTING));

FTENET_ICE_SendOffer(b, cl, b->clients[cl].ice, "sdpanswer");
break;
}

if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got bad offer/answer:\n%s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", data);
}
else
{
if (b->ice)
{
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got answer:\n%s\n", b->ice?b->ice->friendlyname:"?", data);
iceapi.Set(b->ice, "sdpanswer", data);
iceapi.Set(b->ice, "state", STRINGIFY(ICE_CONNECTING));
break;
}

if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got bad offer/answer:\n%s\n", b->ice?b->ice->friendlyname:"?", data);
}
break;
case ICEMSG_CANDIDATE:
Expand All @@ -5327,12 +5356,20 @@ static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon)
if (b->generic.islisten)
{
if (cl >= 0 && cl < b->numclients && b->clients[cl].ice)
{
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got candidate:\n%s\n", b->clients[cl].ice->friendlyname, data);
iceapi.Set(b->clients[cl].ice, "sdp", data);
}
}
else
{
if (b->ice)
{
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got candidate:\n%s\n", b->ice->friendlyname, data);
iceapi.Set(b->ice, "sdp", data);
}
}
break;
}
Expand Down Expand Up @@ -5479,4 +5516,4 @@ void ICE_Init(void)
Cvar_Register(&net_ice_debug, "networking");
Cmd_AddCommand("net_ice_show", ICE_Show_f);
}
#endif
#endif
Loading

0 comments on commit f2d54f3

Please sign in to comment.