#pragma once #if !defined(MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED) #define MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED 1 #include #include #include #include #include #include "../debug/assert.hpp" #include "../internal/common.hpp" #include "../types/result.hpp" namespace ossl { struct ErrorFrame { std::string message; std::string file; std::string function; std::string data; unsigned long numeric = 0; int line = 0; int flags = 0; }; struct [[nodiscard]] Error { int sslError = SSL_ERROR_NONE; std::vector frames; [[nodiscard]] bool isSuccess() const MIJIN_NOEXCEPT { return sslError == SSL_ERROR_NONE; } static inline Error current(int sslError = -1) MIJIN_NOEXCEPT; static inline Error current(SSL* handle, int result) MIJIN_NOEXCEPT { return current(SSL_get_error(handle, result)); } }; template using Result = mijin::ResultBase; // callback typedefs using verify_callback_t = int (*) (int, X509_STORE_CTX *); template class Base { protected: using base_t = Base; THandle handle_ = nullptr; protected: explicit Base(THandle handle) MIJIN_NOEXCEPT : handle_(handle) {} public: Base() MIJIN_NOEXCEPT = default; Base(const Base& other) MIJIN_NOEXCEPT : handle_(other.handle_) { if (handle_) { TActual::upReferences(handle_); } } Base(Base&& other) MIJIN_NOEXCEPT : handle_(std::exchange(other.handle_, {})) {} ~Base() MIJIN_NOEXCEPT { static_cast(*this).free(); } TActual& operator=(const Base& other) MIJIN_NOEXCEPT { if (this == &other) { return static_cast(*this); } static_cast(*this).free(); handle_ = other.handle_; if (handle_) { TActual::upReferences(handle_); } return static_cast(*this); } TActual& operator=(Base&& other) MIJIN_NOEXCEPT { if (this == &other) { return static_cast(*this); } static_cast(*this).free(); handle_ = std::exchange(other.handle_, {}); return static_cast(*this); } auto operator<=>(const Base&) const MIJIN_NOEXCEPT = default; operator bool() const MIJIN_NOEXCEPT { return static_cast(handle_); } bool operator!() const MIJIN_NOEXCEPT { return !static_cast(handle_); } [[nodiscard]] THandle getHandle() const MIJIN_NOEXCEPT { return handle_; } [[nodiscard]] THandle releaseHandle() MIJIN_NOEXCEPT { return std::exchange(handle_, nullptr); } }; class X509Store : public Base { public: using Base::Base; Error create() MIJIN_NOEXCEPT { MIJIN_ASSERT(handle_ == nullptr, "X509 Store already created."); ERR_clear_error(); handle_ = X509_STORE_new(); if (handle_ == nullptr) { return Error::current(); } return {}; } void free() MIJIN_NOEXCEPT { if (handle_ != nullptr) { X509_STORE_free(handle_); handle_ = nullptr; } } Error loadFile(const char* file) const MIJIN_NOEXCEPT { ERR_clear_error(); if (!X509_STORE_load_file(handle_, file)) { return Error::current(); } return {}; } static void upReferences(X509_STORE* handle) MIJIN_NOEXCEPT { X509_STORE_up_ref(handle); } }; class Context : public Base { public: Error create(const SSL_METHOD* method) MIJIN_NOEXCEPT { MIJIN_ASSERT(handle_ == nullptr, "Context already created."); ERR_clear_error(); handle_ = SSL_CTX_new(method); if (handle_ == nullptr) { return Error::current(); } return {}; } void free() MIJIN_NOEXCEPT { if (handle_ == nullptr) { return; } SSL_CTX_free(handle_); handle_ = nullptr; } void setVerify(int mode, verify_callback_t callback = nullptr) const MIJIN_NOEXCEPT { SSL_CTX_set_verify(handle_, mode, callback); } void setCertStore(X509Store store) const MIJIN_NOEXCEPT { SSL_CTX_set_cert_store(handle_, store.releaseHandle()); } Error setMinProtoVersion(int version) const MIJIN_NOEXCEPT { ERR_clear_error(); if (!SSL_CTX_set_min_proto_version(handle_, version)) { return Error::current(); } return {}; } static void upReferences(SSL_CTX* handle) MIJIN_NOEXCEPT { SSL_CTX_up_ref(handle); } }; class Bio : public Base { public: Error createPair(Bio& otherBio, std::size_t writeBuf = 0, std::size_t otherWriteBuf = 0) MIJIN_NOEXCEPT { MIJIN_ASSERT(handle_ == nullptr, "Ssl already created."); MIJIN_ASSERT(otherBio.handle_ == nullptr, "Ssl already created."); ERR_clear_error(); if (!BIO_new_bio_pair(&handle_, writeBuf, &otherBio.handle_, otherWriteBuf)) { return Error::current(); } return {}; } void free() MIJIN_NOEXCEPT { if (handle_ == nullptr) { return; } BIO_free_all(handle_); handle_ = nullptr; } [[nodiscard]] std::size_t ctrlPending() const MIJIN_NOEXCEPT { return BIO_ctrl_pending(handle_); } [[nodiscard]] std::size_t ctrlWPending() const MIJIN_NOEXCEPT { return BIO_ctrl_wpending(handle_); } Result write(const void* data, int length) const MIJIN_NOEXCEPT { ERR_clear_error(); const int result = BIO_write(handle_, data, length); if (result <= 0) { return Error::current(); } return result; } Result read(void* data, int length) const MIJIN_NOEXCEPT { ERR_clear_error(); const int result = BIO_read(handle_, data, length); if (result <= 0) { return Error::current(); } return result; } [[nodiscard]] int getReadRequest() const MIJIN_NOEXCEPT { return BIO_get_read_request(handle_); } [[nodiscard]] int getWriteGuarantee() const MIJIN_NOEXCEPT { return BIO_get_write_guarantee(handle_); } [[nodiscard]] int getWritePending() const MIJIN_NOEXCEPT { return BIO_wpending(handle_); } Error flush() const MIJIN_NOEXCEPT { ERR_clear_error(); if (!BIO_flush(handle_)) { return Error::current(); } return {}; } static void upReferences(BIO* handle) MIJIN_NOEXCEPT { BIO_up_ref(handle); } }; class Ssl : public Base { public: Error create(const Context& context) MIJIN_NOEXCEPT { MIJIN_ASSERT(handle_ == nullptr, "Ssl already created."); ERR_clear_error(); handle_ = SSL_new(context.getHandle()); if (handle_ == nullptr) { return Error::current(); } return {}; } void free() MIJIN_NOEXCEPT { if (handle_ == nullptr) { return; } SSL_free(handle_); handle_ = nullptr; } void setBio(Bio readBio, Bio writeBio) const MIJIN_NOEXCEPT { SSL_set_bio(handle_, readBio.releaseHandle(), writeBio.releaseHandle()); } void setBio(Bio&& bio) const MIJIN_NOEXCEPT { BIO* bioHandle = bio.releaseHandle(); SSL_set_bio(handle_, bioHandle, bioHandle); } Error setTLSExtHostname(const char* hostname) const MIJIN_NOEXCEPT { ERR_clear_error(); if (const int result = SSL_set_tlsext_host_name(handle_, hostname); result != 1) { return Error::current(handle_, result); } return {}; } Error setHost(const char* hostname) const MIJIN_NOEXCEPT { ERR_clear_error(); if (const int result = SSL_set1_host(handle_, hostname); result != 1) { return Error::current(handle_, result); } return {}; } Error connect() const MIJIN_NOEXCEPT { ERR_clear_error(); if (const int result = SSL_connect(handle_); result != 1) { return Error::current(handle_, result); } return {}; } Error shutdown() const MIJIN_NOEXCEPT { ERR_clear_error(); if (const int result = SSL_shutdown(handle_); result != 1) { if (result == 0) { return Error{.sslError = SSL_ERROR_WANT_WRITE}; // TODO? } return Error::current(handle_, result); } return {}; } [[nodiscard]] long getVerifyResult() const MIJIN_NOEXCEPT { return SSL_get_verify_result(handle_); } Result write(const void* data, int length) const MIJIN_NOEXCEPT { ERR_clear_error(); const int result = SSL_write(handle_, data, length); if (result <= 0) { return Error::current(handle_, result); } return result; } Result read(void* data, int length) const MIJIN_NOEXCEPT { ERR_clear_error(); const int result = SSL_read(handle_, data, length); if (result <= 0) { return Error::current(handle_, result); } return result; } [[nodiscard]] int pending() const MIJIN_NOEXCEPT { return SSL_pending(handle_); } static void upReferences(SSL* handle) MIJIN_NOEXCEPT { SSL_up_ref(handle); } }; Error Error::current(int sslError_) MIJIN_NOEXCEPT { Error error = { .sslError = sslError_ }; const char* file = nullptr; int line = 0; const char* func = nullptr; const char* data = nullptr; int flags = 0; while (const unsigned long numeric = ERR_get_error_all(&file, &line, &func, &data, &flags)) { error.frames.push_back({ .message = ERR_error_string(numeric, nullptr), .file = file != nullptr ? file : "", .function = func != nullptr ? func : "", .data = data != nullptr ? data : "", .line = line, .flags = flags }); } return error; } } #endif // !defined(MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED)