diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/http.cpp | 75 | ||||
-rw-r--r-- | zen/http.h | 15 | ||||
-rw-r--r-- | zen/open_ssl.cpp | 581 | ||||
-rw-r--r-- | zen/open_ssl.h | 49 | ||||
-rw-r--r-- | zen/socket.h | 5 | ||||
-rw-r--r-- | zen/warn_static.h | 8 |
6 files changed, 695 insertions, 38 deletions
diff --git a/zen/http.cpp b/zen/http.cpp index f8538c93..43c9dcbf 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -7,6 +7,7 @@ #include "http.h" #include "socket.h" + #include "open_ssl.h" using namespace zen; @@ -14,11 +15,15 @@ using namespace zen; class HttpInputStream::Impl { public: - Impl(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError - const std::vector<std::pair<std::string, std::string>>* postParams) : //issue POST if bound, GET otherwise + Impl(const Zstring& url, + const std::vector<std::pair<std::string, std::string>>* postParams, //issue POST if bound, GET otherwise + bool disableGetCache /*not relevant for POST (= never cached)*/, + const Zstring& userAgent, + const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const IOCallback& notifyUnbufferedIO) : //throw SysError notifyUnbufferedIO_(notifyUnbufferedIO) { - ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ ); + ZEN_ON_SCOPE_FAIL(cleanup(); /*destructor call would lead to member double clean-up!!!*/); const Zstring urlFmt = afterFirst(url, Zstr("://"), IF_MISSING_RETURN_NONE); const Zstring server = beforeFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_ALL); @@ -33,12 +38,15 @@ public: throw SysError(L"URL uses unexpected protocol."); }(); - assert(!useTls); //not supported by our plain socket! - (void)useTls; - - socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError - //HTTP default port: 80, see %WINDIR%\system32\drivers\etc\services + if (useTls) //HTTP default port: 443, see %WINDIR%\system32\drivers\etc\services + { + socket_ = std::make_unique<Socket>(server, Zstr("https")); //throw SysError + tlsCtx_ = std::make_unique<TlsContext>(socket_->get(), server, caCertFilePath); //throw SysError + } + else //HTTP default port: 80, see %WINDIR%\system32\drivers\etc\services + socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError + //we don't support "chunked transfer encoding" => HTTP 1.0 std::map<std::string, std::string, LessAsciiNoCase> headers; headers["Host" ] = utfTo<std::string>(server); //only required for HTTP/1.1 headers["User-Agent"] = utfTo<std::string>(userAgent); @@ -46,12 +54,11 @@ public: const std::string postBuf = postParams ? xWwwFormUrlEncode(*postParams) : ""; - if (!postParams) //HTTP GET + if (!postParams /*HTTP GET*/ && disableGetCache) headers["Pragma"] = "no-cache"; //HTTP 1.0 only! superseeded by "Cache-Control" - //consider internetIsAlive() test; not relevant for POST (= never cached) else //HTTP POST { - headers["Content-type"] = "application/x-www-form-urlencoded"; + headers["Content-Type"] = "application/x-www-form-urlencoded"; headers["Content-Length"] = numberTo<std::string>(postBuf.size()); } @@ -64,9 +71,13 @@ public: //send request for (size_t bytesToSend = msg.size(); bytesToSend > 0;) - bytesToSend -= tryWriteSocket(socket_->get(), &*(msg.end() - bytesToSend), bytesToSend); //throw SysError + bytesToSend -= tlsCtx_ ? + tlsCtx_->tryWrite( &*(msg.end() - bytesToSend), bytesToSend) : //throw SysError + tryWriteSocket(socket_->get(), &*(msg.end() - bytesToSend), bytesToSend); //throw SysError - shutdownSocketSend(socket_->get()); //throw SysError + //shutdownSocketSend(socket_->get()); //throw SysError + //NO! Sending TCP FIN before receiving response (aka "TCP Half Closed") is not always supported! e.g. Cloudflare server will immediately end connection: recv() returns 0. + //"clients SHOULD NOT half-close their TCP connections": https://github.com/httpwg/http-core/issues/22 //receive response: std::string headBuf; @@ -164,7 +175,9 @@ private: return 0; bytesToRead = static_cast<size_t>(std::min(static_cast<int64_t>(bytesToRead), contentRemaining_)); //[!] contentRemaining_ > 4 GB possible! } - const size_t bytesReceived = tryReadSocket(socket_->get(), buffer, bytesToRead); //throw SysError; may return short, only 0 means EOF! + const size_t bytesReceived = tlsCtx_ ? + tlsCtx_->tryRead( buffer, bytesToRead) : //throw SysError; may return short, only 0 means EOF! + tryReadSocket (socket_->get(), buffer, bytesToRead); // if (contentRemaining_ >= 0) contentRemaining_ -= bytesReceived; @@ -174,14 +187,15 @@ private: return bytesReceived; //"zero indicates end of file" } - Impl (const Impl&) = delete; - Impl& operator=(const Impl&) = delete; - void cleanup() { } - std::unique_ptr<Socket> socket_; //*bound* after constructor has run + Impl (const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + std::unique_ptr<Socket> socket_; //*bound* after constructor has run + std::unique_ptr<TlsContext> tlsCtx_; //optional: support HTTPS int statusCode_ = 0; std::map<std::string, std::string, LessAsciiNoCase> responseHeaders_; @@ -208,14 +222,17 @@ std::string HttpInputStream::readAll() { return bufferedLoad<std::string>(*pimpl namespace { -std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError - const std::vector<std::pair<std::string, std::string>>* postParams) //issue POST if bound, GET otherwise +std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, + const std::vector<std::pair<std::string, std::string>>* postParams /*issue POST if bound, GET otherwise*/, + const Zstring& userAgent, + const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const IOCallback& notifyUnbufferedIO) //throw SysError { Zstring urlRed = url; //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." for (int redirects = 0; redirects < 6; ++redirects) { - auto response = std::make_unique<HttpInputStream::Impl>(urlRed, userAgent, notifyUnbufferedIO, postParams); //throw SysError + auto response = std::make_unique<HttpInputStream::Impl>(urlRed, postParams, false /*disableGetCache*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection const int statusCode = response->getStatusCode(); @@ -310,16 +327,16 @@ std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const st } -HttpInputStream zen::sendHttpPost(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO, - const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError +HttpInputStream zen::sendHttpPost(const Zstring& url, const std::vector<std::pair<std::string, std::string>>& postParams, + const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError { - return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, &postParams); //throw SysError + return sendHttpRequestImpl(url, &postParams, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError } -HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO) //throw SysError +HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError { - return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, nullptr); //throw SysError + return sendHttpRequestImpl(url, nullptr /*postParams*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError } @@ -328,9 +345,11 @@ bool zen::internetIsAlive() //noexcept try { auto response = std::make_unique<HttpInputStream::Impl>(Zstr("http://www.google.com/"), + nullptr /*postParams*/, + true /*disableGetCache*/, Zstr("FreeFileSync"), - nullptr /*notifyUnbufferedIO*/, - nullptr /*postParams*/); //throw SysError + nullptr /*caCertFilePath*/, + nullptr /*notifyUnbufferedIO*/); //throw SysError const int statusCode = response->getStatusCode(); //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!! @@ -15,7 +15,7 @@ namespace zen { /* - thread-safe! (Window/Linux/macOS) - - HTTPS supported only for Windows + - Linux/macOS: init OpenSSL before use! */ class HttpInputStream { @@ -36,9 +36,16 @@ private: }; -HttpInputStream sendHttpGet (const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError -HttpInputStream sendHttpPost(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO /*throw X*/, // - const std::vector<std::pair<std::string, std::string>>& postParams); +HttpInputStream sendHttpGet(const Zstring& url, + const Zstring& userAgent, + const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError + +HttpInputStream sendHttpPost(const Zstring& url, + const std::vector<std::pair<std::string, std::string>>& postParams, + const Zstring& userAgent, + const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const IOCallback& notifyUnbufferedIO /*throw X*/); bool internetIsAlive(); //noexcept std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs); diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp new file mode 100644 index 00000000..0f07e5e3 --- /dev/null +++ b/zen/open_ssl.cpp @@ -0,0 +1,581 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "open_ssl.h" +#include <openssl/pem.h> +#include <openssl/err.h> +#include <openssl/ssl.h> + +using namespace zen; + + +namespace +{ +/* Sign a file using SHA-256: + openssl dgst -sha256 -sign private.pem -out file.sig file.txt + + verify the signature: (caveat: public key expected to be in pkix format!) + openssl dgst -sha256 -verify public.pem -signature file.sig file.txt */ + + +std::wstring formatOpenSSLError(const std::wstring& functionName, unsigned long ec) +{ + char errorBuf[256] = {}; //== buffer size used by ERR_error_string(); err.c: it seems the message uses at most ~200 bytes + ::ERR_error_string_n(ec, errorBuf, sizeof(errorBuf)); //includes null-termination + + return formatSystemError(functionName, replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(ec)), utfTo<std::wstring>(errorBuf)); +} + + +std::wstring formatLastOpenSSLError(const std::wstring& functionName) +{ + const unsigned long ec = ::ERR_peek_last_error(); + ::ERR_clear_error(); //clean up for next OpenSSL operation on this thread + return formatOpenSSLError(functionName, ec); +} + +//================================================================================ + +std::shared_ptr<EVP_PKEY> generateRsaKeyPair(int bits) //throw SysError +{ + EVP_PKEY_CTX* keyCtx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, //int id, + nullptr); //ENGINE* e + if (!keyCtx) + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_CTX_new_id")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_CTX_free(keyCtx)); + + if (::EVP_PKEY_keygen_init(keyCtx) != 1) + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_keygen_init")); + + //"RSA keys set the key length during key generation rather than parameter generation" + if (::EVP_PKEY_CTX_set_rsa_keygen_bits(keyCtx, bits) <= 0) //"[...] return a positive value for success" => effectively returns "1" + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_CTX_set_rsa_keygen_bits")); + + EVP_PKEY* keyPair = nullptr; + if (::EVP_PKEY_keygen(keyCtx, &keyPair) != 1) + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_keygen")); + + return std::shared_ptr<EVP_PKEY>(keyPair, ::EVP_PKEY_free); +} + +//================================================================================ + +using BioToEvpFunc = EVP_PKEY* (*)(BIO* bp, EVP_PKEY** x, pem_password_cb* cb, void* u); + +std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpFunc bioToEvp, const wchar_t* functionName) //throw SysError +{ + BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); + if (!bio) + throw SysError(formatLastOpenSSLError(L"BIO_new_mem_buf")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + if (EVP_PKEY* evpKey = bioToEvp(bio, //BIO* bp, + nullptr, //EVP_PKEY** x, + nullptr, //pem_password_cb* cb, + nullptr)) //void* u + return std::shared_ptr<EVP_PKEY>(evpKey, ::EVP_PKEY_free); + throw SysError(formatLastOpenSSLError(functionName)); +} + + +using BioToRsaFunc = RSA* (*)(BIO* bp, RSA** x, pem_password_cb* cb, void* u); + +std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaFunc bioToRsa, const wchar_t* functionName) //throw SysError +{ + BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); + if (!bio) + throw SysError(formatLastOpenSSLError(L"BIO_new_mem_buf")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + RSA* rsa = bioToRsa(bio, //BIO* bp, + nullptr, //RSA** x, + nullptr, //pem_password_cb* cb, + nullptr); //void* u + if (!rsa) + throw SysError(formatLastOpenSSLError(functionName)); + ZEN_ON_SCOPE_EXIT(::RSA_free(rsa)); + + EVP_PKEY* evpKey = ::EVP_PKEY_new(); + if (!evpKey) + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new")); + std::shared_ptr<EVP_PKEY> sharedKey(evpKey, ::EVP_PKEY_free); + + if (::EVP_PKEY_set1_RSA(evpKey, rsa) != 1) //calls RSA_up_ref() + transfers ownership to evpKey + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_RSA")); + + return sharedKey; +} + +//-------------------------------------------------------------------------------- + +std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamType streamType, bool publicKey) //throw SysError +{ + switch (streamType) + { + case RsaStreamType::pkix: + return publicKey ? + streamToEvpKey(keyStream, ::PEM_read_bio_PUBKEY, L"PEM_read_bio_PUBKEY") : //throw SysError + streamToEvpKey(keyStream, ::PEM_read_bio_PrivateKey, L"PEM_read_bio_PrivateKey"); // + + case RsaStreamType::pkcs1: + return publicKey ? + streamToEvpKey(keyStream, ::PEM_read_bio_RSAPublicKey, L"PEM_read_bio_RSAPublicKey") : //throw SysError + streamToEvpKey(keyStream, ::PEM_read_bio_RSAPrivateKey, L"PEM_read_bio_RSAPrivateKey"); // + + case RsaStreamType::pkcs1_raw: + break; + } + + auto tmp = reinterpret_cast<const unsigned char*>(keyStream.c_str()); + EVP_PKEY* evpKey = (publicKey ? ::d2i_PublicKey : ::d2i_PrivateKey)(EVP_PKEY_RSA, //int type, + nullptr, //EVP_PKEY** a, + &tmp, /*changes tmp pointer itself!*/ //const unsigned char** pp, + static_cast<long>(keyStream.size())); //long length + if (!evpKey) + throw SysError(formatLastOpenSSLError(publicKey ? L"d2i_PublicKey" : L"d2i_PrivateKey")); + return std::shared_ptr<EVP_PKEY>(evpKey, ::EVP_PKEY_free); +} + +//================================================================================ + +using EvpToBioFunc = int (*)(BIO* bio, EVP_PKEY* evpKey); + +std::string evpKeyToStream(EVP_PKEY* evpKey, EvpToBioFunc evpToBio, const wchar_t* functionName) //throw SysError +{ + BIO* bio = ::BIO_new(BIO_s_mem()); + if (!bio) + throw SysError(formatLastOpenSSLError(L"BIO_new")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + if (evpToBio(bio, evpKey) != 1) + throw SysError(formatLastOpenSSLError(functionName)); + //--------------------------------------------- + const int keyLen = BIO_pending(bio); + if (keyLen < 0) + throw SysError(formatLastOpenSSLError(L"BIO_pending")); + if (keyLen == 0) + throw SysError(L"BIO_pending failed."); //no more error details + + std::string keyStream(keyLen, '\0'); + + if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen) + throw SysError(formatLastOpenSSLError(L"BIO_read")); + return keyStream; +} + + +using RsaToBioFunc = int (*)(BIO* bp, RSA* x); + +std::string evpKeyToStream(EVP_PKEY* evpKey, RsaToBioFunc rsaToBio, const wchar_t* functionName) //throw SysError +{ + BIO* bio = ::BIO_new(BIO_s_mem()); + if (!bio) + throw SysError(formatLastOpenSSLError(L"BIO_new")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + RSA* rsa = ::EVP_PKEY_get0_RSA(evpKey); //unowned reference! + if (!rsa) + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_get0_RSA")); + + if (rsaToBio(bio, rsa) != 1) + throw SysError(formatLastOpenSSLError(functionName)); + //--------------------------------------------- + const int keyLen = BIO_pending(bio); + if (keyLen < 0) + throw SysError(formatLastOpenSSLError(L"BIO_pending")); + if (keyLen == 0) + throw SysError(L"BIO_pending failed."); //no more error details + + std::string keyStream(keyLen, '\0'); + + if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen) + throw SysError(formatLastOpenSSLError(L"BIO_read")); + return keyStream; +} + + +//fix OpenSSL API inconsistencies: +int PEM_write_bio_PrivateKey2(BIO* bio, EVP_PKEY* key) +{ + return ::PEM_write_bio_PrivateKey(bio, //BIO* bp, + key, //EVP_PKEY* x, + nullptr, //const EVP_CIPHER* enc, + nullptr, //unsigned char* kstr, + 0, //int klen, + nullptr, //pem_password_cb* cb, + nullptr); //void* u +} + +int PEM_write_bio_RSAPrivateKey2(BIO* bio, RSA* rsa) +{ + return ::PEM_write_bio_RSAPrivateKey(bio, //BIO* bp, + rsa, //RSA* x, + nullptr, //const EVP_CIPHER* enc, + nullptr, //unsigned char* kstr, + 0, //int klen, + nullptr, //pem_password_cb* cb, + nullptr); //void* u +} + +int PEM_write_bio_RSAPublicKey2(BIO* bio, RSA* rsa) { return ::PEM_write_bio_RSAPublicKey(bio, rsa); } + +//-------------------------------------------------------------------------------- + +std::string keyToStream(EVP_PKEY* evpKey, RsaStreamType streamType, bool publicKey) //throw SysError +{ + switch (streamType) + { + case RsaStreamType::pkix: + return publicKey ? + evpKeyToStream(evpKey, ::PEM_write_bio_PUBKEY, L"PEM_write_bio_PUBKEY") : //throw SysError + evpKeyToStream(evpKey, ::PEM_write_bio_PrivateKey2, L"PEM_write_bio_PrivateKey"); // + + case RsaStreamType::pkcs1: + return publicKey ? + evpKeyToStream(evpKey, ::PEM_write_bio_RSAPublicKey2, L"PEM_write_bio_RSAPublicKey") : //throw SysError + evpKeyToStream(evpKey, ::PEM_write_bio_RSAPrivateKey2, L"PEM_write_bio_RSAPrivateKey"); // + + case RsaStreamType::pkcs1_raw: + break; + } + + unsigned char* buf = nullptr; + const int bufSize = (publicKey ? ::i2d_PublicKey : ::i2d_PrivateKey)(evpKey, &buf); + if (bufSize <= 0) + throw SysError(formatLastOpenSSLError(publicKey ? L"i2d_PublicKey" : L"i2d_PrivateKey")); + ZEN_ON_SCOPE_EXIT(::OPENSSL_free(buf)); //memory is only allocated for bufSize > 0 + + return { reinterpret_cast<const char*>(buf), static_cast<size_t>(bufSize) }; +} + +//================================================================================ + +std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //throw SysError +{ + //https://www.openssl.org/docs/manmaster/man3/EVP_DigestSign.html + EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create(); + if (!mdctx) + throw SysError(L"EVP_MD_CTX_create failed."); //no more error details + ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx)); + + if (::EVP_DigestSignInit(mdctx, //EVP_MD_CTX* ctx, + nullptr, //EVP_PKEY_CTX** pctx, + EVP_sha256(), //const EVP_MD* type, + nullptr, //ENGINE* e, + privateKey) != 1) //EVP_PKEY* pkey + throw SysError(formatLastOpenSSLError(L"EVP_DigestSignInit")); + + if (::EVP_DigestSignUpdate(mdctx, //EVP_MD_CTX* ctx, + message.c_str(), //const void* d, + message.size()) != 1) //size_t cnt + throw SysError(formatLastOpenSSLError(L"EVP_DigestSignUpdate")); + + size_t sigLenMax = 0; //"first call to EVP_DigestSignFinal returns the maximum buffer size required" + if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx, + nullptr, //unsigned char* sigret, + &sigLenMax) != 1) //size_t* siglen + throw SysError(formatLastOpenSSLError(L"EVP_DigestSignFinal")); + + std::string signature(sigLenMax, '\0'); + size_t sigLen = sigLenMax; + + if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx, + reinterpret_cast<unsigned char*>(&signature[0]), //unsigned char* sigret, + &sigLen) != 1) //size_t* siglen + throw SysError(formatLastOpenSSLError(L"EVP_DigestSignFinal")); + signature.resize(sigLen); + + return signature; +} + + +void verifySignature(const std::string& message, const std::string& signature, EVP_PKEY* publicKey) //throw SysError +{ + //https://www.openssl.org/docs/manmaster/man3/EVP_DigestVerify.html + EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create(); + if (!mdctx) + throw SysError(L"EVP_MD_CTX_create failed."); //no more error details + ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx)); + + if (::EVP_DigestVerifyInit(mdctx, //EVP_MD_CTX* ctx, + nullptr, //EVP_PKEY_CTX** pctx, + EVP_sha256(), //const EVP_MD* type, + nullptr, //ENGINE* e, + publicKey) != 1) //EVP_PKEY* pkey + throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyInit")); + + if (::EVP_DigestVerifyUpdate(mdctx, //EVP_MD_CTX* ctx, + message.c_str(), //const void* d, + message.size()) != 1) //size_t cnt + throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyUpdate")); + + if (::EVP_DigestVerifyFinal(mdctx, //EVP_MD_CTX* ctx, + reinterpret_cast<const unsigned char*>(signature.c_str()), //const unsigned char* sig, + signature.size()) != 1) //size_t siglen + throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyFinal")); +} +} + + +std::string zen::convertRsaKey(const std::string& keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey) //throw SysError +{ + assert(typeFrom != typeTo); + std::shared_ptr<EVP_PKEY> evpKey = streamToKey(keyStream, typeFrom, publicKey); //throw SysError + return keyToStream(evpKey.get(), typeTo, publicKey); //throw SysError +} + + +void zen::verifySignature(const std::string& message, const std::string& signature, const std::string& publicKeyStream, RsaStreamType streamType) //throw SysError +{ + std::shared_ptr<EVP_PKEY> publicKey = streamToKey(publicKeyStream, streamType, true /*publicKey*/); //throw SysError + ::verifySignature(message, signature, publicKey.get()); //throw SysError +} + + +namespace +{ +std::wstring formatSslErrorRaw(int ec) +{ + switch (ec) + { + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_NONE); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_SSL); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_READ); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_WRITE); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_X509_LOOKUP); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_SYSCALL); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_ZERO_RETURN); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_CONNECT); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_ACCEPT); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_ASYNC); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_ASYNC_JOB); + } + return L"Unknown SSL error: " + numberTo<std::wstring>(ec); +} + + +std::wstring formatX509ErrorRaw(long ec) +{ + switch (ec) + { + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_OK); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNSPECIFIED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_GET_CRL); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CERT_SIGNATURE_FAILURE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CRL_SIGNATURE_FAILURE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CERT_NOT_YET_VALID); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CERT_HAS_EXPIRED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CRL_NOT_YET_VALID); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CRL_HAS_EXPIRED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_OUT_OF_MEM); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CERT_CHAIN_TOO_LONG); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CERT_REVOKED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_INVALID_CA); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_PATH_LENGTH_EXCEEDED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_INVALID_PURPOSE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CERT_UNTRUSTED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CERT_REJECTED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUBJECT_ISSUER_MISMATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_AKID_SKID_MISMATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_KEYUSAGE_NO_CERTSIGN); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_INVALID_NON_CA); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_INVALID_EXTENSION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_INVALID_POLICY_EXTENSION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_NO_EXPLICIT_POLICY); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_DIFFERENT_CRL_SCOPE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNNESTED_RESOURCE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_PERMITTED_VIOLATION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_EXCLUDED_VIOLATION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUBTREE_MINMAX); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_APPLICATION_VERIFICATION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CRL_PATH_VALIDATION_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_PATH_LOOP); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUITE_B_INVALID_VERSION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUITE_B_INVALID_ALGORITHM); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUITE_B_INVALID_CURVE); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_HOSTNAME_MISMATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_EMAIL_MISMATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_IP_ADDRESS_MISMATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_DANE_NO_MATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_EE_KEY_TOO_SMALL); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CA_KEY_TOO_SMALL); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_CA_MD_TOO_WEAK); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_INVALID_CALL); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_STORE_LOOKUP); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_NO_VALID_SCTS); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_OCSP_VERIFY_NEEDED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_OCSP_VERIFY_FAILED); + ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_OCSP_CERT_UNKNOWN); + } + return L"Unknown X509 error: " + numberTo<std::wstring>(ec); +} +} + +class TlsContext::Impl +{ +public: + Impl(int socket, //throw SysError + const std::string& server, + const Zstring* caCertFilePath /*optional: enable certificate validation*/) + { + ZEN_ON_SCOPE_FAIL(cleanup(); /*destructor call would lead to member double clean-up!!!*/); + + ctx_ = ::SSL_CTX_new(TLS_client_method()); + if (!ctx_) + throw SysError(formatLastOpenSSLError(L"SSL_CTX_new")); + + ssl_ = ::SSL_new(ctx_); + if (!ssl_) + throw SysError(formatLastOpenSSLError(L"SSL_new")); + + BIO* bio = ::BIO_new_socket(socket, BIO_NOCLOSE); + if (!bio) + throw SysError(formatLastOpenSSLError(L"BIO_new_socket")); + ::SSL_set0_rbio(ssl_, bio); //pass ownership + + if (::BIO_up_ref(bio) != 1) + throw SysError(formatLastOpenSSLError(L"BIO_up_ref")); + ::SSL_set0_wbio(ssl_, bio); //pass ownership + + assert(::SSL_get_mode(ssl_) == SSL_MODE_AUTO_RETRY); //verify OpenSSL default + ::SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE); + + if (::SSL_set_tlsext_host_name(ssl_, server.c_str()) != 1) //enable SNI (Server Name Indication) + throw SysError(formatLastOpenSSLError(L"SSL_set_tlsext_host_name")); + + if (caCertFilePath) + { + if (!::SSL_CTX_load_verify_locations(ctx_, utfTo<std::string>(*caCertFilePath).c_str(), nullptr)) + throw SysError(formatLastOpenSSLError(L"SSL_CTX_load_verify_locations")); + //alternative: SSL_CTX_set_default_verify_paths(): use OpenSSL default paths considering SSL_CERT_FILE environment variable + + //1. enable check for valid certificate: see SSL_get_verify_result() + ::SSL_set_verify(ssl_, SSL_VERIFY_PEER, nullptr); + + //2. enable check that the certificate matches our host: see SSL_get_verify_result() + if (::SSL_set1_host(ssl_, server.c_str()) != 1) + throw SysError(L"SSL_set1_host failed."); //no more error details + } + + const int rv = ::SSL_connect(ssl_); //implicitly calls SSL_set_connect_state() + if (rv != 1) + throw SysError(formatLastOpenSSLError(L"SSL_connect") + L" " + formatSslErrorRaw(::SSL_get_error(ssl_, rv))); + + if (caCertFilePath) + { + const long verifyResult = ::SSL_get_verify_result(ssl_); + if (verifyResult != X509_V_OK) + throw SysError(formatSystemError(L"SSL_get_verify_result", formatX509ErrorRaw(verifyResult), L"")); + } + } + + ~Impl() + { + //"SSL_shutdown() must not be called if a previous fatal error has occurred on a connection" + const bool scopeFail = std::uncaught_exceptions() > exeptionCount_; + if (!scopeFail) + { + //"It is acceptable for an application to only send its shutdown alert and then close + //the underlying connection without waiting for the peer's response." + [[maybe_unused]] const int rv = ::SSL_shutdown(ssl_); + assert(rv == 0); //"the close_notify was sent but the peer did not send it back yet." + } + + cleanup(); + } + + size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! + { + if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + + size_t bytesReceived = 0; + const int rv = ::SSL_read_ex(ssl_, buffer, bytesToRead, &bytesReceived); + if (rv != 1) + { + const int sslError = ::SSL_get_error(ssl_, rv); + if (sslError == SSL_ERROR_ZERO_RETURN || //EOF + close_notify alert + (sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0)) //EOF: only expected for HTTP/1.0 + return 0; + throw SysError(formatLastOpenSSLError(L"SSL_read_ex") + L" " + formatSslErrorRaw(sslError)); + } + assert(bytesReceived > 0); //SSL_read_ex() considers EOF an error! + if (bytesReceived > bytesToRead) //better safe than sorry + throw SysError(L"SSL_read_ex: buffer overflow."); + + return bytesReceived; //"zero indicates end of file" + } + + size_t tryWrite(const void* buffer, size_t bytesToWrite) //throw SysError; may return short! CONTRACT: bytesToWrite > 0 + { + if (bytesToWrite == 0) + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + + size_t bytesWritten = 0; + const int rv = ::SSL_write_ex(ssl_, buffer, bytesToWrite, &bytesWritten); + if (rv != 1) + throw SysError(formatLastOpenSSLError(L"SSL_write_ex") + L" " + formatSslErrorRaw(::SSL_get_error(ssl_, rv))); + + if (bytesWritten > bytesToWrite) + throw SysError(L"SSL_write_ex: buffer overflow."); + if (bytesWritten == 0) + throw SysError(L"SSL_write_ex: zero bytes processed"); + + return bytesWritten; + } + +private: + void cleanup() + { + if (ssl_) + ::SSL_free(ssl_); + + if (ctx_) + ::SSL_CTX_free(ctx_); + } + + Impl (const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + SSL_CTX* ctx_ = nullptr; + SSL* ssl_ = nullptr; + const int exeptionCount_ = std::uncaught_exceptions(); +}; + + +zen::TlsContext::TlsContext(int socket, const Zstring& server, const Zstring* caCertFilePath) : + pimpl_(std::make_unique<Impl>(socket, utfTo<std::string>(server), caCertFilePath)) {} //throw SysError +zen::TlsContext::~TlsContext() {} +size_t zen::TlsContext::tryRead ( void* buffer, size_t bytesToRead ) { return pimpl_->tryRead(buffer, bytesToRead); } //throw SysError +size_t zen::TlsContext::tryWrite(const void* buffer, size_t bytesToWrite) { return pimpl_->tryWrite(buffer, bytesToWrite); } //throw SysError diff --git a/zen/open_ssl.h b/zen/open_ssl.h new file mode 100644 index 00000000..5bf4e9ce --- /dev/null +++ b/zen/open_ssl.h @@ -0,0 +1,49 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef OPEN_SSL_H_801974580936508934568792347506 +#define OPEN_SSL_H_801974580936508934568792347506 + +#include <zen/zstring.h> +#include <zen/sys_error.h> + + +namespace zen //init OpenSSL before use! +{ +enum class RsaStreamType +{ + pkix, //base-64-encoded SubjectPublicKeyInfo structure ("BEGIN PUBLIC KEY") + pkcs1, //base-64-encoded RSA number and exponent ("BEGIN RSA PUBLIC KEY") + pkcs1_raw +}; + +//verify signatures produced with: "openssl dgst -sha256 -sign private.pem -out file.sig file.txt" +void verifySignature(const std::string& message, + const std::string& signature, + const std::string& publicKeyStream, + RsaStreamType streamType); //throw SysError + +std::string convertRsaKey(const std::string& keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey); //throw SysError + + +class TlsContext +{ +public: + TlsContext(int socket, //throw SysError + const Zstring& server, + const Zstring* caCertFilePath /*optional: enable certificate validation*/); + ~TlsContext(); + + size_t tryRead( void* buffer, size_t bytesToRead ); //throw SysError; may return short, only 0 means EOF! + size_t tryWrite(const void* buffer, size_t bytesToWrite); //throw SysError; may return short! CONTRACT: bytesToWrite > 0 + +private: + class Impl; + const std::unique_ptr<Impl> pimpl_; +}; +} + +#endif //OPEN_SSL_H_801974580936508934568792347506 diff --git a/zen/socket.h b/zen/socket.h index 0b4c823d..7ca0c93f 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -105,7 +105,7 @@ size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //thro THROW_LAST_SYS_ERROR_WSA(L"recv"); if (static_cast<size_t>(bytesReceived) > bytesToRead) //better safe than sorry - throw SysError(L"HttpInputStream::tryRead: buffer overflow."); + throw SysError(L"recv: buffer overflow."); return bytesReceived; //"zero indicates end of file" } @@ -138,10 +138,11 @@ size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite } +//initiate termination of connection by sending TCP FIN package inline void shutdownSocketSend(SocketType socket) //throw SysError { - if (::shutdown(socket, SHUT_WR) != 0) + if (::shutdown(socket, SHUT_WR) != 0) THROW_LAST_SYS_ERROR_WSA(L"shutdown"); } diff --git a/zen/warn_static.h b/zen/warn_static.h index fb8fbb95..17e7cf25 100644 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -8,10 +8,10 @@ #define WARN_STATIC_H_08724567834560832745 /* -Portable Compile-Time Warning ------------------------------ -Usage: - warn_static("my message") + Portable Compile-Time Warning + ----------------------------- + Usage: + warn_static("my message") */ #define ZEN_STATIC_WARNING_STRINGIZE(NUM) #NUM |