diff options
author | B. Stack <bgstack15@gmail.com> | 2022-01-04 10:50:14 -0500 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2022-01-04 10:50:14 -0500 |
commit | 75bc2e56125125511a0505718dcb2c3d4150a933 (patch) | |
tree | b8252ff8a09d9143ed2dc299d082f9d86535c1a2 /zen | |
parent | Merge branch 'b11.15' into 'master' (diff) | |
download | FreeFileSync-75bc2e56125125511a0505718dcb2c3d4150a933.tar.gz FreeFileSync-75bc2e56125125511a0505718dcb2c3d4150a933.tar.bz2 FreeFileSync-75bc2e56125125511a0505718dcb2c3d4150a933.zip |
add upstream 11.16
Diffstat (limited to 'zen')
-rw-r--r-- | zen/file_access.cpp | 59 | ||||
-rw-r--r-- | zen/http.cpp | 380 | ||||
-rw-r--r-- | zen/http.h | 12 | ||||
-rw-r--r-- | zen/open_ssl.cpp | 709 | ||||
-rw-r--r-- | zen/open_ssl.h | 17 | ||||
-rw-r--r-- | zen/stream_buffer.h | 161 | ||||
-rw-r--r-- | zen/string_base.h | 6 | ||||
-rw-r--r-- | zen/sys_info.cpp | 27 | ||||
-rw-r--r-- | zen/sys_info.h | 2 | ||||
-rw-r--r-- | zen/type_traits.h | 6 | ||||
-rw-r--r-- | zen/zstring.cpp | 8 | ||||
-rw-r--r-- | zen/zstring.h | 9 |
12 files changed, 718 insertions, 678 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 6940b22f..2fbcf803 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -17,7 +17,7 @@ #include "guid.h" #include <sys/vfs.h> //statfs - #include <sys/time.h> //lutimes + //#include <sys/time.h> //lutimes #ifdef HAVE_SELINUX #include <selinux/selinux.h> #endif @@ -309,42 +309,51 @@ namespace { void setWriteTimeNative(const Zstring& itemPath, const timespec& modTime, ProcSymlink procSl) //throw FileError { - /* - [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? - => fallback to "retarded-idiot version"! -- DarkByte - - [2015-03-09] - - cannot reproduce issues with NTFS and utimensat() on Ubuntu - - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch" - => let's give utimensat another chance: - using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"! - */ + /* [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? + => fallback to "retarded-idiot version"! -- DarkByte + + [2015-03-09] + - cannot reproduce issues with NTFS and utimensat() on Ubuntu + - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch" + => let's give utimensat another chance: + using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"! + cp: https://github.com/coreutils/coreutils/blob/master/src/cp.c + => utimens: https://github.com/coreutils/gnulib/blob/master/lib/utimens.c + touch: https://github.com/coreutils/coreutils/blob/master/src/touch.c + => fdutimensat: https://github.com/coreutils/gnulib/blob/master/lib/fdutimensat.c */ timespec newTimes[2] = {}; - newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: https://freefilesync.org/forum/viewtopic.php?t=1701 + newTimes[0].tv_sec = ::time(nullptr); //access time; don't use UTIME_NOW/UTIME_OMIT: more bugs! https://freefilesync.org/forum/viewtopic.php?t=1701 newTimes[1] = modTime; //modification time //test: even modTime == 0 is correctly applied (no NOOP!) test2: same behavior for "utime()" - if (procSl == ProcSymlink::follow) + //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP: + //https://freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works (but not for gvfs SFTP) + if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, procSl == ProcSymlink::direct ? AT_SYMLINK_NOFOLLOW : 0) == 0) + return; + try { - //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP: - //https://freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works (but not for gvfs SFTP) - if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, 0) == 0) - return; + if (procSl == ProcSymlink::direct) + try + { + if (getItemType(itemPath) == ItemType::symlink) //throw FileError + THROW_LAST_SYS_ERROR("utimensat(AT_SYMLINK_NOFOLLOW)"); //use lutimes()? just a wrapper around utimensat()! + //else: fall back + } + catch (const FileError& e) { throw SysError(e.toString()); } //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: https://freefilesync.org/forum/viewtopic.php?t=387 - const int fdFile = ::open(itemPath.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); //2017-07-04: O_WRONLY | O_APPEND seems to avoid EOPNOTSUPP on gvfs SFTP! + //2017-07-04: O_WRONLY | O_APPEND seems to avoid EOPNOTSUPP on gvfs SFTP! + const int fdFile = ::open(itemPath.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); if (fdFile == -1) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "open"); + THROW_LAST_SYS_ERROR("open"); ZEN_ON_SCOPE_EXIT(::close(fdFile)); if (::futimens(fdFile, newTimes) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "futimens"); - } - else - { - if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "utimensat"); + THROW_LAST_SYS_ERROR("futimens"); + + //need more fallbacks? e.g. futimes()? careful, bugs! futimes() rounds instead of truncates when falling back on utime()! } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), e.toString()); } } diff --git a/zen/http.cpp b/zen/http.cpp index 05ed81d1..fe3b8c4c 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -5,11 +5,15 @@ // ***************************************************************************** #include "http.h" - #include "socket.h" - #include "open_ssl.h" + + #include <libcurl/curl_wrap.h> //DON'T include <curl/curl.h> directly! + #include "stream_buffer.h" + #include "thread.h" using namespace zen; +const std::chrono::seconds HTTP_ACCESS_TIME_OUT(20); + @@ -17,15 +21,15 @@ class HttpInputStream::Impl { public: Impl(const Zstring& url, - const std::string* postBuf /*issue POST if bound, GET otherwise*/, + const std::string* postBuf, //issue POST if bound, GET otherwise const std::string& contentType, //required for POST - bool disableGetCache /*not relevant for POST (= never cached)*/, + bool disableGetCache, //not relevant for POST (= never cached) const Zstring& userAgent, - const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IoCallback& notifyUnbufferedIO) : //throw SysError, X + const Zstring& caCertFilePath, //optional: enable certificate validation + const IoCallback& notifyUnbufferedIO /*throw X*/) : //throw SysError, X 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!!! //may be sending large POST: call back first if (notifyUnbufferedIO_) notifyUnbufferedIO_(0); //throw X @@ -43,76 +47,95 @@ public: throw SysError(L"URL uses unexpected protocol."); }(); - assert(postBuf || contentType.empty()); - std::map<std::string, std::string, LessAsciiNoCase> headers; + assert(postBuf || contentType.empty()); if (postBuf && !contentType.empty()) headers["Content-Type"] = contentType; - 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 and gzip transfer encoding" => HTTP 1.0 => no "Content-Length" support! - headers["Host" ] = utfTo<std::string>(server); //only required for HTTP/1.1 but a few servers expect it even for HTTP/1.0 - headers["User-Agent"] = utfTo<std::string>(userAgent); - headers["Accept" ] = "*/*"; //won't hurt? - - if (!postBuf /*HTTP GET*/ && disableGetCache) - headers["Pragma"] = "no-cache"; //HTTP 1.0 only! superseeded by "Cache-Control" - - if (postBuf) - headers["Content-Length"] = numberTo<std::string>(postBuf->size()); - - //https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line - std::string msg = (postBuf ? "POST " : "GET ") + utfTo<std::string>(page) + " HTTP/1.0\r\n"; - for (const auto& [name, value] : headers) - msg += name + ": " + value + "\r\n"; - msg += "\r\n"; - if (postBuf) - msg += *postBuf; - - //send request - for (size_t bytesToSend = msg.size(); bytesToSend > 0;) - bytesToSend -= tlsCtx_ ? - tlsCtx_->tryWrite( &*(msg.end() - bytesToSend), bytesToSend) : //throw SysError - tryWriteSocket(socket_->get(), &*(msg.end() - bytesToSend), bytesToSend); //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; - const std::string headerDelim = "\r\n\r\n"; - for (std::string buf;;) + if (!postBuf /*=> HTTP GET*/ && disableGetCache) //libcurl doesn't cache internally, so it should be enough to set this header + headers["Cache-Control"] = "no-cache"; //= similar to WinInet's INTERNET_FLAG_RELOAD + //caveat: INTERNET_FLAG_RELOAD issues "Pragma: no-cache" instead if "request is going through a proxy" + + + auto promiseHeader = std::make_shared<std::promise<std::string>>(); + std::future<std::string> futHeader = promiseHeader->get_future(); + + worker_ = InterruptibleThread([asyncStreamOut = this->asyncStreamIn_, promiseHeader, headers = std::move(headers), + server, useTls, caCertFilePath, userAgent = utfTo<std::string>(userAgent), + postBuf = postBuf ? std::optional<std::string>(*postBuf) : std::nullopt, //[!] life-time! + serverRelPath = utfTo<std::string>(page)] { - const size_t blockSize = std::min(static_cast<size_t>(1024), memBuf_.size()); //smaller block size: try to only read header part - buf.resize(buf.size() + blockSize); - const size_t bytesReceived = tryRead(&*(buf.end() - blockSize), blockSize); //throw SysError - buf.resize(buf.size() - (blockSize - bytesReceived)); //caveat: unsigned arithmetics + setCurrentThreadName(Zstr("HttpInputStream ") + server); - if (contains(buf, headerDelim)) + bool headerReceived = false; + try { - headBuf = beforeFirst(buf, headerDelim, IfNotFoundReturn::none); - const std::string bodyBuf = afterFirst (buf, headerDelim, IfNotFoundReturn::none); - //put excess bytes into instance buffer for body retrieval - assert(bufPos_ == 0 && bufPosEnd_ == 0); - bufPosEnd_ = bodyBuf.size(); - std::copy(bodyBuf.begin(), bodyBuf.end(), reinterpret_cast<char*>(&memBuf_[0])); - break; + std::vector<std::string> curlHeaders; + for (const auto& [name, value] : headers) + curlHeaders.push_back(name + ": " + value); + + std::vector<CurlOption> extraOptions {{CURLOPT_USERAGENT, userAgent.c_str()}}; + //CURLOPT_FOLLOWLOCATION already off by default :) + if (postBuf) + { + extraOptions.emplace_back(CURLOPT_POSTFIELDS, postBuf->c_str()); + extraOptions.emplace_back(CURLOPT_POSTFIELDSIZE_LARGE, postBuf->size()); //postBuf not necessarily null-terminated! + } + + //carefully with these callbacks! First receive HTTP header without blocking, + //and only then allow AsyncStreamBuffer::write() which can block! + + std::string headerBuf; + auto onHeaderData = [&](const std::string_view& headerLine) + { + if (headerReceived) + throw SysError(L"Unexpected header data after end of HTTP header."); + + //"The callback will be called once for each header and only complete header lines are passed on to the callback" (including \r\n at the end) + headerBuf += headerLine; + + if (headerLine == "\r\n") + { + headerReceived = true; + promiseHeader->set_value(std::move(headerBuf)); + } + }; + + HttpSession httpSession(server, useTls, caCertFilePath, HTTP_ACCESS_TIME_OUT); //throw SysError + + auto writeResponse = [&](std::span<const char> buf) + { + if (!headerReceived) + throw SysError(L"Received HTTP body without header."); + + return asyncStreamOut->write(buf.data(), buf.size()); //throw ThreadStopRequest + }; + + httpSession.perform(serverRelPath, //throw SysError, ThreadStopRequest + curlHeaders, extraOptions, + writeResponse /*throw ThreadStopRequest*/, + nullptr /*readRequest*/, + onHeaderData /*throw SysError*/); + + if (!headerReceived) + throw SysError(L"HTTP response is missing header."); + + asyncStreamOut->closeStream(); } - if (bytesReceived == 0) - break; - } - //parse header - const std::string statusBuf = beforeFirst(headBuf, "\r\n", IfNotFoundReturn::all); - const std::string headersBuf = afterFirst (headBuf, "\r\n", IfNotFoundReturn::none); + catch (SysError&) //let ThreadStopRequest pass through! + { + if (!headerReceived) + promiseHeader->set_exception(std::current_exception()); + + asyncStreamOut->setWriteError(std::current_exception()); + } + }); + + const std::string headBuf = futHeader.get(); //throw SysError + //parse header: https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line + const std::string& statusBuf = beforeFirst(headBuf, "\r\n", IfNotFoundReturn::all); + const std::string& headersBuf = afterFirst (headBuf, "\r\n", IfNotFoundReturn::none); const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitOnEmpty::allow); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF if (statusItems.size() < 2 || !startsWith(statusItems[0], "HTTP/")) @@ -124,9 +147,9 @@ public: responseHeaders_[trimCpy(beforeFirst(line, ':', IfNotFoundReturn::all))] = /**/ trimCpy(afterFirst (line, ':', IfNotFoundReturn::none)); - //try to get "Content-Length" header if available - if (const std::string* value = getHeader("Content-Length")) - contentRemaining_ = stringTo<int64_t>(*value) - (bufPosEnd_ - bufPos_); + /* let's NOT consider "Content-Length" header: + - may be unavailable ("Transfer-Encoding: chunked") + - may refer to compressed data size ("Content-Encoding: gzip") */ //let's not get too finicky: at least report the logical amount of bytes sent/received (excluding HTTP headers) if (notifyUnbufferedIO_) notifyUnbufferedIO_(postBuf ? postBuf->size() : 0); //throw X @@ -134,7 +157,6 @@ public: ~Impl() { cleanup(); } - const int getStatusCode() const { return statusCode_; } const std::string* getHeader(const std::string& name) const @@ -145,79 +167,37 @@ public: size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! { - const size_t blockSize = getBlockSize(); - assert(memBuf_.size() >= blockSize); - assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); - - auto it = static_cast<std::byte*>(buffer); - const auto itEnd = it + bytesToRead; - for (;;) - { - const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_); - std::memcpy(it, &memBuf_[0] + bufPos_, junkSize); - bufPos_ += junkSize; - it += junkSize; - - if (it == itEnd) - break; - //-------------------------------------------------------------------- - const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 - bufPos_ = 0; - bufPosEnd_ = bytesRead; - - if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X - - if (bytesRead == 0) //end of file - break; - } - return it - static_cast<std::byte*>(buffer); + const size_t bytesRead = asyncStreamIn_->read(buffer, bytesToRead); //throw SysError + reportBytesProcessed(); //throw X + return bytesRead; + //no need for asyncStreamIn_->checkWriteErrors(): once end of stream is reached, asyncStreamOut->closeStream() was called => no errors occured } size_t getBlockSize() const { return 64 * 1024; } private: - size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! - { - assert(bytesToRead <= getBlockSize()); //block size might be 1000 while reading HTTP header - - if (contentRemaining_ >= 0) - { - if (contentRemaining_ == 0) - return 0; - bytesToRead = static_cast<size_t>(std::min(static_cast<int64_t>(bytesToRead), contentRemaining_)); //[!] contentRemaining_ > 4 GB possible! - } - 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; - - if (bytesReceived == 0 && contentRemaining_ > 0) - throw SysError(formatSystemError("HttpInputStream::tryRead", L"", L"Incomplete server response: " + - numberTo<std::wstring>(contentRemaining_) + L" more bytes expected.")); + Impl (const Impl&) = delete; + Impl& operator=(const Impl&) = delete; - return bytesReceived; //"zero indicates end of file" + void reportBytesProcessed() //throw X + { + const int64_t totalBytesDownloaded = asyncStreamIn_->getTotalBytesWritten(); + if (notifyUnbufferedIO_) notifyUnbufferedIO_(totalBytesDownloaded - totalBytesReported_); //throw X + totalBytesReported_ = totalBytesDownloaded; } void cleanup() { + asyncStreamIn_->setReadError(std::make_exception_ptr(ThreadStopRequest())); } - 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 + std::shared_ptr<AsyncStreamBuffer> asyncStreamIn_ = std::make_shared<AsyncStreamBuffer>(512 * 1024); + InterruptibleThread worker_; + int64_t totalBytesReported_ = 0; int statusCode_ = 0; std::map<std::string, std::string, LessAsciiNoCase> responseHeaders_; - int64_t contentRemaining_ = -1; //consider "Content-Length" if available - const IoCallback notifyUnbufferedIO_; //throw X - - std::vector<std::byte> memBuf_ = std::vector<std::byte>(getBlockSize()); - size_t bufPos_ = 0; //buffered I/O; see file_io.cpp - size_t bufPosEnd_ = 0; // }; @@ -238,7 +218,7 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, const std::string* postBuf /*issue POST if bound, GET otherwise*/, const std::string& contentType, //required for POST const Zstring& userAgent, - const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const Zstring& caCertFilePath /*optional: enable certificate validation*/, const IoCallback& notifyUnbufferedIO) //throw SysError, X { Zstring urlRed = url; @@ -338,14 +318,14 @@ std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const st } -HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath, const IoCallback& notifyUnbufferedIO) //throw SysError, X +HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring& caCertFilePath, const IoCallback& notifyUnbufferedIO) //throw SysError, X { return sendHttpRequestImpl(url, nullptr /*postBuf*/, "" /*contentType*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X, X } 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, X + const Zstring& userAgent, const Zstring& caCertFilePath, const IoCallback& notifyUnbufferedIO) //throw SysError, X { return sendHttpPost(url, xWwwFormUrlEncode(postParams), "application/x-www-form-urlencoded", userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X } @@ -353,7 +333,7 @@ HttpInputStream zen::sendHttpPost(const Zstring& url, const std::vector<std::pai HttpInputStream zen::sendHttpPost(const Zstring& url, const std::string& postBuf, const std::string& contentType, - const Zstring& userAgent, const Zstring* caCertFilePath, const IoCallback& notifyUnbufferedIO) //throw SysError, X + const Zstring& userAgent, const Zstring& caCertFilePath, const IoCallback& notifyUnbufferedIO) //throw SysError, X { return sendHttpRequestImpl(url, &postBuf, contentType, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X } @@ -368,7 +348,7 @@ bool zen::internetIsAlive() //noexcept "" /*contentType*/, true /*disableGetCache*/, Zstr("FreeFileSync"), - nullptr /*caCertFilePath*/, + Zstring() /*caCertFilePath*/, nullptr /*notifyUnbufferedIO*/); //throw SysError const int statusCode = response->getStatusCode(); @@ -386,72 +366,72 @@ std::wstring zen::formatHttpError(int sc) { switch (sc) { - //*INDENT-OFF* - case 300: return L"Multiple choices."; - case 301: return L"Moved permanently."; - case 302: return L"Moved temporarily."; - case 303: return L"See other"; - case 304: return L"Not modified."; - case 305: return L"Use proxy."; - case 306: return L"Switch proxy."; - case 307: return L"Temporary redirect."; - case 308: return L"Permanent redirect."; - - case 400: return L"Bad request."; - case 401: return L"Unauthorized."; - case 402: return L"Payment required."; - case 403: return L"Forbidden."; - case 404: return L"Not found."; - case 405: return L"Method not allowed."; - case 406: return L"Not acceptable."; - case 407: return L"Proxy authentication required."; - case 408: return L"Request timeout."; - case 409: return L"Conflict."; - case 410: return L"Gone."; - case 411: return L"Length required."; - case 412: return L"Precondition failed."; - case 413: return L"Payload too large."; - case 414: return L"URI too long."; - case 415: return L"Unsupported media type."; - case 416: return L"Range not satisfiable."; - case 417: return L"Expectation failed."; - case 418: return L"I'm a teapot."; - case 421: return L"Misdirected request."; - case 422: return L"Unprocessable entity."; - case 423: return L"Locked."; - case 424: return L"Failed dependency."; - case 425: return L"Too early."; - case 426: return L"Upgrade required."; - case 428: return L"Precondition required."; - case 429: return L"Too many requests."; - case 431: return L"Request header fields too large."; - case 451: return L"Unavailable for legal reasons."; - - case 500: return L"Internal server error."; - case 501: return L"Not implemented."; - case 502: return L"Bad gateway."; - case 503: return L"Service unavailable."; - case 504: return L"Gateway timeout."; - case 505: return L"HTTP version not supported."; - case 506: return L"Variant also negotiates."; - case 507: return L"Insufficient storage."; - case 508: return L"Loop detected."; - case 510: return L"Not extended."; - case 511: return L"Network authentication required."; - - //Cloudflare errors regarding origin server: - case 520: return L"Unknown error (Cloudflare)"; - case 521: return L"Web server is down (Cloudflare)"; - case 522: return L"Connection timed out (Cloudflare)"; - case 523: return L"Origin is unreachable (Cloudflare)"; - case 524: return L"A timeout occurred (Cloudflare)"; - case 525: return L"SSL handshake failed (Cloudflare)"; - case 526: return L"Invalid SSL certificate (Cloudflare)"; - case 527: return L"Railgun error (Cloudflare)"; - case 530: return L"Origin DNS error (Cloudflare)"; - - default: return L""; - //*INDENT-ON* + //*INDENT-OFF* + case 300: return L"Multiple choices."; + case 301: return L"Moved permanently."; + case 302: return L"Moved temporarily."; + case 303: return L"See other"; + case 304: return L"Not modified."; + case 305: return L"Use proxy."; + case 306: return L"Switch proxy."; + case 307: return L"Temporary redirect."; + case 308: return L"Permanent redirect."; + + case 400: return L"Bad request."; + case 401: return L"Unauthorized."; + case 402: return L"Payment required."; + case 403: return L"Forbidden."; + case 404: return L"Not found."; + case 405: return L"Method not allowed."; + case 406: return L"Not acceptable."; + case 407: return L"Proxy authentication required."; + case 408: return L"Request timeout."; + case 409: return L"Conflict."; + case 410: return L"Gone."; + case 411: return L"Length required."; + case 412: return L"Precondition failed."; + case 413: return L"Payload too large."; + case 414: return L"URI too long."; + case 415: return L"Unsupported media type."; + case 416: return L"Range not satisfiable."; + case 417: return L"Expectation failed."; + case 418: return L"I'm a teapot."; + case 421: return L"Misdirected request."; + case 422: return L"Unprocessable entity."; + case 423: return L"Locked."; + case 424: return L"Failed dependency."; + case 425: return L"Too early."; + case 426: return L"Upgrade required."; + case 428: return L"Precondition required."; + case 429: return L"Too many requests."; + case 431: return L"Request header fields too large."; + case 451: return L"Unavailable for legal reasons."; + + case 500: return L"Internal server error."; + case 501: return L"Not implemented."; + case 502: return L"Bad gateway."; + case 503: return L"Service unavailable."; + case 504: return L"Gateway timeout."; + case 505: return L"HTTP version not supported."; + case 506: return L"Variant also negotiates."; + case 507: return L"Insufficient storage."; + case 508: return L"Loop detected."; + case 510: return L"Not extended."; + case 511: return L"Network authentication required."; + + //Cloudflare errors regarding origin server: + case 520: return L"Unknown error (Cloudflare)"; + case 521: return L"Web server is down (Cloudflare)"; + case 522: return L"Connection timed out (Cloudflare)"; + case 523: return L"Origin is unreachable (Cloudflare)"; + case 524: return L"A timeout occurred (Cloudflare)"; + case 525: return L"SSL handshake failed (Cloudflare)"; + case 526: return L"Invalid SSL certificate (Cloudflare)"; + case 527: return L"Railgun error (Cloudflare)"; + case 530: return L"Origin DNS error (Cloudflare)"; + + default: return L""; + //*INDENT-ON* } }(); @@ -13,10 +13,8 @@ namespace zen { -/* - - thread-safe! (Window/Linux/macOS) - - Linux/macOS: init OpenSSL before use! -*/ +/* - thread-safe! (Window/Linux/macOS) + - Linux/macOS: init libcurl before use! */ class HttpInputStream { public: @@ -38,19 +36,19 @@ private: HttpInputStream sendHttpGet(const Zstring& url, const Zstring& userAgent, - const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const Zstring& caCertFilePath /*optional: enable certificate validation*/, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X 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 Zstring& caCertFilePath /*optional: enable certificate validation*/, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X HttpInputStream sendHttpPost(const Zstring& url, const std::string& postBuf, const std::string& contentType, const Zstring& userAgent, - const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const Zstring& caCertFilePath /*optional: enable certificate validation*/, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X bool internetIsAlive(); //noexcept diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index e875351c..62560d78 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -5,13 +5,18 @@ // ***************************************************************************** #include "open_ssl.h" -#include <bit> //std::endian -#include <stdexcept> #include "base64.h" -#include "build_info.h" +#include "thread.h" #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/ssl.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + #include <openssl/core_names.h> + #include <openssl/encoder.h> + #include <openssl/decoder.h> + #include <openssl/param_build.h> +#endif + using namespace zen; @@ -29,7 +34,8 @@ void zen::openSslInit() //see apps_shutdown(): https://github.com/openssl/openssl/blob/master/apps/openssl.c //see Curl_ossl_cleanup(): https://github.com/curl/curl/blob/master/lib/vtls/openssl.c - //excplicitly init OpenSSL on main thread: seems to initialize atomically! But it still might help to avoid issues: + assert(runningOnMainThread()); + //excplicitly init OpenSSL on main thread: seems to initialize atomically! But it still might help to avoid issues: [[maybe_unused]] const int rv = ::OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT | OPENSSL_INIT_NO_LOAD_CONFIG, nullptr); assert(rv == 1); //https://www.openssl.org/docs/man1.1.0/ssl/OPENSSL_init_ssl.html } @@ -101,67 +107,83 @@ std::shared_ptr<EVP_PKEY> generateRsaKeyPair(int bits) //throw SysError //================================================================================ -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 char* functionName) //throw SysError -{ - BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); - if (!bio) - throw SysError(formatLastOpenSSLError("BIO_new_mem_buf")); - ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - - if (EVP_PKEY* evp = bioToEvp(bio, //BIO* bp - nullptr, //EVP_PKEY** x - nullptr, //pem_password_cb* cb - nullptr)) //void* u - return std::shared_ptr<EVP_PKEY>(evp, ::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 char* functionName) //throw SysError -{ - BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); - if (!bio) - throw SysError(formatLastOpenSSLError("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* evp = ::EVP_PKEY_new(); - if (!evp) - throw SysError(formatLastOpenSSLError("EVP_PKEY_new")); - std::shared_ptr<EVP_PKEY> sharedKey(evp, ::EVP_PKEY_free); - - if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted) - throw SysError(formatLastOpenSSLError("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, "PEM_read_bio_PUBKEY") : //throw SysError - streamToEvpKey(keyStream, ::PEM_read_bio_PrivateKey, "PEM_read_bio_PrivateKey"); // + { + BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); + if (!bio) + throw SysError(formatLastOpenSSLError("BIO_new_mem_buf")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + if (EVP_PKEY* evp = (publicKey ? + ::PEM_read_bio_PUBKEY : + ::PEM_read_bio_PrivateKey) + (bio, //BIO* bp + nullptr, //EVP_PKEY** x + nullptr, //pem_password_cb* cb + nullptr)) //void* u + return std::shared_ptr<EVP_PKEY>(evp, ::EVP_PKEY_free); + throw SysError(formatLastOpenSSLError(publicKey ? "PEM_read_bio_PUBKEY" : "PEM_read_bio_PrivateKey")); + } case RsaStreamType::pkcs1: - return publicKey ? - streamToEvpKey(keyStream, ::PEM_read_bio_RSAPublicKey, "PEM_read_bio_RSAPublicKey") : //throw SysError - streamToEvpKey(keyStream, ::PEM_read_bio_RSAPrivateKey, "PEM_read_bio_RSAPrivateKey"); // + { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY* evp = nullptr; + auto guardEvp = makeGuard<ScopeGuardRunMode::onExit>([&] { if (evp) ::EVP_PKEY_free(evp); }); + + const int selection = publicKey ? OSSL_KEYMGMT_SELECT_PUBLIC_KEY : OSSL_KEYMGMT_SELECT_PRIVATE_KEY; + + OSSL_DECODER_CTX* decCtx = ::OSSL_DECODER_CTX_new_for_pkey(&evp, //EVP_PKEY** pkey + "PEM", //const char* input_type + nullptr, //const char* input_struct + "RSA", //const char* keytype + selection, //int selection + nullptr, //OSSL_LIB_CTX* libctx + nullptr); //const char* propquery + if (!decCtx) + throw SysError(formatLastOpenSSLError("OSSL_DECODER_CTX_new_for_pkey")); + ZEN_ON_SCOPE_EXIT(::OSSL_DECODER_CTX_free(decCtx)); + + //key stream is password-protected? => OSSL_DECODER_CTX_set_passphrase() + + const unsigned char* keyBuf = reinterpret_cast<const unsigned char*>(keyStream.c_str()); + size_t keyLen = keyStream.size(); + if (::OSSL_DECODER_from_data(decCtx, &keyBuf, &keyLen) != 1) + throw SysError(formatLastOpenSSLError("OSSL_DECODER_from_data")); + + guardEvp.dismiss(); //pass ownership + return std::shared_ptr<EVP_PKEY>(evp, ::EVP_PKEY_free); // +#else + BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); + if (!bio) + throw SysError(formatLastOpenSSLError("BIO_new_mem_buf")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + RSA* rsa = (publicKey ? + ::PEM_read_bio_RSAPublicKey : + ::PEM_read_bio_RSAPrivateKey)(bio, //BIO* bp + nullptr, //RSA** x + nullptr, //pem_password_cb* cb + nullptr); //void* u + if (!rsa) + throw SysError(formatLastOpenSSLError(publicKey ? "PEM_read_bio_RSAPublicKey" : "PEM_read_bio_RSAPrivateKey")); + ZEN_ON_SCOPE_EXIT(::RSA_free(rsa)); + + EVP_PKEY* evp = ::EVP_PKEY_new(); + if (!evp) + throw SysError(formatLastOpenSSLError("EVP_PKEY_new")); + std::shared_ptr<EVP_PKEY> sharedKey(evp, ::EVP_PKEY_free); + + if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted) + throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA")); + + return sharedKey; +#endif + } case RsaStreamType::raw: break; @@ -179,102 +201,113 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp //================================================================================ -using EvpToBioFunc = int (*)(BIO* bio, const EVP_PKEY* evp); - -std::string evpKeyToStream(const EVP_PKEY* evp, EvpToBioFunc evpToBio, const char* functionName) //throw SysError -{ - BIO* bio = ::BIO_new(BIO_s_mem()); - if (!bio) - throw SysError(formatLastOpenSSLError("BIO_new")); - ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - - if (evpToBio(bio, evp) != 1) - throw SysError(formatLastOpenSSLError(functionName)); - //--------------------------------------------- - const int keyLen = BIO_pending(bio); - if (keyLen < 0) - throw SysError(formatLastOpenSSLError("BIO_pending")); - if (keyLen == 0) - throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details - - std::string keyStream(keyLen, '\0'); - - if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen) - throw SysError(formatLastOpenSSLError("BIO_read")); - return keyStream; -} - - -using RsaToBioFunc = int (*)(BIO* bp, const RSA* x); - -std::string evpKeyToStream(const EVP_PKEY* evp, RsaToBioFunc rsaToBio, const char* functionName) //throw SysError -{ - BIO* bio = ::BIO_new(BIO_s_mem()); - if (!bio) - throw SysError(formatLastOpenSSLError("BIO_new")); - ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - - const RSA* rsa = ::EVP_PKEY_get0_RSA(evp); //unowned reference! - if (!rsa) - throw SysError(formatLastOpenSSLError("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("BIO_pending")); - if (keyLen == 0) - throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details - - std::string keyStream(keyLen, '\0'); - - if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen) - throw SysError(formatLastOpenSSLError("BIO_read")); - return keyStream; -} - - -//fix OpenSSL API inconsistencies: -int PEM_write_bio_PrivateKey2(BIO* bio, const EVP_PKEY* key) -{ - return ::PEM_write_bio_PrivateKey(bio, //BIO* bp - key, //const EVP_PKEY* x - nullptr, //const EVP_CIPHER* enc - nullptr, //const unsigned char* kstr - 0, //int klen - nullptr, //pem_password_cb* cb - nullptr); //void* u -} - -int PEM_write_bio_RSAPrivateKey2(BIO* bio, const RSA* rsa) -{ - return ::PEM_write_bio_RSAPrivateKey(bio, //BIO* bp - rsa, //const RSA* x - nullptr, //const EVP_CIPHER* enc - nullptr, //const unsigned char* kstr - 0, //int klen - nullptr, //pem_password_cb* cb - nullptr); //void* u -} - -int PEM_write_bio_RSAPublicKey2(BIO* bio, const RSA* rsa) { return ::PEM_write_bio_RSAPublicKey(bio, rsa); } - -//-------------------------------------------------------------------------------- - std::string keyToStream(const EVP_PKEY* evp, RsaStreamType streamType, bool publicKey) //throw SysError { + //assert(::EVP_PKEY_get_base_id(evp) == EVP_PKEY_RSA); + switch (streamType) { case RsaStreamType::pkix: - return publicKey ? - evpKeyToStream(evp, ::PEM_write_bio_PUBKEY, "PEM_write_bio_PUBKEY") : //throw SysError - evpKeyToStream(evp, ::PEM_write_bio_PrivateKey2, "PEM_write_bio_PrivateKey"); // + { + //fix OpenSSL API inconsistencies: + auto PEM_write_bio_PrivateKey2 = [](BIO* bio, const EVP_PKEY* key) + { + return ::PEM_write_bio_PrivateKey(bio, //BIO* bp + key, //const EVP_PKEY* x + nullptr, //const EVP_CIPHER* enc + nullptr, //const unsigned char* kstr + 0, //int klen + nullptr, //pem_password_cb* cb + nullptr); //void* u + }; + + BIO* bio = ::BIO_new(BIO_s_mem()); + if (!bio) + throw SysError(formatLastOpenSSLError("BIO_new")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + if ((publicKey ? + ::PEM_write_bio_PUBKEY : + PEM_write_bio_PrivateKey2)(bio, evp) != 1) + throw SysError(formatLastOpenSSLError(publicKey ? "PEM_write_bio_PUBKEY" : "PEM_write_bio_PrivateKey")); + //--------------------------------------------- + const int keyLen = BIO_pending(bio); + if (keyLen < 0) + throw SysError(formatLastOpenSSLError("BIO_pending")); + if (keyLen == 0) + throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details + + std::string keyStream(keyLen, '\0'); + + if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen) + throw SysError(formatLastOpenSSLError("BIO_read")); + return keyStream; + } case RsaStreamType::pkcs1: - return publicKey ? - evpKeyToStream(evp, ::PEM_write_bio_RSAPublicKey2, "PEM_write_bio_RSAPublicKey") : //throw SysError - evpKeyToStream(evp, ::PEM_write_bio_RSAPrivateKey2, "PEM_write_bio_RSAPrivateKey"); // + { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const int selection = publicKey ? OSSL_KEYMGMT_SELECT_PUBLIC_KEY : OSSL_KEYMGMT_SELECT_PRIVATE_KEY; + + OSSL_ENCODER_CTX* encCtx = ::OSSL_ENCODER_CTX_new_for_pkey(evp, //const EVP_PKEY* pkey + selection, //int selection + "PEM", //const char* output_type + nullptr, //const char* output_structure + nullptr); //const char* propquery + if (!encCtx) + throw SysError(formatLastOpenSSLError("OSSL_ENCODER_CTX_new_for_pkey")); + ZEN_ON_SCOPE_EXIT(::OSSL_ENCODER_CTX_free(encCtx)); + + //password-protect stream? => OSSL_ENCODER_CTX_set_passphrase() + + unsigned char* keyBuf = nullptr; + size_t keyLen = 0; + if (::OSSL_ENCODER_to_data(encCtx, &keyBuf, &keyLen) != 1) + throw SysError(formatLastOpenSSLError("OSSL_ENCODER_to_data")); + ZEN_ON_SCOPE_EXIT(::OPENSSL_free(keyBuf)); + + return {reinterpret_cast<const char*>(keyBuf), keyLen}; +#else + //fix OpenSSL API inconsistencies: + auto PEM_write_bio_RSAPrivateKey2 = [](BIO* bio, const RSA* rsa) + { + return ::PEM_write_bio_RSAPrivateKey(bio, //BIO* bp + rsa, //const RSA* x + nullptr, //const EVP_CIPHER* enc + nullptr, //const unsigned char* kstr + 0, //int klen + nullptr, //pem_password_cb* cb + nullptr); //void* u + }; + auto PEM_write_bio_RSAPublicKey2 = [](BIO* bio, const RSA* rsa) { return ::PEM_write_bio_RSAPublicKey(bio, rsa); }; + + BIO* bio = ::BIO_new(BIO_s_mem()); + if (!bio) + throw SysError(formatLastOpenSSLError("BIO_new")); + ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); + + const RSA* rsa = ::EVP_PKEY_get0_RSA(evp); //unowned reference! + if (!rsa) + throw SysError(formatLastOpenSSLError("EVP_PKEY_get0_RSA")); + + if ((publicKey ? + PEM_write_bio_RSAPublicKey2 : + PEM_write_bio_RSAPrivateKey2)(bio, rsa) != 1) + throw SysError(formatLastOpenSSLError(publicKey ? "PEM_write_bio_RSAPublicKey" : "PEM_write_bio_RSAPrivateKey")); + //--------------------------------------------- + const int keyLen = BIO_pending(bio); + if (keyLen < 0) + throw SysError(formatLastOpenSSLError("BIO_pending")); + if (keyLen == 0) + throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details + + std::string keyStream(keyLen, '\0'); + + if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen) + throw SysError(formatLastOpenSSLError("BIO_read")); + return keyStream; +#endif + } case RsaStreamType::raw: break; @@ -373,266 +406,6 @@ void zen::verifySignature(const std::string& message, const std::string& signatu } -namespace -{ -std::wstring getSslErrorLiteral(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); - ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_CLIENT_HELLO_CB); - - default: - return L"SSL error " + numberTo<std::wstring>(ec); - } -} - - -std::wstring formatX509ErrorCode(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); - - default: - return replaceCpy<std::wstring>(L"X509 error %x", L"%x", 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("SSL_CTX_new")); - - ssl_ = ::SSL_new(ctx_); - if (!ssl_) - throw SysError(formatLastOpenSSLError("SSL_new")); - - BIO* bio = ::BIO_new_socket(socket, BIO_NOCLOSE); - if (!bio) - throw SysError(formatLastOpenSSLError("BIO_new_socket")); - ::SSL_set0_rbio(ssl_, bio); //pass ownership - - if (::BIO_up_ref(bio) != 1) - throw SysError(formatLastOpenSSLError("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("SSL_set_tlsext_host_name")); - - if (caCertFilePath) - { - if (!::SSL_CTX_load_verify_locations(ctx_, utfTo<std::string>(*caCertFilePath).c_str(), nullptr)) - throw SysError(formatLastOpenSSLError("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) //no ownership transfer - throw SysError(formatSystemError("SSL_set1_host", L"", L"Unexpected failure.")); //no more error details - } - - const int rv = ::SSL_connect(ssl_); //implicitly calls SSL_set_connect_state() - if (rv != 1) - throw SysError(formatLastOpenSSLError("SSL_connect") + L' ' + getSslErrorLiteral(::SSL_get_error(ssl_, rv))); - - if (caCertFilePath) - { - const long verifyResult = ::SSL_get_verify_result(ssl_); - if (verifyResult != X509_V_OK) - throw SysError(formatSystemError("SSL_get_verify_result", formatX509ErrorCode(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) - return 0; //EOF + close_notify alert - -#if OPENSSL_VERSION_NUMBER >= 0x30000000L /*OpenSSL 3.0.0*/ || \ - OPENSSL_VERSION_NUMBER == 0x1010105fL /*OpenSSL 1.1.1e*/ - const auto ec = ::ERR_peek_last_error(); - if (sslError == SSL_ERROR_SSL && ERR_GET_REASON(ec) == SSL_R_UNEXPECTED_EOF_WHILE_READING) //EOF: only expected for HTTP/1.0 -#else //obsolete handling: https://github.com/openssl/openssl/issues/10880#issuecomment-575746226 - if ((sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0)) //EOF: only expected for HTTP/1.0 -#endif - return 0; - - throw SysError(formatLastOpenSSLError("SSL_read_ex") + L' ' + getSslErrorLiteral(sslError)); - } - assert(bytesReceived > 0); //SSL_read_ex() considers EOF an error! - if (bytesReceived > bytesToRead) //better safe than sorry - throw SysError(formatSystemError("SSL_read_ex", L"", L"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("SSL_write_ex") + L' ' + getSslErrorLiteral(::SSL_get_error(ssl_, rv))); - - if (bytesWritten > bytesToWrite) - throw SysError(formatSystemError("SSL_write_ex", L"", L"Buffer overflow.")); - if (bytesWritten == 0) - throw SysError(formatSystemError("SSL_write_ex", L"", L"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 - - bool zen::isPuttyKeyStream(const std::string& keyStream) { std::string firstLine(keyStream.begin(), std::find_if(keyStream.begin(), keyStream.end(), isLineBreak<char>)); @@ -727,8 +500,8 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: const auto block2 = std::string("\0\0\0\1", 4) + passphrase; unsigned char key[2 * SHA_DIGEST_LENGTH] = {}; - SHA1(reinterpret_cast<const unsigned char*>(block1.c_str()), block1.size(), &key[0]); //no-fail - SHA1(reinterpret_cast<const unsigned char*>(block2.c_str()), block2.size(), &key[SHA_DIGEST_LENGTH]); // + ::SHA1(reinterpret_cast<const unsigned char*>(block1.c_str()), block1.size(), &key[0]); //no-fail + ::SHA1(reinterpret_cast<const unsigned char*>(block2.c_str()), block2.size(), &key[SHA_DIGEST_LENGTH]); // EVP_CIPHER_CTX* cipCtx = ::EVP_CIPHER_CTX_new(); if (!cipCtx) @@ -771,7 +544,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: macKeyBlob += passphrase; unsigned char macKey[SHA_DIGEST_LENGTH] = {}; - SHA1(reinterpret_cast<const unsigned char*>(macKeyBlob.c_str()), macKeyBlob.size(), &macKey[0]); //no-fail + ::SHA1(reinterpret_cast<const unsigned char*>(macKeyBlob.c_str()), macKeyBlob.size(), &macKey[0]); //no-fail auto numToBeString = [](size_t n) -> std::string { @@ -886,6 +659,40 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: throw SysError(formatLastOpenSSLError("BN_mod")); //---------------------------------------------------------- +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD* paramBld = ::OSSL_PARAM_BLD_new(); + if (!paramBld) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_new")); + ZEN_ON_SCOPE_EXIT(::OSSL_PARAM_BLD_free(paramBld)); + + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_N, n.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(n)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_E, e.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(e)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_D, d.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(d)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_FACTOR1, p.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(p)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_FACTOR2, q.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(q)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(dmp1)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(dmq1)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(iqmp)")); + + OSSL_PARAM* sslParams = ::OSSL_PARAM_BLD_to_param(paramBld); + if (!sslParams) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_to_param")); + ZEN_ON_SCOPE_EXIT(::OSSL_PARAM_free(sslParams)); + + + EVP_PKEY_CTX* evpCtx = ::EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr); + if (!evpCtx) + throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_new_from_name(RSA)")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_CTX_free(evpCtx)); + + if (::EVP_PKEY_fromdata_init(evpCtx) != 1) + throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata_init")); + + EVP_PKEY* evp = nullptr; + if (::EVP_PKEY_fromdata(evpCtx, &evp, EVP_PKEY_KEYPAIR, sslParams) != 1) + throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); +#else RSA* rsa = ::RSA_new(); if (!rsa) throw SysError(formatLastOpenSSLError("RSA_new")); @@ -907,7 +714,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted) throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA")); - +#endif return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError } //---------------------------------------------------------- @@ -919,7 +726,37 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: std::unique_ptr<BIGNUM, BnFree> pub = extractBigNumPub (); // std::unique_ptr<BIGNUM, BnFree> pri = extractBigNumPriv(); // //---------------------------------------------------------- - +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD* paramBld = ::OSSL_PARAM_BLD_new(); + if (!paramBld) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_new")); + ZEN_ON_SCOPE_EXIT(::OSSL_PARAM_BLD_free(paramBld)); + + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_FFC_P, p.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(p)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_FFC_Q, q.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(q)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_FFC_G, g.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(g)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_PUB_KEY, pub.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(pub)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_PRIV_KEY, pri.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(pri)")); + + OSSL_PARAM* sslParams = ::OSSL_PARAM_BLD_to_param(paramBld); + if (!sslParams) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_to_param")); + ZEN_ON_SCOPE_EXIT(::OSSL_PARAM_free(sslParams)); + + + EVP_PKEY_CTX* evpCtx = ::EVP_PKEY_CTX_new_from_name(nullptr, "DSA", nullptr); + if (!evpCtx) + throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_new_from_name(DSA)")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_CTX_free(evpCtx)); + + if (::EVP_PKEY_fromdata_init(evpCtx) != 1) + throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata_init")); + + EVP_PKEY* evp = nullptr; + if (::EVP_PKEY_fromdata(evpCtx, &evp, EVP_PKEY_KEYPAIR, sslParams) != 1) + throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); +#else DSA* dsa = ::DSA_new(); if (!dsa) throw SysError(formatLastOpenSSLError("DSA_new")); @@ -938,7 +775,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (::EVP_PKEY_set1_DSA(evp, dsa) != 1) //no ownership transfer (internally ref-counted) throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_DSA")); - +#endif return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError } //---------------------------------------------------------- @@ -953,7 +790,51 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: const std::string pointStream = extractStringPub(); std::unique_ptr<BIGNUM, BnFree> pri = extractBigNumPriv(); //throw SysError //---------------------------------------------------------- +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const char* groupName = [&] + { + if (algoShort == "nistp256") + return SN_X9_62_prime256v1; //same as SECG secp256r1 + if (algoShort == "nistp384") + return SN_secp384r1; + if (algoShort == "nistp521") + return SN_secp521r1; + throw SysError(L"Unknown elliptic curve: " + utfTo<std::wstring>(algorithm)); + }(); + + OSSL_PARAM_BLD* paramBld = ::OSSL_PARAM_BLD_new(); + if (!paramBld) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_new")); + ZEN_ON_SCOPE_EXIT(::OSSL_PARAM_BLD_free(paramBld)); + + if (::OSSL_PARAM_BLD_push_utf8_string(paramBld, OSSL_PKEY_PARAM_GROUP_NAME, groupName, 0) != 1) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_utf8_string(group)")); + + if (::OSSL_PARAM_BLD_push_octet_string(paramBld, OSSL_PKEY_PARAM_PUB_KEY, &pointStream[0], pointStream.size()) != 1) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_octet_string(pub)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_PRIV_KEY, pri.get()) != 1) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(priv)")); + + OSSL_PARAM* sslParams = ::OSSL_PARAM_BLD_to_param(paramBld); + if (!sslParams) + throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_to_param")); + ZEN_ON_SCOPE_EXIT(::OSSL_PARAM_free(sslParams)); + + + EVP_PKEY_CTX* evpCtx = ::EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr); + if (!evpCtx) + throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_new_from_name(EC)")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_CTX_free(evpCtx)); + + if (::EVP_PKEY_fromdata_init(evpCtx) != 1) + throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata_init")); + + EVP_PKEY* evp = nullptr; + if (::EVP_PKEY_fromdata(evpCtx, &evp, EVP_PKEY_KEYPAIR, sslParams) != 1) + throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); +#else const int curveNid = [&] { if (algoShort == "nistp256") @@ -999,7 +880,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (::EVP_PKEY_set1_EC_KEY(evp, ecKey) != 1) //no ownership transfer (internally ref-counted) throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_EC_KEY")); - +#endif return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError } //---------------------------------------------------------- diff --git a/zen/open_ssl.h b/zen/open_ssl.h index 2b0c7245..d1b823de 100644 --- a/zen/open_ssl.h +++ b/zen/open_ssl.h @@ -36,23 +36,6 @@ std::string convertRsaKey(const std::string& keyStream, RsaStreamType typeFrom, bool isPuttyKeyStream(const std::string& keyStream); std::string convertPuttyKeyToPkix(const std::string& keyStream, const std::string& passphrase); //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/stream_buffer.h b/zen/stream_buffer.h new file mode 100644 index 00000000..8b8cd0d7 --- /dev/null +++ b/zen/stream_buffer.h @@ -0,0 +1,161 @@ +// ***************************************************************************** +// * 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 STREAM_BUFFER_H_08492572089560298 +#define STREAM_BUFFER_H_08492572089560298 + +#include <condition_variable> +#include "ring_buffer.h" +#include "string_tools.h" + + +namespace zen +{ +/* implement streaming API on top of libcurl's icky callback-based design + => support copying arbitrarily-large files: https://freefilesync.org/forum/viewtopic.php?t=4471 + => maximum performance through async processing (prefetching + output buffer!) + => cost per worker thread creation ~ 1/20 ms */ +class AsyncStreamBuffer +{ +public: + explicit AsyncStreamBuffer(size_t bufferSize) : bufSize_(bufferSize) { ringBuf_.reserve(bufferSize); } + + //context of input thread, blocking + //return "bytesToRead" bytes unless end of stream! + size_t read(void* buffer, size_t bytesToRead) //throw <write error> + { + 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__)); + + auto it = static_cast<std::byte*>(buffer); + const auto itEnd = it + bytesToRead; + + for (std::unique_lock dummy(lockStream_); it != itEnd;) + { + assert(!errorRead_); + conditionBytesWritten_.wait(dummy, [this] { return errorWrite_ || !ringBuf_.empty() || eof_; }); + + if (errorWrite_) + std::rethrow_exception(errorWrite_); //throw <write error> + + const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), ringBuf_.size()); + ringBuf_.extract_front(it, it + junkSize); + it += junkSize; + + conditionBytesRead_.notify_all(); + + if (eof_) //end of file + break; + } + + const size_t bytesRead = it - static_cast<std::byte*>(buffer); + totalBytesRead_ += bytesRead; + return bytesRead; + } + + //context of output thread, blocking + void write(const void* buffer, size_t bytesToWrite) //throw <read error> + { + totalBytesWritten_ += bytesToWrite; //bytes already processed as far as raw FTP access is concerned + + auto it = static_cast<const std::byte*>(buffer); + const auto itEnd = it + bytesToWrite; + + for (std::unique_lock dummy(lockStream_); it != itEnd;) + { + assert(!eof_ && !errorWrite_); + /* => can't use InterruptibleThread's interruptibleWait() :( + -> AsyncStreamBuffer is used for input and output streaming + => both AsyncStreamBuffer::write()/read() would have to implement interruptibleWait() + => one of these usually called from main thread + => but interruptibleWait() cannot be called from main thread! */ + conditionBytesRead_.wait(dummy, [this] { return errorRead_ || ringBuf_.size() < bufSize_; }); + + if (errorRead_) + std::rethrow_exception(errorRead_); //throw <read error> + + const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufSize_ - ringBuf_.size()); + ringBuf_.insert_back(it, it + junkSize); + it += junkSize; + + conditionBytesWritten_.notify_all(); + } + } + + //context of output thread + void closeStream() + { + { + std::lock_guard dummy(lockStream_); + assert(!eof_ && !errorWrite_); + eof_ = true; + } + conditionBytesWritten_.notify_all(); + } + + //context of input thread + void setReadError(const std::exception_ptr& error) + { + { + std::lock_guard dummy(lockStream_); + assert(!errorRead_); + if (!errorRead_) + errorRead_ = error; + } + conditionBytesRead_.notify_all(); + } + + //context of output thread + void setWriteError(const std::exception_ptr& error) + { + { + std::lock_guard dummy(lockStream_); + assert(!errorWrite_); + if (!errorWrite_) + errorWrite_ = error; + } + conditionBytesWritten_.notify_all(); + } + + //context of *output* thread + void checkReadErrors() //throw <read error> + { + std::lock_guard dummy(lockStream_); + if (errorRead_) + std::rethrow_exception(errorRead_); //throw <read error> + } + +#if 0 //function not needed: when EOF is reached (without errors), reading is done => no further error can occur! + void checkWriteErrors() //throw <write error> + { + std::lock_guard dummy(lockStream_); + if (errorWrite_) + std::rethrow_exception(errorWrite_); //throw <write error> + } +#endif + + uint64_t getTotalBytesWritten() const { return totalBytesWritten_; } + uint64_t getTotalBytesRead () const { return totalBytesRead_; } + +private: + AsyncStreamBuffer (const AsyncStreamBuffer&) = delete; + AsyncStreamBuffer& operator=(const AsyncStreamBuffer&) = delete; + + const size_t bufSize_; + std::mutex lockStream_; + RingBuffer<std::byte> ringBuf_; //prefetch/output buffer + bool eof_ = false; + std::exception_ptr errorWrite_; + std::exception_ptr errorRead_; + std::condition_variable conditionBytesWritten_; + std::condition_variable conditionBytesRead_; + + std::atomic<uint64_t> totalBytesWritten_{0}; //std:atomic is uninitialized by default! + std::atomic<uint64_t> totalBytesRead_ {0}; // +}; +} + +#endif //STREAM_BUFFER_H_08492572089560298 diff --git a/zen/string_base.h b/zen/string_base.h index 5c17fb47..693ce118 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -10,7 +10,6 @@ #include <algorithm> #include <atomic> #include "string_tools.h" -#include "legacy_compiler.h" //constinit //Zbase - a policy based string class optimizing performance and flexibility @@ -298,6 +297,11 @@ private: Zbase& operator= (int) = delete; //detect usage errors by creating an intentional ambiguity with "Char" Zbase& operator+= (int) = delete; // void push_back (int) = delete; // + Zbase (std::nullptr_t) = delete; + Zbase(size_t count, std::nullptr_t) = delete; + Zbase& operator= (std::nullptr_t) = delete; + Zbase& operator+= (std::nullptr_t) = delete; + void push_back (std::nullptr_t) = delete; Char* rawStr_; }; diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index edb8dd9d..54a5ecae 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -28,7 +28,7 @@ Zstring zen::getLoginUser() //throw FileError { const uid_t userIdNo = ::getuid(); //never fails - if (userIdNo != 0) //nofail; root(0) => consider as request for elevation, NOT impersonation + if (userIdNo != 0) //nofail; non-root { std::vector<char> buf(std::max<long>(10000, ::sysconf(_SC_GETPW_R_SIZE_MAX))); //::sysconf may return long(-1) passwd buf2 = {}; @@ -45,7 +45,7 @@ Zstring zen::getLoginUser() //throw FileError return pwsEntry->pw_name; } - //else root(0): what now!? + //else: root(0) => consider as request for elevation, NOT impersonation! //getlogin() is smarter than simply evaluating $LOGNAME! even in contexts without //$LOGNAME, e.g. "sudo su" on Ubuntu, it returns the correct non-root user! @@ -72,6 +72,29 @@ Zstring zen::getLoginUser() //throw FileError } +Zstring zen::getUserDescription() //throw FileError +{ + const Zstring userName = getLoginUser(); //throw FileError + const Zstring computerName = []() -> Zstring //throw FileError + { + std::vector<char> buf(10000); + if (::gethostname(&buf[0], buf.size()) != 0) + THROW_LAST_FILE_ERROR(_("Cannot get process information."), "gethostname"); + + Zstring hostName = &buf[0]; + if (endsWithAsciiNoCase(hostName, ".local")) //strip fluff (macOS) => apparently not added on Linux? + hostName = beforeLast(hostName, '.', IfNotFoundReturn::none); + + return hostName; + }(); + + if (contains(getUpperCase(computerName), getUpperCase(userName))) + return userName; //no need for text duplication! e.g. "Zenju (Zenju-PC)" + + return userName + Zstr(" (") + computerName + Zstr(')'); //e.g. "Admin (Zenju-PC)" +} + + namespace { } diff --git a/zen/sys_info.h b/zen/sys_info.h index 0126ad2f..0393e1fb 100644 --- a/zen/sys_info.h +++ b/zen/sys_info.h @@ -15,6 +15,8 @@ namespace zen //COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize Zstring getLoginUser(); //throw FileError +Zstring getUserDescription();//throw FileError + struct ComputerModel { diff --git a/zen/type_traits.h b/zen/type_traits.h index a4194c05..aca80393 100644 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -184,4 +184,10 @@ LessDescending<Predicate> makeSortDirection(Predicate pred, std::false_type) { r template<class T> constexpr bool HasMemberTypeV_##TYPENAME = HasMemberType_##TYPENAME<T>::value; } + +//--------------------------------------------------------------------------- +//ZEN macro consistency checks: => place in most-used header! + + + #endif //TYPE_TRAITS_H_3425628658765467 diff --git a/zen/zstring.cpp b/zen/zstring.cpp index c9861219..34d52b2c 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -166,10 +166,10 @@ std::weak_ordering compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* std::weak_ordering compareNatural(const Zstring& lhs, const Zstring& rhs) { - //Unicode normal forms: - // Windows: CompareString() already ignores NFD/NFC differences: nice... - // Linux: g_unichar_toupper() can't ignore differences - // macOS: CFStringCompare() considers differences + /* Unicode normal forms: + Windows: CompareString() already ignores NFD/NFC differences: nice... + Linux: g_unichar_toupper() can't ignore differences + macOS: CFStringCompare() considers differences */ const Zstring& lhsNorm = getUnicodeNormalForm(lhs); const Zstring& rhsNorm = getUnicodeNormalForm(rhs); diff --git a/zen/zstring.h b/zen/zstring.h index 1afebe58..b8dfb9a3 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -138,7 +138,7 @@ Zstring getFileExtension(const Zstring& filePath) //common unicode characters const wchar_t EN_DASH = L'\u2013'; const wchar_t EM_DASH = L'\u2014'; -const wchar_t* const SPACED_DASH = L" \u2014 "; //using 'EM DASH' + const wchar_t* const SPACED_DASH = L" \u2014 "; //using 'EM DASH' const wchar_t LTR_MARK = L'\u200E'; //UTF-8: E2 80 8E const wchar_t* const ELLIPSIS = L"\u2026"; //"..." const wchar_t MULT_SIGN = L'\u00D7'; //fancy "x" @@ -151,11 +151,4 @@ const wchar_t BIDI_POP_DIR_ISOLATE = L'\u2069'; //UTF-8: E2 81 A9 => not work const wchar_t BIDI_DIR_EMBEDDING_RTL = L'\u202B'; //UTF-8: E2 80 AB => not working on Win 10 const wchar_t BIDI_POP_DIR_FORMATTING = L'\u202C'; //UTF-8: E2 80 AC => not working on Win 10 - - - -//--------------------------------------------------------------------------- -//ZEN macro consistency checks: - - #endif //ZSTRING_H_73425873425789 |