Add support for PSK for DTLS via gnutls and openssl. User needs to supply the keys.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6161 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2022-01-16 18:41:34 +00:00
parent 0085e5e8d8
commit 2e627df7b5
9 changed files with 439 additions and 87 deletions

View file

@ -1164,6 +1164,7 @@ void CL_CheckForResend (void)
switch(NET_SendPacket (cls.sockets, 0, NULL, &connectinfo.adr[0]))
{
case NETERR_CLOGGED: //temporary failure
connectinfo.clogged = true;
return;
default:
break;
@ -1195,7 +1196,7 @@ void CL_CheckForResend (void)
Cvar_ForceSet(&cl_servername, cls.servername);
if (!connectinfo.numadr)
if (!connectinfo.numadr || !cls.sockets)
return; //nothing to do yet...
if (!connectinfo.clogged)
connectinfo.time = realtime+t2-t1; // for retransmit requests
@ -1220,7 +1221,7 @@ void CL_CheckForResend (void)
#endif
if (connectinfo.istransfer || connectinfo.numadr>1)
Con_TPrintf ("Connecting to %s(%s)...\n", cls.servername, NET_AdrToString(data, sizeof(data), to));
Con_TPrintf ("Connecting to %s" S_COLOR_GRAY "(%s)" S_COLOR_WHITE "...\n", cls.servername, NET_AdrToString(data, sizeof(data), to));
else
Con_TPrintf ("Connecting to %s...\n", cls.servername);
}
@ -1365,7 +1366,7 @@ void CL_BeginServerReconnect(void)
connectinfo.istransfer = false;
connectinfo.time = 0;
connectinfo.tries = 0; //re-ensure routes.
connectinfo.nextadr = 0; //should at least be consistent, other than packetloss. yay. :\
connectinfo.nextadr = 0; //should at least be consistent, other than packetloss. yay.
NET_InitClient(false);
}
@ -3689,7 +3690,7 @@ void CL_ConnectionlessPacket (void)
if (!CL_IsPendingServerAddress(&net_from))
return;
if (NET_DTLS_Create(cls.sockets, &net_from))
if (NET_DTLS_Create(cls.sockets, &net_from, cls.servername))
{
connectinfo.dtlsupgrade = DTLS_ACTIVE;
connectinfo.numadr = 1; //fixate on this resolved address.

View file

@ -29,7 +29,7 @@ cvar_t log_dosformat = CVARF("log_dosformat", "0", CVAR_NOTFROMSERVER);
qboolean log_newline[LOG_TYPES];
#ifdef IPLOG
cvar_t iplog_autodump = CVARFD("ipautodump", "1", CVAR_NOTFROMSERVER, "Enables dumping the 'iplog.txt' file, which contains a log of usernames seen for a given IP, which is useful for detecting fake-nicks.");
cvar_t iplog_autodump = CVARFD("ipautodump", "0", CVAR_ARCHIVE|CVAR_NOTFROMSERVER, "Enables dumping the 'iplog.txt' file, which contains a log of usernames seen for a given IP, which is useful for detecting fake-nicks.");
#endif
static char log_dir[MAX_OSPATH];
@ -637,6 +637,8 @@ struct certlog_s
{
link_t l;
char *hostname;
qboolean trusted; //when true, the user has given explicit trust
//when false we buldozed straight through and will only complain when it changes (legacy mode).
size_t certsize;
qbyte cert[1];
};
@ -654,7 +656,7 @@ static struct certlog_s *CertLog_Find(const char *hostname)
}
return NULL;
}
static void CertLog_Update(const char *hostname, const void *cert, size_t certsize)
static void CertLog_Update(const char *hostname, const void *cert, size_t certsize, qboolean trusted)
{
struct certlog_s *l = CertLog_Find(hostname);
if (l)
@ -675,7 +677,7 @@ static void CertLog_Write(void)
vfsfile_t *f = FS_OpenVFS(CERTLOG_FILENAME, "wb", FS_ROOT);
if (f)
{
VFS_PRINTF(f, "version 1.0\n");
VFS_PRINTF(f, "version 1.1\n");
for (l=(struct certlog_s*)certlog.next ; l != (struct certlog_s*)&certlog ; l = (struct certlog_s*)l->l.next)
{
@ -690,7 +692,7 @@ static void CertLog_Write(void)
certhex[i*2] = 0;
VFS_PRINTF(f, "%s \"", l->hostname);
VFS_PUTS(f, certhex);
VFS_PRINTF(f, "\"\n");
VFS_PRINTF(f, "\" %i\n", l->trusted?true:false);
}
}
}
@ -720,6 +722,7 @@ static void CertLog_Import(const char *filename)
char addressstring[512];
char certhex[32768];
char certdata[16384];
char trusted[16];
char line[65536], *l;
size_t i, certsize;
vfsfile_t *f;
@ -738,6 +741,7 @@ static void CertLog_Import(const char *filename)
l = line;
l = COM_ParseOut(l, addressstring, sizeof(addressstring));
l = COM_ParseOut(l, certhex, sizeof(certhex));
l = COM_ParseOut(l, trusted, sizeof(trusted));
certsize = 0;
for (i = 0; certsize < sizeof(certdata); i++)
@ -746,7 +750,7 @@ static void CertLog_Import(const char *filename)
break;
certdata[certsize++] = (hexdecode(certhex[(i<<1)+0])<<4)|hexdecode(certhex[(i<<1)+1]);
}
CertLog_Update(addressstring, certdata, certsize);
CertLog_Update(addressstring, certdata, certsize, atoi(trusted));
}
}
static void CertLog_UntrustAll_f(void)
@ -775,7 +779,7 @@ static void CertLog_Add_Prompted(void *vctx, promptbutton_t button)
struct certprompt_s *ctx = vctx;
if (button == PROMPT_YES) //button_yes / button_left
{
CertLog_Update(ctx->hostname, ctx->cert, ctx->certsize);
CertLog_Update(ctx->hostname, ctx->cert, ctx->certsize, true);
CertLog_Write();
CL_BeginServerReconnect();
@ -786,8 +790,10 @@ static void CertLog_Add_Prompted(void *vctx, promptbutton_t button)
certlog_curprompt = NULL;
}
qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems)
{
{ //this is specifically for dtls certs.
extern cvar_t net_enable_dtls;
struct certlog_s *l;
qboolean trusted = (net_enable_dtls.ival >= 2);
if (certlog_curprompt)
return false;
@ -797,7 +803,14 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize,
CertLog_Import(NULL);
l = CertLog_Find(hostname);
if (!l || l->certsize != certsize || memcmp(l->cert, cert, certsize))
if (!l && !trusted)
{ //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);
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 (qrenderer)
{
@ -839,6 +852,8 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize,
}
return false; //can't connect yet...
}
else if (!l->trusted)
Con_Printf(CON_WARNING"Server certificate for %s was previously auto-pinned."CON_DEFAULT" ^[/seta %s 2^]+ for actual security.\n", hostname, net_enable_dtls.name);
return true;
}
#endif

View file

@ -187,10 +187,11 @@ enum certprops_e
size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize);
#ifdef HAVE_DTLS
qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to);
qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to, const char *hostname);
qboolean NET_DTLS_Decode(struct ftenet_connections_s *col);
qboolean NET_DTLS_Disconnect(struct ftenet_connections_s *col, netadr_t *to);
void NET_DTLS_Timeouts(struct ftenet_connections_s *col);
extern cvar_t dtls_psk_hint, dtls_psk_user, dtls_psk_key;
#endif
extern cvar_t timeout;
extern cvar_t tls_ignorecertificateerrors; //evil evil evil.

View file

@ -154,6 +154,8 @@ static int (VARGS *qgnutls_credentials_set)(gnutls_session_t, gnutls_credentials
static int (VARGS *qgnutls_init)(gnutls_session_t * session, gnutls_connection_end_t con_end);
static void (VARGS *qgnutls_deinit)(gnutls_session_t session);
static int (VARGS *qgnutls_set_default_priority)(gnutls_session_t session);
static int (VARGS *qgnutls_set_default_priority_append)(gnutls_session_t session, const char *add_prio, const char **err_pos, unsigned flags);
static int (VARGS *qgnutls_certificate_allocate_credentials)(gnutls_certificate_credentials_t *sc);
static int (VARGS *qgnutls_anon_allocate_client_credentials)(gnutls_anon_client_credentials_t *sc);
static int (VARGS *qgnutls_global_init)(void);
@ -193,8 +195,12 @@ static int (VARGS *qgnutls_dtls_cookie_send)(gnutls_datum_t * key, void *client_
static void (VARGS *qgnutls_dtls_prestate_set)(gnutls_session_t session, gnutls_dtls_prestate_st * prestate);
static void (VARGS *qgnutls_dtls_set_mtu)(gnutls_session_t session, unsigned int mtu);
//static int (VARGS *qgnutls_psk_allocate_client_credentials)(gnutls_psk_client_credentials_t *sc);
//static int (VARGS *qgnutls_psk_set_client_credentials)(gnutls_psk_client_credentials_t res, const char *username, const gnutls_datum_t * key, gnutls_psk_key_flags flags);
static int (VARGS *qgnutls_psk_allocate_server_credentials)(gnutls_psk_server_credentials_t *sc);
static void (VARGS *qgnutls_psk_set_server_credentials_function)(gnutls_psk_server_credentials_t cred, gnutls_psk_server_credentials_function *func);
static int (VARGS *qgnutls_psk_set_server_credentials_hint)(gnutls_psk_server_credentials_t res, const char *hint);
static const char *(VARGS *qgnutls_psk_client_get_hint)(gnutls_session_t session);
static int (VARGS *qgnutls_psk_allocate_client_credentials)(gnutls_psk_client_credentials_t *sc);
static void (VARGS *qgnutls_psk_set_client_credentials_function)(gnutls_psk_client_credentials_t cred, gnutls_psk_client_credentials_function *func);
#endif
static unsigned int (VARGS *qgnutls_sec_param_to_pk_bits)(gnutls_pk_algorithm_t algo, gnutls_sec_param_t param);
@ -254,9 +260,12 @@ static qboolean Init_GNUTLS(void)
GNUTLS_FUNC(gnutls_dtls_cookie_verify) \
GNUTLS_FUNC(gnutls_dtls_cookie_send) \
GNUTLS_FUNC(gnutls_dtls_prestate_set) \
GNUTLS_FUNC(gnutls_dtls_set_mtu)
// GNUTLS_FUNC(gnutls_psk_allocate_client_credentials)
// GNUTLS_FUNC(gnutls_psk_set_client_credentials)
GNUTLS_FUNC(gnutls_dtls_set_mtu) \
GNUTLS_FUNC(gnutls_psk_allocate_server_credentials) \
GNUTLS_FUNC(gnutls_psk_set_server_credentials_function) \
GNUTLS_FUNC(gnutls_psk_set_server_credentials_hint) \
GNUTLS_FUNC(gnutls_psk_allocate_client_credentials) \
GNUTLS_FUNC(gnutls_psk_set_client_credentials_function)
#else
#define GNUTLS_DTLS_STUFF
#endif
@ -288,6 +297,8 @@ static qboolean Init_GNUTLS(void)
#define GNUTLS_FUNCS \
GNUTLS_FUNC(gnutls_bye) \
GNUTLS_FUNC(gnutls_alert_get) \
GNUTLS_FUNC(gnutls_alert_get_name) \
GNUTLS_FUNC(gnutls_perror) \
GNUTLS_FUNC(gnutls_handshake) \
GNUTLS_FUNC(gnutls_transport_set_ptr) \
@ -340,6 +351,7 @@ static qboolean Init_GNUTLS(void)
{(void**)&qgnutls_init, "gnutls_init"},
{(void**)&qgnutls_deinit, "gnutls_deinit"},
{(void**)&qgnutls_set_default_priority, "gnutls_set_default_priority"},
{(void**)&qgnutls_set_default_priority_append, "gnutls_set_default_priority_append"},
{(void**)&qgnutls_certificate_allocate_credentials, "gnutls_certificate_allocate_credentials"},
{(void**)&qgnutls_anon_allocate_client_credentials, "gnutls_anon_allocate_client_credentials"},
{(void**)&qgnutls_global_init, "gnutls_global_init"},
@ -378,8 +390,13 @@ static qboolean Init_GNUTLS(void)
{(void**)&qgnutls_dtls_cookie_send, "gnutls_dtls_cookie_send"},
{(void**)&qgnutls_dtls_prestate_set, "gnutls_dtls_prestate_set"},
{(void**)&qgnutls_dtls_set_mtu, "gnutls_dtls_set_mtu"},
// {(void**)&qgnutls_psk_allocate_client_credentials, "gnutls_psk_allocate_client_credentials"},
// {(void**)&qgnutls_psk_set_client_credentials, "gnutls_psk_set_client_credentials"},
{(void**)&qgnutls_psk_allocate_server_credentials, "gnutls_psk_allocate_server_credentials"},
{(void**)&qgnutls_psk_set_server_credentials_function, "gnutls_psk_set_server_credentials_function"},
{(void**)&qgnutls_psk_set_server_credentials_hint, "gnutls_psk_set_server_credentials_hint"},
{(void**)&qgnutls_psk_client_get_hint, "gnutls_psk_client_get_hint"},
{(void**)&qgnutls_psk_allocate_client_credentials, "gnutls_psk_allocate_client_credentials"},
{(void**)&qgnutls_psk_set_client_credentials_function, "gnutls_psk_set_client_credentials_function"},
#endif
{(void**)&qgnutls_sec_param_to_pk_bits, "gnutls_sec_param_to_pk_bits"},
@ -459,6 +476,65 @@ typedef struct
// int mtu;
} gnutlsfile_t;
static void SSL_SetCertificateName(gnutlsfile_t *f, const char *hostname)
{
int i;
if (hostname)
{
const char *host = strstr(hostname, "://");
if (host)
hostname = host+3;
//any dtls:// prefix will have been stripped now.
if (*hostname == '[')
{ //eg: [::1]:foo - skip the lead [ and strip the ] and any trailing data (hopefully just a :port or nothing)
hostname++;
host = strchr(hostname, ']');
if (host && host-hostname < sizeof(f->certname))
{
memcpy(f->certname, hostname, host-hostname);
f->certname[host-hostname] = 0;
hostname = f->certname;
}
}
else
{ //eg: 127.0.0.1:port - strip the port number if specified.
host = strchr(hostname, ':');
if (host && host-hostname < sizeof(f->certname))
{
memcpy(f->certname, hostname, host-hostname);
f->certname[host-hostname] = 0;
hostname = f->certname;
}
}
for (i = 0; hostname[i]; i++)
{
if (hostname[i] >= 'a' && hostname[i] <= 'z')
;
else if (hostname[i] >= 'A' && hostname[i] <= 'Z')
;
else if (hostname[i] >= '0' && hostname[i] <= '9')
;
else if (hostname[i] == '-' || hostname[i] == '.')
;
else
{
hostname = NULL; //something invalid. bum.
break;
}
}
//we should have a cleaned up host name now, ready for (ab)use in certificates.
}
if (!hostname)
*f->certname = 0;
else if (hostname == f->certname)
;
else if (strlen(hostname) >= sizeof(f->certname))
*f->certname = 0;
else
memcpy(f->certname, hostname, strlen(hostname)+1);
}
#define CAFILE "/etc/ssl/certs/ca-certificates.crt"
static void SSL_Close(vfsfile_t *vfs)
@ -703,6 +779,7 @@ static int SSL_DoHandshake(gnutlsfile_t *file)
{
case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
case GNUTLS_E_CERTIFICATE_ERROR: err = VFS_ERROR_UNTRUSTED; break;
case GNUTLS_E_SESSION_EOF:
case GNUTLS_E_PREMATURE_TERMINATION: err = VFS_ERROR_EOF; break;
case GNUTLS_E_PUSH_ERROR: err = file->pusherror; break;
case GNUTLS_E_PULL_ERROR: err = file->pullerror; break;
@ -920,6 +997,7 @@ static int DTLS_Pull_Timeout(gnutls_transport_ptr_t p, unsigned int timeout)
static gnutls_anon_client_credentials_t anoncred[2];
#else
static gnutls_certificate_credentials_t xcred[2];
static qboolean servercertfail;
#endif
#ifdef HAVE_DTLS
static gnutls_datum_t cookie_key;
@ -987,7 +1065,7 @@ static qboolean SSL_LoadPrivateCert(gnutls_certificate_credentials_t cred)
memset(&priv, 0, sizeof(priv));
memset(&pub, 0, sizeof(pub));
if ((!privf || !pubf) && hostname)
if ((!privf || !pubf))// && hostname)
{ //not found? generate a new one.
//FIXME: how to deal with race conditions with multiple servers on the same host?
//delay till the first connection? we at least write both files at the sameish time.
@ -1170,7 +1248,7 @@ qboolean SSL_InitGlobal(qboolean isserver)
{
#if 1
if (!SSL_LoadPrivateCert(xcred[isserver]))
initstatus[isserver] = -1;
servercertfail = true;
#else
int ret = -1;
char keyfile[MAX_OSPATH];
@ -1202,12 +1280,39 @@ qboolean SSL_InitGlobal(qboolean isserver)
return false;
return true;
}
#if 0
#ifdef HAVE_DTLS
static int GetPSKForUser(gnutls_session_t sess, const char *username, gnutls_datum_t * key)
{ //serverside. name must match what we expect (this isn't very secure), and we return the key we require for that user name.
if (!strcmp(username, dtls_psk_user.string))
{
Con_Printf("GetPSKForUser: %s\n", username);
key->size = 0;
key->data = key->size?gnutls_malloc(key->size):0;
key->size = (strlen(dtls_psk_key.string)+1)/2;
key->data = (*qgnutls_malloc)(key->size);
key->size = Base16_DecodeBlock(dtls_psk_key.string, key->data, key->size);
return 0;
}
return -1;
}
static int GetPSKForServer(gnutls_session_t sess, char **username, gnutls_datum_t *key)
{ //clientside. return the appropriate username for the hint, along with the matching key.
//this could be made more fancy with a database, but we'll keep it simple with cvars.
const char *svhint = qgnutls_psk_client_get_hint(sess);
if (!svhint)
svhint = "";
if ((!*dtls_psk_hint.string&&*dtls_psk_user.string) || (*dtls_psk_hint.string&&!strcmp(svhint, dtls_psk_hint.string)))
{ //okay, hints match (or ours is unset), report our user as appropriate.
*username = strcpy((*qgnutls_malloc)(strlen(dtls_psk_user.string)+1), dtls_psk_user.string);
key->size = (strlen(dtls_psk_key.string)+1)/2;
key->data = (*qgnutls_malloc)(key->size);
key->size = Base16_DecodeBlock(dtls_psk_key.string, key->data, key->size);
return 0;
}
else if (!*dtls_psk_user.string && !*dtls_psk_hint.string)
Con_Printf(CON_ERROR"Server requires a Pre-Shared Key (hint: \"%s\"). Please set %s, %s, and %s accordingly.\n", svhint, dtls_psk_hint.name, dtls_psk_user.name, dtls_psk_key.name);
else
Con_Printf(CON_ERROR"Server requires different Pre-Shared Key credentials (hint: \"%s\", expected \"%s\"). Please set %s, %s, and %s accordingly.\n", svhint, dtls_psk_hint.string, dtls_psk_hint.name, dtls_psk_user.name, dtls_psk_key.name);
return -1;
}
#endif
@ -1224,39 +1329,36 @@ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboole
//qgnutls_kx_set_priority (newf->session, kx_prio);
qgnutls_credentials_set (newf->session, GNUTLS_CRD_ANON, anoncred[isserver]);
#else
#if 0//def HAVE_DTLS
if (datagram)
{ //use some arbitrary PSK for dtls clients.
if (isserver)
{
#ifdef HAVE_DTLS
if (datagram && !isserver)
{ //do psk as needed. we can still do the cert stuff if the server isn't doing psk.
gnutls_psk_client_credentials_t pskcred;
qgnutls_psk_allocate_client_credentials(&pskcred);
qgnutls_psk_set_client_credentials_function(pskcred, GetPSKForServer);
qgnutls_set_default_priority_append (newf->session, "+ECDHE-PSK:+DHE-PSK:+PSK", NULL, 0);
qgnutls_credentials_set(newf->session, GNUTLS_CRD_PSK, pskcred);
}
else if (datagram && isserver && (*dtls_psk_user.string || servercertfail))
{ //offer some arbitrary PSK for dtls clients.
gnutls_psk_server_credentials_t pskcred;
qgnutls_psk_allocate_server_credentials(&pskcred);
qgnutls_psk_set_server_credentials_function(pskcred, GetPSKForUser);
qgnutls_psk_set_server_credentials_hint(pskcred, "id-quake-ex-dtls");
qgnutls_credentials_set(newf->session, GNUTLS_CRD_PSK, pskcred);
}
else
{
#ifdef HAVE_CLIENT
extern cvar_t name;
const char *namestr = name.string;
#else
const char *namestr = "Anonymous";
#endif
gnutls_psk_client_credentials_t pskcred;
const gnutls_datum_t key = { (void *) "deadbeef", 0 };
qgnutls_psk_allocate_client_credentials(&pskcred);
qgnutls_psk_set_client_credentials(pskcred, namestr, &key, GNUTLS_PSK_KEY_HEX);
if (*dtls_psk_hint.string)
qgnutls_psk_set_server_credentials_hint(pskcred, dtls_psk_hint.string);
qgnutls_set_default_priority_append (newf->session, ("-KX-ALL:+ECDHE-PSK:+DHE-PSK:+PSK")+(servercertfail?0:8), NULL, 0);
qgnutls_credentials_set(newf->session, GNUTLS_CRD_PSK, pskcred);
}
}
else
#endif
qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, xcred[isserver]);
#endif
// Use default priorities
{
// Use default priorities for regular tls sessions
qgnutls_set_default_priority (newf->session);
}
#endif
if (xcred[isserver])
qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, xcred[isserver]);
// tell gnutls how to send/receive data
qgnutls_transport_set_ptr (newf->session, newf);
@ -1297,10 +1399,7 @@ static vfsfile_t *GNUTLS_OpenVFS(const char *hostname, vfsfile_t *source, qboole
newf->funcs.Tell = SSL_Tell;
newf->funcs.seekstyle = SS_UNSEEKABLE;
if (hostname)
Q_strncpyz(newf->certname, hostname, sizeof(newf->certname));
else
Q_strncpyz(newf->certname, "", sizeof(newf->certname));
SSL_SetCertificateName(newf, hostname);
if (!SSL_InitConnection(newf, isserver, false))
{
@ -1433,7 +1532,7 @@ static void *GNUDTLS_CreateContext(const char *remotehost, void *cbctx, neterr_t
// Sys_Printf("DTLS_CreateContext: server=%i\n", isserver);
Q_strncpyz(newf->certname, remotehost?remotehost:"", sizeof(newf->certname));
SSL_SetCertificateName(newf, remotehost);
if (!SSL_InitConnection(newf, isserver, true))
{
@ -1587,7 +1686,7 @@ static qboolean GNUDTLS_CheckConnection(void *cbctx, void *peeraddr, size_t peer
//and this is the result...
qgnutls_dtls_prestate_set(f->session, &prestate);
qgnutls_dtls_set_mtu(f->session, 1440);
qgnutls_dtls_set_mtu(f->session, 1400);
//still need to do the whole certificate thing though.
f->handshaking = true;
@ -1628,6 +1727,8 @@ static const dtlsfuncs_t *GNUDTLS_InitServer(void)
{
if (!SSL_InitGlobal(true))
return NULL; //unable to init a server certificate. don't allow dtls to init.
if (servercertfail && !*dtls_psk_user.string)
return NULL;
return &dtlsfuncs_gnutls;
}
static const dtlsfuncs_t *GNUDTLS_InitClient(void)

View file

@ -165,13 +165,18 @@ static void QDECL NET_Enable_DTLS_Changed(struct cvar_s *var, char *oldvalue)
if (!svs.sockets->dtlsfuncs)
{
if (var->ival >= 2)
Con_Printf("%sUnable to set %s to \"%s\", no DTLS certificate available.\n", (var->ival >= 2)?CON_ERROR:CON_WARNING, var->name, var->string);
Con_Printf("%sUnable to set %s to \"%s\", no DTLS provider available.\n", (var->ival >= 2)?CON_ERROR:CON_WARNING, var->name, var->string);
var->ival = 0; //disable the cvar (internally) if we don't have a usable certificate. this allows us to default the cvar to enabled without it breaking otherwise.
}
}
}
cvar_t net_enable_dtls = CVARAFCD("net_enable_dtls", "", "sv_listen_dtls", 0, NET_Enable_DTLS_Changed, "Controls serverside dtls support.\n0: dtls blocked, not advertised.\n1: clientside choice.\n2: used where possible (recommended setting).\n3: disallow non-dtls clients (sv_port_tcp should be eg tls://[::]:27500 to also disallow unencrypted tcp connections).");
#endif
#if defined(HAVE_DTLS)
cvar_t dtls_psk_hint = CVARFD("dtls_psk_hint", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the public server identity.");
cvar_t dtls_psk_user = CVARFD("dtls_psk_user", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the username to use when the client+server's hints match.");
cvar_t dtls_psk_key = CVARFD("dtls_psk_key", "", CVAR_NOUNSAFEEXPAND, "For DTLS-PSK handshakes. This specifies the hexadecimal key which must match between client+server. Will only be used when client+server's hint settings match.");
#endif
#ifdef HAVE_CLIENT
static void QDECL cl_delay_packets_Announce(cvar_t *var, char *oldval)
@ -2997,7 +3002,7 @@ static neterr_t FTENET_DTLS_DoSendPacket(void *cbctx, const qbyte *data, size_t
struct dtlspeer_s *peer = cbctx;
return NET_SendPacketCol(peer->col, length, data, &peer->addr);
}
qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to)
qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const char *hostname)
{
extern cvar_t timeout;
struct dtlspeer_s *peer;
@ -3010,7 +3015,6 @@ qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to)
}
if (!peer)
{
char hostname[256];
peer = Z_Malloc(sizeof(*peer));
peer->addr = *to;
peer->col = col;
@ -3020,7 +3024,7 @@ qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to)
else
peer->funcs = DTLS_InitClient();
if (peer->funcs)
peer->dtlsstate = peer->funcs->CreateContext(NET_BaseAdrToString(hostname, sizeof(hostname), to), peer, FTENET_DTLS_DoSendPacket, col->islisten);
peer->dtlsstate = peer->funcs->CreateContext(hostname, peer, FTENET_DTLS_DoSendPacket, col->islisten);
peer->timeout = realtime+timeout.value;
if (peer->dtlsstate)
@ -8009,14 +8013,14 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char
break;
case NP_DTLS:
adr->prot = NP_DGRAM;
NET_EnsureRoute(collection, routename, host, adr);
if (NET_DTLS_Create(collection, adr))
if (NET_EnsureRoute(collection, routename, host, adr))
if (NET_DTLS_Create(collection, adr, host))
{
adr->prot = NP_DTLS;
return true;
}
adr->prot = NP_DTLS;
break;
return false;
case NP_WS:
case NP_WSS:
case NP_TLS:
@ -9034,6 +9038,11 @@ void SVNET_RegisterCvars(void)
#if defined(HAVE_DTLS) && defined(HAVE_SERVER)
Cvar_Register (&net_enable_dtls, "networking");
#endif
#ifdef HAVE_DTLS
Cvar_Register (&dtls_psk_hint, "networking");
Cvar_Register (&dtls_psk_user, "networking");
Cvar_Register (&dtls_psk_key, "networking");
#endif
}
void NET_CloseServer(void)

View file

@ -1896,6 +1896,14 @@ static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t s
Plug_Net_Close,
Plug_Net_SetTLSClient,
Plug_Net_GetTLSBinding,
Sys_RandomBytes,
TLS_GetKnownCertificate,
#ifdef HAVE_CLIENT
CertLog_ConnectOkay,
#else
NULL,
#endif
};
if (structsize == sizeof(funcs))
return &funcs;

View file

@ -217,7 +217,6 @@ char cvargroup_servercontrol[] = "server control variables";
vfsfile_t *sv_fraglogfile;
void SV_AcceptClient (netadr_t *adr, int userid, char *userinfo);
void PRH2_SetPlayerClass(client_t *cl, int classnum, qboolean fromqc);
void SV_DeDupeName(const char *val, client_t *cl, char *newname, size_t newnamesize);
@ -3424,12 +3423,16 @@ void SVC_DirectConnect(int expectedreliablesequence)
// note an extra qbyte is needed to replace spectator key
Q_strncpyz (info.userinfo, Cmd_Argv(4), sizeof(info.userinfo)-1);
if (info.protocol == SCP_NETQUAKE)
if (info.protocol >= SCP_NETQUAKE)
{
Info_RemoveKey(info.userinfo, "mod"); //its served its purpose.
Info_RemoveKey(info.userinfo, "modver"); //its served its purpose.
Info_RemoveKey(info.userinfo, "flags"); //its served its purpose.
}
}
#ifdef HAVE_DTLS
if (net_enable_dtls.ival > 2 && (net_from.prot == NP_DGRAM || net_from.prot == NP_STREAM || net_from.prot == NP_WS))
if (net_enable_dtls.ival > 2 && (net_from.prot == NP_DGRAM || net_from.prot == NP_STREAM || net_from.prot == NP_WS) && net_from.type != NA_LOOPBACK)
{
SV_RejectMessage (info.protocol, "This server requires the use of DTLS/TLS/WSS.\n");
return;
@ -4118,7 +4121,7 @@ qboolean SV_ConnectionlessPacket (void)
else
{
//NET_DTLS_Disconnect(svs.sockets, &net_from);
if (NET_DTLS_Create(svs.sockets, &net_from))
if (NET_DTLS_Create(svs.sockets, &net_from, NULL))
Netchan_OutOfBandPrint(NS_SERVER, &net_from, "dtlsopened");
}
}

View file

@ -1,7 +1,10 @@
#include "plugin.h"
#include "netinc.h"
plugfsfuncs_t *fsfuncs;
static plugfsfuncs_t *fsfuncs;
static plugnetfuncs_t *netfuncs;
static cvar_t *pdtls_psk_hint, *pdtls_psk_user, *pdtls_psk_key;
#undef SHA1
#undef HMAC
@ -230,7 +233,7 @@ static int OSSL_Verify_Peer(int preverify_ok, X509_STORE_CTX *x509_ctx)
if (err == X509_V_ERR_CERT_HAS_EXPIRED || err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)
{
size_t knownsize;
qbyte *knowndata = TLS_GetKnownCertificate(uctx->peername, &knownsize);
qbyte *knowndata = netfuncs->TLS_GetKnownCertificate(uctx->peername, &knownsize);
if (knowndata)
{ //check
size_t blobsize;
@ -274,7 +277,7 @@ static int OSSL_Verify_Peer(int preverify_ok, X509_STORE_CTX *x509_ctx)
probs |= CERTLOG_UNKNOWN;
break;
}
if (CertLog_ConnectOkay(uctx->peername, blob, blobsize, probs))
if (netfuncs->CertLog_ConnectOkay && netfuncs->CertLog_ConnectOkay(uctx->peername, blob, blobsize, probs))
return 1; //ignore the errors...
}
#endif
@ -399,6 +402,65 @@ static void OSSL_OpenPubKey(void)
BIO_free(bio);
}
static char *OSSL_SetCertificateName(char *out, const char *hostname)
{ //glorified strcpy...
int i;
if (hostname)
{
const char *host = strstr(hostname, "://");
if (host)
hostname = host+3;
//any dtls:// prefix will have been stripped now.
if (*hostname == '[')
{ //eg: [::1]:foo - skip the lead [ and strip the ] and any trailing data (hopefully just a :port or nothing)
hostname++;
host = strchr(hostname, ']');
if (host)
{
memcpy(out, hostname, host-hostname);
out[host-hostname] = 0;
hostname = out;
}
}
else
{ //eg: 127.0.0.1:port - strip the port number if specified.
host = strchr(hostname, ':');
if (host)
{
memcpy(out, hostname, host-hostname);
out[host-hostname] = 0;
hostname = out;
}
}
for (i = 0; hostname[i]; i++)
{
if (hostname[i] >= 'a' && hostname[i] <= 'z')
;
else if (hostname[i] >= 'A' && hostname[i] <= 'Z')
;
else if (hostname[i] >= '0' && hostname[i] <= '9')
;
else if (hostname[i] == '-' || hostname[i] == '.')
;
else
{
hostname = NULL; //something invalid. bum.
break;
}
}
//we should have a cleaned up host name now, ready for (ab)use in certificates.
}
if (!hostname)
*out = 0;
else if (hostname == out)
;
else
memcpy(out, hostname, strlen(hostname)+1);
return out;
}
static vfsfile_t *OSSL_OpenVFS(const char *hostname, vfsfile_t *source, qboolean isserver)
{
BIO *sink;
@ -406,6 +468,9 @@ static vfsfile_t *OSSL_OpenVFS(const char *hostname, vfsfile_t *source, qboolean
if (!OSSL_Init())
return NULL; //FAIL!
if (!hostname)
hostname = "";
n = calloc(sizeof(*n) + strlen(hostname)+1, 1);
n->funcs.ReadBytes = OSSL_FRead;
@ -417,7 +482,7 @@ static vfsfile_t *OSSL_OpenVFS(const char *hostname, vfsfile_t *source, qboolean
n->funcs.Flush = NULL;
n->funcs.seekstyle = SS_UNSEEKABLE;
n->cert.peername = strcpy((char*)(n+1), hostname);
n->cert.peername = OSSL_SetCertificateName((char*)(n+1), hostname);
n->cert.dtls = false;
ERR_print_errors_cb(OSSL_PrintError_CB, NULL);
@ -489,6 +554,9 @@ typedef struct {
// BIO *sink;
qbyte *pending;
size_t pendingsize;
void *peeraddr;
size_t peeraddrsize;
} ossldtls_t;
static int OSSL_Bio_DWrite(BIO *h, const char *buf, int size)
{
@ -539,11 +607,15 @@ static long OSSL_Bio_DCtrl(BIO *h, int cmd, long arg1, void *arg2)
return 1;
case BIO_CTRL_DGRAM_GET_PEER:
return 0;
case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: //we're non-blocking, so this doesn't affect us.
case BIO_CTRL_DGRAM_GET_MTU_OVERHEAD:
case BIO_CTRL_WPENDING:
case BIO_CTRL_DGRAM_QUERY_MTU:
case BIO_CTRL_DGRAM_SET_MTU:
case BIO_CTRL_DGRAM_GET_FALLBACK_MTU:
return 0;
@ -576,17 +648,61 @@ static int OSSL_Bio_DDestroy(BIO *h)
return 1;
}
static int dehex(int i)
{
if (i >= '0' && i <= '9')
return (i-'0');
else if (i >= 'A' && i <= 'F')
return (i-'A'+10);
else
return (i-'a'+10);
}
static size_t Base16_DecodeBlock_(const char *in, qbyte *out, size_t outsize)
{
qbyte *start = out;
if (!out)
return ((strlen(in)+1)/2) + 1;
for (; ishexcode(in[0]) && ishexcode(in[1]) && outsize > 0; outsize--, in+=2)
*out++ = (dehex(in[0])<<4) | dehex(in[1]);
return out-start;
}
static unsigned int OSSL_SV_Validate_PSK(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len)
{
if (!strcmp(identity, pdtls_psk_user->string))
{ //Yay! We know this one!
return Base16_DecodeBlock_(pdtls_psk_key->string, psk, max_psk_len);
}
return 0; //0 for error, or something.
}
unsigned int OSSL_CL_Validate_PSK(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len)
{ //if our hint cvar matches, then report our user+key cvars to the server
if ((!*hint && *pdtls_psk_user->string && !*pdtls_psk_hint->string) || (*hint && !strcmp(hint, pdtls_psk_hint->string)))
{
//FIXME: avoid crashing QE
Q_strlcpy(identity, pdtls_psk_user->string, max_identity_len);
return Base16_DecodeBlock_(pdtls_psk_key->string, psk, max_psk_len);
}
return 0; //we don't know what to report.
}
static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver)
{ //if remotehost is null then their certificate will not be validated.
ossldtls_t *n = calloc(sizeof(*n) + strlen(remotehost)+1, 1);
ossldtls_t *n;
BIO *sink;
if (!remotehost)
remotehost = "";
n = calloc(sizeof(*n) + strlen(remotehost)+1, 1);
n->cbctx = cbctx;
n->push = push;
n->ctx = SSL_CTX_new(isserver?DTLS_server_method():DTLS_client_method());
n->cert.peername = strcpy((char*)(n+1), remotehost);
n->cert.peername = OSSL_SetCertificateName((char*)(n+1), remotehost);
n->cert.dtls = true;
if (n->ctx)
@ -599,6 +715,28 @@ static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*p
SSL_CTX_set_verify_depth(n->ctx, 5);
SSL_CTX_set_options(n->ctx, SSL_OP_NO_COMPRESSION); //compression allows guessing the contents of the stream somehow.
if (isserver)
{
if (*pdtls_psk_user->string)
{
if (*pdtls_psk_user->string)
SSL_CTX_use_psk_identity_hint(n->ctx, pdtls_psk_hint->string);
SSL_CTX_set_psk_server_callback(n->ctx, OSSL_SV_Validate_PSK);
}
if (vhost.servercert && vhost.privatekey)
{
SSL_CTX_use_certificate(n->ctx, vhost.servercert);
SSL_CTX_use_PrivateKey(n->ctx, vhost.privatekey);
assert(1==SSL_CTX_check_private_key(n->ctx));
}
}
else
{
if (*pdtls_psk_user->string)
SSL_CTX_set_psk_client_callback(n->ctx, OSSL_CL_Validate_PSK);
}
//SSL_CTX_use_certificate_file
//FIXME: SSL_CTX_use_certificate_file aka SSL_CTX_use_certificate(PEM_read_bio_X509)
//FIXME: SSL_CTX_use_PrivateKey_file aka SSL_CTX_use_PrivateKey(PEM_read_bio_PrivateKey)
@ -619,8 +757,10 @@ static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*p
}
BIO_get_ssl(n->bio, &n->ssl);
SSL_set_app_data(n->ssl, n);
SSL_set_ex_data(n->ssl, ossl_fte_certctx, &n->cert);
SSL_set_tlsext_host_name(n->ssl, remotehost); //let the server know which cert to send
if (*n->cert.peername)
SSL_set_tlsext_host_name(n->ssl, n->cert.peername); //let the server know which cert to send
BIO_do_connect(n->bio);
ERR_print_errors_cb(OSSL_PrintError_CB, NULL);
return n;
@ -629,6 +769,71 @@ static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*p
return NULL;
}
static char dtlscookiekey[16];
static int OSSL_GenCookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
{
ossldtls_t *f = SSL_get_app_data(ssl);
SHA_CTX ctx;
SHA1_Init(&ctx);
SHA1_Update(&ctx, dtlscookiekey, sizeof(dtlscookiekey));
SHA1_Update(&ctx, "somethinghashy", 15);
SHA1_Update(&ctx, f->peeraddr, f->peeraddrsize);
SHA1_Final(cookie, &ctx);
*cookie_len = SHA_DIGEST_LENGTH;
return 1;
}
static int OSSL_VerifyCookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len)
{
unsigned char match[DTLS1_COOKIE_LENGTH];
unsigned int matchsize;
if (OSSL_GenCookie(ssl, match, &matchsize))
if (cookie_len == matchsize && !memcmp(cookie, match, matchsize))
return 1;
return 0; //not valid.
}
qboolean OSSL_CheckConnection(void *cbctx, void *peeraddr, size_t peeraddrsize, void *indata, size_t insize, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), void (*EstablishTrueContext)(void **cbctx, void *state))
{
int ret;
static ossldtls_t *pending;
BIO_ADDR *bioaddr = BIO_ADDR_new();
if (!pending)
{
pending = OSSL_CreateContext("localhost", cbctx, push, true);
SSL_CTX_set_cookie_generate_cb(pending->ctx, OSSL_GenCookie);
SSL_CTX_set_cookie_verify_cb(pending->ctx, OSSL_VerifyCookie);
}
SSL_set_app_data(pending->ssl, pending);
//make sure its kept current...
pending->cbctx = cbctx;
pending->push = push;
pending->pending = indata;
pending->pendingsize = insize;
ret = DTLSv1_listen(pending->ssl, bioaddr);
BIO_ADDR_free(bioaddr);
if (ret >= 1)
{
pending->pending = NULL;
pending->pendingsize = 0;
EstablishTrueContext(&pending->cbctx, pending);
pending = NULL; //returned to called. next request gets a new one.
return true;
}
//0 = nonfatal
//-1 = fatal
return false;
}
static void OSSL_DestroyContext(void *ctx)
{
ossldtls_t *o = (ossldtls_t*)ctx;
@ -709,7 +914,7 @@ static neterr_t OSSL_Timeouts(void *ctx)
static dtlsfuncs_t ossl_dtlsfuncs =
{
OSSL_CreateContext,
NULL,
OSSL_CheckConnection,
OSSL_DestroyContext,
OSSL_Transmit,
OSSL_Received,
@ -841,12 +1046,15 @@ static ftecrypto_t crypto_openssl =
NULL,
};
void *TLS_GetKnownCertificate(const char *certname, size_t *size){return NULL;}
qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems){return false;}
qboolean Plug_Init(void)
{
fsfuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs));
if (!fsfuncs)
netfuncs = plugfuncs->GetEngineInterface(plugnetfuncs_name, sizeof(*netfuncs));
if (!fsfuncs || !netfuncs)
return false;
pdtls_psk_hint = cvarfuncs->GetNVFDG("dtls_psk_hint", "", 0, NULL, "DTLS stuff");
pdtls_psk_user = cvarfuncs->GetNVFDG("dtls_psk_user", "", 0, NULL, "DTLS stuff");
pdtls_psk_key = cvarfuncs->GetNVFDG("dtls_psk_key", "", 0, NULL, "DTLS stuff");
netfuncs->RandomBytes(dtlscookiekey, sizeof(dtlscookiekey)); //something random so people can't guess cookies for arbitrary victim IPs.
return plugfuncs->ExportInterface("Crypto", &crypto_openssl, sizeof(crypto_openssl)); //export a named interface struct to the engine
}

View file

@ -374,6 +374,12 @@ typedef struct //for when you need basic socket access, hopefully rare...
F(void, Close, (qhandle_t socket));
F(int, SetTLSClient, (qhandle_t sock, const char *certhostname)); //adds a tls layer to the socket (and specifies the peer's required hostname)
F(int, GetTLSBinding, (qhandle_t sock, char *outdata, int *datalen)); //to avoid MITM attacks with compromised cert authorities
//for (d)tls plugins to use.
F(qboolean, RandomBytes, (qbyte *string, int len));
F(void *, TLS_GetKnownCertificate, (const char *certname, size_t *size));
F(qboolean, CertLog_ConnectOkay, (const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems));
#define N_WOULDBLOCK -1
#define N_FATALERROR -2
#define NET_CLIENTPORT -1