2021-06-21 13:44:05 +00:00
# include "plugin.h"
# include "netinc.h"
2022-01-16 18:41:34 +00:00
static plugfsfuncs_t * fsfuncs ;
static plugnetfuncs_t * netfuncs ;
static cvar_t * pdtls_psk_hint , * pdtls_psk_user , * pdtls_psk_key ;
2021-06-21 13:44:05 +00:00
# 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
2022-01-08 10:01:05 +00:00
}
2021-06-21 13:44:05 +00:00
}
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 ;
2022-01-16 18:41:34 +00:00
qbyte * knowndata = netfuncs - > TLS_GetKnownCertificate ( uctx - > peername , & knownsize ) ;
2021-06-21 13:44:05 +00:00
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 ;
}
2022-01-16 18:41:34 +00:00
if ( netfuncs - > CertLog_ConnectOkay & & netfuncs - > CertLog_ConnectOkay ( uctx - > peername , blob , blobsize , probs ) )
2021-06-21 13:44:05 +00:00
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 ) ;
}
2022-01-16 18:41:34 +00:00
static char * OSSL_SetCertificateName ( char * out , const char * hostname )
{ //glorified strcpy...
int i ;
if ( hostname )
{
const char * host = strstr ( hostname , " :// " ) ;
if ( host )
hostname = host + 3 ;
//any dtls:// prefix will have been stripped now.
if ( * hostname = = ' [ ' )
{ //eg: [::1]:foo - skip the lead [ and strip the ] and any trailing data (hopefully just a :port or nothing)
hostname + + ;
host = strchr ( hostname , ' ] ' ) ;
if ( host )
{
memcpy ( out , hostname , host - hostname ) ;
out [ host - hostname ] = 0 ;
hostname = out ;
}
}
else
{ //eg: 127.0.0.1:port - strip the port number if specified.
host = strchr ( hostname , ' : ' ) ;
if ( host )
{
memcpy ( out , hostname , host - hostname ) ;
out [ host - hostname ] = 0 ;
hostname = out ;
}
}
for ( i = 0 ; hostname [ i ] ; i + + )
{
if ( hostname [ i ] > = ' a ' & & hostname [ i ] < = ' z ' )
;
else if ( hostname [ i ] > = ' A ' & & hostname [ i ] < = ' Z ' )
;
else if ( hostname [ i ] > = ' 0 ' & & hostname [ i ] < = ' 9 ' )
;
else if ( hostname [ i ] = = ' - ' | | hostname [ i ] = = ' . ' )
;
else
{
hostname = NULL ; //something invalid. bum.
break ;
}
}
//we should have a cleaned up host name now, ready for (ab)use in certificates.
}
if ( ! hostname )
* out = 0 ;
else if ( hostname = = out )
;
else
memcpy ( out , hostname , strlen ( hostname ) + 1 ) ;
return out ;
}
2021-06-21 13:44:05 +00:00
static vfsfile_t * OSSL_OpenVFS ( const char * hostname , vfsfile_t * source , qboolean isserver )
{
BIO * sink ;
osslvfs_t * n ;
if ( ! OSSL_Init ( ) )
return NULL ; //FAIL!
2022-01-16 18:41:34 +00:00
if ( ! hostname )
hostname = " " ;
2021-06-21 13:44:05 +00:00
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 ;
2022-01-16 18:41:34 +00:00
n - > cert . peername = OSSL_SetCertificateName ( ( char * ) ( n + 1 ) , hostname ) ;
2021-06-21 13:44:05 +00:00
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 ;
2022-01-16 18:41:34 +00:00
void * peeraddr ;
size_t peeraddrsize ;
2021-06-21 13:44:05 +00:00
} 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 ;
2022-01-16 18:41:34 +00:00
case BIO_CTRL_DGRAM_GET_PEER :
return 0 ;
2021-06-21 13:44:05 +00:00
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 :
2022-01-16 18:41:34 +00:00
case BIO_CTRL_DGRAM_GET_FALLBACK_MTU :
2021-06-21 13:44:05 +00:00
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 ;
}
2022-01-16 18:41:34 +00:00
static int dehex ( int i )
{
if ( i > = ' 0 ' & & i < = ' 9 ' )
return ( i - ' 0 ' ) ;
else if ( i > = ' A ' & & i < = ' F ' )
return ( i - ' A ' + 10 ) ;
else
return ( i - ' a ' + 10 ) ;
}
static size_t Base16_DecodeBlock_ ( const char * in , qbyte * out , size_t outsize )
{
qbyte * start = out ;
if ( ! out )
return ( ( strlen ( in ) + 1 ) / 2 ) + 1 ;
for ( ; ishexcode ( in [ 0 ] ) & & ishexcode ( in [ 1 ] ) & & outsize > 0 ; outsize - - , in + = 2 )
* out + + = ( dehex ( in [ 0 ] ) < < 4 ) | dehex ( in [ 1 ] ) ;
return out - start ;
}
static unsigned int OSSL_SV_Validate_PSK ( SSL * ssl , const char * identity , unsigned char * psk , unsigned int max_psk_len )
{
if ( ! strcmp ( identity , pdtls_psk_user - > string ) )
{ //Yay! We know this one!
return Base16_DecodeBlock_ ( pdtls_psk_key - > string , psk , max_psk_len ) ;
}
return 0 ; //0 for error, or something.
}
unsigned int OSSL_CL_Validate_PSK ( SSL * ssl , const char * hint , char * identity , unsigned int max_identity_len , unsigned char * psk , unsigned int max_psk_len )
{ //if our hint cvar matches, then report our user+key cvars to the server
if ( ( ! * hint & & * pdtls_psk_user - > string & & ! * pdtls_psk_hint - > string ) | | ( * hint & & ! strcmp ( hint , pdtls_psk_hint - > string ) ) )
{
2022-01-16 18:41:59 +00:00
# ifndef NOLEGACY
if ( * hint )
{
//Try to avoid crashing QE servers by recognising its hint and blocking it when the hashes of the user+key are wrong.
quint32_t digest [ SHA_DIGEST_LENGTH / 4 ] ;
SHA1 ( hint , strlen ( hint ) , ( qbyte * ) digest ) ;
if ( ( digest [ 0 ] ^ digest [ 1 ] ^ digest [ 2 ] ^ digest [ 3 ] ^ digest [ 4 ] ) = = 0xb6c27b61 )
{
SHA1 ( pdtls_psk_key - > string , strlen ( pdtls_psk_key - > string ) , ( qbyte * ) digest ) ;
if ( strcmp ( hint , pdtls_psk_user - > string ) | | ( digest [ 0 ] ^ digest [ 1 ] ^ digest [ 2 ] ^ digest [ 3 ] ^ digest [ 4 ] ) ! = 0x3dd348e4 )
{
Con_Printf ( CON_WARNING " Possible QEx Server, please set your ^[%s \\ type \\ %s^] and ^[%s \\ type \\ %s^] cvars correctly, their current values are likely to crash the server. \n " , pdtls_psk_user - > name , pdtls_psk_user - > name , pdtls_psk_key - > name , pdtls_psk_key - > name ) ;
return 0 ; //don't report anything.
}
}
}
# endif
2022-01-16 18:41:34 +00:00
Q_strlcpy ( identity , pdtls_psk_user - > string , max_identity_len ) ;
return Base16_DecodeBlock_ ( pdtls_psk_key - > string , psk , max_psk_len ) ;
}
2022-01-16 18:41:59 +00:00
else if ( * hint )
Con_Printf ( CON_WARNING " Unable to supply PSK response to server (hint is \" %s \" ). \n "
" Please set ^[%s \\ type \\ %s^], ^[%s \\ type \\ %s^], and ^[%s \\ type \\ %s^] cvars to match the server. \n " , hint , pdtls_psk_hint - > name , pdtls_psk_hint - > name , pdtls_psk_user - > name , pdtls_psk_user - > name , pdtls_psk_key - > name , pdtls_psk_key - > name ) ;
2022-01-16 18:41:34 +00:00
return 0 ; //we don't know what to report.
}
2021-06-21 13:44:05 +00:00
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.
2022-01-16 18:41:34 +00:00
ossldtls_t * n ;
2021-06-21 13:44:05 +00:00
BIO * sink ;
2022-01-16 18:41:34 +00:00
if ( ! remotehost )
remotehost = " " ;
n = calloc ( sizeof ( * n ) + strlen ( remotehost ) + 1 , 1 ) ;
2021-06-21 13:44:05 +00:00
n - > cbctx = cbctx ;
n - > push = push ;
n - > ctx = SSL_CTX_new ( isserver ? DTLS_server_method ( ) : DTLS_client_method ( ) ) ;
2022-01-16 18:41:34 +00:00
n - > cert . peername = OSSL_SetCertificateName ( ( char * ) ( n + 1 ) , remotehost ) ;
2021-06-21 13:44:05 +00:00
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.
2022-01-16 18:41:34 +00:00
if ( isserver )
{
if ( * pdtls_psk_user - > string )
{
if ( * pdtls_psk_user - > string )
SSL_CTX_use_psk_identity_hint ( n - > ctx , pdtls_psk_hint - > string ) ;
SSL_CTX_set_psk_server_callback ( n - > ctx , OSSL_SV_Validate_PSK ) ;
}
if ( vhost . servercert & & vhost . privatekey )
{
SSL_CTX_use_certificate ( n - > ctx , vhost . servercert ) ;
SSL_CTX_use_PrivateKey ( n - > ctx , vhost . privatekey ) ;
assert ( 1 = = SSL_CTX_check_private_key ( n - > ctx ) ) ;
}
}
else
{
2022-01-16 18:41:59 +00:00
// if (*pdtls_psk_user->string)
2022-01-16 18:41:34 +00:00
SSL_CTX_set_psk_client_callback ( n - > ctx , OSSL_CL_Validate_PSK ) ;
}
2021-06-21 13:44:05 +00:00
//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 ) ;
2022-01-16 18:41:34 +00:00
SSL_set_app_data ( n - > ssl , n ) ;
2021-06-21 13:44:05 +00:00
SSL_set_ex_data ( n - > ssl , ossl_fte_certctx , & n - > cert ) ;
2022-01-16 18:41:34 +00:00
if ( * n - > cert . peername )
SSL_set_tlsext_host_name ( n - > ssl , n - > cert . peername ) ; //let the server know which cert to send
2021-06-21 13:44:05 +00:00
BIO_do_connect ( n - > bio ) ;
ERR_print_errors_cb ( OSSL_PrintError_CB , NULL ) ;
return n ;
}
}
return NULL ;
}
2022-01-16 18:41:34 +00:00
2022-01-16 18:41:53 +00:00
static qbyte dtlscookiekey [ 16 ] ;
2022-01-16 18:41:34 +00:00
static int OSSL_GenCookie ( SSL * ssl , unsigned char * cookie , unsigned int * cookie_len )
{
ossldtls_t * f = SSL_get_app_data ( ssl ) ;
2022-01-16 18:41:53 +00:00
qbyte * blurgh = alloca ( sizeof ( dtlscookiekey ) + f - > peeraddrsize ) ;
2022-01-16 18:41:34 +00:00
2022-01-16 18:41:53 +00:00
memcpy ( blurgh , dtlscookiekey , sizeof ( dtlscookiekey ) ) ;
memcpy ( blurgh + sizeof ( dtlscookiekey ) , f - > peeraddr , f - > peeraddrsize ) ;
2022-01-16 18:41:34 +00:00
2022-01-16 18:41:53 +00:00
SHA1 ( blurgh , sizeof ( dtlscookiekey ) + f - > peeraddrsize , cookie ) ;
2022-01-16 18:41:34 +00:00
* cookie_len = SHA_DIGEST_LENGTH ;
return 1 ;
}
static int OSSL_VerifyCookie ( SSL * ssl , const unsigned char * cookie , unsigned int cookie_len )
{
unsigned char match [ DTLS1_COOKIE_LENGTH ] ;
unsigned int matchsize ;
if ( OSSL_GenCookie ( ssl , match , & matchsize ) )
if ( cookie_len = = matchsize & & ! memcmp ( cookie , match , matchsize ) )
return 1 ;
return 0 ; //not valid.
}
qboolean OSSL_CheckConnection ( void * cbctx , void * peeraddr , size_t peeraddrsize , void * indata , size_t insize , neterr_t ( * push ) ( void * cbctx , const qbyte * data , size_t datasize ) , void ( * EstablishTrueContext ) ( void * * cbctx , void * state ) )
{
int ret ;
static ossldtls_t * pending ;
BIO_ADDR * bioaddr = BIO_ADDR_new ( ) ;
if ( ! pending )
{
pending = OSSL_CreateContext ( " localhost " , cbctx , push , true ) ;
SSL_CTX_set_cookie_generate_cb ( pending - > ctx , OSSL_GenCookie ) ;
SSL_CTX_set_cookie_verify_cb ( pending - > ctx , OSSL_VerifyCookie ) ;
}
SSL_set_app_data ( pending - > ssl , pending ) ;
//make sure its kept current...
pending - > cbctx = cbctx ;
pending - > push = push ;
pending - > pending = indata ;
pending - > pendingsize = insize ;
ret = DTLSv1_listen ( pending - > ssl , bioaddr ) ;
BIO_ADDR_free ( bioaddr ) ;
if ( ret > = 1 )
{
pending - > pending = NULL ;
pending - > pendingsize = 0 ;
EstablishTrueContext ( & pending - > cbctx , pending ) ;
pending = NULL ; //returned to called. next request gets a new one.
return true ;
}
//0 = nonfatal
//-1 = fatal
return false ;
}
2021-06-21 13:44:05 +00:00
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 ;
2022-01-16 18:41:59 +00:00
return NETERR_DISCONNECTED ; //eof or something
2021-06-21 13:44:05 +00:00
}
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 ;
2022-01-08 10:01:05 +00:00
if ( ! message )
r = 0 ;
else
{
o - > pending = message - > data ;
o - > pendingsize = message - > cursize ;
r = BIO_read ( o - > bio , message - > data , message - > maxsize ) ;
o - > pending = NULL ;
o - > pendingsize = 0 ;
}
2021-06-21 13:44:05 +00:00
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 ;
2022-01-16 18:41:59 +00:00
return NETERR_DISCONNECTED ; //eof or something
2021-06-21 13:44:05 +00:00
}
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 ,
2022-01-16 18:41:34 +00:00
OSSL_CheckConnection ,
2021-06-21 13:44:05 +00:00
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 ;
2022-01-16 18:41:53 +00:00
inited = true ;
#if 0 //def LOADERTHREAD
2021-06-21 13:44:05 +00:00
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 ( ) ;
// 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 ) ;
2022-01-16 18:41:53 +00:00
#if 0 //def LOADERTHREAD
2021-06-21 13:44:05 +00:00
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 ,
} ;
qboolean Plug_Init ( void )
{
fsfuncs = plugfuncs - > GetEngineInterface ( plugfsfuncs_name , sizeof ( * fsfuncs ) ) ;
2022-01-16 18:41:34 +00:00
netfuncs = plugfuncs - > GetEngineInterface ( plugnetfuncs_name , sizeof ( * netfuncs ) ) ;
if ( ! fsfuncs | | ! netfuncs )
2021-06-21 13:44:05 +00:00
return false ;
2022-01-16 18:41:34 +00:00
pdtls_psk_hint = cvarfuncs - > GetNVFDG ( " dtls_psk_hint " , " " , 0 , NULL , " DTLS stuff " ) ;
pdtls_psk_user = cvarfuncs - > GetNVFDG ( " dtls_psk_user " , " " , 0 , NULL , " DTLS stuff " ) ;
pdtls_psk_key = cvarfuncs - > GetNVFDG ( " dtls_psk_key " , " " , 0 , NULL , " DTLS stuff " ) ;
netfuncs - > RandomBytes ( dtlscookiekey , sizeof ( dtlscookiekey ) ) ; //something random so people can't guess cookies for arbitrary victim IPs.
2022-01-16 18:41:53 +00:00
OSSL_Init ( ) ; //shoving this here solves threading issues (eg two loader threads racing to open an https image url)
2021-06-21 13:44:05 +00:00
return plugfuncs - > ExportInterface ( " Crypto " , & crypto_openssl , sizeof ( crypto_openssl ) ) ; //export a named interface struct to the engine
}