summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2022-01-04 16:21:19 +0000
committerB. Stack <bgstack15@gmail.com>2022-01-04 16:21:19 +0000
commit2c8ae2c99308b4f0bf2bb08161829efee43e31ca (patch)
treeb8252ff8a09d9143ed2dc299d082f9d86535c1a2 /zen
parentMerge branch 'b11.15' into 'master' (diff)
parentadd upstream 11.16 (diff)
downloadFreeFileSync-2c8ae2c99308b4f0bf2bb08161829efee43e31ca.tar.gz
FreeFileSync-2c8ae2c99308b4f0bf2bb08161829efee43e31ca.tar.bz2
FreeFileSync-2c8ae2c99308b4f0bf2bb08161829efee43e31ca.zip
Merge branch 'b11.16' into 'master'11.16
add upstream 11.16 See merge request opensource-tracking/FreeFileSync!40
Diffstat (limited to 'zen')
-rw-r--r--zen/file_access.cpp59
-rw-r--r--zen/http.cpp380
-rw-r--r--zen/http.h12
-rw-r--r--zen/open_ssl.cpp709
-rw-r--r--zen/open_ssl.h17
-rw-r--r--zen/stream_buffer.h161
-rw-r--r--zen/string_base.h6
-rw-r--r--zen/sys_info.cpp27
-rw-r--r--zen/sys_info.h2
-rw-r--r--zen/type_traits.h6
-rw-r--r--zen/zstring.cpp8
-rw-r--r--zen/zstring.h9
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*
}
}();
diff --git a/zen/http.h b/zen/http.h
index 07c3f28c..9457309c 100644
--- a/zen/http.h
+++ b/zen/http.h
@@ -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
bgstack15