From 3bc7b62567ef6069386e969da7c4d51a0ccae958 Mon Sep 17 00:00:00 2001 From: Raoul Wols Date: Tue, 13 Jul 2021 11:23:08 +0200 Subject: [PATCH] separate class for extracting errors --- src/linux_tcp/sslconnected.h | 79 +++----------------- src/linux_tcp/sslerrorprinter.h | 123 ++++++++++++++++++++++++++++++++ src/linux_tcp/sslhandshake.h | 14 +++- 3 files changed, 144 insertions(+), 72 deletions(-) create mode 100644 src/linux_tcp/sslerrorprinter.h diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index cd2d08c..d25185f 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -19,6 +19,7 @@ #include "poll.h" #include "sslwrapper.h" #include "sslshutdown.h" +#include "sslerrorprinter.h" /** * Set up namespace @@ -163,15 +164,23 @@ private: return true; default: + { // we are now in an error state _state = state_error; + // get a human-readable error string + const SslErrorPrinter printer{error}; + + // ensure it is null-terminated + const std::string message{printer.data(), printer.size()}; + // report an error to user-space - _parent->onError(this, strerror(error).data()); + _parent->onError(this, message.data()); // ssl level error, we have to tear down the tcp connection return false; } + } } /** @@ -304,74 +313,6 @@ private: // proceed with the write operation or the event loop return _out && isWritable() ? write(monitor) : proceed(); } - - /** - * Get the error description of the returnvalue of SSL_get_error - * @param[in] returnvalue The return value of SSL_get_error - * @param bp Pointer to a BIO in case the error is SSL_ERROR_SSL - * @return A static string describing the error, or nullptr. - * In the case of a nullptr, the passed-in BIO describes the error. - */ - static const char *strerror(int returnvalue, ::BIO *bp) - { - // check the return value of the SSL_get_error function, which has a very unfortunate name. - switch (returnvalue) - { - // It can be a syscall error. - case SSL_ERROR_SYSCALL: - - // The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected - // EOF from the peer. This will be properly reported as SSL_ERROR_SSL - // with reason code SSL_R_UNEXPECTED_EOF_WHILE_READING in the - // OpenSSL 3.0 release because it is truly a TLS protocol error to - // terminate the connection without a SSL_shutdown(). - if (errno == 0) return "SSL_R_UNEXPECTED_EOF_WHILE_READING"; - - // Otherwise we ask the OS for a description of the error. - return ::strerror(errno); - - // It can be an error in OpenSSL. In that case the error stack contains - // more information. The documentation notes: if this error occurs then - // no further I/O operations should be performed on the connection and - // SSL_shutdown() must not be called. - case SSL_ERROR_SSL: - - // invoke the convenience function to extract the whole error stack - ::ERR_print_errors(bp); - - // we return nullptr because the error is described by the bio - return nullptr; - - default: - // we don't know what kind of error this is - return "unknown ssl error"; - } - } - - /** - * Get a human-readable string from the return value of SSL_get_error in case it's an error. - * @param[in] returnvalue The return value. Must not be a non-error. - * @return std::string describing the error. - */ - static std::string strerror(int returnvalue) - { - // create a BIO so we can call our strerror overload - std::unique_ptr<::BIO, decltype(&::BIO_free)> bio(::BIO_new(::BIO_s_mem()), &::BIO_free); - - // deduce the error message, and return it if it's not null - if (const char *message = strerror(returnvalue, bio.get())) return {message}; - - // the error is described in the BIO - // get the buffer memory structure - ::BUF_MEM *bufmem; - - // get it from the bio - ::BIO_get_mem_ptr(bio.get(), &bufmem); - - // ensure it's null-terminated by copying it to std::string - return {bufmem->data, bufmem->length}; - } - public: /** diff --git a/src/linux_tcp/sslerrorprinter.h b/src/linux_tcp/sslerrorprinter.h new file mode 100644 index 0000000..cdd1e90 --- /dev/null +++ b/src/linux_tcp/sslerrorprinter.h @@ -0,0 +1,123 @@ +/** + * SslErrorPrinter.h + * + * Flushes the SSL error stack to a BIO. + * You can get at the string content via the data() and size() methods. + * After constructing an instance of this class, the SSL error stack + * is empty. + * + * @copyright 2021 copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Dependencies + */ +#include "openssl.h" +#include +#include + +/** + * Begin namespace + */ +namespace AMQP { + +/** + * Class declaration + */ +class SslErrorPrinter final +{ + /** + * pointer to the BIO + * @var unique_ptr + */ + std::unique_ptr<::BIO, decltype(&::BIO_free)> _bio; + + /** + * pointer to the (data, size) tuple + * @var ::BUF_MEM* + */ + ::BUF_MEM *_bufmem = nullptr; + + /** + * In case of a syscall error, the static string pointing to the error + * @var const char* + */ + const char *_strerror = nullptr; + +public: + /** + * Constructor + * @throw std::bad_alloc if the BIO couldn't be allocated + */ + SslErrorPrinter(int retval) : _bio(nullptr, &::BIO_free) + { + // check the return value of the SSL_get_error function, which has a very unfortunate name. + switch (retval) + { + // It can be a syscall error. + case SSL_ERROR_SYSCALL: + { + // The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected + // EOF from the peer. This will be properly reported as SSL_ERROR_SSL + // with reason code SSL_R_UNEXPECTED_EOF_WHILE_READING in the + // OpenSSL 3.0 release because it is truly a TLS protocol error to + // terminate the connection without a SSL_shutdown(). + if (errno == 0) _strerror = "SSL_R_UNEXPECTED_EOF_WHILE_READING"; + + // Otherwise we ask the OS for a description of the error. + else _strerror = ::strerror(errno); + + // done + break; + } + // It can be an error in OpenSSL. In that case the error stack contains + // more information. The documentation notes: if this error occurs then + // no further I/O operations should be performed on the connection and + // SSL_shutdown() must not be called. + case SSL_ERROR_SSL: + { + // create a new bio + _bio = decltype(_bio)(::BIO_new(::BIO_s_mem()), &::BIO_free); + + // check if it was allocated + if (!_bio) throw std::bad_alloc(); + + // invoke the convenience function to extract the whole error stack + ::ERR_print_errors(_bio.get()); + + // get it from the bio + ::BIO_get_mem_ptr(_bio.get(), &_bufmem); + + // done + break; + } + default: + { + // we don't know what kind of error this is + _strerror = "unknown ssl error"; + + // done + break; + } + } + } + + /** + * data ptr + * @return const char * + */ + const char *data() const noexcept { return _strerror ? _strerror : _bufmem->data; } + + /** + * length of the string + * @return size_t + */ + std::size_t size() const noexcept { return _strerror ? std::strlen(_strerror) : _bufmem->length; } +}; + +} diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index 33092b1..bd24d0e 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -20,6 +20,7 @@ #include "poll.h" #include "sslwrapper.h" #include "sslcontext.h" +#include "sslerrorprinter.h" /** * Set up namespace @@ -80,12 +81,19 @@ private: /** * Helper method to report an error * @param monitor + * @param retval return value of SSL_get_error * @return TcpState* */ - TcpState *reportError(const Monitor &monitor) + TcpState *reportError(const Monitor &monitor, int retval) { + // extract a human-readable error string + const SslErrorPrinter printer{retval}; + + // ensure it's null-terminated + const std::string message{printer.data(), printer.size()}; + // we have an error - report this to the user - _parent->onError(this, "failed to setup ssl connection"); + _parent->onError(this, message.data()); // stop if connection is gone if (!monitor.valid()) return nullptr; @@ -182,7 +190,7 @@ public: switch (error) { case SSL_ERROR_WANT_READ: return proceed(readable); case SSL_ERROR_WANT_WRITE: return proceed(readable | writable); - default: return reportError(monitor); + default: return reportError(monitor, error); } }