#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))
		{
#ifdef HAVE_CLIENT
			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);
	}

#ifdef HAVE_CLIENT
	//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"SChannel: Certificate generation failed...\n");	//happens in wine.
	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
