443 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			443 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
 | 
						|
 | 
						|
#pragma once
 | 
						|
 | 
						|
#if !defined(MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED)
 | 
						|
#define MIJIN_NET_OPENSSL_WRAPPERS_HPP_INCLUDED 1
 | 
						|
 | 
						|
#include <utility>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
#include <openssl/err.h>
 | 
						|
#include <openssl/ssl.h>
 | 
						|
#include <openssl/x509_vfy.h>
 | 
						|
 | 
						|
#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<ErrorFrame> 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<typename TSuccess>
 | 
						|
using Result = mijin::ResultBase<TSuccess, Error>;
 | 
						|
 | 
						|
// callback typedefs
 | 
						|
using verify_callback_t = int (*) (int, X509_STORE_CTX *);
 | 
						|
 | 
						|
template<typename TActual, typename THandle>
 | 
						|
class Base
 | 
						|
{
 | 
						|
protected:
 | 
						|
    using base_t = Base<TActual, THandle>;
 | 
						|
 | 
						|
    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<TActual&>(*this).free();
 | 
						|
    }
 | 
						|
 | 
						|
    TActual& operator=(const Base& other) MIJIN_NOEXCEPT
 | 
						|
    {
 | 
						|
        if (this == &other)
 | 
						|
        {
 | 
						|
            return static_cast<TActual&>(*this);
 | 
						|
        }
 | 
						|
        static_cast<TActual&>(*this).free();
 | 
						|
        handle_ = other.handle_;
 | 
						|
        if (handle_)
 | 
						|
        {
 | 
						|
            TActual::upReferences(handle_);
 | 
						|
        }
 | 
						|
        return static_cast<TActual&>(*this);
 | 
						|
    }
 | 
						|
 | 
						|
    TActual& operator=(Base&& other) MIJIN_NOEXCEPT
 | 
						|
    {
 | 
						|
        if (this == &other)
 | 
						|
        {
 | 
						|
            return static_cast<TActual&>(*this);
 | 
						|
        }
 | 
						|
        static_cast<TActual&>(*this).free();
 | 
						|
        handle_ = std::exchange(other.handle_, {});
 | 
						|
        return static_cast<TActual&>(*this);
 | 
						|
    }
 | 
						|
    auto operator<=>(const Base&) const MIJIN_NOEXCEPT = default;
 | 
						|
    operator bool() const MIJIN_NOEXCEPT { return static_cast<bool>(handle_); }
 | 
						|
    bool operator!() const MIJIN_NOEXCEPT { return !static_cast<bool>(handle_); }
 | 
						|
 | 
						|
    [[nodiscard]]
 | 
						|
    THandle getHandle() const MIJIN_NOEXCEPT { return handle_; }
 | 
						|
 | 
						|
    [[nodiscard]]
 | 
						|
    THandle releaseHandle() MIJIN_NOEXCEPT { return std::exchange(handle_, nullptr); }
 | 
						|
};
 | 
						|
 | 
						|
class X509Store : public Base<X509Store, X509_STORE*>
 | 
						|
{
 | 
						|
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<Context, SSL_CTX*>
 | 
						|
{
 | 
						|
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<Bio, BIO*>
 | 
						|
{
 | 
						|
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<int> 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<int> 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<Ssl, SSL*>
 | 
						|
{
 | 
						|
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<int> 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<int> 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)
 |