d781018df3
Defaults to using Q2E's protocol 2023 (but not netchan). FTEQ2 servers can host both vanilla and Q2E clients simultaneously, but its recommend to use the vanilla gamecode to avoid localisation issues.
1770 lines
58 KiB
C
1770 lines
58 KiB
C
#include "quakedef.h"
|
|
#if defined(HAVE_WINSSPI)
|
|
|
|
/*regarding HAVE_DTLS
|
|
DTLS1.0 is supported from win8 onwards, or patched-win7
|
|
DTLS1.2 is supported from win10-1607 onwards.
|
|
|
|
unlike the other tls providers we use pfx files (to work around microsoft making it so fucking hard to actually import/export the damn private key any other way)
|
|
pfx files are encrypted with your username as a password
|
|
convert from pem to pfx:
|
|
openssl pkcs12 -inkey private.pem -in fullchain.pem -export -nodes -out identity.pfx -passout pass:$USER
|
|
(in the above formula, swap out $USER for %USERNAME% if you're using a windows cmd prompt)
|
|
you can start fte with an arg like the following for a fully CA-signed cert:
|
|
fteqwsv -pfx c:/foo/bar.pfx
|
|
(otherwise it'll use identity.pfx from your basedir, autogenerated with some dodgy info)
|
|
|
|
TODO: RTC connections do not validate the client peer.
|
|
TODO: get someone else to figure out the PSK stuff. no way can I test/debug/write that without documentation nor working drivers.
|
|
Note: testing this stuff is a pain when eg browsers do NOT support DTLS1.0 any more.
|
|
*/
|
|
|
|
#include "winquake.h"
|
|
#include "netinc.h"
|
|
#define SECURITY_WIN32
|
|
#include <security.h>
|
|
#include <sspi.h>
|
|
#include <wincrypt.h>
|
|
#include <schannel.h>
|
|
|
|
#define SP_PROT_TLS1_1_SERVER 0x00000100
|
|
#define SP_PROT_TLS1_1_CLIENT 0x00000200
|
|
|
|
#define SP_PROT_TLS1_2_SERVER 0x00000400
|
|
#define SP_PROT_TLS1_2_CLIENT 0x00000800
|
|
|
|
#define SP_PROT_DTLS_SERVER 0x00010000
|
|
#define SP_PROT_DTLS_CLIENT 0x00020000
|
|
#define SP_PROT_DTLS1_0_SERVER SP_PROT_DTLS_SERVER
|
|
#define SP_PROT_DTLS1_0_CLIENT SP_PROT_DTLS_CLIENT
|
|
#define SP_PROT_DTLS1_2_SERVER 0x00040000
|
|
#define SP_PROT_DTLS1_2_CLIENT 0x00080000
|
|
#define SP_PROT_DTLS1_X_SERVER (SP_PROT_DTLS1_0_SERVER | SP_PROT_DTLS1_2_SERVER)
|
|
#define SP_PROT_DTLS1_X_CLIENT (SP_PROT_DTLS1_0_CLIENT | SP_PROT_DTLS1_2_CLIENT)
|
|
|
|
|
|
//avoid the use of outdated/insecure protocols
|
|
//so no ssl2/ssl3
|
|
#define USE_PROT_SERVER (SP_PROT_TLS1_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER)
|
|
#define USE_PROT_CLIENT (SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT)
|
|
|
|
#define USE_PROT_DGRAM_SERVER (SP_PROT_DTLS1_X_SERVER)
|
|
#define USE_PROT_DGRAM_CLIENT (SP_PROT_DTLS1_X_CLIENT)
|
|
|
|
#ifndef szOID_RSA_SHA512RSA
|
|
#define szOID_RSA_SHA512RSA "1.2.840.113549.1.1.13"
|
|
#endif
|
|
#ifndef SCH_CRED_SNI_CREDENTIAL
|
|
#define SCH_CRED_SNI_CREDENTIAL 0x00080000
|
|
#endif
|
|
|
|
#ifndef SEC_I_MESSAGE_FRAGMENT
|
|
#define SEC_I_MESSAGE_FRAGMENT 0x00090364L
|
|
#endif
|
|
#ifndef SEC_E_INVALID_PARAMETER
|
|
#define SEC_E_INVALID_PARAMETER 0x8009035DL
|
|
#endif
|
|
|
|
#ifndef SECBUFFER_ALERT
|
|
#define SECBUFFER_ALERT 17
|
|
#endif
|
|
#ifndef SECPKG_ATTR_DTLS_MTU
|
|
#define SECPKG_ATTR_DTLS_MTU 34
|
|
#endif
|
|
|
|
#ifndef CRYPT_ARCHIVABLE
|
|
#define CRYPT_ARCHIVABLE 0x00004000
|
|
#endif
|
|
|
|
|
|
//hungarian ensures we hit no macros.
|
|
static struct
|
|
{
|
|
dllhandle_t *lib;
|
|
SECURITY_STATUS (WINAPI *pDecryptMessage) (PCtxtHandle,PSecBufferDesc,ULONG,PULONG);
|
|
SECURITY_STATUS (WINAPI *pEncryptMessage) (PCtxtHandle,ULONG,PSecBufferDesc,ULONG);
|
|
SECURITY_STATUS (WINAPI *pAcquireCredentialsHandleA) (SEC_CHAR*,SEC_CHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp);
|
|
// SECURITY_STATUS (WINAPI *pInitializeSecurityContextA) (PCredHandle,PCtxtHandle,SEC_CHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp);
|
|
SECURITY_STATUS (WINAPI *pInitializeSecurityContextW) (PCredHandle,PCtxtHandle,SEC_WCHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp);
|
|
SECURITY_STATUS (WINAPI *pAcceptSecurityContext) (PCredHandle,PCtxtHandle,PSecBufferDesc,unsigned long,unsigned long,PCtxtHandle,PSecBufferDesc,unsigned long SEC_FAR *,PTimeStamp);
|
|
SECURITY_STATUS (WINAPI *pCompleteAuthToken) (PCtxtHandle,PSecBufferDesc);
|
|
SECURITY_STATUS (WINAPI *pSetContextAttributesA) (PCtxtHandle,unsigned long,void*,unsigned long);
|
|
SECURITY_STATUS (WINAPI *pQueryContextAttributesA) (PCtxtHandle,ULONG,PVOID);
|
|
SECURITY_STATUS (WINAPI *pFreeCredentialsHandle) (PCredHandle);
|
|
SECURITY_STATUS (WINAPI *pDeleteSecurityContext) (PCtxtHandle);
|
|
} secur;
|
|
static struct
|
|
{
|
|
dllhandle_t *lib;
|
|
BOOL (WINAPI *pCertGetCertificateChain) (HCERTCHAINENGINE,PCCERT_CONTEXT,LPFILETIME,HCERTSTORE,PCERT_CHAIN_PARA,DWORD,LPVOID,PCCERT_CHAIN_CONTEXT*);
|
|
BOOL (WINAPI *pCertVerifyCertificateChainPolicy) (LPCSTR,PCCERT_CHAIN_CONTEXT,PCERT_CHAIN_POLICY_PARA,PCERT_CHAIN_POLICY_STATUS);
|
|
void (WINAPI *pCertFreeCertificateChain) (PCCERT_CHAIN_CONTEXT);
|
|
PCCERT_CONTEXT (WINAPI *pCertCreateCertificateContext) (DWORD dwCertEncodingType, const BYTE *pbCertEncoded, DWORD cbCertEncoded);
|
|
DWORD (WINAPI *pCertNameToStrA) (DWORD dwCertEncodingType, PCERT_NAME_BLOB pName, DWORD dwStrType, LPCSTR psz, DWORD csz);
|
|
BOOL (WINAPI *pCertFreeCertificateContext) (PCCERT_CONTEXT pCertContext);
|
|
PCCERT_CONTEXT (WINAPI *pCertCreateSelfSignCertificate) (HCRYPTPROV,PCERT_NAME_BLOB,DWORD,PCRYPT_KEY_PROV_INFO,PCRYPT_ALGORITHM_IDENTIFIER,PSYSTEMTIME,PSYSTEMTIME,PCERT_EXTENSIONS);
|
|
BOOL (WINAPI *pCertStrToNameA) (DWORD,LPCSTR,DWORD,void *,BYTE *,DWORD *,LPCSTR *);
|
|
HCERTSTORE (WINAPI *pCertOpenStore) (LPCSTR lpszStoreProvider, DWORD dwEncodingType, HCRYPTPROV hCryptProv, DWORD dwFlags, const void *pvPara);
|
|
BOOL (WINAPI *pCertAddCertificateContextToStore) (HCERTSTORE hCertStore, PCCERT_CONTEXT pCertContext, DWORD dwAddDisposition, PCCERT_CONTEXT *ppStoreContext);
|
|
BOOL (WINAPI *pPFXExportCertStoreEx) (HCERTSTORE hStore, CRYPT_DATA_BLOB *pPFX, LPCWSTR szPassword, void *pvPara, DWORD dwFlags);
|
|
BOOL (WINAPI *pCertCloseStore) (HCERTSTORE hCertStore, DWORD dwFlags);
|
|
HCERTSTORE (WINAPI *pPFXImportCertStore) (CRYPT_DATA_BLOB *pPFX, LPCWSTR szPassword, DWORD dwFlags);
|
|
PCCERT_CONTEXT (WINAPI *pCertFindCertificateInStore) (HCERTSTORE hCertStore, DWORD dwCertEncodingType, DWORD dwFindFlags, DWORD dwFindType, const void *pvFindPara, PCCERT_CONTEXT pPrevCertContext);
|
|
BOOL (WINAPI *pCryptAcquireCertificatePrivateKey) (PCCERT_CONTEXT pCert, DWORD dwFlags, void *pvParameters, HCRYPTPROV *phCryptProvOrNCryptKey, DWORD *pdwKeySpec, BOOL *pfCallerFreeProvOrNCryptKey);
|
|
BOOL (WINAPI *pCertSetCertificateContextProperty) (PCCERT_CONTEXT pCertContext, DWORD dwPropId, DWORD dwFlags, const void *pvData);
|
|
} crypt;
|
|
static struct
|
|
{
|
|
dllhandle_t *lib;
|
|
BOOL (WINAPI *pCryptAcquireContextW) (HCRYPTPROV *phProv, LPCWSTR szContainer, LPCWSTR szProvider, DWORD dwProvType, DWORD dwFlags);
|
|
BOOL (WINAPI *pCryptGenKey) (HCRYPTPROV hProv, ALG_ID Algid, DWORD dwFlags, HCRYPTKEY *phKey);
|
|
} advapi;
|
|
void SSL_Init(void)
|
|
{
|
|
dllfunction_t secur_functable[] =
|
|
{
|
|
{(void**)&secur.pDecryptMessage, "DecryptMessage"},
|
|
{(void**)&secur.pEncryptMessage, "EncryptMessage"},
|
|
{(void**)&secur.pAcquireCredentialsHandleA, "AcquireCredentialsHandleA"},
|
|
// {(void**)&secur.pInitializeSecurityContextA, "InitializeSecurityContextA"},
|
|
{(void**)&secur.pInitializeSecurityContextW, "InitializeSecurityContextW"},
|
|
{(void**)&secur.pAcceptSecurityContext, "AcceptSecurityContext"},
|
|
{(void**)&secur.pCompleteAuthToken, "CompleteAuthToken"},
|
|
{(void**)&secur.pSetContextAttributesA, "SetContextAttributesA"},
|
|
{(void**)&secur.pQueryContextAttributesA, "QueryContextAttributesA"},
|
|
{(void**)&secur.pFreeCredentialsHandle, "FreeCredentialsHandle"},
|
|
{(void**)&secur.pDeleteSecurityContext, "DeleteSecurityContext"},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
dllfunction_t crypt_functable[] =
|
|
{
|
|
{(void**)&crypt.pCertGetCertificateChain, "CertGetCertificateChain"},
|
|
{(void**)&crypt.pCertVerifyCertificateChainPolicy, "CertVerifyCertificateChainPolicy"},
|
|
{(void**)&crypt.pCertFreeCertificateChain, "CertFreeCertificateChain"},
|
|
{(void**)&crypt.pCertCreateCertificateContext, "CertCreateCertificateContext"},
|
|
{(void**)&crypt.pCertNameToStrA, "CertNameToStrA"},
|
|
{(void**)&crypt.pCertFreeCertificateContext, "CertFreeCertificateContext"},
|
|
{(void**)&crypt.pCertCreateSelfSignCertificate, "CertCreateSelfSignCertificate"},
|
|
{(void**)&crypt.pCertStrToNameA, "CertStrToNameA"},
|
|
{(void**)&crypt.pCertOpenStore, "CertOpenStore"},
|
|
{(void**)&crypt.pCertAddCertificateContextToStore, "CertAddCertificateContextToStore"},
|
|
{(void**)&crypt.pPFXExportCertStoreEx, "PFXExportCertStoreEx"},
|
|
{(void**)&crypt.pCertCloseStore, "CertCloseStore"},
|
|
{(void**)&crypt.pPFXImportCertStore, "PFXImportCertStore"},
|
|
{(void**)&crypt.pCertFindCertificateInStore, "CertFindCertificateInStore"},
|
|
{(void**)&crypt.pCryptAcquireCertificatePrivateKey, "CryptAcquireCertificatePrivateKey"},
|
|
{(void**)&crypt.pCertSetCertificateContextProperty, "CertSetCertificateContextProperty"},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
dllfunction_t advapi_functable[] =
|
|
{
|
|
{(void**)&advapi.pCryptAcquireContextW, "CryptAcquireContextW"},
|
|
{(void**)&advapi.pCryptGenKey, "CryptGenKey"},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
if (!secur.lib)
|
|
secur.lib = Sys_LoadLibrary("secur32.dll", secur_functable);
|
|
if (!crypt.lib)
|
|
crypt.lib = Sys_LoadLibrary("crypt32.dll", crypt_functable);
|
|
if (!advapi.lib)
|
|
advapi.lib = Sys_LoadLibrary("advapi32.dll", advapi_functable);
|
|
}
|
|
qboolean SSL_Inited(void)
|
|
{
|
|
return !!secur.lib && !!crypt.lib && !!advapi.lib;
|
|
}
|
|
|
|
#define MessageAttribute (ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | /*ISC_REQ_EXTENDED_ERROR |*/ ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_MANUAL_CRED_VALIDATION | ISC_REQ_USE_SUPPLIED_CREDS)
|
|
|
|
struct sslbuf
|
|
{
|
|
size_t datasize;
|
|
char *data;
|
|
size_t avail;
|
|
};
|
|
|
|
typedef struct {
|
|
vfsfile_t funcs;
|
|
vfsfile_t *stream;
|
|
|
|
wchar_t wpeername[256];
|
|
qboolean datagram;
|
|
enum
|
|
{
|
|
HS_ESTABLISHED,
|
|
|
|
HS_ERROR,
|
|
|
|
HS_STARTCLIENT,
|
|
HS_CLIENT,
|
|
|
|
HS_STARTSERVER,
|
|
HS_SERVER
|
|
} handshaking;
|
|
|
|
struct sslbuf outraw;
|
|
struct sslbuf outcrypt;
|
|
struct sslbuf inraw;
|
|
struct sslbuf incrypt;
|
|
|
|
|
|
CredHandle cred;
|
|
SecHandle sechnd;
|
|
int headersize, mtu, footersize; //schannel is strict about its mtus.
|
|
char headerdata[1024], footerdata[1024];
|
|
|
|
double resendtimer; //for the client so it can keep retrying the handshake.
|
|
|
|
#ifdef HAVE_DTLS
|
|
void *cbctx;
|
|
neterr_t (*transmit)(void *cbctx, const qbyte *data, size_t datasize);
|
|
#endif
|
|
} sslfile_t;
|
|
|
|
static int SSPI_ExpandBuffer(struct sslbuf *buf, size_t bytes)
|
|
{
|
|
if (bytes < buf->datasize)
|
|
return buf->datasize;
|
|
Z_ReallocElements((void**)&buf->data, &buf->datasize, bytes, 1);
|
|
return bytes;
|
|
}
|
|
|
|
static int SSPI_CopyIntoBuffer(struct sslbuf *buf, const void *data, unsigned int bytes, qboolean expand)
|
|
{
|
|
if (bytes > buf->datasize - buf->avail)
|
|
{
|
|
if (!expand || SSPI_ExpandBuffer(buf, buf->avail + bytes + 1024) < buf->avail + bytes)
|
|
bytes = buf->datasize - buf->avail;
|
|
}
|
|
memcpy(buf->data + buf->avail, data, bytes);
|
|
buf->avail += bytes;
|
|
|
|
return bytes;
|
|
}
|
|
|
|
static void SSPI_Error(sslfile_t *f, const char *error, ...)
|
|
{
|
|
va_list argptr;
|
|
char string[1024];
|
|
va_start (argptr, error);
|
|
vsnprintf (string,sizeof(string)-1, error,argptr);
|
|
va_end (argptr);
|
|
|
|
f->handshaking = HS_ERROR;
|
|
if (*string)
|
|
{
|
|
if (f->datagram)
|
|
Con_Printf(CON_ERROR "%s", string);
|
|
else
|
|
Sys_Printf(CON_ERROR "%s", string);
|
|
}
|
|
if (f->stream)
|
|
VFS_CLOSE(f->stream);
|
|
|
|
secur.pDeleteSecurityContext(&f->sechnd);
|
|
secur.pFreeCredentialsHandle(&f->cred);
|
|
f->stream = NULL;
|
|
}
|
|
|
|
static neterr_t SSPI_TryFlushCryptOut(sslfile_t *f)
|
|
{
|
|
int sent;
|
|
if (f->outcrypt.avail)
|
|
{
|
|
#ifdef HAVE_DTLS
|
|
if (f->transmit)
|
|
{
|
|
neterr_t e = f->transmit(f->cbctx, f->outcrypt.data, f->outcrypt.avail);
|
|
f->outcrypt.avail = 0;
|
|
return e;
|
|
}
|
|
#endif
|
|
sent = VFS_WRITE(f->stream, f->outcrypt.data, f->outcrypt.avail);
|
|
}
|
|
else
|
|
return NETERR_SENT;
|
|
|
|
if (f->datagram)
|
|
f->outcrypt.avail = 0; //anything unsent is a dropped packet...
|
|
else if (sent > 0)
|
|
{
|
|
memmove(f->outcrypt.data, f->outcrypt.data + sent, f->outcrypt.avail - sent);
|
|
f->outcrypt.avail -= sent;
|
|
}
|
|
return NETERR_SENT;
|
|
}
|
|
|
|
static int SSPI_CheckNewInCrypt(sslfile_t *f)
|
|
{
|
|
int newd;
|
|
if (!f->stream)
|
|
return VFS_ERROR_EOF;
|
|
newd = VFS_READ(f->stream, f->incrypt.data+f->incrypt.avail, f->incrypt.datasize - f->incrypt.avail);
|
|
if (newd < 0)
|
|
return newd;
|
|
else
|
|
f->incrypt.avail += newd;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//convert inbound crypt->data
|
|
static void SSPI_Decode(sslfile_t *f)
|
|
{
|
|
SECURITY_STATUS ss;
|
|
SecBufferDesc BuffDesc;
|
|
SecBuffer SecBuff[4];
|
|
ULONG ulQop = 0;
|
|
SecBuffer *extra = NULL;
|
|
int i;
|
|
|
|
if (!f->incrypt.avail)
|
|
return;
|
|
|
|
BuffDesc.ulVersion = SECBUFFER_VERSION;
|
|
BuffDesc.cBuffers = countof(SecBuff);
|
|
BuffDesc.pBuffers = SecBuff;
|
|
|
|
SecBuff[0].BufferType = SECBUFFER_DATA;
|
|
SecBuff[0].cbBuffer = f->incrypt.avail;
|
|
SecBuff[0].pvBuffer = f->incrypt.data;
|
|
|
|
SecBuff[1].BufferType = SECBUFFER_EMPTY; //space for header
|
|
SecBuff[2].BufferType = SECBUFFER_EMPTY; //space for footer
|
|
SecBuff[3].BufferType = SECBUFFER_EMPTY; //space for extra marker
|
|
|
|
ss = secur.pDecryptMessage(&f->sechnd, &BuffDesc, 0, &ulQop);
|
|
if (FAILED(ss))
|
|
{
|
|
if (f->datagram)
|
|
return; //some sort of corruption? ignore that packet.
|
|
if (ss == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
if (f->incrypt.avail == f->incrypt.datasize)
|
|
SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024);
|
|
return; //no error if its incomplete, we can just get more data later on.
|
|
}
|
|
switch(ss)
|
|
{
|
|
case SEC_E_DECRYPT_FAILURE: SSPI_Error(f, "DecryptMessage failed: SEC_E_DECRYPT_FAILURE\n", ss); break;
|
|
case SEC_E_INVALID_HANDLE: SSPI_Error(f, "DecryptMessage failed: SEC_E_INVALID_HANDLE\n"); break;
|
|
case SEC_E_UNFINISHED_CONTEXT_DELETED: SSPI_Error(f, "DecryptMessage failed: SEC_E_UNFINISHED_CONTEXT_DELETED\n"); break; //peer aborted?
|
|
default: SSPI_Error(f, "DecryptMessage failed: %0#lx\n", ss); break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < BuffDesc.cBuffers; i++)
|
|
{
|
|
switch(SecBuff[i].BufferType)
|
|
{
|
|
case SECBUFFER_DATA:
|
|
if (SSPI_CopyIntoBuffer(&f->inraw, SecBuff[i].pvBuffer, SecBuff[i].cbBuffer, true) != SecBuff[i].cbBuffer)
|
|
SSPI_Error(f, "outraw buffer overflowed\n");
|
|
break;
|
|
case SECBUFFER_EXTRA:
|
|
if (extra)
|
|
SSPI_Error(f, "multiple extra buffers\n");
|
|
extra = &SecBuff[i];
|
|
break;
|
|
case SECBUFFER_EMPTY:
|
|
case SECBUFFER_MISSING:
|
|
case SECBUFFER_STREAM_TRAILER:
|
|
case SECBUFFER_STREAM_HEADER:
|
|
break;
|
|
default:
|
|
SSPI_Error(f, "got unexpected buffer type\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
//retain the extra. if there's no extra then mark it so.
|
|
if (extra)
|
|
{
|
|
memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - extra->cbBuffer), extra->cbBuffer);
|
|
f->incrypt.avail = extra->cbBuffer;
|
|
}
|
|
else
|
|
f->incrypt.avail = 0;
|
|
}
|
|
|
|
//convert outgoing data->crypt
|
|
static neterr_t SSPI_Encode(sslfile_t *f)
|
|
{
|
|
SECURITY_STATUS ss;
|
|
SecBufferDesc BuffDesc;
|
|
SecBuffer SecBuff[4];
|
|
ULONG ulQop = 0;
|
|
|
|
if (f->outcrypt.avail)
|
|
{
|
|
SSPI_TryFlushCryptOut(f);
|
|
if (f->outcrypt.avail)
|
|
return NETERR_CLOGGED; //don't flood too much
|
|
}
|
|
|
|
|
|
//don't corrupt the handshake data.
|
|
if (f->handshaking)
|
|
return NETERR_CLOGGED;
|
|
|
|
if (!f->outraw.avail)
|
|
return NETERR_SENT;
|
|
|
|
BuffDesc.ulVersion = SECBUFFER_VERSION;
|
|
BuffDesc.cBuffers = 4;
|
|
BuffDesc.pBuffers = SecBuff;
|
|
|
|
SecBuff[0].BufferType = SECBUFFER_STREAM_HEADER;
|
|
SecBuff[0].cbBuffer = f->headersize;
|
|
SecBuff[0].pvBuffer = f->headerdata;
|
|
|
|
SecBuff[1].BufferType = SECBUFFER_DATA;
|
|
SecBuff[1].cbBuffer = f->outraw.avail;
|
|
SecBuff[1].pvBuffer = f->outraw.data;
|
|
|
|
SecBuff[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
|
SecBuff[2].cbBuffer = f->footersize;
|
|
SecBuff[2].pvBuffer = f->footerdata;
|
|
|
|
SecBuff[3].BufferType = SECBUFFER_EMPTY;
|
|
SecBuff[3].cbBuffer = 0;
|
|
SecBuff[3].pvBuffer = NULL;
|
|
|
|
ss = secur.pEncryptMessage(&f->sechnd, ulQop, &BuffDesc, 0);
|
|
|
|
if (ss < 0)
|
|
{
|
|
switch (ss)
|
|
{
|
|
case SEC_E_ENCRYPT_FAILURE: SSPI_Error(f, "EncryptMessage failed SEC_E_ENCRYPT_FAILURE (in: %i, max out %i)\n", f->outraw.avail, f->outcrypt.avail);
|
|
default: SSPI_Error(f, "EncryptMessage failed %x\n", ss);
|
|
}
|
|
return NETERR_DISCONNECTED;
|
|
}
|
|
|
|
f->outraw.avail = 0;
|
|
|
|
//fixme: these should be made non-fatal.
|
|
if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[0].pvBuffer, SecBuff[0].cbBuffer, true) < SecBuff[0].cbBuffer)
|
|
{
|
|
SSPI_Error(f, "crypt buffer overflowed\n");
|
|
return NETERR_DISCONNECTED;
|
|
}
|
|
if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[1].pvBuffer, SecBuff[1].cbBuffer, true) < SecBuff[1].cbBuffer)
|
|
{
|
|
SSPI_Error(f, "crypt buffer overflowed\n");
|
|
return NETERR_DISCONNECTED;
|
|
}
|
|
if (SSPI_CopyIntoBuffer(&f->outcrypt, SecBuff[2].pvBuffer, SecBuff[2].cbBuffer, true) < SecBuff[2].cbBuffer)
|
|
{
|
|
SSPI_Error(f, "crypt buffer overflowed\n");
|
|
return NETERR_DISCONNECTED;
|
|
}
|
|
|
|
return SSPI_TryFlushCryptOut(f);
|
|
}
|
|
|
|
char *narrowen(char *out, size_t outlen, wchar_t *wide);
|
|
static DWORD VerifyKnownCertificates(DWORD status, wchar_t *domain, qbyte *data, size_t datasize, qboolean datagram)
|
|
{
|
|
size_t knownsize;
|
|
void *knowncert;
|
|
char realdomain[256];
|
|
unsigned int probs = 0;
|
|
if (datagram)
|
|
{
|
|
if (status == CERT_E_UNTRUSTEDROOT || status == CERT_E_UNTRUSTEDTESTROOT)
|
|
probs |= CERTLOG_MISSINGCA;
|
|
if (status == CERT_E_EXPIRED)
|
|
probs |= CERTLOG_EXPIRED;
|
|
if (status == SEC_E_WRONG_PRINCIPAL)
|
|
probs |= CERTLOG_WRONGHOST;
|
|
if (status == CERT_E_UNTRUSTEDROOT || SUCCEEDED(status))
|
|
{
|
|
#ifndef SERVERONLY
|
|
if (CertLog_ConnectOkay(narrowen(realdomain, sizeof(realdomain), domain), data, datasize, probs))
|
|
status = SEC_E_OK;
|
|
else
|
|
#endif
|
|
if (SUCCEEDED(status))
|
|
status = TRUST_E_EXPLICIT_DISTRUST;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
narrowen(realdomain, sizeof(realdomain), domain);
|
|
knowncert = TLS_GetKnownCertificate(realdomain, &knownsize);
|
|
if (knowncert)
|
|
{
|
|
if (knownsize == datasize && !memcmp(data, knowncert, datasize))
|
|
{ //what we know about matched
|
|
if (status == CERT_E_UNTRUSTEDROOT || status == CERT_E_EXPIRED)
|
|
status = SEC_E_OK;
|
|
}
|
|
else
|
|
{
|
|
if (status != CERT_E_EXPIRED)
|
|
Con_Printf("%ls has an unexpected certificate\n", domain);
|
|
if (status == SEC_E_OK) //we (think) we know better.
|
|
status = TRUST_E_EXPLICIT_DISTRUST;
|
|
}
|
|
BZ_Free(knowncert);
|
|
}
|
|
|
|
#ifndef SERVERONLY
|
|
//self-signed and expired certs are understandable in many situations.
|
|
//prompt and cache (although this connection attempt will fail).
|
|
if (status == CERT_E_UNTRUSTEDROOT || status == CERT_E_UNTRUSTEDTESTROOT)
|
|
probs |= CERTLOG_MISSINGCA;
|
|
else if (status == CERT_E_EXPIRED)
|
|
probs |= CERTLOG_EXPIRED;
|
|
else if (status == SEC_E_WRONG_PRINCIPAL)
|
|
probs |= CERTLOG_WRONGHOST;
|
|
else if (status != SEC_E_OK)
|
|
probs |= CERTLOG_UNKNOWN;
|
|
if (status == CERT_E_UNTRUSTEDROOT || status == CERT_E_UNTRUSTEDTESTROOT || status == CERT_E_EXPIRED)
|
|
if (CertLog_ConnectOkay(realdomain, data, datasize, probs))
|
|
return SEC_E_OK;
|
|
#endif
|
|
|
|
return status;
|
|
}
|
|
|
|
static DWORD VerifyServerCertificate(PCCERT_CONTEXT pServerCert, PWSTR pwszServerName, DWORD dwCertFlags, qboolean datagram)
|
|
{
|
|
HTTPSPolicyCallbackData polHttps;
|
|
CERT_CHAIN_POLICY_PARA PolicyPara;
|
|
CERT_CHAIN_POLICY_STATUS PolicyStatus;
|
|
CERT_CHAIN_PARA ChainPara;
|
|
PCCERT_CHAIN_CONTEXT pChainContext;
|
|
DWORD Status;
|
|
LPSTR rgszUsages[] =
|
|
{
|
|
szOID_PKIX_KP_SERVER_AUTH,
|
|
szOID_SERVER_GATED_CRYPTO,
|
|
szOID_SGC_NETSCAPE
|
|
};
|
|
DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR);
|
|
|
|
if(pServerCert == NULL)
|
|
return SEC_E_WRONG_PRINCIPAL;
|
|
if(!*pwszServerName)
|
|
return SEC_E_WRONG_PRINCIPAL;
|
|
|
|
// Build certificate chain.
|
|
memset(&ChainPara, 0, sizeof(ChainPara));
|
|
ChainPara.cbSize = sizeof(ChainPara);
|
|
ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
|
|
ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages;
|
|
ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;
|
|
|
|
if (!crypt.pCertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, 0, NULL, &pChainContext))
|
|
{
|
|
Status = GetLastError();
|
|
Sys_Printf("Error %#lx returned by CertGetCertificateChain!\n", Status);
|
|
}
|
|
else
|
|
{
|
|
// Validate certificate chain.
|
|
memset(&polHttps, 0, sizeof(HTTPSPolicyCallbackData));
|
|
polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData);
|
|
polHttps.dwAuthType = AUTHTYPE_SERVER;
|
|
polHttps.fdwChecks = dwCertFlags;
|
|
polHttps.pwszServerName = pwszServerName;
|
|
|
|
memset(&PolicyPara, 0, sizeof(PolicyPara));
|
|
PolicyPara.cbSize = sizeof(PolicyPara);
|
|
PolicyPara.pvExtraPolicyPara = &polHttps;
|
|
|
|
memset(&PolicyStatus, 0, sizeof(PolicyStatus));
|
|
PolicyStatus.cbSize = sizeof(PolicyStatus);
|
|
|
|
if (!crypt.pCertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus))
|
|
{
|
|
Status = GetLastError();
|
|
Sys_Printf("Error %#lx returned by CertVerifyCertificateChainPolicy!\n", Status);
|
|
}
|
|
else
|
|
{
|
|
Status = VerifyKnownCertificates(PolicyStatus.dwError, pwszServerName, pServerCert->pbCertEncoded, pServerCert->cbCertEncoded, datagram);
|
|
if (Status)
|
|
{
|
|
char fmsg[512];
|
|
char *err;
|
|
switch (Status)
|
|
{
|
|
case CERT_E_EXPIRED: err = "CERT_E_EXPIRED"; break;
|
|
case CERT_E_VALIDITYPERIODNESTING: err = "CERT_E_VALIDITYPERIODNESTING"; break;
|
|
case CERT_E_ROLE: err = "CERT_E_ROLE"; break;
|
|
case CERT_E_PATHLENCONST: err = "CERT_E_PATHLENCONST"; break;
|
|
case CERT_E_CRITICAL: err = "CERT_E_CRITICAL"; break;
|
|
case CERT_E_PURPOSE: err = "CERT_E_PURPOSE"; break;
|
|
case CERT_E_ISSUERCHAINING: err = "CERT_E_ISSUERCHAINING"; break;
|
|
case CERT_E_MALFORMED: err = "CERT_E_MALFORMED"; break;
|
|
case CERT_E_UNTRUSTEDROOT: err = "CERT_E_UNTRUSTEDROOT"; break;
|
|
case CERT_E_CHAINING: err = "CERT_E_CHAINING"; break;
|
|
case TRUST_E_FAIL: err = "TRUST_E_FAIL"; break;
|
|
case TRUST_E_EXPLICIT_DISTRUST: err = "blocked"; break;
|
|
case CERT_E_REVOKED: err = "CERT_E_REVOKED"; break;
|
|
case CERT_E_UNTRUSTEDTESTROOT: err = "CERT_E_UNTRUSTEDTESTROOT"; break;
|
|
case CERT_E_REVOCATION_FAILURE: err = "CERT_E_REVOCATION_FAILURE"; break;
|
|
case CERT_E_CN_NO_MATCH:
|
|
err = fmsg;
|
|
Q_strncpyz(fmsg, "Certificate is for ", sizeof(fmsg));
|
|
crypt.pCertNameToStrA(X509_ASN_ENCODING, &pServerCert->pCertInfo->Subject, 0, fmsg+strlen(fmsg), sizeof(fmsg)-strlen(fmsg));
|
|
break;
|
|
case CERT_E_WRONG_USAGE: err = "CERT_E_WRONG_USAGE"; break;
|
|
default: err = va("%#x", (int)Status); break;
|
|
}
|
|
Con_Printf(CON_ERROR "Error verifying certificate for '%ls': %s\n", pwszServerName, err);
|
|
|
|
if (tls_ignorecertificateerrors.ival)
|
|
{
|
|
Con_Printf(CON_WARNING "pretending it didn't happen... (tls_ignorecertificateerrors is set)\n");
|
|
Status = SEC_E_OK;
|
|
}
|
|
}
|
|
else
|
|
Status = SEC_E_OK;
|
|
}
|
|
crypt.pCertFreeCertificateChain(pChainContext);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
static PCCERT_CONTEXT SSPI_GetServerCertificate(void)
|
|
{
|
|
//frankly this is all kinda fucked
|
|
static PCCERT_CONTEXT ret;
|
|
static const char *issuertext = "CN=127.0.0.1, O=\"FTE QuakeWorld\", OU=Fallback, C=QW";
|
|
wchar_t *const container = L"diediedie";
|
|
static const int provtype = PROV_RSA_SCHANNEL;
|
|
static wchar_t *const provname = MS_DEF_RSA_SCHANNEL_PROV_W;
|
|
CRYPT_KEY_PROV_INFO kpi;
|
|
CERT_NAME_BLOB issuerblob;
|
|
HCRYPTPROV prov = 0;
|
|
HCRYPTKEY hkey = 0; //nope, not registry related.
|
|
|
|
CRYPT_ALGORITHM_IDENTIFIER sigalg;
|
|
SYSTEMTIME expiredate;
|
|
int i;
|
|
const char *pfxname = NULL;
|
|
|
|
qofs_t fsz = 0;
|
|
CRYPT_DATA_BLOB pfxblob = {0, NULL};
|
|
HCERTSTORE store;
|
|
wchar_t password[512];
|
|
DWORD fucksake = countof(password);
|
|
static qboolean tried;
|
|
|
|
if (ret||tried)
|
|
return ret;
|
|
tried = true;
|
|
|
|
i = COM_CheckParm("-pfx");
|
|
if (i && i < com_argc-1)
|
|
pfxname = com_argv[i+1];
|
|
|
|
if (pfxname)
|
|
pfxblob.pbData = FS_MallocFile(pfxname, FS_SYSTEM, &fsz);
|
|
if (!pfxblob.pbData)
|
|
pfxblob.pbData = FS_MallocFile("identity.pfx", FS_ROOT, &fsz);
|
|
if (pfxblob.pbData)
|
|
{
|
|
pfxblob.cbData = fsz;
|
|
if (!GetUserNameW(password, &fucksake)) //use their username as a password. at least it'll block most accidental redistriction. should prolly mix in their computer name, w/e
|
|
*password = 0;
|
|
store = crypt.pPFXImportCertStore(&pfxblob, password, 0);
|
|
if (!store) //try a couple of other passwords, for people creating their own manually.
|
|
store = crypt.pPFXImportCertStore(&pfxblob, L"", 0);
|
|
if (!store)
|
|
store = crypt.pPFXImportCertStore(&pfxblob, NULL, 0);
|
|
|
|
if (store)
|
|
{
|
|
HCRYPTPROV hprov = 0;
|
|
DWORD keyspec = 0;
|
|
BOOL willbefalse = false;
|
|
|
|
ret = crypt.pCertFindCertificateInStore(store, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL);
|
|
if (ret && crypt.pCryptAcquireCertificatePrivateKey(ret, 0, NULL, &hprov, &keyspec, &willbefalse))
|
|
{
|
|
char fdn[1024];
|
|
char digest[DIGEST_MAXSIZE];
|
|
char b64[DIGEST_MAXSIZE*2+1];
|
|
size_t dgsz = CalcHash(&hash_sha1, digest, sizeof(digest), ret->pbCertEncoded, ret->cbCertEncoded);
|
|
Base64_EncodeBlock(digest, dgsz, b64, sizeof(b64));
|
|
Con_Printf("Loaded Certificate fingerprint is %s\n", b64);
|
|
crypt.pCertNameToStrA(ret->dwCertEncodingType, &ret->pCertInfo->Subject, CERT_X500_NAME_STR, fdn, sizeof(fdn));
|
|
Con_Printf("Loaded Certificate DN: %s\n", fdn);
|
|
return ret;
|
|
}
|
|
}
|
|
Con_Printf(CON_ERROR"pfx certificate failed to load.\n");
|
|
pfxname = NULL; //don't overwrite an overridden name. it
|
|
}
|
|
else if (pfxname)
|
|
Con_Printf(CON_WARNING"Generating new pfx file: %s\n", pfxname);
|
|
|
|
memset(&sigalg, 0, sizeof(sigalg));
|
|
sigalg.pszObjId = szOID_RSA_SHA512RSA;
|
|
|
|
GetSystemTime(&expiredate);
|
|
expiredate.wYear += 5; //5 years hence. woo
|
|
expiredate.wDay = 1; //work around feb nightmares... not to be confused with Week-Day...
|
|
|
|
|
|
memset(&issuerblob, 0, sizeof(issuerblob));
|
|
crypt.pCertStrToNameA(X509_ASN_ENCODING, issuertext, CERT_X500_NAME_STR, NULL, issuerblob.pbData, &issuerblob.cbData, NULL);
|
|
issuerblob.pbData = Z_Malloc(issuerblob.cbData);
|
|
crypt.pCertStrToNameA(X509_ASN_ENCODING, issuertext, CERT_X500_NAME_STR, NULL, issuerblob.pbData, &issuerblob.cbData, NULL);
|
|
|
|
advapi.pCryptAcquireContextW(&prov, container, provname, provtype, CRYPT_NEWKEYSET|CRYPT_MACHINE_KEYSET);
|
|
if (!prov)
|
|
{ //try again. fucking retarded api.
|
|
if (!advapi.pCryptAcquireContextW(&prov, container, provname, provtype, CRYPT_MACHINE_KEYSET))
|
|
{
|
|
Con_Printf(CON_ERROR"CryptAcquireContext failed.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
advapi.pCryptGenKey(prov, AT_KEYEXCHANGE, CRYPT_EXPORTABLE|CRYPT_ARCHIVABLE, &hkey);
|
|
kpi.pwszContainerName = container;
|
|
kpi.pwszProvName = provname;
|
|
kpi.dwProvType = provtype;
|
|
kpi.dwFlags = CERT_SET_KEY_CONTEXT_PROP_ID;
|
|
kpi.cProvParam = 0;
|
|
kpi.dwKeySpec = AT_KEYEXCHANGE;
|
|
|
|
ret = crypt.pCertCreateSelfSignCertificate(
|
|
prov,
|
|
&issuerblob,
|
|
0,
|
|
&kpi,
|
|
&sigalg,
|
|
NULL,
|
|
&expiredate,
|
|
NULL
|
|
);
|
|
if (!ret)
|
|
{ //try and downgrade the signature algo if it failed.
|
|
sigalg.pszObjId = szOID_RSA_SHA1RSA;
|
|
ret = crypt.pCertCreateSelfSignCertificate(
|
|
prov,
|
|
&issuerblob,
|
|
0,
|
|
&kpi,
|
|
&sigalg,
|
|
NULL,
|
|
&expiredate,
|
|
NULL
|
|
);
|
|
}
|
|
if (!ret)
|
|
Con_Printf(CON_ERROR"Certificate generation failed...\n");
|
|
else
|
|
{
|
|
//this is stupid and redundant, yet apparently still needed.
|
|
kpi.pwszContainerName = container;
|
|
kpi.pwszProvName = provname;
|
|
kpi.dwProvType = provtype;
|
|
kpi.dwFlags = CRYPT_MACHINE_KEYSET;
|
|
kpi.dwKeySpec = AT_KEYEXCHANGE;
|
|
crypt.pCertSetCertificateContextProperty(ret, CERT_KEY_PROV_INFO_PROP_ID, 0, &kpi);
|
|
|
|
{
|
|
HCRYPTPROV hprov = 0;
|
|
DWORD keyspec = 0;
|
|
BOOL willbefalse = false;
|
|
if (!crypt.pCryptAcquireCertificatePrivateKey(ret, 0, NULL, &hprov, &keyspec, &willbefalse))
|
|
{
|
|
Con_Printf(CON_ERROR"Private key is defective.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//write it to disk
|
|
{
|
|
wchar_t password[512];
|
|
CRYPT_DATA_BLOB blob = {0};
|
|
HCERTSTORE store = crypt.pCertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, CERT_STORE_CREATE_NEW_FLAG, NULL);
|
|
if (store)
|
|
{
|
|
if (crypt.pCertAddCertificateContextToStore(store, ret, CERT_STORE_ADD_ALWAYS, NULL))
|
|
{
|
|
DWORD fucksake = countof(password);
|
|
if (!GetUserNameW(password, &fucksake)) //use their username as a password. at least it'll block most accidental redistriction. should prolly mix in their computer name, w/e
|
|
*password = 0;
|
|
|
|
if (crypt.pPFXExportCertStoreEx(store, &blob, password, NULL, EXPORT_PRIVATE_KEYS|REPORT_NO_PRIVATE_KEY|REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY))
|
|
{
|
|
blob.pbData = alloca(blob.cbData);
|
|
if (crypt.pPFXExportCertStoreEx(store, &blob, password, NULL, EXPORT_PRIVATE_KEYS|REPORT_NO_PRIVATE_KEY|REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY))
|
|
{
|
|
if (blob.cbData)
|
|
{
|
|
if (!FS_WriteFile(pfxname?pfxname:"identity.pfx", blob.pbData, blob.cbData, pfxname?FS_SYSTEM:FS_ROOT))
|
|
Con_Printf(CON_ERROR"FS_WriteFile(%s) failed\n", pfxname?pfxname:"identity.pfx");
|
|
}
|
|
else
|
|
Con_Printf(CON_ERROR"PFXExportCertStoreEx no data\n");
|
|
}
|
|
else
|
|
Con_Printf(CON_ERROR"PFXExportCertStoreEx failed\n");
|
|
}
|
|
else
|
|
Con_Printf(CON_ERROR"PFXExportCertStoreEx failed\n");
|
|
}
|
|
else
|
|
Con_Printf(CON_ERROR"CertAddCertificateContextToStore failed\n");
|
|
crypt.pCertCloseStore(store, 0);
|
|
}
|
|
else
|
|
Con_Printf(CON_ERROR"CertOpenStore failed\n");
|
|
}
|
|
|
|
{
|
|
char fdn[1024];
|
|
char digest[DIGEST_MAXSIZE];
|
|
char b64[DIGEST_MAXSIZE*2+1];
|
|
size_t dgsz = CalcHash(&hash_sha1, digest, sizeof(digest), ret->pbCertEncoded, ret->cbCertEncoded);
|
|
Base64_EncodeBlock(digest, dgsz, b64, sizeof(b64));
|
|
Con_Printf("Generated Certificate fingerprint is %s\n", b64);
|
|
crypt.pCertNameToStrA(ret->dwCertEncodingType, &ret->pCertInfo->Subject, CERT_X500_NAME_STR, fdn, sizeof(fdn));
|
|
Con_Printf("Generated Certificate DN: %s\n", fdn);
|
|
}
|
|
}
|
|
Z_Free(issuerblob.pbData);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void SSPI_GenServerCredentials(sslfile_t *f)
|
|
{
|
|
SECURITY_STATUS ss;
|
|
TimeStamp Lifetime;
|
|
SCHANNEL_CRED SchannelCred;
|
|
PCCERT_CONTEXT cred;
|
|
|
|
memset(&SchannelCred, 0, sizeof(SchannelCred));
|
|
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
SchannelCred.grbitEnabledProtocols = f->datagram?USE_PROT_DGRAM_SERVER:USE_PROT_SERVER;
|
|
SchannelCred.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER|SCH_CRED_DISABLE_RECONNECTS; /*don't use windows login info or anything*/
|
|
|
|
cred = SSPI_GetServerCertificate();
|
|
SchannelCred.cCreds = 1;
|
|
SchannelCred.paCred = &cred;
|
|
|
|
if (!cred)
|
|
{
|
|
SSPI_Error(f, localtext("Unable to load/generate certificate\n"));
|
|
return;
|
|
}
|
|
|
|
ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_INBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime);
|
|
if (FAILED(ss) && f->datagram)
|
|
{ //try again with just dtls1, for <win10
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_DTLS1_0_SERVER;
|
|
ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_INBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime);
|
|
}
|
|
if (FAILED(ss))
|
|
{
|
|
SSPI_Error(f, localtext("WinSSPI: AcquireCredentialsHandle failed %#x\n"), ss);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void SSPI_Handshake (sslfile_t *f)
|
|
{
|
|
SECURITY_STATUS ss;
|
|
TimeStamp Lifetime;
|
|
SecBufferDesc OutBuffDesc;
|
|
SecBuffer OutSecBuff[8];
|
|
SecBufferDesc InBuffDesc;
|
|
SecBuffer InSecBuff[8];
|
|
ULONG ContextAttributes;
|
|
SCHANNEL_CRED SchannelCred;
|
|
int i;
|
|
qboolean retries = 5;
|
|
|
|
retry:
|
|
|
|
if (f->outcrypt.avail)
|
|
{
|
|
//don't let things build up too much
|
|
SSPI_TryFlushCryptOut(f);
|
|
if (f->outcrypt.avail)
|
|
return;
|
|
}
|
|
|
|
//FIXME: skip this if we've had no new data since last time
|
|
|
|
OutBuffDesc.ulVersion = SECBUFFER_VERSION;
|
|
OutBuffDesc.cBuffers = countof(OutSecBuff);
|
|
OutBuffDesc.pBuffers = OutSecBuff;
|
|
|
|
OutSecBuff[0].BufferType = SECBUFFER_TOKEN;
|
|
OutSecBuff[0].cbBuffer = f->outcrypt.datasize - f->outcrypt.avail;
|
|
OutSecBuff[0].pvBuffer = f->outcrypt.data + f->outcrypt.avail;
|
|
|
|
for (i = 0; i < OutBuffDesc.cBuffers; i++)
|
|
{
|
|
OutSecBuff[i].BufferType = SECBUFFER_EMPTY;
|
|
OutSecBuff[i].pvBuffer = NULL;
|
|
OutSecBuff[i].cbBuffer = 0;
|
|
}
|
|
|
|
if (f->handshaking == HS_ERROR)
|
|
return; //gave up.
|
|
else if (f->handshaking == HS_STARTCLIENT)
|
|
{
|
|
PCCERT_CONTEXT cert;
|
|
|
|
//no input data yet.
|
|
f->handshaking = HS_CLIENT;
|
|
|
|
memset(&SchannelCred, 0, sizeof(SchannelCred));
|
|
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
SchannelCred.grbitEnabledProtocols = f->datagram?USE_PROT_DGRAM_CLIENT:USE_PROT_CLIENT;
|
|
SchannelCred.dwFlags |= SCH_CRED_SNI_CREDENTIAL | SCH_CRED_NO_DEFAULT_CREDS; /*don't use windows login info or anything*/
|
|
|
|
//just always use the same credentials, cos we're lazy and lame, and windows users don't give a shit about privacy anyway.
|
|
cert = SSPI_GetServerCertificate();
|
|
if (cert)
|
|
{
|
|
SchannelCred.cCreds = 1;
|
|
SchannelCred.paCred = &cert;
|
|
}
|
|
|
|
ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime);
|
|
if (FAILED(ss) && f->datagram)
|
|
{
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_DTLS1_0_CLIENT;
|
|
ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime);
|
|
}
|
|
if (ss < 0)
|
|
{
|
|
SSPI_Error(f, localtext("WINSSPI: AcquireCredentialsHandle failed (%x)\n"), (int)ss);
|
|
return;
|
|
}
|
|
ss = secur.pInitializeSecurityContextW (&f->cred, NULL, f->wpeername, MessageAttribute|(f->datagram?ISC_REQ_DATAGRAM:ISC_REQ_STREAM), 0, SECURITY_NATIVE_DREP, NULL, 0, &f->sechnd, &OutBuffDesc, &ContextAttributes, &Lifetime);
|
|
}
|
|
else if (f->handshaking == HS_CLIENT)
|
|
{
|
|
//only if we actually have data.
|
|
if (!f->incrypt.avail && !f->datagram)
|
|
return;
|
|
|
|
InBuffDesc.ulVersion = SECBUFFER_VERSION;
|
|
InBuffDesc.cBuffers = 4;
|
|
InBuffDesc.pBuffers = InSecBuff;
|
|
|
|
i = 0;
|
|
|
|
if (f->incrypt.avail)
|
|
{
|
|
InSecBuff[i].BufferType = SECBUFFER_TOKEN;
|
|
InSecBuff[i].cbBuffer = f->incrypt.avail;
|
|
InSecBuff[i].pvBuffer = f->incrypt.data;
|
|
i++;
|
|
}
|
|
|
|
for (; i < InBuffDesc.cBuffers; i++)
|
|
{
|
|
InSecBuff[i].BufferType = SECBUFFER_EMPTY;
|
|
InSecBuff[i].pvBuffer = NULL;
|
|
InSecBuff[i].cbBuffer = 0;
|
|
}
|
|
|
|
ss = secur.pInitializeSecurityContextW (&f->cred, &f->sechnd, NULL, MessageAttribute|(f->datagram?ISC_REQ_DATAGRAM:ISC_REQ_STREAM), 0, SECURITY_NETWORK_DREP, &InBuffDesc, 0, NULL, &OutBuffDesc, &ContextAttributes, &Lifetime);
|
|
|
|
if (ss == SEC_E_INCOMPLETE_MESSAGE)
|
|
{ //TLS splits the data randomly, so this should not be considered fatal
|
|
// Con_Printf("SEC_E_INCOMPLETE_MESSAGE (available %i)\n", (int)f->incrypt.avail);
|
|
if (!f->datagram && f->incrypt.avail == f->incrypt.datasize)
|
|
SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024);
|
|
return;
|
|
}
|
|
else if (ss == SEC_E_INVALID_TOKEN)
|
|
{
|
|
// Con_Printf("SEC_E_INVALID_TOKEN\n");
|
|
if (f->datagram)
|
|
return; //our udp protocol may have non-dtls packets mixed in. besides, we don't want to die from spoofed packets.
|
|
}
|
|
// else if (ss == SEC_I_MESSAGE_FRAGMENT)
|
|
// Con_Printf("SEC_I_MESSAGE_FRAGMENT\n");
|
|
// else if (ss == SEC_I_CONTINUE_NEEDED)
|
|
// Con_Printf("SEC_I_CONTINUE_NEEDED\n");
|
|
// else
|
|
// Con_Printf("InitializeSecurityContextA %x\n", ss);
|
|
|
|
|
|
//any extra data should still remain for the next time around. this might be more handshake data or payload data.
|
|
if (InSecBuff[1].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - InSecBuff[1].cbBuffer), InSecBuff[1].cbBuffer);
|
|
f->incrypt.avail = InSecBuff[1].cbBuffer;
|
|
}
|
|
else f->incrypt.avail = 0;
|
|
}
|
|
else if (f->handshaking == HS_STARTSERVER || f->handshaking == HS_SERVER)
|
|
{
|
|
//only if we actually have data.
|
|
if (!f->incrypt.avail)
|
|
return;
|
|
|
|
InBuffDesc.ulVersion = SECBUFFER_VERSION;
|
|
InBuffDesc.cBuffers = countof(InSecBuff);
|
|
InBuffDesc.pBuffers = InSecBuff;
|
|
i = 0;
|
|
|
|
if (f->incrypt.avail)
|
|
{
|
|
InSecBuff[i].BufferType = SECBUFFER_TOKEN;
|
|
InSecBuff[i].cbBuffer = f->incrypt.avail;
|
|
InSecBuff[i].pvBuffer = f->incrypt.data;
|
|
i++;
|
|
}
|
|
|
|
if (f->datagram)
|
|
{ //for dtls's cookie
|
|
InSecBuff[i].BufferType = SECBUFFER_EXTRA;
|
|
InSecBuff[i].cbBuffer = 11;
|
|
InSecBuff[i].pvBuffer = "Hello World";
|
|
i++;
|
|
}
|
|
|
|
for (; i < InBuffDesc.cBuffers; i++)
|
|
{
|
|
InSecBuff[i].BufferType = SECBUFFER_EMPTY;
|
|
InSecBuff[i].pvBuffer = NULL;
|
|
InSecBuff[i].cbBuffer = 0;
|
|
}
|
|
|
|
i = 1;
|
|
OutSecBuff[i++].BufferType = SECBUFFER_EXTRA;
|
|
OutSecBuff[i++].BufferType = SECBUFFER_ALERT;
|
|
|
|
#define ServerMessageAttribute (ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY | /*ASC_REQ_EXTENDED_ERROR |*/ ASC_REQ_ALLOCATE_MEMORY)
|
|
ContextAttributes = ServerMessageAttribute|(f->datagram?ASC_REQ_DATAGRAM:ASC_REQ_STREAM);
|
|
// if (f->peerhash) //for ICE
|
|
// ContextAttributes |= ASC_REQ_MUTUAL_AUTH;
|
|
|
|
ss = secur.pAcceptSecurityContext(&f->cred, (f->handshaking==HS_SERVER)?&f->sechnd:NULL, &InBuffDesc,
|
|
ContextAttributes, SECURITY_NETWORK_DREP, &f->sechnd,
|
|
&OutBuffDesc, &ContextAttributes, NULL);
|
|
if (f->datagram && ss == SEC_I_CONTINUE_NEEDED && f->handshaking != HS_SERVER)
|
|
{ //this seems wrong, but schannel complains if we don't continue to pass null above.
|
|
secur.pDeleteSecurityContext(&f->sechnd); //avoid leaks
|
|
memset(&f->sechnd, 0, sizeof(f->sechnd));
|
|
}
|
|
else
|
|
f->handshaking = HS_SERVER;
|
|
|
|
if (ss == SEC_E_INVALID_TOKEN)
|
|
{ //sender sent us something that wasn't (d)tls.
|
|
// Con_Printf("SEC_E_INVALID_TOKEN\n");
|
|
if (f->datagram)
|
|
return;
|
|
}
|
|
else if (ss == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
// Con_Printf("SEC_E_INCOMPLETE_MESSAGE\n");
|
|
if (!f->datagram && f->incrypt.avail == f->incrypt.datasize)
|
|
SSPI_ExpandBuffer(&f->incrypt, f->incrypt.datasize+1024);
|
|
return;
|
|
}
|
|
// else if (FAILED(ss))
|
|
// Con_Printf("AcceptSecurityContext %x\n", ss);
|
|
|
|
//any extra data should still remain for the next time around. this might be more handshake data or payload data.
|
|
if (InSecBuff[1].BufferType == SECBUFFER_EXTRA && !f->datagram)
|
|
{
|
|
memmove(f->incrypt.data, f->incrypt.data + (f->incrypt.avail - InSecBuff[1].cbBuffer), InSecBuff[1].cbBuffer);
|
|
f->incrypt.avail = InSecBuff[1].cbBuffer;
|
|
}
|
|
else f->incrypt.avail = 0;
|
|
}
|
|
else
|
|
return;
|
|
|
|
|
|
if (ss == SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
//FIXME: load/generate our identity, redo the pAcquireCredentialsHandleA
|
|
Con_TPrintf(CON_WARNING"server requires credentials, attempting to ignore\n");
|
|
goto retry;
|
|
}
|
|
|
|
if (FAILED(ss))
|
|
{
|
|
const char *fname = (f->handshaking>=HS_STARTSERVER)?"AcceptSecurityContext":"InitializeSecurityContext";
|
|
switch(ss)
|
|
{
|
|
case SEC_E_ALGORITHM_MISMATCH: SSPI_Error(f, "%s failed: SEC_E_ALGORITHM_MISMATCH\n", fname); break;
|
|
case SEC_E_INVALID_HANDLE: SSPI_Error(f, "%s failed: SEC_E_INVALID_HANDLE\n", fname); break;
|
|
case SEC_E_ILLEGAL_MESSAGE: SSPI_Error(f, "%s failed: SEC_E_ILLEGAL_MESSAGE\n", fname); break;
|
|
case SEC_E_INVALID_TOKEN: SSPI_Error(f, "%s failed: SEC_E_INVALID_TOKEN\n", fname); break;
|
|
case SEC_E_INVALID_PARAMETER: SSPI_Error(f, "%s failed: SEC_E_INVALID_PARAMETER\n", fname); break;
|
|
case SEC_E_INTERNAL_ERROR: SSPI_Error(f, "%s failed: SEC_E_INTERNAL_ERROR\n", fname); break;
|
|
default: SSPI_Error(f, "%s failed: %lx\n", fname, (long)ss); break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss))
|
|
{
|
|
ss = secur.pCompleteAuthToken (&f->sechnd, &OutBuffDesc);
|
|
if (ss < 0)
|
|
{
|
|
SSPI_Error(f, "CompleteAuthToken failed\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
//its all okay and established if we get this far.
|
|
if (ss == SEC_E_OK)
|
|
{
|
|
SecPkgContext_StreamSizes strsizes;
|
|
CERT_CONTEXT *remotecert;
|
|
|
|
// Con_Printf("ContextAttributes: %x (%s, %s)\n", ContextAttributes, (f->handshaking == HS_SERVER)?"sv":"cl", f->datagram?"dtls":"tls");
|
|
|
|
if (f->datagram)
|
|
{ //ask for a bit bigger. we use sendto for mtu guessing, schannel spitting out errors for arbitrary lower values is just annoying - it knows nothing about the actual connection.
|
|
ULONG mtu = 8192;
|
|
secur.pSetContextAttributesA(&f->sechnd, SECPKG_ATTR_DTLS_MTU, &mtu, sizeof(mtu));
|
|
}
|
|
|
|
secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_STREAM_SIZES, &strsizes);
|
|
f->headersize = strsizes.cbHeader;
|
|
f->footersize = strsizes.cbTrailer;
|
|
|
|
f->mtu = strsizes.cbMaximumMessage - (f->headersize+f->footersize); //compute the maximum payload we can actually get with that.
|
|
|
|
if (f->handshaking != HS_SERVER)
|
|
{ //server takes an annonymous client. client expects a proper certificate.
|
|
if (*f->wpeername)
|
|
{
|
|
ss = secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &remotecert);
|
|
if (ss != SEC_E_OK)
|
|
{
|
|
SSPI_Error(f, localtext("unable to read server's certificate\n"));
|
|
return;
|
|
}
|
|
if (VerifyServerCertificate(remotecert, f->wpeername, 0, f->datagram))
|
|
{
|
|
SSPI_Error(f, localtext("Error validating certificate\n"));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
Sys_Printf("SSL/TLS Server name not specified, skipping verification\n");
|
|
}
|
|
|
|
f->handshaking = HS_ESTABLISHED;
|
|
}
|
|
|
|
//send early, send often.
|
|
#ifdef HAVE_DTLS
|
|
if (f->transmit)
|
|
{
|
|
for (i = 0; i < OutBuffDesc.cBuffers; i++)
|
|
if (OutSecBuff[i].BufferType == SECBUFFER_TOKEN && OutSecBuff[i].cbBuffer)
|
|
{
|
|
f->resendtimer = realtime+0.2;
|
|
f->transmit(f->cbctx, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
i = 0;
|
|
if (SSPI_CopyIntoBuffer(&f->outcrypt, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer, true) < OutSecBuff[i].cbBuffer)
|
|
{
|
|
SSPI_Error(f, "crypt overflow\n");
|
|
return;
|
|
}
|
|
SSPI_TryFlushCryptOut(f);
|
|
}
|
|
|
|
if (f->handshaking == HS_ESTABLISHED)
|
|
SSPI_Encode(f);
|
|
else if (ss == SEC_I_MESSAGE_FRAGMENT) //looks like we can connect faster if we loop when we get this result.
|
|
if (retries --> 0)
|
|
goto retry;
|
|
}
|
|
|
|
static int QDECL SSPI_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread)
|
|
{
|
|
sslfile_t *f = (sslfile_t *)file;
|
|
int err = SSPI_CheckNewInCrypt(f);
|
|
|
|
if (f->handshaking)
|
|
{
|
|
SSPI_Handshake(f);
|
|
return err;
|
|
}
|
|
|
|
SSPI_Encode(f);
|
|
|
|
SSPI_Decode(f);
|
|
|
|
bytestoread = min(bytestoread, f->inraw.avail);
|
|
if (bytestoread)
|
|
{
|
|
memcpy(buffer, f->inraw.data, bytestoread);
|
|
f->inraw.avail -= bytestoread;
|
|
memmove(f->inraw.data, f->inraw.data + bytestoread, f->inraw.avail);
|
|
}
|
|
else
|
|
{
|
|
if (err)
|
|
return err;
|
|
}
|
|
return bytestoread;
|
|
}
|
|
static int QDECL SSPI_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite)
|
|
{
|
|
sslfile_t *f = (sslfile_t *)file;
|
|
|
|
//don't endlessly accept data faster than we can push it out.
|
|
//we'll buffer a little, but don't go overboard
|
|
if (f->outcrypt.avail > 8192)
|
|
return 0;
|
|
|
|
bytestowrite = SSPI_CopyIntoBuffer(&f->outraw, buffer, bytestowrite, false);
|
|
|
|
if (f->handshaking)
|
|
{
|
|
SSPI_CheckNewInCrypt(f); //make sure its ticking over
|
|
SSPI_Handshake(f);
|
|
}
|
|
else
|
|
{
|
|
SSPI_Encode(f);
|
|
}
|
|
|
|
return bytestowrite;
|
|
}
|
|
static qboolean QDECL SSPI_Seek (struct vfsfile_s *file, qofs_t pos)
|
|
{
|
|
SSPI_Error((sslfile_t*)file, "unable to seek on streams\n");
|
|
return false;
|
|
}
|
|
static qofs_t QDECL SSPI_Tell (struct vfsfile_s *file)
|
|
{
|
|
SSPI_Error((sslfile_t*)file, "unable to seek on streams\n");
|
|
return 0;
|
|
}
|
|
static qofs_t QDECL SSPI_GetLen (struct vfsfile_s *file)
|
|
{
|
|
return 0;
|
|
}
|
|
static qboolean QDECL SSPI_Close (struct vfsfile_s *file)
|
|
{
|
|
sslfile_t *f = (sslfile_t *)file;
|
|
qboolean success = f->stream != NULL;
|
|
SSPI_Error(f, "");
|
|
|
|
Z_Free(f->outraw.data);
|
|
Z_Free(f->outcrypt.data);
|
|
Z_Free(f->inraw.data);
|
|
Z_Free(f->incrypt.data);
|
|
|
|
Z_Free(f);
|
|
return success;
|
|
}
|
|
|
|
#include <wchar.h>
|
|
static qboolean CleanUpHostname(const char *hostname, wchar_t *out, size_t outcount)
|
|
{ //strip any fte-specific junk, like dtls:// or [] or :
|
|
const char *hostnameend;
|
|
const char *host = strstr(hostname, "://");
|
|
int i = 0;
|
|
if (host)
|
|
hostname = host+3;
|
|
hostnameend = hostname+strlen(hostname);
|
|
//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)
|
|
hostnameend = host;
|
|
}
|
|
else
|
|
{ //eg: 127.0.0.1:port - strip the port number if specified.
|
|
host = strchr(hostname, ':');
|
|
if (host)
|
|
hostnameend = host;
|
|
}
|
|
|
|
while(hostname < hostnameend)
|
|
{
|
|
int err;
|
|
int c = utf8_decode(&err, hostname, (void*)&hostname);
|
|
if (c > WCHAR_MAX)
|
|
err = true; //no 16bit surrogates. they're evil.
|
|
else if (i == outcount - 1)
|
|
err = true; //no space to store it
|
|
else
|
|
out[i++] = c;
|
|
if (err)
|
|
{
|
|
out[i] = 0;
|
|
return false;
|
|
}
|
|
}
|
|
out[i] = 0;
|
|
return true;
|
|
}
|
|
|
|
static vfsfile_t *SSPI_OpenVFS(const char *servername, vfsfile_t *source, qboolean server)
|
|
{
|
|
sslfile_t *newf;
|
|
if (!source || !SSL_Inited())
|
|
return NULL;
|
|
|
|
/*
|
|
if (server) //unsupported
|
|
return NULL;
|
|
*/
|
|
|
|
newf = Z_Malloc(sizeof(*newf));
|
|
if (!CleanUpHostname(server?"":servername, newf->wpeername, countof(newf->wpeername)))
|
|
{
|
|
Z_Free(newf);
|
|
return NULL;
|
|
}
|
|
|
|
newf->handshaking = server?HS_STARTSERVER:HS_STARTCLIENT;
|
|
newf->stream = source;
|
|
newf->funcs.Close = SSPI_Close;
|
|
newf->funcs.Flush = NULL;
|
|
newf->funcs.GetLen = SSPI_GetLen;
|
|
newf->funcs.ReadBytes = SSPI_ReadBytes;
|
|
newf->funcs.Seek = SSPI_Seek;
|
|
newf->funcs.Tell = SSPI_Tell;
|
|
newf->funcs.WriteBytes = SSPI_WriteBytes;
|
|
newf->funcs.seekstyle = SS_UNSEEKABLE;
|
|
|
|
SSPI_ExpandBuffer(&newf->outraw, 8192);
|
|
SSPI_ExpandBuffer(&newf->outcrypt, 8192);
|
|
SSPI_ExpandBuffer(&newf->inraw, 8192);
|
|
SSPI_ExpandBuffer(&newf->incrypt, 8192);
|
|
|
|
if (server)
|
|
SSPI_GenServerCredentials(newf);
|
|
|
|
return &newf->funcs;
|
|
}
|
|
|
|
#ifndef SECPKG_ATTR_UNIQUE_BINDINGS
|
|
#define SECPKG_ATTR_UNIQUE_BINDINGS 25
|
|
typedef struct _SecPkgContext_Bindings
|
|
{
|
|
unsigned long BindingsLength;
|
|
SEC_CHANNEL_BINDINGS *Bindings;
|
|
} SecPkgContext_Bindings, *PSecPkgContext_Bindings;
|
|
#endif
|
|
static int SSPI_GetChannelBinding(vfsfile_t *vf, qbyte *binddata, size_t *bindsize)
|
|
{
|
|
int ret;
|
|
sslfile_t *f = (sslfile_t*)vf;
|
|
SecPkgContext_Bindings bindings;
|
|
if (vf->Close != SSPI_Close)
|
|
return -2; //not one of ours.
|
|
|
|
bindings.BindingsLength = 0;
|
|
bindings.Bindings = NULL;
|
|
ret = 0;
|
|
switch(secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_UNIQUE_BINDINGS, &bindings))
|
|
{
|
|
case SEC_E_OK:
|
|
if (bindings.Bindings->cbApplicationDataLength <= *bindsize && !Q_strncasecmp(((char*)bindings.Bindings)+bindings.Bindings->dwApplicationDataOffset, "tls-unique:", 11))
|
|
{
|
|
//will contain 'tls-unique:BINARYDATA'
|
|
*bindsize = bindings.Bindings->cbApplicationDataLength-11;
|
|
memcpy(binddata, ((unsigned char*) bindings.Bindings) + bindings.Bindings->dwApplicationDataOffset+11, bindings.Bindings->cbApplicationDataLength-11);
|
|
ret = 1;
|
|
}
|
|
//FIXME: leak
|
|
//secur.pFreeContextBuffer(bindings.Bindings);
|
|
break;
|
|
case SEC_E_UNSUPPORTED_FUNCTION:
|
|
ret = -1; //schannel doesn't support it. too old an OS, I guess.
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#include "netinc.h"
|
|
#if defined(HAVE_DTLS)
|
|
static void *SSPI_DTLS_CreateContext(const dtlscred_t *credinfo, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver)
|
|
{
|
|
sslfile_t *ctx;
|
|
if (!SSL_Inited())
|
|
return NULL;
|
|
|
|
ctx = Z_Malloc(sizeof(*ctx));
|
|
ctx->datagram = true;
|
|
ctx->handshaking = isserver?HS_STARTSERVER:HS_STARTCLIENT;
|
|
ctx->cbctx = cbctx;
|
|
ctx->transmit = push;
|
|
|
|
if (!CleanUpHostname((credinfo&&credinfo->peer.name)?credinfo->peer.name:"", ctx->wpeername, countof(ctx->wpeername)))
|
|
{
|
|
Z_Free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
SSPI_ExpandBuffer(&ctx->outraw, 8192);
|
|
SSPI_ExpandBuffer(&ctx->outcrypt, 65536);
|
|
SSPI_ExpandBuffer(&ctx->inraw, 8192);
|
|
SSPI_ExpandBuffer(&ctx->incrypt, 65536);
|
|
|
|
if (isserver)
|
|
SSPI_GenServerCredentials(ctx);
|
|
else
|
|
SSPI_Handshake(ctx); //begin the initial handshake now
|
|
return ctx;
|
|
}
|
|
|
|
static void SSPI_DTLS_DestroyContext(void *vctx)
|
|
{
|
|
SSPI_Close(vctx);
|
|
}
|
|
|
|
|
|
static neterr_t SSPI_DTLS_Transmit(void *ctx, const qbyte *data, size_t datasize)
|
|
{
|
|
sslfile_t *f = (sslfile_t *)ctx;
|
|
|
|
if (!datasize)
|
|
{ //we use these as a way to probe whether its sendable or not yet.
|
|
if (f->handshaking)
|
|
{
|
|
if (f->resendtimer < realtime)
|
|
SSPI_Handshake(f); //keep trying...
|
|
return NETERR_CLOGGED;
|
|
}
|
|
return NETERR_SENT;
|
|
}
|
|
|
|
if (f->handshaking)
|
|
SSPI_Handshake(f); //keep trying...
|
|
if (f->handshaking == HS_ERROR)
|
|
return NETERR_DISCONNECTED;
|
|
if (f->handshaking)
|
|
return NETERR_CLOGGED; //not ready yet
|
|
|
|
if (datasize >= f->mtu)
|
|
return NETERR_MTU; //we're not allowed.
|
|
|
|
//sspi likes writing over the source data. make sure nothing is hurt by copying it out first.
|
|
f->outraw.avail = 0;
|
|
SSPI_CopyIntoBuffer(&f->outraw, data, datasize, true);
|
|
return SSPI_Encode(f);
|
|
}
|
|
|
|
static neterr_t SSPI_DTLS_Received(void *ctx, sizebuf_t *msg)
|
|
{
|
|
int ret;
|
|
sslfile_t *f = (sslfile_t *)ctx;
|
|
|
|
f->incrypt.data = msg->data;
|
|
f->incrypt.avail = f->incrypt.datasize = msg->cursize;
|
|
|
|
if (f->handshaking)
|
|
{
|
|
SSPI_Handshake(f);
|
|
ret = NETERR_CLOGGED; //not ready yet
|
|
|
|
if (f->handshaking == HS_ERROR)
|
|
ret = NETERR_DISCONNECTED;
|
|
}
|
|
else
|
|
{
|
|
SSPI_Decode(f);
|
|
ret = NETERR_SENT;
|
|
|
|
if (f->inraw.avail < msg->maxsize)
|
|
msg->cursize = f->inraw.avail;
|
|
else
|
|
msg->cursize = msg->maxsize;
|
|
memcpy(msg->data, f->inraw.data, msg->cursize);
|
|
f->inraw.avail = 0;
|
|
}
|
|
f->incrypt.data = NULL;
|
|
return ret;
|
|
}
|
|
static neterr_t SSPI_DTLS_Timeouts(void *ctx)
|
|
{
|
|
sslfile_t *f = (sslfile_t *)ctx;
|
|
if (f->handshaking)
|
|
{
|
|
SSPI_DTLS_Transmit(ctx, NULL, 0);
|
|
return NETERR_CLOGGED;
|
|
}
|
|
return NETERR_SENT;
|
|
}
|
|
|
|
static qboolean SSPI_DTLS_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))
|
|
{ //we got a packet that might be a 'dtls hello' packet. try figuring out what's going on.
|
|
|
|
SECURITY_STATUS ss;
|
|
SecBufferDesc OutBuffDesc;
|
|
SecBuffer OutSecBuff[8];
|
|
SecBufferDesc InBuffDesc;
|
|
SecBuffer InSecBuff[8];
|
|
ULONG ContextAttributes;
|
|
int i;
|
|
char replymessage[4094];
|
|
|
|
static sslfile_t *pending;
|
|
if (!pending)
|
|
{
|
|
pending = SSPI_DTLS_CreateContext(NULL, NULL, NULL, true);
|
|
if (!pending)
|
|
return false;
|
|
}
|
|
|
|
InBuffDesc.ulVersion = SECBUFFER_VERSION;
|
|
InBuffDesc.cBuffers = countof(InSecBuff);
|
|
InBuffDesc.pBuffers = InSecBuff;
|
|
i = 0;
|
|
|
|
InSecBuff[i].BufferType = SECBUFFER_TOKEN;
|
|
InSecBuff[i].cbBuffer = insize;
|
|
InSecBuff[i].pvBuffer = indata;
|
|
i++;
|
|
|
|
//for dtls's cookie
|
|
InSecBuff[i].BufferType = SECBUFFER_EXTRA;
|
|
InSecBuff[i].cbBuffer = peeraddrsize;
|
|
InSecBuff[i].pvBuffer = peeraddr;
|
|
i++;
|
|
|
|
for (; i < InBuffDesc.cBuffers; i++)
|
|
{
|
|
InSecBuff[i].BufferType = SECBUFFER_EMPTY;
|
|
InSecBuff[i].pvBuffer = NULL;
|
|
InSecBuff[i].cbBuffer = 0;
|
|
}
|
|
|
|
OutBuffDesc.ulVersion = SECBUFFER_VERSION;
|
|
OutBuffDesc.cBuffers = countof(OutSecBuff);
|
|
OutBuffDesc.pBuffers = OutSecBuff;
|
|
|
|
OutSecBuff[0].BufferType = SECBUFFER_TOKEN;
|
|
OutSecBuff[0].cbBuffer = sizeof(replymessage);
|
|
OutSecBuff[0].pvBuffer = replymessage;
|
|
|
|
for (i = 1; i < OutBuffDesc.cBuffers; i++)
|
|
{
|
|
OutSecBuff[i].BufferType = SECBUFFER_EMPTY;
|
|
OutSecBuff[i].pvBuffer = NULL;
|
|
OutSecBuff[i].cbBuffer = 0;
|
|
}
|
|
|
|
i = 1;
|
|
OutSecBuff[i++].BufferType = SECBUFFER_EXTRA;
|
|
OutSecBuff[i++].BufferType = SECBUFFER_ALERT;
|
|
|
|
ContextAttributes = ServerMessageAttribute|ASC_REQ_DATAGRAM;
|
|
ss = secur.pAcceptSecurityContext(&pending->cred, NULL, &InBuffDesc,
|
|
ContextAttributes, SECURITY_NETWORK_DREP, &pending->sechnd,
|
|
&OutBuffDesc, &ContextAttributes, NULL);
|
|
|
|
//expect SEC_I_CONTINUE_NEEDED for a outgoing challenge (cookie request)
|
|
//expect SEC_I_MESSAGE_FRAGMENT for anything more.
|
|
|
|
for (i = 0; i < OutBuffDesc.cBuffers; i++)
|
|
if (OutSecBuff[i].BufferType == SECBUFFER_TOKEN && OutSecBuff[i].cbBuffer)
|
|
push(cbctx, OutSecBuff[i].pvBuffer, OutSecBuff[i].cbBuffer);
|
|
if (ss == SEC_I_MESSAGE_FRAGMENT)
|
|
{ //looks like we got a reply to the dtls handshake. lock down its ip.
|
|
pending->cbctx = cbctx;
|
|
pending->transmit = push;
|
|
pending->handshaking = HS_SERVER;
|
|
EstablishTrueContext(&pending->cbctx, pending);
|
|
pending = NULL;
|
|
return true;
|
|
}
|
|
//try to avoid memory leaks. this stuff isn't documented nearly well enough.
|
|
secur.pDeleteSecurityContext(&pending->sechnd);
|
|
memset(&pending->sechnd, 0, sizeof(pending->sechnd));
|
|
|
|
return false;
|
|
}
|
|
|
|
qboolean SSPI_DTLS_GenTempCertificate(const char *subject, struct dtlslocalcred_s *cred)
|
|
{ //exporting+importing certs here is just too damn painful. use a single cert and a dummy value for the key. for webrtc this SHOULD be okay, even if its expired...
|
|
PCCERT_CONTEXT cert = SSPI_GetServerCertificate();
|
|
if (!cert)
|
|
return false;
|
|
|
|
cred->cert = memcpy(BZ_Malloc(cert->cbCertEncoded), cert->pbCertEncoded, cert->cbCertEncoded);
|
|
cred->certsize = cert->cbCertEncoded;
|
|
cred->key = NULL;
|
|
cred->keysize = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int SSPI_DTLS_GetPeerCertificate(void *ctx, enum certprops_e prop, char *out, size_t outsize)
|
|
{
|
|
sslfile_t *f = (sslfile_t *)ctx;
|
|
CERT_CONTEXT *cert = NULL;
|
|
|
|
safeswitch(prop)
|
|
{
|
|
case QCERT_ISENCRYPTED:
|
|
if (f->handshaking == HS_ESTABLISHED)
|
|
return 0; //handshake is done, its all encrypted.
|
|
return -1; //still pending, doesn't count as encrypted yet.
|
|
|
|
case QCERT_PEERCERTIFICATE:
|
|
secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &cert);
|
|
if (cert && cert->cbCertEncoded <= outsize)
|
|
{
|
|
memcpy(out, cert->pbCertEncoded, cert->cbCertEncoded);
|
|
return cert->cbCertEncoded;
|
|
}
|
|
return -1;
|
|
|
|
case QCERT_PEERSUBJECT:
|
|
secur.pQueryContextAttributesA(&f->sechnd, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &cert);
|
|
if (cert && cert->cbCertEncoded <= outsize)
|
|
return crypt.pCertNameToStrA(cert->dwCertEncodingType, &cert->pCertInfo->Subject, CERT_X500_NAME_STR, out, outsize);
|
|
return -1;
|
|
case QCERT_LOCALCERTIFICATE:
|
|
case QCERT_LOBBYSTATUS:
|
|
case QCERT_LOBBYSENDCHAT:
|
|
safedefault:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static const dtlsfuncs_t dtlsfuncs_schannel =
|
|
{
|
|
SSPI_DTLS_CreateContext,
|
|
SSPI_DTLS_CheckConnection,
|
|
SSPI_DTLS_DestroyContext,
|
|
SSPI_DTLS_Transmit,
|
|
SSPI_DTLS_Received,
|
|
SSPI_DTLS_Timeouts,
|
|
SSPI_DTLS_GetPeerCertificate,
|
|
SSPI_DTLS_GenTempCertificate
|
|
};
|
|
static const dtlsfuncs_t *SSPI_DTLS_InitServer(void)
|
|
{
|
|
//make sure we can load a cert...
|
|
if (!SSL_Inited() || !SSPI_GetServerCertificate())
|
|
return NULL; //otherwise refuse to run as a server instead of causing cert issues with every restart for users.
|
|
|
|
return &dtlsfuncs_schannel;
|
|
}
|
|
static const dtlsfuncs_t *SSPI_DTLS_InitClient(void)
|
|
{
|
|
return &dtlsfuncs_schannel;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
|
#define SSPI_VerifyHash NULL //old versions of msvc are crippled...
|
|
#else
|
|
#define STATUS_SUCCESS ((NTSTATUS)0x00000000)
|
|
#define STATUS_INVALID_SIGNATURE ((NTSTATUS)0xC000A000)
|
|
#include <bcrypt.h>
|
|
static enum hashvalidation_e SSPI_VerifyHash(const qbyte *hashdata, size_t hashsize, const qbyte *pemcert, size_t pemcertsize, const qbyte *signdata, size_t signsize)
|
|
{
|
|
NTSTATUS status;
|
|
BCRYPT_KEY_HANDLE pubkey;
|
|
const char *pem = pemcert;
|
|
const char *pemend;
|
|
qbyte *der;
|
|
size_t dersize;
|
|
|
|
static const void *(WINAPI *pCertCreateContext) (DWORD dwContextType, DWORD dwEncodingType, const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, PCERT_CREATE_CONTEXT_PARA pCreatePara);
|
|
static BOOL (WINAPI *pCryptImportPublicKeyInfoEx2) (DWORD dwCertEncodingType, PCERT_PUBLIC_KEY_INFO pInfo, DWORD dwFlags, void *pvAuxInfo, BCRYPT_KEY_HANDLE *phKey);
|
|
static BOOL (WINAPI *pCertFreeCertificateContext) (PCCERT_CONTEXT pCertContext);
|
|
static dllhandle_t *crypt32;
|
|
static dllfunction_t crypt32funcs[] = {
|
|
{(void**)&pCertCreateContext, "CertCreateContext"},
|
|
{(void**)&pCryptImportPublicKeyInfoEx2, "CryptImportPublicKeyInfoEx2"}, //WARNING: fails on wine.
|
|
{(void**)&pCertFreeCertificateContext, "CertFreeCertificateContext"},
|
|
{NULL,NULL}
|
|
};
|
|
|
|
static NTSTATUS (WINAPI *pBCryptVerifySignature) (BCRYPT_KEY_HANDLE hKey, VOID *pPaddingInfo, PUCHAR pbHash, ULONG cbHash, PUCHAR pbSignature, ULONG cbSignature, ULONG dwFlags);
|
|
static NTSTATUS (WINAPI *pBCryptDestroyKey) (BCRYPT_KEY_HANDLE hKey);
|
|
static dllhandle_t *bcrypt;
|
|
static dllfunction_t bcryptfuncs[] = {
|
|
{(void**)&pBCryptVerifySignature, "BCryptVerifySignature"},
|
|
{(void**)&pBCryptDestroyKey, "BCryptDestroyKey"},
|
|
{NULL,NULL}
|
|
};
|
|
|
|
if (!crypt32)
|
|
crypt32 = Sys_LoadLibrary("crypt32.dll", crypt32funcs);
|
|
if (!bcrypt)
|
|
bcrypt = Sys_LoadLibrary("bcrypt.dll", bcryptfuncs);
|
|
if (!crypt32 || !bcrypt)
|
|
{
|
|
Con_Printf("Unable to obtain required crypto functions\n");
|
|
return VH_UNSUPPORTED;
|
|
}
|
|
|
|
if (!pem)
|
|
return VH_AUTHORITY_UNKNOWN; //no public cert/key for authority.
|
|
pem = strstr(pem, "-----BEGIN CERTIFICATE-----");
|
|
if (!pem)
|
|
return VH_UNSUPPORTED; //not a pem
|
|
pem += strlen("-----BEGIN CERTIFICATE-----");
|
|
pemend = strstr(pem, "-----END CERTIFICATE-----");
|
|
if (!pemend)
|
|
return VH_UNSUPPORTED;
|
|
dersize = Base64_DecodeBlock(pem, pemend, NULL, 0); //guess
|
|
der = alloca(dersize);
|
|
dersize = Base64_DecodeBlock(pem, pemend, der, dersize);
|
|
//okay, now its in binary der format.
|
|
|
|
//make sense of the cert and pull out its public key...
|
|
{
|
|
const CERT_CONTEXT* cert = pCertCreateContext(CERT_STORE_CERTIFICATE_CONTEXT, X509_ASN_ENCODING, der, dersize, 0, NULL);
|
|
if (!pCryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, &cert->pCertInfo->SubjectPublicKeyInfo, 0, NULL, &pubkey))
|
|
return VH_UNSUPPORTED;
|
|
pCertFreeCertificateContext(cert);
|
|
}
|
|
|
|
//yay, now we can do what we actually wanted in the first place.
|
|
status = pBCryptVerifySignature(pubkey, NULL, (qbyte*)hashdata, hashsize, (qbyte*)signdata, signsize, 0);
|
|
pBCryptDestroyKey(pubkey);
|
|
if (status == STATUS_SUCCESS)
|
|
return VH_CORRECT; //its okay
|
|
else if (status == STATUS_INVALID_SIGNATURE)
|
|
return VH_INCORRECT; //its bad
|
|
return VH_UNSUPPORTED; //some weird transient error...?
|
|
}
|
|
#endif
|
|
|
|
ftecrypto_t crypto_sspi =
|
|
{
|
|
"SChannel",
|
|
SSPI_OpenVFS,
|
|
SSPI_GetChannelBinding,
|
|
SSPI_DTLS_InitClient,
|
|
SSPI_DTLS_InitServer,
|
|
SSPI_VerifyHash,
|
|
NULL,//SSPI_GenerateHash,
|
|
};
|
|
#endif
|