mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-25 13:21:36 +00:00
1768 lines
58 KiB
C
1768 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:
|
|
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
|