diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f2ae1c6..551a12ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1074,6 +1074,34 @@ IF(FTE_PLUG_QI) SET(INSTALLTARGS ${INSTALLTARGS} plug_qi) ENDIF() +SET(FTE_PLUG_OPENSSL false CACHE BOOL "Compile OpenSSL (beware license).") +IF(FTE_PLUG_OPENSSL) + #the openssl license is incompatible with the GPL, so while we have code to use it distributing the binaries built with it is not a (legal) option. + #note that openssl 3.0.0 upwards are apache-2 licensed, which IS gpl-3 compatible (though not gpl-2). debian has not caught up with that yet, however. + FIND_PACKAGE(OpenSSL) + IF(NOT OPENSSL_FOUND) + MESSAGE(WARNING "openssl library NOT available. you'll have to use some other library.") + ELSE() + IF(OPENSSL_VERSION_MAJOR LESS 3 AND NOT FTE_PRIVATE_USE_ONLY) + MESSAGE(WARNING "openssl library version is not 3 or above. You may not distribute plugin binaries due to license conflict.") + ELSE() + MESSAGE(WARNING "Using openssl. Resulting plugin must be licensed as GPLv3.") + ENDIF() + SET(FTE_LIB_DEFINES ${FTE_LIB_DEFINES}) + + ADD_LIBRARY(plug_openssl MODULE + plugins/plugin.c + plugins/net_ssl_openssl.c + ) + SET_TARGET_PROPERTIES(plug_openssl PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN;${FTE_LIB_DEFINES}") + SET_TARGET_PROPERTIES(plug_openssl PROPERTIES OUTPUT_NAME "openssl") + SET_TARGET_PROPERTIES(plug_openssl PROPERTIES PREFIX "fteplug_") + SET_TARGET_PROPERTIES(plug_openssl PROPERTIES LINK_FLAGS "-Wl,--no-undefined") + TARGET_LINK_LIBRARIES(plug_openssl ${SYS_LIBS} ${OPENSSL_LIBRARIES}) + SET(INSTALLTARGS ${INSTALLTARGS} plug_openssl) + ENDIF() +ENDIF() + #ODE Physics library plugin SET(FTE_PLUG_ODE true CACHE BOOL "Compile ODE rigid body physics plugin.") IF(FTE_PLUG_ODE) diff --git a/build_setup.sh b/build_setup.sh index 21c14cd4..acf885f8 100755 --- a/build_setup.sh +++ b/build_setup.sh @@ -38,22 +38,22 @@ TARGETS_WINDOWS="sv-rel m-rel qcc-rel qccgui-scintilla qccgui-dbg m-dbg sv-dbg p PLUGINS_DROID="qi ezhud irc" -PLUGINS_LINUXx86="ode qi ezhud xmpp irc" -PLUGINS_LINUXx64="ode qi ezhud xmpp irc" +PLUGINS_LINUXx86="openxr ode qi ezhud xmpp irc" +PLUGINS_LINUXx64="openxr ode qi ezhud xmpp irc" PLUGINS_LINUXx32="qi ezhud xmpp irc" PLUGINS_LINUXarmhf="qi ezhud xmpp irc" if [ "$(uname -m)" != "x86_64" ]; then - PLUGINS_LINUXx86="ffmpeg ode qi ezhud xmpp irc" + PLUGINS_LINUXx86="openxr ode qi ezhud xmpp irc" fi if [ "$(uname -m)" == "x86_64" ]; then - PLUGINS_LINUX64="ffmpeg ode qi ezhud xmpp irc" + PLUGINS_LINUX64="openxr ode qi ezhud xmpp irc" fi #windows is always cross compiled, so we don't have issues with non-native ffmpeg #windows doesn't cross compile, so no system dependancy issues #skip some dependancies if we're running on cygwin, ode is buggy. if [ "$(uname -s)" == "Linux" ]; then - PLUGINS_WIN32="ffmpeg ode qi ezhud xmpp irc" - PLUGINS_WIN64="ffmpeg ode qi ezhud xmpp irc" + PLUGINS_WIN32="ode qi ezhud xmpp irc" + PLUGINS_WIN64="ode qi ezhud xmpp irc" else PLUGINS_WIN32="qi ezhud xmpp irc" PLUGINS_WIN64="qi ezhud xmpp irc" @@ -317,6 +317,9 @@ if [ "$BUILD_LINUXx86" == "y" ]; then if [[ "$PLUGINS_LINUXx86" =~ "ffmpeg" ]]; then debianpackages libswscale-dev libavcodec-dev || otherpackages || exit fi + if [[ "$PLUGINS_LINUXx86" =~ "openxr" ]]; then + debianpackages libopenxr-dev || otherpackages || exit + fi fi if [ "$BUILD_LINUXx64" == "y" ]; then #for building linux targets @@ -325,6 +328,9 @@ if [ "$BUILD_LINUXx64" == "y" ]; then if [[ "$PLUGINS_LINUXx64" =~ "ffmpeg" ]]; then debianpackages libswscale-dev libavcodec-dev || otherpackages || exit fi + if [[ "$PLUGINS_LINUXx64" =~ "openxr" ]]; then + debianpackages libopenxr-dev || otherpackages || exit + fi fi if [ "$BUILD_LINUXx32" == "y" ]; then #for building linux targets @@ -499,11 +505,11 @@ if [ $UID -ne 0 ] && [ $REBUILD_TOOLCHAINS == "y" ]; then echo "Prebuilding ODE library (linux x86_64)..." make FTE_TARGET=linux64 plugins-rel NATIVE_PLUGINS=ode CPUOPTIMISATIONS=-fno-finite-math-only 2>&1 >>/dev/null fi - if [ "$BUILD_WIN32" == "y" ]; then + if [ "$BUILD_WIN32" == "y" ] && [[ "$PLUGINS_WIN32" =~ "ffmpeg" ]]; then echo "Obtaining ffmpeg library (win32)..." make FTE_TARGET=win32 plugins-rel NATIVE_PLUGINS=ffmpeg 2>&1 >>/dev/null fi - if [ "$BUILD_WIN64" == "y" ]; then + if [ "$BUILD_WIN64" == "y" ] && [[ "$PLUGINS_WIN64" =~ "ffmpeg" ]]; then echo "Obtaining ffmpeg library (win64)..." make FTE_TARGET=win64 plugins-rel NATIVE_PLUGINS=ffmpeg 2>&1 >>/dev/null fi diff --git a/engine/Makefile b/engine/Makefile index 7016e64a..0db67625 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -2087,7 +2087,7 @@ endif plugins-dbg: @-mkdir -p $(DEBUG_DIR) @if test -e ../plugins/Makefile; \ - then $(MAKE) native -C ../plugins OUT_DIR="$(DEBUG_DIR)" CC="$(CC) $(W32_CFLAGS) $(DEBUG_CFLAGS)" CXX="$(CXX) $(W32_CFLAGS) $(subst -Wno-pointer-sign,,$(DEBUG_CFLAGS))" ARCH="$(ARCH)" BASE_CFLAGS="$(BASE_CFLAGS) $(BRANDFLAGS)" BASE_CXXFLAGS="$(subst -Wno-pointer-sign,,$(BASE_CFLAGS)) $(BRANDFLAGS)" FTE_TARGET="$(FTE_TARGET)"; \ + then $(MAKE) native -C ../plugins OUT_DIR="$(DEBUG_DIR)" CC="$(CC) $(W32_CFLAGS) $(DEBUG_CFLAGS)" CXX="$(CXX) $(W32_CFLAGS) $(subst -Wno-pointer-sign,,$(DEBUG_CFLAGS))" PKGCONFIG="$(PKGCONFIG)" ARCH="$(ARCH)" BASE_CFLAGS="$(BASE_CFLAGS) $(BRANDFLAGS)" BASE_CXXFLAGS="$(subst -Wno-pointer-sign,,$(BASE_CFLAGS)) $(BRANDFLAGS)" FTE_TARGET="$(FTE_TARGET)"; \ else echo no plugins directory installed; \ fi plugins: @@ -2095,7 +2095,7 @@ plugins: plugins-rel: @-mkdir -p $(RELEASE_DIR) @if test -e ../plugins/Makefile; \ - then $(MAKE) native -C ../plugins OUT_DIR="$(RELEASE_DIR)" CC="$(CC) $(W32_CFLAGS) $(RELEASE_CFLAGS)" CXX="$(CXX) $(W32_CFLAGS) $(subst -Wno-pointer-sign,,$(RELEASE_CFLAGS))" ARCH="$(ARCH)" BASE_CFLAGS="$(BASE_CFLAGS) $(BRANDFLAGS)" BASE_CXXFLAGS="$(subst -Wno-pointer-sign,,$(BASE_CFLAGS)) $(BRANDFLAGS)" FTE_TARGET="$(FTE_TARGET)"; \ + then $(MAKE) native -C ../plugins OUT_DIR="$(RELEASE_DIR)" CC="$(CC) $(W32_CFLAGS) $(RELEASE_CFLAGS)" CXX="$(CXX) $(W32_CFLAGS) $(subst -Wno-pointer-sign,,$(RELEASE_CFLAGS))" PKGCONFIG="$(PKGCONFIG)" ARCH="$(ARCH)" BASE_CFLAGS="$(BASE_CFLAGS) $(BRANDFLAGS)" BASE_CXXFLAGS="$(subst -Wno-pointer-sign,,$(BASE_CFLAGS)) $(BRANDFLAGS)" FTE_TARGET="$(FTE_TARGET)"; \ else echo no plugins directory installed; \ fi plugins-rel: diff --git a/plugins/Makefile b/plugins/Makefile index a940b113..71487fab 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -120,6 +120,7 @@ help: #linux users are expected to have the library installed locally already. If your version is too old or missing, run the following command to install it (to /usr/local), then delete the gz and directory. #wget http://ffmpeg.org/releases/ffmpeg-1.2.tar.gz && cd tar xvfz ffmpeg-1.2.tar.gz && cd ffmpeg-1.2/ && ./configure --disable-yasm --enable-shared && make && sudo make install #we use ffmpeg's version for some reason, as opposed to libav. not sure what the differences are meant to be, but libav seemed to have non-depricated functions defined, docs that say to use them, and these functions missing. +ifeq ($(findstring win,$(FTE_TARGET)),win) AV_VER=ffmpeg-4.0 ifeq (0,1) AV_ARCHIVEEXT=.z7 @@ -213,6 +214,17 @@ $(PLUG_PREFIX)ffmpeg$(PLUG_NATIVE_EXT): $(AV_DEP) $(FFMPEG_ZIP) $(PLUG_PREFIX)ffmpeg$(PLUG_NATIVE_EXT): $(AVPLUG_OBJS) $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -s -o $@ -shared $(PLUG_CFLAGS) $(AV_CFLAGS) $(AVPLUG_OBJS) $(PLUG_DEFFILE) $(PLUG_LDFLAGS) $(AV_LDFLAGS) + +#above uses wget and zips and mess. +else +#just use pkg-config +$(PLUG_PREFIX)ffmpeg$(PLUG_NATIVE_EXT): $(AVPLUG_OBJS) + $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -s -o $@ -shared $(PLUG_CFLAGS) $(AVPLUG_OBJS) $(PLUG_DEFFILE) $(PLUG_LDFLAGS) `$(PKGCONFIG) --cflags --libs libavformat libavcodec libavutil libswscale` +ifeq ($(shell $(PKGCONFIG) --exists libavformat libavcodec libavutil libswscale && echo 1),1) +NATIVE_PLUGINS+=ffmpeg +endif +endif + ###################################### ###################################### @@ -355,8 +367,8 @@ NATIVE_PLUGINS+=irc #OpenXR plugin... $(PLUG_PREFIX)openxr$(PLUG_NATIVE_EXT): openxr.c plugin.c - $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $@ -shared $(PLUG_CFLAGS) $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) -DXR_NO_PROTOTYPES `pkg-config --cflags openxr` -DGLQUAKE -DVKQUAKE -DD3D11QUAKE -ifeq ($(shell pkg-config --exists openxr && echo 1),1) + $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $@ -shared $(PLUG_CFLAGS) $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) -DXR_NO_PROTOTYPES `$(PKGCONFIG) --cflags openxr` -DGLQUAKE -DVKQUAKE -DD3D11QUAKE +ifeq ($(shell $(PKGCONFIG) --exists openxr && echo 1),1) NATIVE_PLUGINS+=openxr endif @@ -370,6 +382,13 @@ $(PLUG_PREFIX)models$(PLUG_NATIVE_EXT): models/gltf.c models/exportiqm.c models/ $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $@ -shared $(PLUG_CFLAGS) -Imodels $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) #NATIVE_PLUGINS+=models +#Openssl crypto plugin, to replace microsoft's shoddy dtls implementation. could also be useful on the BSDs, yay system components? +$(PLUG_PREFIX)openssl$(PLUG_NATIVE_EXT): net_ssl_openssl.c plugin.c + $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $@ -shared $(PLUG_CFLAGS) $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) `$(PKGCONFIG) --libs --cflags openssl` +#ifeq ($(shell $(PKGCONFIG) --exists openssl && echo 1),1) +#Disabled due to licensing issues #NATIVE_PLUGINS+=openssl +#endif + diff --git a/plugins/net_ssl_openssl.c b/plugins/net_ssl_openssl.c new file mode 100644 index 00000000..efe34f4a --- /dev/null +++ b/plugins/net_ssl_openssl.c @@ -0,0 +1,846 @@ +#include "plugin.h" +#include "netinc.h" + +plugfsfuncs_t *fsfuncs; + +#undef SHA1 +#undef HMAC +#include "openssl/bio.h" +#include "openssl/ssl.h" +#include "openssl/err.h" +#include "openssl/conf.h" + +#define assert(c) {if (!(c)) Con_Printf("assert failed: "STRINGIFY(c)"\n");} + +static qboolean OSSL_Init(void); +static int ossl_fte_certctx; +struct fte_certctx_s +{ + const char *peername; + qboolean dtls; +}; + +static struct +{ + X509 *servercert; + EVP_PKEY *privatekey; +} vhost; + +static BIO_METHOD *biometh_vfs; + +static int OSSL_Bio_FWrite(BIO *h, const char *buf, int size) +{ + vfsfile_t *f = BIO_get_data(h); + int r = VFS_WRITE(f, buf, size); + BIO_clear_retry_flags(h); + if (r == 0) + { + BIO_set_retry_write(h); + r = -1; //paranoia + } + return r; +} +static int OSSL_Bio_FRead(BIO *h, char *buf, int size) +{ + vfsfile_t *f = BIO_get_data(h); + int r = VFS_READ(f, buf, size); + BIO_clear_retry_flags(h); + if (r == 0) //no data yet. + { + BIO_set_retry_read(h); + r = -1; //shouldn't be needed, but I'm paranoid + } + return r; +} +static int OSSL_Bio_FPuts(BIO *h, const char *buf) +{ + return OSSL_Bio_FWrite(h, buf, strlen(buf)); +} +static long OSSL_Bio_FCtrl(BIO *h, int cmd, long arg1, void *arg2) +{ + vfsfile_t *f = BIO_get_data(h); + switch(cmd) + { + case BIO_CTRL_FLUSH: + VFS_FLUSH(f); + return 1; + default: + Con_Printf("OSSL_Bio_FCtrl: unknown cmd %i\n", cmd); + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + return 0; + } + return 0; //failure +} +static long OSSL_Bio_FOtherCtrl(BIO *h, int cmd, BIO_info_cb *cb) +{ + switch(cmd) + { + default: + Con_Printf("OSSL_Bio_FOtherCtrl unknown cmd %i\n", cmd); + return 0; + } + return 0; //failure +} +static int OSSL_Bio_FCreate(BIO *h) +{ //we'll have to fill this in after we create the bio. + BIO_set_data(h, NULL); + return 1; +} +static int OSSL_Bio_FDestroy(BIO *h) +{ + vfsfile_t *f = BIO_get_data(h); + VFS_CLOSE(f); + BIO_set_data(h, NULL); + return 1; +} + +static int OSSL_PrintError_CB (const char *str, size_t len, void *u) +{ + Con_Printf("%s\n", str); + return 1; +} + +typedef struct +{ + vfsfile_t funcs; + struct fte_certctx_s cert; + SSL_CTX *ctx; + BIO *bio; + SSL *ssl; +} osslvfs_t; +static int QDECL OSSL_FRead (struct vfsfile_s *file, void *buffer, int bytestoread) +{ + osslvfs_t *o = (osslvfs_t*)file; + int r = BIO_read(o->bio, buffer, bytestoread); + if (r <= 0) + { + if (BIO_should_io_special(o->bio)) + { + switch(BIO_get_retry_reason(o->bio)) + { + //these are temporary errors, try again later. + case BIO_RR_SSL_X509_LOOKUP: + return -1; //certificate failure. + case BIO_RR_ACCEPT: + case BIO_RR_CONNECT: + return -1; //should never happen + }; + } + if (BIO_should_retry(o->bio)) + return 0; + return -1; //eof or something + } + return r; +} +static int QDECL OSSL_FWrite (struct vfsfile_s *file, const void *buffer, int bytestowrite) +{ + osslvfs_t *o = (osslvfs_t*)file; + int r = BIO_write(o->bio, buffer, bytestowrite); + if (r <= 0) + { + if (BIO_should_io_special(o->bio)) + { + switch(BIO_get_retry_reason(o->bio)) + { + //these are temporary errors, try again later. + case BIO_RR_SSL_X509_LOOKUP: + return -1; //certificate failure. + case BIO_RR_ACCEPT: + case BIO_RR_CONNECT: + return -1; //should never happen + } + } + if (BIO_should_retry(o->bio)) + return 0; + return -1; //eof or something + } + return r; +} +//static qboolean QDECL OSSL_Seek (struct vfsfile_s *file, qofs_t pos); //returns false for error +//static qofs_t QDECL OSSL_Tell (struct vfsfile_s *file); +//static qofs_t QDECL OSSL_GetLen (struct vfsfile_s *file); //could give some lag +static qboolean QDECL OSSL_Close (struct vfsfile_s *file) +{ + osslvfs_t *o = (osslvfs_t*)file; + BIO_free(o->bio); + SSL_CTX_free(o->ctx); + free(o); + return true; //success, I guess +} +//static void QDECL OSSL_Flush (struct vfsfile_s *file); + + + +static qboolean print_cn_name(X509_NAME* const name, const char *utf8match, const char *prefix) +{ + int idx = -1; + qboolean success = 0; + unsigned char *utf8 = NULL; + X509_NAME_ENTRY* entry; + ASN1_STRING* data; + int length; + + do + { + if(!name) break; /* failed */ + + idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + if(!(idx > -1)) break; /* failed */ + + entry = X509_NAME_get_entry(name, idx); + if(!entry) break; /* failed */ + + data = X509_NAME_ENTRY_get_data(entry); + if(!data) break; /* failed */ + + length = ASN1_STRING_to_UTF8(&utf8, data); + if(!utf8 || !(length > 0)) break; /* failed */ + + if (utf8match) + success = !strcmp(utf8, utf8match); + else + { + Con_Printf("%s%s", prefix, utf8); + success = 1; + } + + } while (0); + + if(utf8) + OPENSSL_free(utf8); + + return success; +} + +static int OSSL_Verify_Peer(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + SSL *ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + struct fte_certctx_s *uctx = SSL_get_ex_data(ssl, ossl_fte_certctx); + + if(preverify_ok == 0) + { + int depth = X509_STORE_CTX_get_error_depth(x509_ctx); + int err = X509_STORE_CTX_get_error(x509_ctx); + + X509* cert = X509_STORE_CTX_get_current_cert(x509_ctx); + X509_NAME* iname = cert ? X509_get_issuer_name(cert) : NULL; + //X509_NAME* sname = cert ? X509_get_subject_name(cert) : NULL; + + if (err == X509_V_ERR_CERT_HAS_EXPIRED || err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) + { + size_t knownsize; + qbyte *knowndata = TLS_GetKnownCertificate(uctx->peername, &knownsize); + if (knowndata) + { //check + size_t blobsize; + qbyte *blob; + qbyte *end; + blobsize = i2d_X509(cert, NULL); + if (blobsize != knownsize) + return 0; //fail if the size doesn't match. + blob = alloca(blobsize); + end = blob; + i2d_X509(cert, &end); + if (memcmp(blob, knowndata, blobsize)) + return 0; + return 1; //exact match to a known cert. yay. allow it. + } + +#ifdef HAVE_CLIENT + if (uctx->dtls) + { + unsigned int probs = 0; + size_t blobsize; + qbyte *blob; + qbyte *end; + blobsize = i2d_X509(cert, NULL); + blob = alloca(blobsize); + end = blob; + i2d_X509(cert, &end); + + switch(err) + { + case 0: + probs |= CERTLOG_WRONGHOST; + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + probs |= CERTLOG_EXPIRED; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + probs |= CERTLOG_MISSINGCA; + break; + default: + probs |= CERTLOG_UNKNOWN; + break; + } + if (CertLog_ConnectOkay(uctx->peername, blob, blobsize, probs)) + return 1; //ignore the errors... + } +#endif + } + + Con_Printf(CON_ERROR"%s ", uctx->peername); + //FIXME: this is probably on a worker thread. expect munged prints. + //print_cn_name(sname, NULL, CON_ERROR); + Con_Printf(" (issued by "); + print_cn_name(iname, NULL, S_COLOR_YELLOW); + Con_Printf(")"); + if(depth == 0) { + /* If depth is 0, its the server's certificate. Print the SANs too */ + // print_san_name("Subject (san)", cert); + } + + if(err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) + Con_Printf(": Error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY\n"); + else if (err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) + Con_Printf(": Error = X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE\n"); + else if(err == X509_V_ERR_CERT_UNTRUSTED) + Con_Printf(": Error = X509_V_ERR_CERT_UNTRUSTED\n"); + else if(err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) + Con_Printf(": Error = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN\n"); + else if(err == X509_V_ERR_CERT_NOT_YET_VALID) + Con_Printf(": Error = X509_V_ERR_CERT_NOT_YET_VALID\n"); + else if(err == X509_V_ERR_CERT_HAS_EXPIRED) + Con_Printf(": Error = X509_V_ERR_CERT_HAS_EXPIRED\n"); + else if(err == X509_V_OK) + Con_Printf(": Error = X509_V_OK\n"); + else if(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) + Con_Printf(": Error = X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT\n"); + else + Con_Printf(": Error = %d\n", err); + } + + if (!preverify_ok && cvarfuncs->GetFloat("tls_ignorecertificateerrors")) + { + Con_Printf(CON_ERROR "%s: Ignoring certificate errors (tls_ignorecertificateerrors is set)\n", uctx->peername); + return 1; + } + + return preverify_ok; +} + + +static vfsfile_t *OSSL_OpenPrivKeyFile(char *nativename, size_t nativesize) +{ +#define privname "privkey.pem" + vfsfile_t *privf; + const char *mode = nativename?"wb":"rb"; + /*int i = COM_CheckParm("-privkey"); + if (i++) + { + if (nativename) + Q_strncpyz(nativename, com_argv[i], nativesize); + privf = FS_OpenVFS(com_argv[i], mode, FS_SYSTEM); + } + else*/ + { + if (nativename) + if (!fsfuncs->NativePath(privname, FS_ROOT, nativename, nativesize)) + return NULL; + + privf = fsfuncs->OpenVFS(privname, mode, FS_ROOT); + } + return privf; +#undef privname +} +static vfsfile_t *OSSL_OpenPubKeyFile(char *nativename, size_t nativesize) +{ +#define fullchainname "fullchain.pem" +#define pubname "cert.pem" + vfsfile_t *pubf = NULL; + const char *mode = nativename?"wb":"rb"; + /*int i = COM_CheckParm("-pubkey"); + if (i++) + { + if (nativename) + Q_strncpyz(nativename, com_argv[i], nativesize); + pubf = FS_OpenVFS(com_argv[i], mode, FS_SYSTEM); + } + else*/ + { + if (!pubf && (!nativename || fsfuncs->NativePath(fullchainname, FS_ROOT, nativename, nativesize))) + pubf = fsfuncs->OpenVFS(fullchainname, mode, FS_ROOT); + if (!pubf && (!nativename || fsfuncs->NativePath(pubname, FS_ROOT, nativename, nativesize))) + pubf = fsfuncs->OpenVFS(pubname, mode, FS_ROOT); + } + return pubf; +#undef pubname +} +static BIO *OSSL_BioFromFile(vfsfile_t *f) +{ + qbyte buf[4096]; + int r; + BIO *b = BIO_new(BIO_s_mem()); + if (f) + { + for(;;) + { + r = VFS_READ(f, buf, sizeof(buf)); + if (r <= 0) + break; + BIO_write(b, buf, r); + } + VFS_CLOSE(f); + } + + return b; +} +static void OSSL_OpenPrivKey(void) +{ + BIO *bio = OSSL_BioFromFile(OSSL_OpenPrivKeyFile(NULL,0)); + vhost.privatekey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + BIO_free(bio); +} +static void OSSL_OpenPubKey(void) +{ + BIO *bio = OSSL_BioFromFile(OSSL_OpenPubKeyFile(NULL,0)); + vhost.servercert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + BIO_free(bio); +} + +static vfsfile_t *OSSL_OpenVFS(const char *hostname, vfsfile_t *source, qboolean isserver) +{ + BIO *sink; + osslvfs_t *n; + if (!OSSL_Init()) + return NULL; //FAIL! + + n = calloc(sizeof(*n) + strlen(hostname)+1, 1); + + n->funcs.ReadBytes = OSSL_FRead; + n->funcs.WriteBytes = OSSL_FWrite; + n->funcs.Seek = NULL; + n->funcs.Tell = NULL; + n->funcs.GetLen = NULL; + n->funcs.Close = OSSL_Close; + n->funcs.Flush = NULL; + n->funcs.seekstyle = SS_UNSEEKABLE; + + n->cert.peername = strcpy((char*)(n+1), hostname); + n->cert.dtls = false; + + ERR_print_errors_cb(OSSL_PrintError_CB, NULL); + + sink = BIO_new(biometh_vfs); + if (sink) + { + n->ctx = SSL_CTX_new(isserver?TLS_server_method():TLS_client_method()); + if (n->ctx) + { + assert(1==SSL_CTX_set_cipher_list(n->ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH")); + + SSL_CTX_set_session_cache_mode(n->ctx, SSL_SESS_CACHE_OFF); + + SSL_CTX_set_default_verify_paths(n->ctx); + SSL_CTX_set_verify(n->ctx, SSL_VERIFY_PEER, OSSL_Verify_Peer); + SSL_CTX_set_verify_depth(n->ctx, 5); + SSL_CTX_set_options(n->ctx, SSL_OP_NO_COMPRESSION); //compression allows guessing the contents of the stream somehow. + + if (isserver) + { + if (vhost.servercert && vhost.privatekey) + { + SSL_CTX_use_certificate(n->ctx, vhost.servercert); + SSL_CTX_use_PrivateKey(n->ctx, vhost.privatekey); + assert(1==SSL_CTX_check_private_key(n->ctx)); + } + } + + n->bio = BIO_new_ssl(n->ctx, !isserver); + + //set up the source/sink + BIO_set_data(sink, source); //source now belongs to the bio + BIO_set_init(sink, true); //our sink is now ready... + n->bio = BIO_push(n->bio, sink); + BIO_free(sink); + sink = NULL; + + BIO_get_ssl(n->bio, &n->ssl); + SSL_set_ex_data(n->ssl, ossl_fte_certctx, &n->cert); + SSL_set_mode(n->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_set_tlsext_host_name(n->ssl, n->cert.peername); //let the server know which cert to send + BIO_do_connect(n->bio); + return &n->funcs; + } + BIO_free(sink); + } + return NULL; +} +/*static int OSSL_GetChannelBinding(vfsfile_t *vf, qbyte *binddata, size_t *bindsize) +{ + //FIXME: not yet supported. tbh I've no idea how to get that data. probably something convoluted. + return -1; +}*/ + + + +static BIO_METHOD *biometh_dtls; +typedef struct { + struct fte_certctx_s cert; + + void *cbctx; + neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize); + + SSL_CTX *ctx; + BIO *bio; + SSL *ssl; + +// BIO *sink; + qbyte *pending; + size_t pendingsize; +} ossldtls_t; +static int OSSL_Bio_DWrite(BIO *h, const char *buf, int size) +{ + ossldtls_t *f = BIO_get_data(h); + neterr_t r = f->push(f->cbctx, buf, size); + + BIO_clear_retry_flags(h); + switch(r) + { + case NETERR_SENT: + return size; + case NETERR_NOROUTE: + case NETERR_DISCONNECTED: + return -1; + case NETERR_MTU: + return -1; + case NETERR_CLOGGED: + BIO_set_retry_write(h); + return -1; + } + return r; +} +static int OSSL_Bio_DRead(BIO *h, char *buf, int size) +{ + ossldtls_t *f = BIO_get_data(h); + + BIO_clear_retry_flags(h); + if (f->pending) + { + size = min(size, f->pendingsize); + memcpy(buf, f->pending, f->pendingsize); + + //we've read it now, don't read it again. + f->pending = 0; + f->pendingsize = 0; + return size; + } + //nothing available. + BIO_set_retry_read(h); + return -1; +} +static long OSSL_Bio_DCtrl(BIO *h, int cmd, long arg1, void *arg2) +{ +// ossldtls_t *f = BIO_get_data(h); + switch(cmd) + { + case BIO_CTRL_FLUSH: + return 1; + + + case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: //we're non-blocking, so this doesn't affect us. + case BIO_CTRL_DGRAM_GET_MTU_OVERHEAD: + case BIO_CTRL_WPENDING: + case BIO_CTRL_DGRAM_QUERY_MTU: + case BIO_CTRL_DGRAM_SET_MTU: + return 0; + + + default: + Con_Printf("OSSL_Bio_DCtrl: unknown cmd %i\n", cmd); + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + return 0; + } + return 0; //failure +} +static long OSSL_Bio_DOtherCtrl(BIO *h, int cmd, BIO_info_cb *cb) +{ + switch(cmd) + { + default: + Con_Printf("OSSL_Bio_DOtherCtrl unknown cmd %i\n", cmd); + return 0; + } + return 0; //failure +} +static int OSSL_Bio_DCreate(BIO *h) +{ //we'll have to fill this in after we create the bio. + BIO_set_data(h, NULL); + return 1; +} +static int OSSL_Bio_DDestroy(BIO *h) +{ + BIO_set_data(h, NULL); + return 1; +} + +static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) +{ //if remotehost is null then their certificate will not be validated. + ossldtls_t *n = calloc(sizeof(*n) + strlen(remotehost)+1, 1); + BIO *sink; + + n->cbctx = cbctx; + n->push = push; + + n->ctx = SSL_CTX_new(isserver?DTLS_server_method():DTLS_client_method()); + + n->cert.peername = strcpy((char*)(n+1), remotehost); + n->cert.dtls = true; + + if (n->ctx) + { + assert(1==SSL_CTX_set_cipher_list(n->ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH")); + + SSL_CTX_set_session_cache_mode(n->ctx, SSL_SESS_CACHE_OFF); + + SSL_CTX_set_verify(n->ctx, SSL_VERIFY_PEER, OSSL_Verify_Peer); + SSL_CTX_set_verify_depth(n->ctx, 5); + SSL_CTX_set_options(n->ctx, SSL_OP_NO_COMPRESSION); //compression allows guessing the contents of the stream somehow. + + //SSL_CTX_use_certificate_file + //FIXME: SSL_CTX_use_certificate_file aka SSL_CTX_use_certificate(PEM_read_bio_X509) + //FIXME: SSL_CTX_use_PrivateKey_file aka SSL_CTX_use_PrivateKey(PEM_read_bio_PrivateKey) + //assert(1==SSL_CTX_check_private_key(n->ctx)); + + { + n->bio = BIO_new_ssl(n->ctx, !isserver); + + //set up the source/sink + sink = BIO_new(biometh_dtls); + if (sink) + { + BIO_set_data(sink, n); + BIO_set_init(sink, true); //our sink is now ready... + n->bio = BIO_push(n->bio, sink); + BIO_free(sink); + sink = NULL; + } + + BIO_get_ssl(n->bio, &n->ssl); + SSL_set_ex_data(n->ssl, ossl_fte_certctx, &n->cert); + SSL_set_tlsext_host_name(n->ssl, remotehost); //let the server know which cert to send + BIO_do_connect(n->bio); + ERR_print_errors_cb(OSSL_PrintError_CB, NULL); + return n; + } + } + + return NULL; +} +static void OSSL_DestroyContext(void *ctx) +{ + ossldtls_t *o = (ossldtls_t*)ctx; + BIO_free(o->bio); + SSL_CTX_free(o->ctx); + free(o); +} +static neterr_t OSSL_Transmit(void *ctx, const qbyte *data, size_t datasize) +{ //we're sending data + ossldtls_t *o = (ossldtls_t*)ctx; + int r = BIO_write(o->bio, data, datasize); + if (r <= 0) + { + if (BIO_should_io_special(o->bio)) + { + switch(BIO_get_retry_reason(o->bio)) + { + //these are temporary errors, try again later. + case BIO_RR_SSL_X509_LOOKUP: + return NETERR_NOROUTE; //certificate failure. + case BIO_RR_ACCEPT: + case BIO_RR_CONNECT: + return NETERR_NOROUTE; //should never happen + } + } + if (BIO_should_retry(o->bio)) + return 0; + return NETERR_NOROUTE; //eof or something + } + return NETERR_SENT; +} +static neterr_t OSSL_Received(void *ctx, sizebuf_t *message) +{ //we have received some encrypted data... + ossldtls_t *o = (ossldtls_t*)ctx; + int r; + + o->pending = message->data; + o->pendingsize = message->cursize; + r = BIO_read(o->bio, message->data, message->maxsize); + o->pending = NULL; + o->pendingsize = 0; + + if (r > 0) + { + message->cursize = r; + return NETERR_SENT; + } + else + { + if (BIO_should_io_special(o->bio)) + { + switch(BIO_get_retry_reason(o->bio)) + { + //these are temporary errors, try again later. + case BIO_RR_SSL_X509_LOOKUP: + return NETERR_NOROUTE; //certificate failure. + case BIO_RR_ACCEPT: + case BIO_RR_CONNECT: + return NETERR_NOROUTE; //should never happen + } + } + if (BIO_should_retry(o->bio)) + return 0; + return NETERR_NOROUTE; //eof or something + } + return NETERR_NOROUTE; +} +static neterr_t OSSL_Timeouts(void *ctx) +{ //keep it ticking over, or something. + return OSSL_Received(ctx, NULL); +} + +static dtlsfuncs_t ossl_dtlsfuncs = +{ + OSSL_CreateContext, + OSSL_DestroyContext, + OSSL_Transmit, + OSSL_Received, + OSSL_Timeouts +}; +static const dtlsfuncs_t *OSSL_InitClient(void) +{ + if (OSSL_Init()) + return &ossl_dtlsfuncs; + return NULL; +} +static const dtlsfuncs_t *OSSL_InitServer(void) +{ + if (OSSL_Init()) + return &ossl_dtlsfuncs; + return NULL; +} + + + + + + + + + + + +static qboolean OSSL_Init(void) +{ + static qboolean inited; + static qboolean init_success; + if (inited) + return init_success; +#ifdef LOADERTHREAD + Sys_LockMutex(com_resourcemutex); + if (inited) //now check again, just in case + { + Sys_UnlockMutex(com_resourcemutex); + return init_success; + } +#endif + + SSL_library_init(); + SSL_load_error_strings(); + ERR_load_BIO_strings(); +// OPENSSL_config(NULL); + ERR_print_errors_cb(OSSL_PrintError_CB, NULL); + + OSSL_OpenPubKey(); + OSSL_OpenPrivKey(); + + biometh_vfs = BIO_meth_new(BIO_get_new_index()|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR, "fte_vfs"); + if (biometh_vfs) + { + BIO_meth_set_write(biometh_vfs, OSSL_Bio_FWrite); + BIO_meth_set_read(biometh_vfs, OSSL_Bio_FRead); + BIO_meth_set_puts(biometh_vfs, OSSL_Bio_FPuts); //I cannot see how gets/puts can work with dtls... +// BIO_meth_set_gets(biometh_vfs, OSSL_Bio_FGets); + BIO_meth_set_ctrl(biometh_vfs, OSSL_Bio_FCtrl); + BIO_meth_set_create(biometh_vfs, OSSL_Bio_FCreate); + BIO_meth_set_destroy(biometh_vfs, OSSL_Bio_FDestroy); + BIO_meth_set_callback_ctrl(biometh_vfs, OSSL_Bio_FOtherCtrl); + init_success |= 1; + } + + biometh_dtls = BIO_meth_new(BIO_get_new_index()|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR, "fte_dtls"); + if (biometh_dtls) + { + BIO_meth_set_write(biometh_dtls, OSSL_Bio_DWrite); + BIO_meth_set_read(biometh_dtls, OSSL_Bio_DRead); +// BIO_meth_set_puts(biometh_dtls, OSSL_Bio_DPuts); //I cannot see how gets/puts can work with dtls... +// BIO_meth_set_gets(biometh_dtls, OSSL_Bio_DGets); + BIO_meth_set_ctrl(biometh_dtls, OSSL_Bio_DCtrl); + BIO_meth_set_create(biometh_dtls, OSSL_Bio_DCreate); + BIO_meth_set_destroy(biometh_dtls, OSSL_Bio_DDestroy); + BIO_meth_set_callback_ctrl(biometh_dtls, OSSL_Bio_DOtherCtrl); + init_success |= 2; + } + + ossl_fte_certctx = SSL_get_ex_new_index(0, "ossl_fte_certctx", NULL, NULL, NULL); + + inited = true; +#ifdef LOADERTHREAD + Sys_UnlockMutex(com_resourcemutex); +#endif + return init_success; +} + +static enum hashvalidation_e OSSL_VerifyHash(const qbyte *hashdata, size_t hashsize, const qbyte *pubkeydata, size_t pubkeysize, const qbyte *signdata, size_t signsize) +{ + int result = VH_UNSUPPORTED; + BIO *bio_pubkey = BIO_new_mem_buf(pubkeydata, pubkeysize); + if (bio_pubkey) + { + X509 *x509_pubkey = PEM_read_bio_X509(bio_pubkey, NULL, NULL, NULL); + if (x509_pubkey) + { + EVP_PKEY *evp_pubkey = X509_get_pubkey(x509_pubkey); + if (evp_pubkey) + { + RSA *rsa_pubkey = EVP_PKEY_get1_RSA(evp_pubkey); + if (rsa_pubkey) + { + if (1 == RSA_verify(NID_sha512, hashdata, hashsize, signdata, signsize, rsa_pubkey)) + result = VH_CORRECT; + else + result = VH_INCORRECT; + RSA_free(rsa_pubkey); + } + EVP_PKEY_free(evp_pubkey); + } + X509_free(x509_pubkey); + } + BIO_free(bio_pubkey); + } + + return result; +} + +static ftecrypto_t crypto_openssl = +{ + "OpenSSL", + OSSL_OpenVFS, + NULL,//OSSL_GetChannelBinding, + OSSL_InitClient, + OSSL_InitServer, + OSSL_VerifyHash, + NULL, +}; + +void *TLS_GetKnownCertificate(const char *certname, size_t *size){return NULL;} +qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems){return false;} +qboolean Plug_Init(void) +{ + fsfuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs)); + if (!fsfuncs) + return false; + return plugfuncs->ExportInterface("Crypto", &crypto_openssl, sizeof(crypto_openssl)); //export a named interface struct to the engine +} \ No newline at end of file