From c8ea3d387510e99e20f1bd4523152ab9d8f6bc22 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 30 Oct 2014 09:21:11 +0100 Subject: [PATCH 1/4] Update Csocket to 1d7e685 > Extend EDisableProtocol > missing ifdef for non-ssl > added ability to disable compression > added new callbacks for Certificate Verification and Handshake completion > added SNI support for client and server, cleanup some of the certificate verification callback code --- include/znc/Csocket.h | 70 ++++- src/Csocket.cpp | 584 ++++++++++++++++++++++++------------------ 2 files changed, 402 insertions(+), 252 deletions(-) diff --git a/include/znc/Csocket.h b/include/znc/Csocket.h index d87d8645..670d6150 100644 --- a/include/znc/Csocket.h +++ b/include/znc/Csocket.h @@ -285,8 +285,12 @@ private: //! backwards compatible wrapper around CGetAddrInfo and gethostbyname int GetAddrInfo( const CS_STRING & sHostname, Csock * pSock, CSSockAddr & csSockAddr ); -//! used to retrieve the context position of the socket to its associated ssl connection. Setup once in InitSSL() via SSL_get_ex_new_index -int GetCsockClassIdx(); +/** + * This returns the [ex_]data index position for SSL objects only. If you want to tie more data + * to the SSL object, you should generate your own at application start so as to avoid collision + * with Csocket SSL_set_ex_data() + */ +int GetCsockSSLIdx(); #ifdef HAVE_LIBSSL //! returns the sock object associated to the particular context. returns NULL on failure or if not available @@ -606,9 +610,11 @@ public: enum EDisableProtocol { EDP_None = 0, //!< disable nothing - EDP_SSLv2 = 1, //!< disable SSL verion 2 - EDP_SSLv3 = 2, //!< disable SSL verion 3 - EDP_TLSv1 = 4, //!< disable TLS verion 1 + EDP_SSLv2 = 1, //!< disable SSL version 2 + EDP_SSLv3 = 2, //!< disable SSL version 3 + EDP_TLSv1 = 4, //!< disable TLS version 1 + EDP_TLSv1_1 = 8, //!< disable TLS version 1.1 + EDP_TLSv1_2 = 16, //!< disable TLS version 1.2 EDP_SSL = (EDP_SSLv2|EDP_SSLv3) }; @@ -851,6 +857,8 @@ public: #ifdef HAVE_LIBSSL //! bitwise setter, @see EDisableProtocol void DisableSSLProtocols( u_int uDisableOpts ) { m_uDisableProtocols = uDisableOpts; } + //! allow disabling compression + void DisableSSLCompression() { m_bNoSSLCompression = true; } //! Set the cipher type ( openssl cipher [to see ciphers available] ) void SetCipher( const CS_STRING & sCipher ); const CS_STRING & GetCipher() const; @@ -865,10 +873,11 @@ public: void SetSSLMethod( int iMethod ); int GetSSLMethod() const; - void SetSSLObject( SSL *ssl ); - void SetCTXObject( SSL_CTX *sslCtx ); + void SetSSLObject( SSL *ssl, bool bDeleteExisting = false ); + void SetCTXObject( SSL_CTX *sslCtx, bool bDeleteExisting = false ); SSL_SESSION * GetSSLSession() const; + //! setting this to NULL will allow the default openssl verification process kick in void SetCertVerifyCB( FPCertVerifyCB pFP ) { m_pCerVerifyCB = pFP; } #endif /* HAVE_LIBSSL */ @@ -888,6 +897,7 @@ public: //! Get the peer's X509 cert #ifdef HAVE_LIBSSL + //! it is up to you, the caller to call X509_free() on this object X509 *GetX509() const; //! Returns the peer's public key @@ -983,6 +993,49 @@ public: * to this ssl session via SSL_set_ex_data */ virtual void SSLFinishSetup( SSL * pSSL ) {} + /** + * @brief gets called when a SNI request is sent, and used to configure a SNI session + * @param sHostname the hostname sent from the client + * @param sPemFile fill this with the location to the pemfile + * @param sPemPass fill this with the pemfile password if there is one + * @return return true to proceed with the SNI server configuration + */ + virtual bool SNIConfigureServer( const CS_STRING & sHostname, CS_STRING & sPemFile, CS_STRING & sPemPass ) { return( false ); } + /** + * @brief called to configure the SNI client + * @param sHostname, the hostname to configure SNI with, you can fill this with GetHostname() if its a valid hostname and not an OP + * @return returning true causes a call to configure SNI with the hostname returned + */ + virtual bool SNIConfigureClient( CS_STRING & sHostname ) { return( false ); } + //! creates a new SSL_CTX based on the setup of this sock + SSL_CTX * SetupServerCTX(); + + /** + * @brief called once the SSL handshake is complete, this is triggered via SSL_CB_HANDSHAKE_DONE in SSL_set_info_callback() + * + * This is a spot where you can look at the finished peer certifificate ... IE + *
+	 * X509 * pCert = GetX509();
+	 * char szName[256];
+	 * memset( szName, '\0', 256 );
+	 * X509_NAME_get_text_by_NID ( X509_get_subject_name( pCert ), NID_commonName, szName, 255 );
+	 * cerr << "Name! " << szName << endl;
+	 * X509_free( pCert );
+	 * 
+ */ + virtual void SSLHandShakeFinished() {} + /** + * @brief this is hooked in via SSL_set_verify, and be default it just returns 1 meaning success + * @param iPreVerify the pre-verification status as determined by openssl internally + * @param pStoreCTX the X509_STORE_CTX containing the certificate + * @return 1 to continue, 0 to abort + * + * This may get called multiple times, for example with a chain certificate which is fairly typical with + * certificates from godaddy, freessl, etc. Additionally, openssl does not do any host verification, they + * leave that up to the you. One easy way to deal with this is to wait for SSLHandShakeFinished() and examine + * the peer certificate @see SSLHandShakeFinished + */ + virtual int VerifyPeerCertificate( int iPreVerify, X509_STORE_CTX * pStoreCTX ) { return( 1 ); } #endif /* HAVE_LIBSSL */ @@ -1090,7 +1143,6 @@ private: void ShrinkSendBuff(); void IncBuffPos( size_t uBytes ); //! checks for configured protocol disabling - void CheckDisabledProtocols(); // NOTE! if you add any new members, be sure to add them to Copy() uint16_t m_uPort; @@ -1120,11 +1172,13 @@ private: SSL_CTX * m_ssl_ctx; uint32_t m_iRequireClientCertFlags; u_int m_uDisableProtocols; + bool m_bNoSSLCompression; FPCertVerifyCB m_pCerVerifyCB; void FREE_SSL(); void FREE_CTX(); + void ConfigureCTXOptions( SSL_CTX * pCTX ); #endif /* HAVE_LIBSSL */ diff --git a/src/Csocket.cpp b/src/Csocket.cpp index 6443ca87..c483ebf9 100644 --- a/src/Csocket.cpp +++ b/src/Csocket.cpp @@ -70,10 +70,10 @@ namespace Csocket { #endif /* _NO_CSOCKET_NS */ -static int g_iCsockSSLIdx = 0; //!< this get setup once in InitSSL -int GetCsockClassIdx() +static int s_iCsockSSLIdx = 0; //!< this gets setup once in InitSSL +int GetCsockSSLIdx() { - return( g_iCsockSSLIdx ); + return( s_iCsockSSLIdx ); } #ifdef _WIN32 @@ -203,23 +203,30 @@ static int _PemPassCB( char *pBuff, int iBuffLen, int rwflag, void * pcSocket ) static int _CertVerifyCB( int preverify_ok, X509_STORE_CTX *x509_ctx ) { - /* - * A small quick example on how to get ahold of the Csock in the data portion of x509_ctx Csock * pSock = GetCsockFromCTX( x509_ctx ); - assert( pSock ); - cerr << pSock->GetRemoteIP() << endl; - */ + if( pSock ) + return( pSock->VerifyPeerCertificate( preverify_ok, x509_ctx ) ); - /* return 1 always for now, probably want to add some code for cert verification */ - return( 1 ); + return( preverify_ok ); } +static void _InfoCallback( const SSL * pSSL, int where, int ret ) +{ + if( ( where & SSL_CB_HANDSHAKE_DONE ) && ret != 0 ) + { + Csock * pSock = static_cast( SSL_get_ex_data( pSSL, GetCsockSSLIdx() ) ); + if( pSock ) + pSock->SSLHandShakeFinished(); + } +} + + Csock * GetCsockFromCTX( X509_STORE_CTX * pCTX ) { Csock * pSock = NULL; SSL * pSSL = ( SSL * ) X509_STORE_CTX_get_ex_data( pCTX, SSL_get_ex_data_X509_STORE_CTX_idx() ); if( pSSL ) - pSock = ( Csock * ) SSL_get_ex_data( pSSL, GetCsockClassIdx() ); + pSock = ( Csock * ) SSL_get_ex_data( pSSL, GetCsockSSLIdx() ); return( pSock ); } #endif /* HAVE_LIBSSL */ @@ -581,7 +588,7 @@ bool InitSSL( ECompType eCompressionType ) } // setting this up once in the begining - g_iCsockSSLIdx = SSL_get_ex_new_index( 0, ( void * )"CsockGlobalIndex", NULL, NULL, NULL ); + s_iCsockSSLIdx = SSL_get_ex_new_index( 0, NULL, NULL, NULL, NULL ); return( true ); } @@ -895,7 +902,7 @@ void CSockCommon::DelCronByAddr( CCron * pcCron ) Csock::Csock( int iTimeout ) : CSockCommon() { #ifdef HAVE_LIBSSL - m_pCerVerifyCB = NULL; + m_pCerVerifyCB = _CertVerifyCB; #endif /* HAVE_LIBSSL */ Init( "", 0, iTimeout ); } @@ -903,7 +910,7 @@ Csock::Csock( int iTimeout ) : CSockCommon() Csock::Csock( const CS_STRING & sHostname, uint16_t iport, int iTimeout ) : CSockCommon() { #ifdef HAVE_LIBSSL - m_pCerVerifyCB = NULL; + m_pCerVerifyCB = _CertVerifyCB; #endif /* HAVE_LIBSSL */ Init( sHostname, iport, iTimeout ); } @@ -1032,6 +1039,8 @@ void Csock::Copy( const Csock & cCopy ) #endif /* HAVE_C_ARES */ #ifdef HAVE_LIBSSL + m_bNoSSLCompression = cCopy.m_bNoSSLCompression; + m_uDisableProtocols = cCopy.m_uDisableProtocols; m_iRequireClientCertFlags = cCopy.m_iRequireClientCertFlags; m_sSSLBuffer = cCopy.m_sSSLBuffer; @@ -1315,6 +1324,32 @@ cs_sock_t Csock::Accept( CS_STRING & sHost, uint16_t & iRPort ) return( iSock ); } +#ifdef HAVE_LIBSSL +static int __SNICallBack( SSL *pSSL, int *piAD, void *pData ) +{ + if( !pSSL || !pData ) + return( SSL_TLSEXT_ERR_NOACK ); + + const char * pServerName = SSL_get_servername( pSSL, TLSEXT_NAMETYPE_host_name ); + if( !pServerName ) + return( SSL_TLSEXT_ERR_NOACK ); + + Csock * pSock = static_cast( pData ); + + CS_STRING sPemFile, sPemPass; + if( !pSock->SNIConfigureServer( pServerName, sPemFile, sPemPass ) ) + return( SSL_TLSEXT_ERR_NOACK ); + + pSock->SetPemLocation( sPemFile ); + pSock->SetPemPass( sPemPass ); + SSL_CTX * pCTX = pSock->SetupServerCTX(); + SSL_set_SSL_CTX( pSSL, pCTX ); + pSock->SetCTXObject( pCTX, true ); + return( SSL_TLSEXT_ERR_OK ); +} + +#endif /* HAVE_LIBSSL */ + bool Csock::AcceptSSL() { #ifdef HAVE_LIBSSL @@ -1322,6 +1357,11 @@ bool Csock::AcceptSSL() if( !SSLServerSetup() ) return( false ); +#if defined( SSL_CTX_set_tlsext_servername_callback ) + SSL_CTX_set_tlsext_servername_callback( m_ssl_ctx, __SNICallBack ); + SSL_CTX_set_tlsext_servername_arg( m_ssl_ctx, this ); +#endif /* SSL_CTX_set_tlsext_servername_callback */ + int err = SSL_accept( m_ssl ); if( err == 1 ) @@ -1341,23 +1381,42 @@ bool Csock::AcceptSSL() return( false ); } -void Csock::CheckDisabledProtocols() -{ #ifdef HAVE_LIBSSL - if( m_ssl_ctx && m_uDisableProtocols > 0 ) +void Csock::ConfigureCTXOptions( SSL_CTX * pCTX ) +{ + if( pCTX ) { long uCTXOptions = 0; - if( EDP_SSLv2 & m_uDisableProtocols ) - uCTXOptions |= SSL_OP_NO_SSLv2; - if( EDP_SSLv3 & m_uDisableProtocols ) - uCTXOptions |= SSL_OP_NO_SSLv3; - if( EDP_TLSv1 & m_uDisableProtocols ) - uCTXOptions |= SSL_OP_NO_TLSv1; + if( m_uDisableProtocols > 0 ) + { +#ifdef SSL_OP_NO_SSLv2 + if( EDP_SSLv2 & m_uDisableProtocols ) + uCTXOptions |= SSL_OP_NO_SSLv2; +#endif /* SSL_OP_NO_SSLv2 */ +#ifdef SSL_OP_NO_SSLv3 + if( EDP_SSLv3 & m_uDisableProtocols ) + uCTXOptions |= SSL_OP_NO_SSLv3; +#endif /* SSL_OP_NO_SSLv3 */ +#ifdef SSL_OP_NO_TLSv1 + if( EDP_TLSv1 & m_uDisableProtocols ) + uCTXOptions |= SSL_OP_NO_TLSv1; +#endif /* SSL_OP_NO_TLSv1 */ +#ifdef SSL_OP_NO_TLSv1_1 + if( EDP_TLSv1_1 & m_uDisableProtocols ) + uCTXOptions |= SSL_OP_NO_TLSv1_1; +#endif /* SSL_OP_NO_TLSv1 */ +#ifdef SSL_OP_NO_TLSv1_2 + if( EDP_TLSv1_2 & m_uDisableProtocols ) + uCTXOptions |= SSL_OP_NO_TLSv1_2; +#endif /* SSL_OP_NO_TLSv1_2 */ + } + if( m_bNoSSLCompression ) + uCTXOptions |= SSL_OP_NO_COMPRESSION; if( uCTXOptions ) - SSL_CTX_set_options( m_ssl_ctx, uCTXOptions ); + SSL_CTX_set_options( pCTX, uCTXOptions ); } -#endif /* HAVE_LIBSSL */ } +#endif /* HAVE_LIBSSL */ bool Csock::SSLClientSetup() { @@ -1462,7 +1521,7 @@ bool Csock::SSLClientSetup() } } - CheckDisabledProtocols(); + ConfigureCTXOptions( m_ssl_ctx ); m_ssl = SSL_new( m_ssl_ctx ); if( !m_ssl ) @@ -1470,8 +1529,15 @@ bool Csock::SSLClientSetup() SSL_set_rfd( m_ssl, ( int )m_iReadSock ); SSL_set_wfd( m_ssl, ( int )m_iWriteSock ); - SSL_set_verify( m_ssl, SSL_VERIFY_PEER, ( m_pCerVerifyCB ? m_pCerVerifyCB : _CertVerifyCB ) ); - SSL_set_ex_data( m_ssl, GetCsockClassIdx(), this ); + SSL_set_verify( m_ssl, SSL_VERIFY_PEER, m_pCerVerifyCB ); + SSL_set_info_callback( m_ssl, _InfoCallback ); + SSL_set_ex_data( m_ssl, GetCsockSSLIdx(), this ); + +#if defined( SSL_set_tlsext_host_name ) + CS_STRING sSNIHostname; + if( SNIConfigureClient( sSNIHostname ) ) + SSL_set_tlsext_host_name( m_ssl, sSNIHostname.c_str() ); +#endif /* SSL_set_tlsext_host_name */ SSLFinishSetup( m_ssl ); return( true ); @@ -1481,6 +1547,171 @@ bool Csock::SSLClientSetup() #endif /* HAVE_LIBSSL */ } +#ifdef HAVE_LIBSSL +SSL_CTX * Csock::SetupServerCTX() +{ + SSL_CTX * pCTX = NULL; + switch( m_iMethod ) + { + case SSL3: + pCTX = SSL_CTX_new( SSLv3_server_method() ); + if( !pCTX ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv3_server_method failed!" ); + return( NULL ); + } + break; + case TLS12: +#ifdef TLS1_2_VERSION + pCTX = SSL_CTX_new( TLSv1_2_server_method() ); + if( !pCTX ) + { + CS_DEBUG( "WARNING: MakeConnection .... TLSv1_2_server_method failed!" ); + return( NULL ); + } + break; +#endif /* TLS1_2_VERSION */ + case TLS11: +#ifdef TLS1_1_VERSION + pCTX = SSL_CTX_new( TLSv1_1_server_method() ); + if( !pCTX ) + { + CS_DEBUG( "WARNING: MakeConnection .... TLSv1_1_server_method failed!" ); + return( NULL ); + } + break; + case TLS1: +#endif /* TLS1_1_VERSION */ + pCTX = SSL_CTX_new( TLSv1_server_method() ); + if( !pCTX ) + { + CS_DEBUG( "WARNING: MakeConnection .... TLSv1_server_method failed!" ); + return( NULL ); + } + break; + case SSL2: +#ifndef OPENSSL_NO_SSL2 + pCTX = SSL_CTX_new( SSLv2_server_method() ); + if( !pCTX ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv2_server_method failed!" ); + return( NULL ); + } + break; +#endif /* OPENSSL_NO_SSL2 */ + /* Fall through if SSL2 is disabled */ + case SSL23: + default: + if( m_iMethod != SSL23 ) + { + CS_DEBUG( "WARNING: SSL Server Method other than SSLv23 specified, but has passed through" ); + } + pCTX = SSL_CTX_new( SSLv23_server_method() ); + if( !pCTX ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv23_server_method failed!" ); + return( NULL ); + } + break; + } + if( !pCTX ) + { + CS_DEBUG( "ERROR: NULL Ptr where there shouldn't be" ); + return( NULL ); + } + + SSL_CTX_set_default_verify_paths( pCTX ); + + // set the pemfile password + SSL_CTX_set_default_passwd_cb( pCTX, _PemPassCB ); + SSL_CTX_set_default_passwd_cb_userdata( pCTX, ( void * )this ); + + if( m_sPemFile.empty() || access( m_sPemFile.c_str(), R_OK ) != 0 ) + { + CS_DEBUG( "Empty, missing, or bad pemfile ... [" << m_sPemFile << "]" ); + SSL_CTX_free( pCTX ); + return( NULL ); + } + + // + // set up the CTX + if( SSL_CTX_use_certificate_chain_file( pCTX, m_sPemFile.c_str() ) <= 0 ) + { + CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" ); + SSLErrors( __FILE__, __LINE__ ); + SSL_CTX_free( pCTX ); + return( NULL ); + } + + if( SSL_CTX_use_PrivateKey_file( pCTX, m_sPemFile.c_str(), SSL_FILETYPE_PEM ) <= 0 ) + { + CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" ); + SSLErrors( __FILE__, __LINE__ ); + SSL_CTX_free( pCTX ); + return( NULL ); + } + + // check to see if this pem file contains a DH structure for use with DH key exchange + // https://github.com/znc/znc/pull/46 + FILE *dhParamsFile = fopen( m_sPemFile.c_str(), "r" ); + if( !dhParamsFile ) + { + CS_DEBUG( "There is a problem with [" << m_sPemFile << "]" ); + SSL_CTX_free( pCTX ); + return( NULL ); + } + + DH * dhParams = PEM_read_DHparams( dhParamsFile, NULL, NULL, NULL ); + fclose( dhParamsFile ); + if( dhParams ) + { + SSL_CTX_set_options( pCTX, SSL_OP_SINGLE_DH_USE ); + if( !SSL_CTX_set_tmp_dh( pCTX, dhParams ) ) + { + CS_DEBUG( "Error setting ephemeral DH parameters from [" << m_sPemFile << "]" ); + SSLErrors( __FILE__, __LINE__ ); + DH_free( dhParams ); + SSL_CTX_free( pCTX ); + return( NULL ); + } + DH_free( dhParams ); + } + else + { + // Presumably PEM_read_DHparams failed, as there was no DH structure. Clearing those errors here so they are removed off the stack + ERR_clear_error(); + } + + // Errors for the following block are non-fatal (ECDHE is nice to have + // but not a requirement) +#if defined( SSL_CTX_set_ecdh_auto ) + // Auto-select sensible curve + if( !SSL_CTX_set_ecdh_auto( pCTX , 1 ) ) + ERR_clear_error(); +#elif defined( SSL_CTX_set_tmp_ecdh ) + // Use a standard, widely-supported curve + EC_KEY * ecdh = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + if( ecdh ) + { + if( !SSL_CTX_set_tmp_ecdh( pCTX, ecdh ) ) + ERR_clear_error(); + EC_KEY_free( ecdh ); + } + else + ERR_clear_error(); +#endif + + if( SSL_CTX_set_cipher_list( pCTX, m_sCipherType.c_str() ) <= 0 ) + { + CS_DEBUG( "Could not assign cipher [" << m_sCipherType << "]" ); + SSL_CTX_free( pCTX ); + return( NULL ); + } + ConfigureCTXOptions( pCTX ); + return( pCTX ); +} +#endif /* HAVE_LIBSSL */ + bool Csock::SSLServerSetup() { #ifdef HAVE_LIBSSL @@ -1497,154 +1728,7 @@ bool Csock::SSLServerSetup() } #endif /* _WIN64 */ - - switch( m_iMethod ) - { - case SSL3: - m_ssl_ctx = SSL_CTX_new( SSLv3_server_method() ); - if( !m_ssl_ctx ) - { - CS_DEBUG( "WARNING: MakeConnection .... SSLv3_server_method failed!" ); - return( false ); - } - break; - case TLS12: -#ifdef TLS1_2_VERSION - m_ssl_ctx = SSL_CTX_new( TLSv1_2_server_method() ); - if( !m_ssl_ctx ) - { - CS_DEBUG( "WARNING: MakeConnection .... TLSv1_2_server_method failed!" ); - return( false ); - } - break; -#endif /* TLS1_2_VERSION */ - case TLS11: -#ifdef TLS1_1_VERSION - m_ssl_ctx = SSL_CTX_new( TLSv1_1_server_method() ); - if( !m_ssl_ctx ) - { - CS_DEBUG( "WARNING: MakeConnection .... TLSv1_1_server_method failed!" ); - return( false ); - } - break; - case TLS1: -#endif /* TLS1_1_VERSION */ - m_ssl_ctx = SSL_CTX_new( TLSv1_server_method() ); - if( !m_ssl_ctx ) - { - CS_DEBUG( "WARNING: MakeConnection .... TLSv1_server_method failed!" ); - return( false ); - } - break; - case SSL2: -#ifndef OPENSSL_NO_SSL2 - m_ssl_ctx = SSL_CTX_new( SSLv2_server_method() ); - if( !m_ssl_ctx ) - { - CS_DEBUG( "WARNING: MakeConnection .... SSLv2_server_method failed!" ); - return( false ); - } - break; -#endif /* OPENSSL_NO_SSL2 */ - /* Fall through if SSL2 is disabled */ - case SSL23: - default: - if( m_iMethod != SSL23 ) - { - CS_DEBUG( "WARNING: SSL Server Method other than SSLv23 specified, but has passed through" ); - } - m_ssl_ctx = SSL_CTX_new( SSLv23_server_method() ); - if( !m_ssl_ctx ) - { - CS_DEBUG( "WARNING: MakeConnection .... SSLv23_server_method failed!" ); - return( false ); - } - break; - } - - SSL_CTX_set_default_verify_paths( m_ssl_ctx ); - - // set the pemfile password - SSL_CTX_set_default_passwd_cb( m_ssl_ctx, _PemPassCB ); - SSL_CTX_set_default_passwd_cb_userdata( m_ssl_ctx, ( void * )this ); - - if( m_sPemFile.empty() || access( m_sPemFile.c_str(), R_OK ) != 0 ) - { - CS_DEBUG( "There is a problem with [" << m_sPemFile << "]" ); - return( false ); - } - - // - // set up the CTX - if( SSL_CTX_use_certificate_chain_file( m_ssl_ctx, m_sPemFile.c_str() ) <= 0 ) - { - CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" ); - SSLErrors( __FILE__, __LINE__ ); - return( false ); - } - - if( SSL_CTX_use_PrivateKey_file( m_ssl_ctx, m_sPemFile.c_str(), SSL_FILETYPE_PEM ) <= 0 ) - { - CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" ); - SSLErrors( __FILE__, __LINE__ ); - return( false ); - } - - // check to see if this pem file contains a DH structure for use with DH key exchange - // https://github.com/znc/znc/pull/46 - FILE *dhParamsFile = fopen( m_sPemFile.c_str(), "r" ); - if( !dhParamsFile ) - { - CS_DEBUG( "There is a problem with [" << m_sPemFile << "]" ); - return( false ); - } - - DH * dhParams = PEM_read_DHparams( dhParamsFile, NULL, NULL, NULL ); - fclose( dhParamsFile ); - if( dhParams ) - { - SSL_CTX_set_options( m_ssl_ctx, SSL_OP_SINGLE_DH_USE ); - if( !SSL_CTX_set_tmp_dh( m_ssl_ctx, dhParams ) ) - { - CS_DEBUG( "Error setting ephemeral DH parameters from [" << m_sPemFile << "]" ); - SSLErrors( __FILE__, __LINE__ ); - DH_free( dhParams ); - return( false ); - } - DH_free( dhParams ); - } - else - { - // Presumably PEM_read_DHparams failed, as there was no DH structure. Clearing those errors here so they are removed off the stack - ERR_clear_error(); - } - - // Errors for the following block are non-fatal (ECDHE is nice to have - // but not a requirement) -#if defined( SSL_CTX_set_ecdh_auto ) - // Auto-select sensible curve - if( !SSL_CTX_set_ecdh_auto( m_ssl_ctx , 1 ) ) - ERR_clear_error(); -#elif defined( SSL_CTX_set_tmp_ecdh ) - // Use a standard, widely-supported curve - EC_KEY * ecdh = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); - if( ecdh ) - { - if( !SSL_CTX_set_tmp_ecdh( m_ssl_ctx, ecdh ) ) - ERR_clear_error(); - EC_KEY_free( ecdh ); - } - else - ERR_clear_error(); -#endif - - if( SSL_CTX_set_cipher_list( m_ssl_ctx, m_sCipherType.c_str() ) <= 0 ) - { - CS_DEBUG( "Could not assign cipher [" << m_sCipherType << "]" ); - return( false ); - } - - CheckDisabledProtocols(); + m_ssl_ctx = SetupServerCTX(); // // setup the SSL @@ -1662,9 +1746,10 @@ bool Csock::SSLServerSetup() SSL_set_accept_state( m_ssl ); if( m_iRequireClientCertFlags ) { - SSL_set_verify( m_ssl, m_iRequireClientCertFlags, ( m_pCerVerifyCB ? m_pCerVerifyCB : _CertVerifyCB ) ); - SSL_set_ex_data( m_ssl, GetCsockClassIdx(), this ); + SSL_set_verify( m_ssl, m_iRequireClientCertFlags, m_pCerVerifyCB ); } + SSL_set_info_callback( m_ssl, _InfoCallback ); + SSL_set_ex_data( m_ssl, GetCsockSSLIdx(), this ); SSLFinishSetup( m_ssl ); return( true ); @@ -1878,30 +1963,30 @@ bool Csock::Write( const char *data, size_t len ) switch( SSL_get_error( m_ssl, iErr ) ) { - case SSL_ERROR_NONE: - m_bsslEstablished = true; - // all ok - break; + case SSL_ERROR_NONE: + m_bsslEstablished = true; + // all ok + break; - case SSL_ERROR_ZERO_RETURN: - { - // weird closer alert - return( false ); - } + case SSL_ERROR_ZERO_RETURN: + { + // weird closer alert + return( false ); + } - case SSL_ERROR_WANT_READ: - // retry - break; + case SSL_ERROR_WANT_READ: + // retry + break; - case SSL_ERROR_WANT_WRITE: - // retry - break; + case SSL_ERROR_WANT_WRITE: + // retry + break; - case SSL_ERROR_SSL: - { - SSLErrors( __FILE__, __LINE__ ); - return( false ); - } + case SSL_ERROR_SSL: + { + SSLErrors( __FILE__, __LINE__ ); + return( false ); + } } if( iErr > 0 ) @@ -2320,8 +2405,18 @@ const CS_STRING & Csock::GetPemPass() const { return( m_sPemPass ); } void Csock::SetSSLMethod( int iMethod ) { m_iMethod = iMethod; } int Csock::GetSSLMethod() const { return( m_iMethod ); } -void Csock::SetSSLObject( SSL *ssl ) { m_ssl = ssl; } -void Csock::SetCTXObject( SSL_CTX *sslCtx ) { m_ssl_ctx = sslCtx; } +void Csock::SetSSLObject( SSL *ssl, bool bDeleteExisting ) +{ + if( bDeleteExisting ) + FREE_SSL(); + m_ssl = ssl; +} +void Csock::SetCTXObject( SSL_CTX *sslCtx, bool bDeleteExisting ) +{ + if( bDeleteExisting ) + FREE_CTX(); + m_ssl_ctx = sslCtx; +} SSL_SESSION * Csock::GetSSLSession() const { @@ -2807,6 +2902,7 @@ void Csock::Init( const CS_STRING & sHostname, uint16_t uPort, int iTimeout ) m_ssl_ctx = NULL; m_iRequireClientCertFlags = 0; m_uDisableProtocols = 0; + m_bNoSSLCompression = false; #endif /* HAVE_LIBSSL */ m_iTcount = 0; m_iReadSock = CS_INVALID_SOCK; @@ -3109,58 +3205,58 @@ void CSocketManager::Loop() switch( bytes ) { - case Csock::READ_EOF: - { - DelSockByAddr( pcSock ); - break; - } - - case Csock::READ_ERR: - { - bool bHandled = false; -#ifdef HAVE_LIBSSL - if( pcSock->GetSSL() ) + case Csock::READ_EOF: { - unsigned long iSSLError = ERR_peek_error(); - if( iSSLError ) - { - char szError[512]; - memset( ( char * ) szError, '\0', 512 ); - ERR_error_string_n( iSSLError, szError, 511 ); - SSLErrors( __FILE__, __LINE__ ); - pcSock->CallSockError( GetSockError(), szError ); - bHandled = true; - } + DelSockByAddr( pcSock ); + break; } + + case Csock::READ_ERR: + { + bool bHandled = false; +#ifdef HAVE_LIBSSL + if( pcSock->GetSSL() ) + { + unsigned long iSSLError = ERR_peek_error(); + if( iSSLError ) + { + char szError[512]; + memset( ( char * ) szError, '\0', 512 ); + ERR_error_string_n( iSSLError, szError, 511 ); + SSLErrors( __FILE__, __LINE__ ); + pcSock->CallSockError( GetSockError(), szError ); + bHandled = true; + } + } #endif - if( !bHandled ) - pcSock->CallSockError( GetSockError() ); - DelSockByAddr( pcSock ); - break; - } + if( !bHandled ) + pcSock->CallSockError( GetSockError() ); + DelSockByAddr( pcSock ); + break; + } - case Csock::READ_EAGAIN: - break; + case Csock::READ_EAGAIN: + break; - case Csock::READ_CONNREFUSED: - pcSock->ConnectionRefused(); - DelSockByAddr( pcSock ); - break; + case Csock::READ_CONNREFUSED: + pcSock->ConnectionRefused(); + DelSockByAddr( pcSock ); + break; - case Csock::READ_TIMEDOUT: - pcSock->Timeout(); - DelSockByAddr( pcSock ); - break; + case Csock::READ_TIMEDOUT: + pcSock->Timeout(); + DelSockByAddr( pcSock ); + break; - default: - { - if( Csock::TMO_READ & pcSock->GetTimeoutType() ) - pcSock->ResetTimer(); // reset the timeout timer + default: + { + if( Csock::TMO_READ & pcSock->GetTimeoutType() ) + pcSock->ResetTimer(); // reset the timeout timer - pcSock->ReadData( cBuff(), bytes ); // Call ReadData() before PushBuff() so that it is called before the ReadLine() event - LD 07/18/05 - pcSock->PushBuff( cBuff(), bytes ); - break; - } + pcSock->ReadData( cBuff(), bytes ); // Call ReadData() before PushBuff() so that it is called before the ReadLine() event - LD 07/18/05 + pcSock->PushBuff( cBuff(), bytes ); + break; + } } } else if( iErrno == SELECT_ERROR ) From 6ae9a439b2ffdc18ff7db50ea85c3949363a1988 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 30 Oct 2014 09:24:00 +0100 Subject: [PATCH 2/4] Fix non-SSL build --- src/Socket.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Socket.cpp b/src/Socket.cpp index faf392b7..f709089b 100644 --- a/src/Socket.cpp +++ b/src/Socket.cpp @@ -20,8 +20,8 @@ #include CZNCSock::CZNCSock(int timeout) : Csock(timeout) { - DisableSSLProtocols(EDP_SSL); #ifdef HAVE_LIBSSL + DisableSSLProtocols(EDP_SSL); CString sCipher = CZNC::Get().GetSSLCiphers(); if (!sCipher.empty()) { SetCipher(sCipher); @@ -30,8 +30,8 @@ CZNCSock::CZNCSock(int timeout) : Csock(timeout) { } CZNCSock::CZNCSock(const CString& sHost, u_short port, int timeout) : Csock(sHost, port, timeout) { - DisableSSLProtocols(EDP_SSL); #ifdef HAVE_LIBSSL + DisableSSLProtocols(EDP_SSL); CString sCipher = CZNC::Get().GetSSLCiphers(); if (!sCipher.empty()) { SetCipher(sCipher); From 54e8b62b8773a67e4065f1d1397443155aaaa379 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 4 Nov 2014 23:02:56 +0100 Subject: [PATCH 3/4] Fix #719: Disable SSL compression --- src/Socket.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Socket.cpp b/src/Socket.cpp index f709089b..57f89422 100644 --- a/src/Socket.cpp +++ b/src/Socket.cpp @@ -21,6 +21,7 @@ CZNCSock::CZNCSock(int timeout) : Csock(timeout) { #ifdef HAVE_LIBSSL + DisableSSLCompression(); DisableSSLProtocols(EDP_SSL); CString sCipher = CZNC::Get().GetSSLCiphers(); if (!sCipher.empty()) { @@ -31,6 +32,7 @@ CZNCSock::CZNCSock(int timeout) : Csock(timeout) { CZNCSock::CZNCSock(const CString& sHost, u_short port, int timeout) : Csock(sHost, port, timeout) { #ifdef HAVE_LIBSSL + DisableSSLCompression(); DisableSSLProtocols(EDP_SSL); CString sCipher = CZNC::Get().GetSSLCiphers(); if (!sCipher.empty()) { From b759c688477a33b63dc0588bd64d34cd654be599 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 6 Nov 2014 20:39:00 +0100 Subject: [PATCH 4/4] Config option for SSL protocols (resolves #720) ZNC currently disables SSLv2 and SSLv3 by default. To keep the ZNC defaults (recommended, may change in the future versions) and for example disable TLSv1 in addition, specify in the global config section: SSLProtocols = -TLSv1 Available (case-insentive) values are: All, SSLv2, SSLv3, TLSv1, TLSv1.1, TLSv1.2 A non-prefixed "absolute" value overrides the ZNC defaults: SSLProtocols = TLSV1 +TLSv1.1 +TLSv1.2 --- include/znc/znc.h | 3 +++ src/Socket.cpp | 4 ++-- src/znc.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/include/znc/znc.h b/include/znc/znc.h index 0355fe73..08d12f33 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -128,6 +128,7 @@ public: unsigned int GetConnectDelay() const { return m_uiConnectDelay; } bool GetProtectWebSessions() const { return m_bProtectWebSessions; } CString GetSSLCiphers() const { return m_sSSLCiphers; } + Csock::EDisableProtocol GetDisabledSSLProtocols() const { return static_cast(m_uDisabledSSLProtocols); } // !Getters // Static allocator @@ -211,6 +212,7 @@ protected: CString m_sPidFile; CString m_sSSLCertFile; CString m_sSSLCiphers; + CString m_sSSLProtocols; VCString m_vsBindHosts; VCString m_vsTrustedProxies; VCString m_vsMotd; @@ -218,6 +220,7 @@ protected: unsigned int m_uiConnectDelay; unsigned int m_uiAnonIPLimit; unsigned int m_uiMaxBufferSize; + unsigned int m_uDisabledSSLProtocols; CModules* m_pModules; unsigned long long m_uBytesRead; unsigned long long m_uBytesWritten; diff --git a/src/Socket.cpp b/src/Socket.cpp index 57f89422..c31f51d9 100644 --- a/src/Socket.cpp +++ b/src/Socket.cpp @@ -22,7 +22,7 @@ CZNCSock::CZNCSock(int timeout) : Csock(timeout) { #ifdef HAVE_LIBSSL DisableSSLCompression(); - DisableSSLProtocols(EDP_SSL); + DisableSSLProtocols(CZNC::Get().GetDisabledSSLProtocols()); CString sCipher = CZNC::Get().GetSSLCiphers(); if (!sCipher.empty()) { SetCipher(sCipher); @@ -33,7 +33,7 @@ CZNCSock::CZNCSock(int timeout) : Csock(timeout) { CZNCSock::CZNCSock(const CString& sHost, u_short port, int timeout) : Csock(sHost, port, timeout) { #ifdef HAVE_LIBSSL DisableSSLCompression(); - DisableSSLProtocols(EDP_SSL); + DisableSSLProtocols(CZNC::Get().GetDisabledSSLProtocols()); CString sCipher = CZNC::Get().GetSSLCiphers(); if (!sCipher.empty()) { SetCipher(sCipher); diff --git a/src/znc.cpp b/src/znc.cpp index 19b730f2..e2c8c5ba 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -55,6 +55,8 @@ CZNC::CZNC() { m_sConnectThrottle.SetTTL(30000); m_pLockFile = NULL; m_bProtectWebSessions = true; + m_uDisabledSSLProtocols = Csock::EDP_SSL; + m_sSSLProtocols = ""; } CZNC::~CZNC() { @@ -479,6 +481,10 @@ bool CZNC::WriteConfig() { config.AddKeyValuePair("SSLCiphers", CString(m_sSSLCiphers)); } + if (!m_sSSLProtocols.empty()) { + config.AddKeyValuePair("SSLProtocols", m_sSSLProtocols); + } + for (unsigned int m = 0; m < m_vsMotd.size(); m++) { config.AddKeyValuePair("Motd", m_vsMotd[m].FirstLine()); } @@ -1094,6 +1100,45 @@ bool CZNC::DoRehash(CString& sError) if (config.FindStringEntry("protectwebsessions", sVal)) m_bProtectWebSessions = sVal.ToBool(); + if (config.FindStringEntry("sslprotocols", m_sSSLProtocols)) { + VCString vsProtocols; + m_sSSLProtocols.Split(" ", vsProtocols, false, "", "", true, true); + + for (CString& sProtocol : vsProtocols) { + + unsigned int uFlag = 0; + bool bEnable = sProtocol.TrimPrefix("+"); + bool bDisable = sProtocol.TrimPrefix("-"); + + if (sProtocol.Equals("All")) { + uFlag = ~0; + } else if (sProtocol.Equals("SSLv2")) { + uFlag = Csock::EDP_SSLv2; + } else if (sProtocol.Equals("SSLv3")) { + uFlag = Csock::EDP_SSLv3; + } else if (sProtocol.Equals("TLSv1")) { + uFlag = Csock::EDP_TLSv1; + } else if (sProtocol.Equals("TLSv1.1")) { + uFlag = Csock::EDP_TLSv1_1; + } else if (sProtocol.Equals("TLSv1.2")) { + uFlag = Csock::EDP_TLSv1_2; + } else { + CUtils::PrintError("Invalid SSLProtocols value [" + sProtocol + "]"); + CUtils::PrintError("The syntax is [SSLProtocols = [+|-] ...]"); + CUtils::PrintError("Available protocols are [SSLv2, SSLv3, TLSv1, TLSv1.1, TLSv1.2]"); + return false; + } + + if (bEnable) { + m_uDisabledSSLProtocols &= ~uFlag; + } else if (bDisable) { + m_uDisabledSSLProtocols |= uFlag; + } else { + m_uDisabledSSLProtocols = ~uFlag; + } + } + } + // This has to be after SSLCertFile is handled since it uses that value const char *szListenerEntries[] = { "listen", "listen6", "listen4",