sohami closed pull request #1366: [DRILL-6581] C++ Client SSL Implementation Fixes/Improvements URL: https://github.com/apache/drill/pull/1366
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/contrib/native/client/example/querySubmitter.cpp b/contrib/native/client/example/querySubmitter.cpp index 519dd93d5eb..a84d1dbe4d7 100644 --- a/contrib/native/client/example/querySubmitter.cpp +++ b/contrib/native/client/example/querySubmitter.cpp @@ -54,7 +54,8 @@ struct Option{ {"certFilePath", "Path to SSL certificate file", false}, {"disableHostnameVerification", "disable host name verification", false}, {"disableCertVerification", "disable certificate verification", false}, - {"useSystemTrustStore", "[Windows only]. Use the system truststore.", false } + {"useSystemTrustStore", "[Windows only]. Use the system truststore.", false }, + {"CustomSSLCtxOptions", "The custom SSL CTX Options", false} }; @@ -315,6 +316,7 @@ int main(int argc, char* argv[]) { std::string disableHostnameVerification=qsOptionValues["disableHostnameVerification"]; std::string disableCertVerification=qsOptionValues["disableCertVerification"]; std::string useSystemTrustStore = qsOptionValues["useSystemTrustStore"]; + std::string customSSLOptions = qsOptionValues["CustomSSLCtxOptions"]; Drill::QueryType type; @@ -416,6 +418,9 @@ int main(int argc, char* argv[]) { if (useSystemTrustStore.length() > 0){ props.setProperty(USERPROP_USESYSTEMTRUSTSTORE, useSystemTrustStore); } + if (customSSLOptions.length() > 0){ + props.setProperty(USERPROP_CUSTOM_SSLCTXOPTIONS, customSSLOptions); + } } if(client.connect(connectStr.c_str(), &props)!=Drill::CONN_SUCCESS){ diff --git a/contrib/native/client/src/clientlib/channel.cpp b/contrib/native/client/src/clientlib/channel.cpp index 535fad77ce7..bdc19f7ad33 100644 --- a/contrib/native/client/src/clientlib/channel.cpp +++ b/contrib/native/client/src/clientlib/channel.cpp @@ -210,7 +210,19 @@ ChannelContext* ChannelFactory::getChannelContext(channelType_t t, DrillUserProp verifyMode = boost::asio::ssl::context::verify_none; } - pChannelContext = new SSLChannelContext(props, tlsVersion, verifyMode); + long customSSLCtxOptions = 0; + std::string sslOptions; + props->getProp(USERPROP_CUSTOM_SSLCTXOPTIONS, sslOptions); + if (!sslOptions.empty()){ + try{ + customSSLCtxOptions = boost::lexical_cast<long>(sslOptions); + } + catch (...){ + DRILL_LOG(LOG_ERROR) << "Unable to parse custom SSL CTX options." << std::endl; + } + } + + pChannelContext = new SSLChannelContext(props, tlsVersion, verifyMode, customSSLCtxOptions); } break; #endif @@ -352,30 +364,32 @@ connectionStatus_t SSLStreamChannel::init(){ connectionStatus_t ret=CONN_SUCCESS; const DrillUserProperties* props = m_pContext->getUserProperties(); - std::string useSystemTrustStore; - props->getProp(USERPROP_USESYSTEMTRUSTSTORE, useSystemTrustStore); - if (useSystemTrustStore != "true"){ - std::string certFile; - props->getProp(USERPROP_CERTFILEPATH, certFile); - try{ - ((SSLChannelContext_t*)m_pContext)->getSslContext().load_verify_file(certFile); - } - catch (boost::system::system_error e){ - DRILL_LOG(LOG_ERROR) << "Channel initialization failure. Certificate file " - << certFile - << " could not be loaded." - << std::endl; - handleError(CONN_SSLERROR, getMessage(ERR_CONN_SSLCERTFAIL, certFile.c_str(), e.what())); - ret = CONN_FAILURE; - } - } + std::string useSystemTrustStore; + props->getProp(USERPROP_USESYSTEMTRUSTSTORE, useSystemTrustStore); + if (useSystemTrustStore != "true"){ + std::string certFile; + props->getProp(USERPROP_CERTFILEPATH, certFile); + try{ + ((SSLChannelContext_t*)m_pContext)->getSslContext().load_verify_file(certFile); + } + catch (boost::system::system_error e){ + DRILL_LOG(LOG_ERROR) << "Channel initialization failure. Certificate file " + << certFile + << " could not be loaded." + << std::endl; + handleError(CONN_SSLERROR, getMessage(ERR_CONN_SSLCERTFAIL, certFile.c_str(), e.what())); + ret = CONN_FAILURE; + // Stop here to propagate the load/verify certificate error. + return ret; + } + } + ((SSLChannelContext_t *)m_pContext)->SetCertHostnameVerificationStatus(true); std::string disableHostVerification; props->getProp(USERPROP_DISABLE_HOSTVERIFICATION, disableHostVerification); if (disableHostVerification != "true") { - std::string hostPortStr = m_pEndpoint->getHost() + ":" + m_pEndpoint->getPort(); ((SSLChannelContext_t *) m_pContext)->getSslContext().set_verify_callback( - boost::asio::ssl::rfc2818_verification(hostPortStr.c_str())); + DrillSSLHostnameVerifier(this)); } m_pSocket=new SslSocket(m_ioService, ((SSLChannelContext_t*)m_pContext)->getSslContext() ); diff --git a/contrib/native/client/src/clientlib/channel.hpp b/contrib/native/client/src/clientlib/channel.hpp index 73273aa7b34..fec4659ccb5 100644 --- a/contrib/native/client/src/clientlib/channel.hpp +++ b/contrib/native/client/src/clientlib/channel.hpp @@ -21,6 +21,12 @@ #include "drill/common.hpp" #include "drill/drillClient.hpp" #include "streamSocket.hpp" +#include "errmsgs.hpp" + +#if defined(IS_SSL_ENABLED) +#include <openssl/ssl.h> +#include <openssl/err.h> +#endif namespace Drill { @@ -34,7 +40,7 @@ class UserProperties; //parse the connection string and set up the host and port to connect to connectionStatus_t getDrillbitEndpoint(); - + const std::string& getProtocol() const {return m_protocol;} const std::string& getHost() const {return m_host;} const std::string& getPort() const {return m_port;} @@ -83,21 +89,41 @@ class UserProperties; SSLChannelContext(DrillUserProperties *props, boost::asio::ssl::context::method tlsVersion, - boost::asio::ssl::verify_mode verifyMode) : - ChannelContext(props), - m_SSLContext(tlsVersion) { + boost::asio::ssl::verify_mode verifyMode, + const long customSSLCtxOptions = 0) : + ChannelContext(props), + m_SSLContext(tlsVersion), + m_certHostnameVerificationStatus(true) + { m_SSLContext.set_default_verify_paths(); m_SSLContext.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use + | customSSLCtxOptions ); m_SSLContext.set_verify_mode(verifyMode); }; + ~SSLChannelContext(){}; boost::asio::ssl::context& getSslContext(){ return m_SSLContext;} + + /// @brief Check the certificate host name verification status. + /// + /// @return FALSE if the verification has failed, TRUE otherwise. + const bool GetCertificateHostnameVerificationStatus() const { return m_certHostnameVerificationStatus; } + + /// @brief Set the certificate host name verification status. + /// + /// @param in_result The host name verification status. + void SetCertHostnameVerificationStatus(bool in_result) { m_certHostnameVerificationStatus = in_result; } + private: boost::asio::ssl::context m_SSLContext; + + // The flag to indicate the host name verification result. + bool m_certHostnameVerificationStatus; }; typedef ChannelContext ChannelContext_t; @@ -147,9 +173,20 @@ class UserProperties; ConnectionEndpoint* getEndpoint(){return m_pEndpoint;} + ChannelContext_t* getChannelContext(){ return m_pContext; } + protected: connectionStatus_t handleError(connectionStatus_t status, std::string msg); + /// @brief Handle protocol handshake exceptions. + /// + /// @param in_err The error. + /// + /// @return the connectionStatus. + virtual connectionStatus_t HandleProtocolHandshakeException(const boost::system::system_error& in_err){ + return handleError(CONN_HANDSHAKE_FAILED, in_err.what()); + } + boost::asio::io_service& m_ioService; boost::asio::io_service m_ioServiceFallback; // used if m_ioService is not provided AsioStreamSocket* m_pSocket; @@ -170,7 +207,7 @@ class UserProperties; try{ m_pSocket->protocolHandshake(useSystemConfig); } catch (boost::system::system_error e) { - status = handleError(CONN_HANDSHAKE_FAILED, e.what()); + status = HandleProtocolHandshakeException(e); } return status; } @@ -199,6 +236,33 @@ class UserProperties; :Channel(ioService, host, port){ } connectionStatus_t init(); + protected: +#if defined(IS_SSL_ENABLED) + /// @brief Handle protocol handshake exceptions for SSL specific failures. + /// + /// @param in_err The error. + /// + /// @return the connectionStatus. + connectionStatus_t HandleProtocolHandshakeException(const boost::system::system_error& in_err) { + const boost::system::error_code& errcode = in_err.code(); + if (!(((SSLChannelContext_t *)m_pContext)->GetCertificateHostnameVerificationStatus())){ + return handleError( + CONN_HANDSHAKE_FAILED, + getMessage(ERR_CONN_SSL_CN, in_err.what())); + } + else if (boost::asio::error::get_ssl_category() == errcode.category() && + SSL_R_CERTIFICATE_VERIFY_FAILED == ERR_GET_REASON(errcode.value())){ + return handleError( + CONN_HANDSHAKE_FAILED, + getMessage(ERR_CONN_SSL_CERTVERIFY, in_err.what())); + } + else{ + return handleError( + CONN_HANDSHAKE_FAILED, + getMessage(ERR_CONN_SSL_GENERAL, in_err.what())); + } + } +#endif }; class ChannelFactory{ @@ -215,6 +279,52 @@ class UserProperties; static ChannelContext_t* getChannelContext(channelType_t t, DrillUserProperties* props); }; + /// @brief Hostname verification callback wrapper. + class DrillSSLHostnameVerifier{ + public: + /// @brief The constructor. + /// + /// @param in_channel The Channel. + DrillSSLHostnameVerifier(Channel* in_channel) : m_channel(in_channel){ + DRILL_LOG(LOG_INFO) + << "DrillSSLHostnameVerifier::DrillSSLHostnameVerifier: +++++ Enter +++++" + << std::endl; + } + + /// @brief Perform certificate verification. + /// + /// @param in_preverified Pre-verified indicator. + /// @param in_ctx Verify context. + bool operator()( + bool in_preverified, + boost::asio::ssl::verify_context& in_ctx){ + DRILL_LOG(LOG_INFO) << "DrillSSLHostnameVerifier::operator(): +++++ Enter +++++" << std::endl; + + // Gets the channel context. + SSLChannelContext_t* context = (SSLChannelContext_t*)(m_channel->getChannelContext()); + + // Retrieve the host before we perform Host name verification. + // This is because host with ZK mode is selected after the connect() function is called. + boost::asio::ssl::rfc2818_verification verifier(m_channel->getEndpoint()->getHost().c_str()); + + // Perform verification. + bool verified = verifier(in_preverified, in_ctx); + + DRILL_LOG(LOG_DEBUG) + << "DrillSSLHostnameVerifier::operator(): Verification Result: " + << verified + << std::endl; + + // Sets the result back to the context. + context->SetCertHostnameVerificationStatus(verified); + return verified; + } + + private: + + // The SSL channel. + Channel* m_channel; + }; } // namespace Drill diff --git a/contrib/native/client/src/clientlib/errmsgs.cpp b/contrib/native/client/src/clientlib/errmsgs.cpp index c1ac80d0649..82f24fd202e 100644 --- a/contrib/native/client/src/clientlib/errmsgs.cpp +++ b/contrib/native/client/src/clientlib/errmsgs.cpp @@ -57,6 +57,9 @@ static Drill::ErrorMessages errorMessages[]={ {ERR_CONN_NOSERVERENC, ERR_CATEGORY_CONN, 0, "Client needs encryption but encryption is disabled on the server." " Please check connection parameters or contact administrator. [Warn: This" " could be due to a bad configuration or a security attack is in progress.]"}, + {ERR_CONN_SSL_GENERAL, ERR_CATEGORY_CONN, 0, "Encountered an exception during SSL handshake. [Details: %s]"}, + {ERR_CONN_SSL_CN, ERR_CATEGORY_CONN, 0, "SSL certificate host name verification failure. [Details: %s]" }, + {ERR_CONN_SSL_CERTVERIFY, ERR_CATEGORY_CONN, 0, "SSL certificate verification failed. [Details: %s]"}, {ERR_QRY_OUTOFMEM, ERR_CATEGORY_QRY, 0, "Out of memory."}, {ERR_QRY_COMMERR, ERR_CATEGORY_QRY, 0, "Communication error. %s"}, {ERR_QRY_INVREADLEN, ERR_CATEGORY_QRY, 0, "Internal Error: Received a message with an invalid read length."}, diff --git a/contrib/native/client/src/clientlib/errmsgs.hpp b/contrib/native/client/src/clientlib/errmsgs.hpp index fac646b815f..7bcb80579d8 100644 --- a/contrib/native/client/src/clientlib/errmsgs.hpp +++ b/contrib/native/client/src/clientlib/errmsgs.hpp @@ -55,7 +55,10 @@ namespace Drill{ #define ERR_CONN_NOSOCKET DRILL_ERR_START+23 #define ERR_CONN_NOSERVERAUTH DRILL_ERR_START+24 #define ERR_CONN_NOSERVERENC DRILL_ERR_START+25 -#define ERR_CONN_MAX DRILL_ERR_START+25 +#define ERR_CONN_SSL_GENERAL DRILL_ERR_START+26 +#define ERR_CONN_SSL_CN DRILL_ERR_START+27 +#define ERR_CONN_SSL_CERTVERIFY DRILL_ERR_START+28 +#define ERR_CONN_MAX DRILL_ERR_START+28 #define ERR_QRY_OUTOFMEM ERR_CONN_MAX+1 #define ERR_QRY_COMMERR ERR_CONN_MAX+2 diff --git a/contrib/native/client/src/clientlib/userProperties.cpp b/contrib/native/client/src/clientlib/userProperties.cpp index f1aa82fa3ca..0ad8af1dd73 100644 --- a/contrib/native/client/src/clientlib/userProperties.cpp +++ b/contrib/native/client/src/clientlib/userProperties.cpp @@ -35,6 +35,7 @@ const std::map<std::string, uint32_t> DrillUserProperties::USER_PROPERTIES=boos ( USERPROP_DISABLE_HOSTVERIFICATION, USERPROP_FLAGS_BOOLEAN|USERPROP_FLAGS_SSLPROP) ( USERPROP_DISABLE_CERTVERIFICATION, USERPROP_FLAGS_BOOLEAN|USERPROP_FLAGS_SSLPROP) ( USERPROP_USESYSTEMTRUSTSTORE, USERPROP_FLAGS_BOOLEAN|USERPROP_FLAGS_SSLPROP) + ( USERPROP_CUSTOM_SSLCTXOPTIONS, USERPROP_FLAGS_STRING|USERPROP_FLAGS_SSLPROP) ( USERPROP_SASL_ENCRYPT, USERPROP_FLAGS_STRING) ; diff --git a/contrib/native/client/src/include/drill/common.hpp b/contrib/native/client/src/include/drill/common.hpp index 18cfc69fff5..b5bb522bee0 100644 --- a/contrib/native/client/src/include/drill/common.hpp +++ b/contrib/native/client/src/include/drill/common.hpp @@ -173,7 +173,8 @@ typedef enum{ #define USERPROP_PASSWORD "password" #define USERPROP_SCHEMA "schema" #define USERPROP_USESSL "enableTLS" -#define USERPROP_TLSPROTOCOL "TLSProtocol" //TLS version +#define USERPROP_TLSPROTOCOL "TLSProtocol" //TLS version. The exact TLS version. +#define USERPROP_CUSTOM_SSLCTXOPTIONS "CustomSSLCtxOptions" // The custom SSL CTX options. #define USERPROP_CERTFILEPATH "certFilePath" // pem file path and name // TODO: support truststore protected by password. // #define USERPROP_CERTPASSWORD "certPassword" // Password for certificate file. diff --git a/contrib/native/client/src/include/drill/userProperties.hpp b/contrib/native/client/src/include/drill/userProperties.hpp index f5d6783ea33..fb6c764e723 100644 --- a/contrib/native/client/src/include/drill/userProperties.hpp +++ b/contrib/native/client/src/include/drill/userProperties.hpp @@ -29,8 +29,7 @@ class DECLSPEC_DRILL_CLIENT DrillUserProperties{ DrillUserProperties(){}; - /// @brief Update the property value associate with the property key if the value is - /// empty. + /// @brief Sets the default property value. /// /// @param in_propName The property name. /// @param in_propValue The property value. ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services